Compare commits

..

No commits in common. "main" and "v1.0.0" have entirely different histories.
main ... v1.0.0

24 changed files with 10196 additions and 5214 deletions

1
.npmrc
View File

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

View File

@ -1,22 +0,0 @@
# Changelog
## 1.0.6 (2026-06-09)
### Breaking Changes
- **ESM Deprecation**: Completely removed ESM exports to align with the "zero-boilerplate" project philosophy. The library now relies solely on global injection via `globalThis`.
- **Build Format**: Switched build output to IIFE format only.
### Improvements
- **Integration**: Simplified dependency handling for `@apigo.cc/state` by using global variables.
- **Docs**: Updated README with new versioning and non-ESM usage instructions.
## 1.0.5 (2026-06-05)
### Features
- **API**: Refactored `Bootstrap.config` to be the unified entry point.
- **Shorthand**: Added support for `[state, key]` array shorthand in `bindDarkMode`.
- **Theme Engine**: Expanded CSS patch coverage to include form switches, checkboxes, range inputs, progress bars, and more.
- **AI Optimized**: Documentation overhaul for better AI usability and zero-config setup.
### Fixes
- Fixed an issue where theme colors did not propagate to component-specific variables like buttons and switches.

View File

@ -1,48 +0,0 @@
# @apigo.cc/bootstrap API 手册 (AI Optimized)
Bootstrap 5.3 自包含集成引擎。
## 0. 快速开始 (Quick Start)
直接在 HTML 中引入(无需打包,完全非 ESM 注入):
```html
<script src="https://cdn.jsdelivr.net/npm/@apigo.cc/bootstrap@1.0.6/dist/bootstrap.min.js"></script>
```
## 1. 运行时接口
- **`bootstrap`**: (Object) 官方原生 Bootstrap 镜像。已挂载至 `globalThis.bootstrap`
- **`Bootstrap`**: (Object) 增强控制接口。已挂载至 `globalThis.Bootstrap`
*(注意:本库自 v1.0.6 起全面废弃 ESM 导出,改为纯全局注入模式。)*
## 2. API 参考
### `Bootstrap.config(options)`
统一配置入口。未定义的 key 将被忽略。
#### 参数 `options`:
- **主题色**: `primary`, `secondary`, `success`, `info`, `warning`, `danger`, `light`, `dark` (Hex 字符串)。
- *行为*: 自动更新 CSS 变量并注入深度样式补丁(覆盖按钮、表单控件、开关、进度条等硬编码样式)。
- **暗黑模式**:
- `darkMode`: (Boolean) 直接设置主题。`true` 为 dark`false` 为 light。
- `bindDarkMode`: (Array) `[state, key]`。绑定响应式状态。
- `state`: 具有 `__watch(key, callback)` 方法的状态对象。
- `key`: 状态对象中的键名。
#### 示例 (Examples):
```javascript
// 1. 设置颜色与暗黑模式简写
Bootstrap.config({
primary: '#a855f7',
bindDarkMode: [LocalStorage, 'isDark']
});
// 2. 手动模式
Bootstrap.config({ darkMode: true });
// 3. 仅更新单一颜色 (自动处理相关组件补丁)
Bootstrap.config({ success: '#22c55e' });
```
## 3. 核心机制 (Internal)
- **运行时样式补丁**: `Bootstrap.config` 动态更新 `id="bs-config-patch"``<style>` 标签。通过 `!important` 覆盖 Bootstrap 内部硬编码的组件颜色(如 `.btn`, `.form-switch`, `.form-range`, `.progress-bar`, `.list-group-item` 等)。
- **零构建支持**: 无需 Sass 重新编译,在纯 ESM 环境下即可实现全量主题定制。

1611
dist/bootstrap.js vendored

