chore: rename package to @apigo.cc/base and bump version to 1.0.8

This commit is contained in:
AI Engineer 2026-06-04 18:52:30 +08:00
parent 41d0745441
commit 48eb3e1311
19 changed files with 1738 additions and 1680 deletions

1
.npmrc Normal file
View File

@ -0,0 +1 @@
//registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN}

View File

@ -1,6 +1,6 @@
# @web/base AI 开发指南 (全面版)
# @apigo.cc/base AI 开发指南 (全面版)
`@web/base` 是基于 State.js 构建的高性能 Web 基础组件库。它采用**原生 ESM、零打包**架构,深度集成 Bootstrap 5旨在为 AI 驱动的开发提供极致精简且功能完备的 UI 与逻辑基建。
`@apigo.cc/base` 是基于 State.js 构建的高性能 Web 基础组件库。它采用**原生 ESM、零打包**架构,深度集成 Bootstrap 5旨在为 AI 驱动的开发提供极致精简且功能完备的 UI 与逻辑基建。
---
@ -17,8 +17,8 @@
<script type="importmap">
{
"imports": {
"@web/state": "path/to/state.mjs",
"@web/base": "path/to/base.mjs"
"@apigo.cc/state": "path/to/state.mjs",
"@apigo.cc/base": "path/to/base.mjs"
}
}
</script>

46
dist/base.js vendored
View File