File diff suppressed because it is too large Load Diff

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": "@apigo.cc/bootstrap",
"version": "1.0.1",
"name": "bootstrap",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {

5188
node_modules/.vite/deps_temp_ee6d9627/bootstrap.js generated vendored Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

3
node_modules/.vite/deps_temp_ee6d9627/package.json generated vendored Normal file
View File

@ -0,0 +1,3 @@
{
"type": "module"
}

8
package-lock.json generated
View File

@ -1,12 +1,12 @@
{
"name": "@apigo.cc/bootstrap",
"version": "1.0.1",
"name": "bootstrap",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@apigo.cc/bootstrap",
"version": "1.0.1",
"name": "bootstrap",
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"bootstrap": "^5.3.8",

View File

@ -1,16 +1,12 @@
{
"name": "@apigo.cc/bootstrap",
"version": "1.0.7",
"name": "bootstrap",
"version": "1.0.0",
"type": "module",
"main": "dist/bootstrap.js",
"files": [
"dist"
],
"scripts": {
"dev": "vite",
"build": "vite build",
"test": "playwright test",
"pub": "node scripts/publish.js"
"test": "playwright test"
},
"keywords": [],
"author": "",

View File

@ -1,48 +0,0 @@
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);
}

View File

@ -1,131 +1,85 @@
import * as bootstrap from 'bootstrap'
import 'bootstrap/dist/css/bootstrap.min.css'
import 'bootstrap-icons/font/bootstrap-icons.css'
import { Hash, LocalStorage, RefreshState } from '@apigo.cc/state'
import { Hash, LocalStorage, RefreshState } from '@web/state'
const GlobalStates = { Hash, LocalStorage }
/**
* @apigo.cc/bootstrap
* @web/bootstrap
* 自包含的 Bootstrap 5.3 集成引擎
*/
// 导出增强功能对象
export const Bootstrap = {
config: (options = {}) => {
const Bootstrap = {
// 原始 Bootstrap 实例引用
...bootstrap,
/**
* 绑定暗色模式到 State 路径
* @param {string|Object} stateOrPath 例如 'LocalStorage.darkMode' 或某个 NewState 对象
* @param {string} [key] 如果第一个参数是对象则需要指定 key
*/
bindDarkMode: (stateOrPath, key) => {
if (typeof document === 'undefined') return
const htmlNode = document.documentElement
const updateTheme = (val) => {
const theme = val ? 'dark' : 'light'
htmlNode.setAttribute('data-bs-theme', theme)
htmlNode.setAttribute('$data-bs-theme', theme)
}
let state, finalKey;
if (typeof stateOrPath === 'string') {
const parts = stateOrPath.split('.')
state = GlobalStates[parts[0]]
finalKey = parts[1]
} else {
state = stateOrPath
finalKey = key
}
if (!state || !finalKey) return console.warn('Bootstrap.bindDarkMode: Invalid state or key')
// 监听状态变化
state.__watch(finalKey, (val) => {
updateTheme(val)
RefreshState(htmlNode)
})
// 初始值同步
updateTheme(state[finalKey])
},
/**
* 动态配置主题
* @param {Object} config { primary: '#6366f1', ... }
*/
config: (config = {}) => {
if (typeof document === 'undefined') return
const root = document.documentElement
// 1. 处理颜色主题 (Colors)
const colors = ['primary', 'secondary', 'success', 'info', 'warning', 'danger', 'light', 'dark']
let cssPatch = ''
colors.forEach(name => {
const hex = options[name]
if (hex) {
root.style.setProperty(`--bs-${name}`, hex)
const rgb = Bootstrap._hexToRgb(hex)
if (rgb) {
const rgbStr = `${rgb.r}, ${rgb.g}, ${rgb.b}`
root.style.setProperty(`--bs-${name}-rgb`, rgbStr)
// 深度补丁:覆盖组件内部硬编码的变量和状态样式
cssPatch += `
.btn-${name} {
--bs-btn-bg: var(--bs-${name}) !important;
--bs-btn-border-color: var(--bs-${name}) !important;
--bs-btn-hover-bg: var(--bs-${name}) !important;
--bs-btn-hover-border-color: var(--bs-${name}) !important;
--bs-btn-active-bg: var(--bs-${name}) !important;
--bs-btn-active-border-color: var(--bs-${name}) !important;
--bs-btn-disabled-bg: var(--bs-${name}) !important;
--bs-btn-disabled-border-color: var(--bs-${name}) !important;
}
.bg-${name} { background-color: var(--bs-${name}) !important; }
.text-${name} { color: var(--bs-${name}) !important; }
.border-${name} { border-color: var(--bs-${name}) !important; }
.badge.bg-${name} { background-color: var(--bs-${name}) !important; }
/* 表单控件补丁 (Form Controls) */
.form-check-input:checked {
background-color: var(--bs-primary) !important;
border-color: var(--bs-primary) !important;
}
.form-check-input:focus {
border-color: var(--bs-primary) !important;
box-shadow: 0 0 0 0.25rem rgba(var(--bs-primary-rgb), 0.25) !important;
}
.form-switch .form-check-input:checked {
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e") !important;
}
.form-range::-webkit-slider-thumb { background: var(--bs-primary) !important; }
.form-range::-moz-range-thumb { background: var(--bs-primary) !important; }
.form-range::-webkit-slider-thumb:active { background-color: rgba(var(--bs-primary-rgb), 0.5) !important; }
/* 进度条与分页 (Progress & Pagination) */
.progress-bar { background-color: var(--bs-primary) !important; }
.page-link { color: var(--bs-primary); }
.active > .page-link, .page-link.active {
background-color: var(--bs-primary) !important;
border-color: var(--bs-primary) !important;
if (config.primary) {
root.style.setProperty('--bs-primary', config.primary)
// 简单计算 RGB 供一些需要透明度的组件使用
const rgb = Bootstrap._hexToRgb(config.primary)
if (rgb) root.style.setProperty('--bs-primary-rgb', `${rgb.r}, ${rgb.g}, ${rgb.b}`)
}
/* 列表组 (List Group) */
.list-group-item.active {
background-color: var(--bs-primary) !important;
border-color: var(--bs-primary) !important;
}
`
}
}
})
if (cssPatch) {
let styleEl = document.getElementById('bs-config-patch')
if (!styleEl) {
styleEl = document.createElement('style')
styleEl.id = 'bs-config-patch'
document.head.appendChild(styleEl)
}
styleEl.innerHTML = cssPatch
}
// 2. 处理暗黑模式 (Dark Mode)
let { bindDarkMode, darkMode } = options
const updateTheme = (val) => {
root.setAttribute('data-bs-theme', val ? 'dark' : 'light')
}
// 支持 [state, key] 简写
if (Array.isArray(bindDarkMode)) {
bindDarkMode = { state: bindDarkMode[0], key: bindDarkMode[1] }
}
if (bindDarkMode && bindDarkMode.state && bindDarkMode.key) {
const { state, key } = bindDarkMode
if (state.__watch) {
state.__watch(key, updateTheme)
updateTheme(state[key])
}
} else if (darkMode !== undefined) {
updateTheme(darkMode)
}
if (config.success) root.style.setProperty('--bs-success', config.success)
if (config.danger) root.style.setProperty('--bs-danger', config.danger)
// ... 其他颜色变量
},
_hexToRgb: (hex) => {
let shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i
hex = hex.replace(shorthandRegex, (m, r, g, b) => r + r + g + g + b + b)
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
return result ? { r: parseInt(result[1], 16), g: parseInt(result[2], 16), b: parseInt(result[3], 16) } : null
return result ? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16)
} : null
}
}
// 挂载到全局
if (typeof globalThis !== 'undefined') {
// 小写变量保持为纯原生或接近原生的引用,供 AI 舒适使用
globalThis.bootstrap = bootstrap;
// 大写变量供需要我们额外增强能力的场景使用
globalThis.Bootstrap = Bootstrap;
}
export default Bootstrap;
export { Bootstrap }
export default Bootstrap

View File

@ -5,8 +5,8 @@
<script type="importmap">
{
"imports": {
"@apigo.cc/state": "../state/src/index.js",
"@apigo.cc/bootstrap": "./src/index.js"
"@web/state": "../state/src/index.js",
"@web/bootstrap": "./src/index.js"
}
}
</script>
@ -22,12 +22,12 @@
</div>
<script type="module">
import { Bootstrap } from '@apigo.cc/bootstrap';
import { LocalStorage } from '@apigo.cc/state';
import { Bootstrap } from '@web/bootstrap';
import { LocalStorage } from '@web/state';
// 初始化
LocalStorage.darkMode = false;
Bootstrap.bindDarkMode(LocalStorage, 'darkMode');
Bootstrap.bindDarkMode('LocalStorage.darkMode');
document.getElementById('toggle-btn').onclick = () => {
LocalStorage.darkMode = !LocalStorage.darkMode;

View File

@ -9,34 +9,32 @@ export default defineConfig({
],
resolve: {
alias: {
'@apigo.cc/state': resolve(__dirname, '../state/src/index.js'),
'@apigo.cc/bootstrap': resolve(__dirname, 'src/index.js')
'@web/state': resolve(__dirname, '../state/src/index.js'),
'@web/bootstrap': resolve(__dirname, 'src/index.js')
}
},
server: {
fs: {
allow: ['..']
}
},
build: {
lib: {
entry: resolve(__dirname, 'src/index.js'),
name: 'ApigoBootstrap',
formats: ['iife']
name: 'Bootstrap',
formats: ['es']
},
rollupOptions: {
external: ['@apigo.cc/state'],
external: ['@web/state'],
output: [
{
format: 'iife',
name: 'ApigoBootstrap',
format: 'es',
entryFileNames: 'bootstrap.js',
globals: {
'@apigo.cc/state': 'ApigoState'
}
minifyInternalExports: false
},
{
format: 'iife',
name: 'ApigoBootstrap',
format: 'es',
entryFileNames: 'bootstrap.min.js',
globals: {
'@apigo.cc/state': 'ApigoState'
},
plugins: [terser()]
}
]