@ -1,4 +1,4 @@
import { Component, NewState, Util, $, Hash, RefreshState } from "@web/state";
import { Component, NewState, Util, $, Hash, RefreshState } from "@apigo.cc/state";
const HTTP = {
get: ({ url, ...opt }) => HTTP.request({ url, method: "GET", ...opt }),
post: ({ url, data, ...opt }) => HTTP.request({ url, method: "POST", data, ...opt }),
@ -293,13 +293,15 @@ Component.register("AutoForm", (container) => {
));
const _pendingAutoFormComponents = [];
const AutoForm = {
register: (name) => {
register: (name, typeName) => {
const entry = { name, typeName: typeName || name };
if (typeof document !== "undefined") {
if (document.readyState !== "loading" && Component.getTemplate("AutoForm")) AutoForm._addAutoFormComponent(name);
else _pendingAutoFormComponents.push(name);
if (document.readyState !== "loading" && Component.getTemplate("AutoForm")) AutoForm._addAutoFormComponent(entry);
else _pendingAutoFormComponents.push(entry);
}
},
_addAutoFormComponent: (name) => {
_addAutoFormComponent: (entry) => {
const { name, typeName } = entry;
const template = Component.getTemplate("AutoForm");
if (template) {
let container = $(template.content, "[control-wrapper]");
@ -308,14 +310,14 @@ const AutoForm = {
if (nested) container = $(nested.content, "[control-wrapper]");
}
if (container && !container.querySelector(name)) {
container.appendChild(Util.makeDom(`<${name} $if="item.type?.toLowerCase() === '${name.toLowerCase()}'" $name="item.name" $.="item.setting || {}" $bind="thisNode.closest('AutoForm').data[item.name]"></${name}>`));
container.appendChild(Util.makeDom(`<${name} $if="item.type?.toLowerCase() === '${typeName.toLowerCase()}'" $name="item.name" $.="item.setting || {}" $bind="thisNode.closest('AutoForm').data[item.name]"></${name}>`));
}
}
}
};
if (typeof document !== "undefined") {
const initAutoForm = () => {
_pendingAutoFormComponents.forEach((name) => AutoForm._addAutoFormComponent(name));
_pendingAutoFormComponents.forEach((entry) => AutoForm._addAutoFormComponent(entry));
_pendingAutoFormComponents.length = 0;
};
if (document.readyState !== "loading") setTimeout(initAutoForm, 100);
@ -756,6 +758,7 @@ Component.register("List", (container) => {
`
));
Component.register("Nav", (container) => {
container.vertical = container.hasAttribute("vertical");
container.click = (item, noselect) => {
if (!item.noselect && !noselect) Hash.nav = item.name;
container.dispatchEvent(new CustomEvent("nav", { detail: { item }, bubbles: false }));
@ -763,20 +766,21 @@ Component.register("Nav", (container) => {
}, Util.makeDom(
/*html*/
`
<div class="navbar navbar-expand bg-body-secondary px-3 pb-0 border-bottom align-items-center">
<img $if="this.state.brand.image" $src="this.state.brand.image" class="me-2" style="height:30px;width:auto;max-width:300px">
<i $if="this.state.brand.icon" $class="bi bi-\${this.state.brand.icon} me-2"></i>
<span $if="this.state.brand.label" class="me-2" $text="this.state.brand.label"></span>
<div class="ms-2"></div>
<div $each="this.state.list" $class="navbar-nav text-truncate \${item.type==='fill'?'flex-fill':''}">
<button $if="item.type==='button'" $class="nav-link \${Hash.nav===item.name?'active':''}" $onclick="this.click(item)">
<i $class="bi bi-\${item.icon} me-2"></i><span $class="d-none d-\${this.state.list.length>5?'lg':'md'}-inline" $text="item.label"></span>
<div $class="\${this.vertical ? 'd-flex flex-column border-end h-100' : 'navbar navbar-expand border-bottom'} bg-body-secondary px-3 \${this.vertical ? 'py-3' : 'pb-0'} align-items-center \${this.vertical ? 'align-items-start' : ''}">
<img $if="this.state.brand.image" $src="this.state.brand.image" $class="\${this.vertical ? 'mb-4' : 'me-2'}" style="height:30px;width:auto;max-width:300px">
<i $if="this.state.brand.icon" $class="bi bi-\${this.state.brand.icon} \${this.vertical ? 'mb-4' : 'me-2'}"></i>
<span $if="this.state.brand.label" $class="\${this.vertical ? 'mb-4 fw-bold' : 'me-2'}" $text="this.state.brand.label"></span>
<div $class="\${this.vertical ? 'w-100' : 'ms-2'}"></div>
<div $each="this.state.list" $class="\${this.vertical ? 'nav nav-pills flex-column w-100' : 'navbar-nav'} text-truncate \${item.type==='fill'?'flex-fill':''}">
<button $if="item.type==='button'" $class="nav-link \${Hash.nav===item.name?'active':''} \${this.vertical ? 'text-start' : ''}" $onclick="this.click(item)">
<i $class="bi bi-\${item.icon} me-2"></i><span $class="\${this.vertical ? '' : 'd-none d-' + (this.state.list.length>5?'lg':'md') + '-inline'}" $text="item.label"></span>
</button>
<div $if="item.type==='dropdown'" class="dropdown">
<button $class="nav-link \${Hash.nav===item.name?'active':''}" data-bs-toggle="dropdown">
<i $class="bi bi-\${item.icon} me-2"></i><span $class="d-none d-\${this.state.list.length>5?'lg':'md'}-inline" $text="item.label"></span>
<div $if="item.type==='dropdown'" $class="dropdown \${this.vertical ? 'w-100' : ''}">
<button $class="nav-link \${Hash.nav===item.name?'active':''} w-100 \${this.vertical ? 'text-start d-flex justify-content-between align-items-center' : ''}" data-bs-toggle="dropdown">
<span><i $class="bi bi-\${item.icon} me-2"></i><span $class="\${this.vertical ? '' : 'd-none d-' + (this.state.list.length>5?'lg':'md') + '-inline'}" $text="item.label"></span></span>
<i $if="this.vertical" class="bi bi-chevron-right small"></i>
</button>
<div class="dropdown-menu dropdown-menu-end p-3 bg-body-secondary shadow" $style="width: \${item.width || 250}px;">
<div $class="dropdown-menu \${this.vertical ? 'position-static border-0 bg-transparent shadow-none ps-3' : 'dropdown-menu-end p-3 bg-body-secondary shadow'}" $style="width: \${this.vertical ? '100%' : (item.width || 250) + 'px'};">
<template $each="item.list" as="subitem">
<button $if="subitem.type==='button'" class="nav-link px-0 w-100 text-start" $onclick="this.click(subitem, true)">
<i $class="bi bi-\${subitem.icon} me-2 d-inline-block" style="width: 16px;"></i><span $text="subitem.label"></span>
@ -869,8 +873,8 @@ globalThis.AutoForm = AutoForm;
globalThis.MouseMover = MouseMover;
if (typeof document !== "undefined") {
const doRefresh = () => RefreshState(document.documentElement);
if (document.readyState !== "loading") doRefresh();
else document.addEventListener("DOMContentLoaded", doRefresh, true);
if (document.readyState !== "loading") setTimeout(doRefresh, 1);
else document.addEventListener("DOMContentLoaded", () => setTimeout(doRefresh, 1), true);
}
export {
APIComponent,

2
dist/base.min.js vendored

File diff suppressed because one or more lines are too long

4
node_modules/.package-lock.json generated vendored
View File

@ -1,6 +1,6 @@
{
"name": "@web/base",
"version": "1.0.2",
"name": "@apigo.cc/base",
"version": "1.0.7",
"lockfileVersion": 3,
"requires": true,
"packages": {

8
package-lock.json generated
View File

@ -1,12 +1,12 @@
{
"name": "@web/base",
"version": "1.0.5",
"name": "@apigo.cc/base",
"version": "1.0.7",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@web/base",
"version": "1.0.5",
"name": "@apigo.cc/base",
"version": "1.0.7",
"devDependencies": {
"@playwright/test": "^1.40.0",
"@rollup/plugin-terser": "^1.0.0",

View File

@ -1,6 +1,6 @@
{
"name": "@web/base",
"version": "1.0.7",
"name": "@apigo.cc/base",
"version": "1.0.8",
"type": "module",
"main": "dist/base.js",
"module": "dist/base.js",
@ -10,7 +10,8 @@
"scripts": {
"dev": "vite",
"build": "vite build",
"test": "playwright test"
"test": "playwright test",
"pub": "node scripts/publish.js"
},
"devDependencies": {
"@playwright/test": "^1.40.0",

48
scripts/publish.js Normal file
View File

@ -0,0 +1,48 @@
import { execSync } from 'child_process';
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
try {
// 1. 获取最新 tag
let tag;
try {
tag = execSync('git describe --tags --abbrev=0', { encoding: 'utf8' }).trim();
} catch (err) {
throw new Error('Failed to find git tags. Please make sure the repository has tags (e.g., v1.0.0) before publishing.');
}
// 去掉 v 前缀
const version = tag.startsWith('v') ? tag.slice(1) : tag;
console.log(`Latest git tag: ${tag}, Version to publish: ${version}`);
// 2. 读取并更新 package.json
const pkgPath = path.join(__dirname, '../package.json');
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
// 保持原有名称(如果已经带有 @apigo.cc/ 前缀)或替换前缀
if (!pkg.name.startsWith('@apigo.cc/')) {
const baseName = pkg.name.includes('/') ? pkg.name.split('/')[1] : pkg.name;
pkg.name = `@apigo.cc/${baseName}`;
}
pkg.version = version;
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
console.log(`Updated package.json: name=${pkg.name}, version=${pkg.version}`);
// 3. 构建
console.log('Running build...');
execSync('npm run build', { stdio: 'inherit', cwd: path.join(__dirname, '..') });
// 4. 发布
console.log('Publishing to npm...');
const args = process.argv.slice(2).join(' ');
execSync(`npm publish --access public ${args}`, { stdio: 'inherit', cwd: path.join(__dirname, '..') });
console.log('Publish successful!');
} catch (error) {
console.error('Publish failed:', error.message);
process.exit(1);
}

2
src/controls.js vendored
View File

@ -1,4 +1,4 @@
import { Component, NewState, Util, $ } from '@web/state'
import { Component, NewState, Util, $ } from '@apigo.cc/state'
import { AutoForm } from './form.js'
/**

View File

@ -1,4 +1,4 @@
import { Component, NewState, Util, $ } from '@web/state'
import { Component, NewState, Util, $ } from '@apigo.cc/state'
import { HTTP } from './http.js'
Component.register('AutoForm', container => {
@ -80,13 +80,15 @@ Component.register('AutoForm', container => {
const _pendingAutoFormComponents = []
export const AutoForm = {
register: name => {
register: (name, typeName) => {
const entry = { name, typeName: typeName || name }
if (typeof document !== 'undefined') {
if (document.readyState !== 'loading' && Component.getTemplate('AutoForm')) AutoForm._addAutoFormComponent(name)
else _pendingAutoFormComponents.push(name)
if (document.readyState !== 'loading' && Component.getTemplate('AutoForm')) AutoForm._addAutoFormComponent(entry)
else _pendingAutoFormComponents.push(entry)
}
},
_addAutoFormComponent: name => {
_addAutoFormComponent: entry => {
const { name, typeName } = entry
const template = Component.getTemplate('AutoForm')
if (template) {
let container = $(template.content, '[control-wrapper]')
@ -95,7 +97,7 @@ export const AutoForm = {
if (nested) container = $(nested.content, '[control-wrapper]')
}
if (container && !container.querySelector(name)) {
container.appendChild(Util.makeDom(`<${name} $if="item.type?.toLowerCase() === '${name.toLowerCase()}'" $name="item.name" $.="item.setting || {}" $bind="thisNode.closest('AutoForm').data[item.name]"></${name}>`))
container.appendChild(Util.makeDom(`<${name} $if="item.type?.toLowerCase() === '${typeName.toLowerCase()}'" $name="item.name" $.="item.setting || {}" $bind="thisNode.closest('AutoForm').data[item.name]"></${name}>`))
}
}
}
@ -104,7 +106,7 @@ export const AutoForm = {
if (typeof document !== 'undefined') {
const initAutoForm = () => {
_pendingAutoFormComponents.forEach(name => AutoForm._addAutoFormComponent(name))
_pendingAutoFormComponents.forEach(entry => AutoForm._addAutoFormComponent(entry))
_pendingAutoFormComponents.length = 0
}
if (document.readyState !== 'loading') setTimeout(initAutoForm, 100)

View File

@ -1,4 +1,4 @@
import { Component, NewState } from '@web/state'
import { Component, NewState } from '@apigo.cc/state'
export const HTTP = {
get: ({ url, ...opt }) => HTTP.request({ url, method: 'GET', ...opt }),

View File

@ -1,4 +1,4 @@
import { NewState } from '@web/state'
import { NewState } from '@apigo.cc/state'
// Re-exports
export * from './http.js'
@ -35,10 +35,10 @@ globalThis.UI = UI
globalThis.AutoForm = AutoForm
globalThis.MouseMover = MouseMover
import { RefreshState } from '@web/state'
import { RefreshState } from '@apigo.cc/state'
if (typeof document !== 'undefined') {
const doRefresh = () => RefreshState(document.documentElement)
if (document.readyState !== 'loading') doRefresh()
else document.addEventListener('DOMContentLoaded', doRefresh, true)
if (document.readyState !== 'loading') setTimeout(doRefresh, 1)
else document.addEventListener('DOMContentLoaded', () => setTimeout(doRefresh, 1), true)
}

View File

@ -1,4 +1,4 @@
import { Component, Util } from '@web/state'
import { Component, Util } from '@apigo.cc/state'
let _mouseMoverMoving = false
let _mouseMoverPos = {}

View File

@ -1,4 +1,4 @@
import { Component, NewState, Util, Hash } from '@web/state'
import { Component, NewState, Util, Hash } from '@apigo.cc/state'
export const VirtualScroll = (options = {}) => {
const itemHeights = new Map()

View File

@ -1,25 +1,27 @@
import { Component, Hash, Util } from '@web/state'
import { Component, Hash, Util } from '@apigo.cc/state'
Component.register('Nav', container => {
container.vertical = container.hasAttribute('vertical')
container.click = (item, noselect) => {
if (!item.noselect && !noselect) Hash.nav = item.name
container.dispatchEvent(new CustomEvent('nav', { detail: { item }, bubbles: false }))
}
}, Util.makeDom(/*html*/`
<div class="navbar navbar-expand bg-body-secondary px-3 pb-0 border-bottom align-items-center">
<img $if="this.state.brand.image" $src="this.state.brand.image" class="me-2" style="height:30px;width:auto;max-width:300px">
<i $if="this.state.brand.icon" $class="bi bi-\${this.state.brand.icon} me-2"></i>
<span $if="this.state.brand.label" class="me-2" $text="this.state.brand.label"></span>
<div class="ms-2"></div>
<div $each="this.state.list" $class="navbar-nav text-truncate \${item.type==='fill'?'flex-fill':''}">
<button $if="item.type==='button'" $class="nav-link \${Hash.nav===item.name?'active':''}" $onclick="this.click(item)">
<i $class="bi bi-\${item.icon} me-2"></i><span $class="d-none d-\${this.state.list.length>5?'lg':'md'}-inline" $text="item.label"></span>
<div $class="\${this.vertical ? 'd-flex flex-column border-end h-100' : 'navbar navbar-expand border-bottom'} bg-body-secondary px-3 \${this.vertical ? 'py-3' : 'pb-0'} align-items-center \${this.vertical ? 'align-items-start' : ''}">
<img $if="this.state.brand.image" $src="this.state.brand.image" $class="\${this.vertical ? 'mb-4' : 'me-2'}" style="height:30px;width:auto;max-width:300px">
<i $if="this.state.brand.icon" $class="bi bi-\${this.state.brand.icon} \${this.vertical ? 'mb-4' : 'me-2'}"></i>
<span $if="this.state.brand.label" $class="\${this.vertical ? 'mb-4 fw-bold' : 'me-2'}" $text="this.state.brand.label"></span>
<div $class="\${this.vertical ? 'w-100' : 'ms-2'}"></div>
<div $each="this.state.list" $class="\${this.vertical ? 'nav nav-pills flex-column w-100' : 'navbar-nav'} text-truncate \${item.type==='fill'?'flex-fill':''}">
<button $if="item.type==='button'" $class="nav-link \${Hash.nav===item.name?'active':''} \${this.vertical ? 'text-start' : ''}" $onclick="this.click(item)">
<i $class="bi bi-\${item.icon} me-2"></i><span $class="\${this.vertical ? '' : 'd-none d-' + (this.state.list.length>5?'lg':'md') + '-inline'}" $text="item.label"></span>
</button>
<div $if="item.type==='dropdown'" class="dropdown">
<button $class="nav-link \${Hash.nav===item.name?'active':''}" data-bs-toggle="dropdown">
<i $class="bi bi-\${item.icon} me-2"></i><span $class="d-none d-\${this.state.list.length>5?'lg':'md'}-inline" $text="item.label"></span>
<div $if="item.type==='dropdown'" $class="dropdown \${this.vertical ? 'w-100' : ''}">
<button $class="nav-link \${Hash.nav===item.name?'active':''} w-100 \${this.vertical ? 'text-start d-flex justify-content-between align-items-center' : ''}" data-bs-toggle="dropdown">
<span><i $class="bi bi-\${item.icon} me-2"></i><span $class="\${this.vertical ? '' : 'd-none d-' + (this.state.list.length>5?'lg':'md') + '-inline'}" $text="item.label"></span></span>
<i $if="this.vertical" class="bi bi-chevron-right small"></i>
</button>
<div class="dropdown-menu dropdown-menu-end p-3 bg-body-secondary shadow" $style="width: \${item.width || 250}px;">
<div $class="dropdown-menu \${this.vertical ? 'position-static border-0 bg-transparent shadow-none ps-3' : 'dropdown-menu-end p-3 bg-body-secondary shadow'}" $style="width: \${this.vertical ? '100%' : (item.width || 250) + 'px'};">
<template $each="item.list" as="subitem">
<button $if="subitem.type==='button'" class="nav-link px-0 w-100 text-start" $onclick="this.click(subitem, true)">
<i $class="bi bi-\${subitem.icon} me-2 d-inline-block" style="width: 16px;"></i><span $text="subitem.label"></span>

View File

@ -1,4 +1,4 @@
import { Component, Util, $ } from '@web/state'
import { Component, Util, $ } from '@apigo.cc/state'
export const UI = {}

View File

@ -1,4 +1,4 @@
import { HTTP, UI, State } from '@web/base';
import { HTTP, UI, State } from '@apigo.cc/base';
export async function runTests() {
console.log('Starting comprehensive Base.js tests...');
@ -30,7 +30,7 @@ export async function runTests() {
// 5. AutoForm & TagsInput Test
console.log('Testing AutoForm...');
const { NewState } = await import('@web/state');
const { NewState } = await import('@apigo.cc/state');
const form = document.createElement('AutoForm');
document.body.appendChild(form);
await new Promise(r => setTimeout(r, 200));
@ -89,7 +89,7 @@ export async function runTests() {
// 7. List Components Basic Verification
console.log('Verifying List Components...');
const { Component } = await import('@web/state');
const { Component } = await import('@apigo.cc/state');
console.log('FastList exists:', Component.exists('FastList'));
const listIds = ['ll', 'gl', 'tt', 'ct'];

View File

@ -8,8 +8,8 @@
<script type="importmap">
{
"imports": {
"@web/state": "../../state/src/index.js",
"@web/base": "../src/index.js"
"@apigo.cc/state": "../../state/src/index.js",
"@apigo.cc/base": "../src/index.js"
}
}
</script>

View File

@ -5,8 +5,8 @@ import terser from '@rollup/plugin-terser';
export default defineConfig({
resolve: {
alias: {
'@web/state': resolve(__dirname, '../state/src/index.js'),
'@web/base': resolve(__dirname, 'src/index.js')
'@apigo.cc/state': resolve(__dirname, '../state/src/index.js'),
'@apigo.cc/base': resolve(__dirname, 'src/index.js')
}
},
server: {
@ -21,7 +21,7 @@ export default defineConfig({
formats: ['es']
},
rollupOptions: {
external: ['@web/state'],
external: ['@apigo.cc/state'],
output: [
{
format: 'es',