Compare commits

...

4 Commits
v1.0.4 ... main

22 changed files with 5198 additions and 10267 deletions

1
.npmrc Normal file
View File

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

22
CHANGELOG.md Normal file
View File

@ -0,0 +1,22 @@
# 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,68 +1,48 @@
# @apigo.cc/bootstrap
# @apigo.cc/bootstrap API 手册 (AI Optimized)
`@apigo.cc/bootstrap` 是一个自包含的 Bootstrap 5.3 引擎集成模块。它内置了 Bootstrap 及其图标库Bootstrap Icons并提供了简单的主题配色与暗色模式绑定机制。
---
## 一、 集成方式
推荐使用 `@apigo.cc/loader` 自动调度:
Bootstrap 5.3 自包含集成引擎。
## 0. 快速开始 (Quick Start)
直接在 HTML 中引入(无需打包,完全非 ESM 注入):
```html
<script src="https://cdn.jsdelivr.net/npm/@apigo.cc/loader@1.0.0/dist/loader.min.js"></script>
<script>
Loader.load('state:1.0.11', 'bootstrap:1.0.2');
</script>
<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`
## 二、 核心 API
*(注意:本库自 v1.0.6 起全面废弃 ESM 导出,改为纯全局注入模式。)*
### 1. 修改主题配色 (`Bootstrap.config`)
## 2. API 参考
您可以通过调用 `Bootstrap.config(colors)` 方法动态修改 Bootstrap 的核心主题颜色。传入十六进制Hex方法会自动为您计算并设置相应的 CSS 变量以及 RGB 变量:
### `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
import { Bootstrap } from '@apigo.cc/bootstrap';
// 动态修改 primary 和 danger 的配色
Bootstrap.config({
primary: '#6366f1',
danger: '#f43f5e'
// 1. 设置颜色与暗黑模式简写
Bootstrap.config({
primary: '#a855f7',
bindDarkMode: [LocalStorage, 'isDark']
});
// 2. 手动模式
Bootstrap.config({ darkMode: true });
// 3. 仅更新单一颜色 (自动处理相关组件补丁)
Bootstrap.config({ success: '#22c55e' });
```
### 2. 绑定暗色模式 (`Bootstrap.bindDarkMode`)
`Bootstrap.bindDarkMode(state, key)` 可以将响应式状态(如 `LocalStorage` 或您自定义的 `NewState`)的某个属性绑定为暗色模式的控制器。
当该属性发生变化时,它会自动在 `<html>` 标签上设置 `data-bs-theme="dark"``data-bs-theme="light"`,从而无缝切换 Bootstrap 5.3 的深色主题模式:
```javascript
import { LocalStorage } from '@apigo.cc/state';
import { Bootstrap } from '@apigo.cc/bootstrap';
// 绑定 LocalStorage.darkMode 状态
Bootstrap.bindDarkMode(LocalStorage, 'darkMode');
// 示例:切换为暗色模式
LocalStorage.darkMode = true;
// 示例:切换回亮色模式
LocalStorage.darkMode = false;
```
---
## 三、 使用原生 Bootstrap 实例
`@apigo.cc/bootstrap` 导出了原生的 Bootstrap 全量实例,您可以直接调用原生 Bootstrap 的组件方法(如 Modal, Popover 等):
```javascript
import { Bootstrap } from '@apigo.cc/bootstrap';
// 使用原生的 Modal 组件
const myModal = new Bootstrap.Modal(document.getElementById('myModal'));
myModal.show();
```
## 3. 核心机制 (Internal)
- **运行时样式补丁**: `Bootstrap.config` 动态更新 `id="bs-config-patch"``<style>` 标签。通过 `!important` 覆盖 Bootstrap 内部硬编码的组件颜色(如 `.btn`, `.form-switch`, `.form-range`, `.progress-bar`, `.list-group-item` 等)。
- **零构建支持**: 无需 Sass 重新编译,在纯 ESM 环境下即可实现全量主题定制。

9945
dist/bootstrap.js vendored

File diff suppressed because it is too large Load Diff

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

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

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

View File

@ -1,9 +1,8 @@
{
"name": "@apigo.cc/bootstrap",
"version": "1.0.4",
"version": "1.0.7",
"type": "module",
"main": "dist/bootstrap.js",
"module": "dist/bootstrap.js",
"files": [
"dist"
],

View File

@ -1,63 +1,131 @@
import * as bootstrap from 'bootstrap'
import 'bootstrap/dist/css/bootstrap.min.css'
import 'bootstrap-icons/font/bootstrap-icons.css'
import { Hash, LocalStorage, RefreshState } from '@web/state'
import { Hash, LocalStorage, RefreshState } from '@apigo.cc/state'
const GlobalStates = { Hash, LocalStorage }
/**
* @web/bootstrap
* @apigo.cc/bootstrap
* 自包含的 Bootstrap 5.3 集成引擎
*/
const Bootstrap = {
// 原始 Bootstrap 实例引用
...bootstrap,
/**
* 绑定暗色模式
* @param {Object} state NewState 对象 ( LocalStorage)
* @param {string} key 键名 ( 'darkMode')
*/
bindDarkMode: (state, key) => {
if (typeof document === 'undefined') return
const htmlNode = document.documentElement
const updateTheme = (val) => {
htmlNode.setAttribute('data-bs-theme', val ? 'dark' : 'light')
}
if (state && key) {
state.__watch(key, updateTheme)
updateTheme(state[key])
}
},
// 导出增强功能对象
export const Bootstrap = {
config: (options = {}) => {
if (typeof document === 'undefined') return
const root = document.documentElement
/**
* 动态配置主题变量
* @param {Object} config { primary: '#6366f1', ... }
*/
config: (config = {}) => {
if (typeof document === 'undefined') return
const root = document.documentElement
const colors = ['primary', 'secondary', 'success', 'info', 'warning', 'danger', 'light', 'dark']
// 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;
}
/* 列表组 (List Group) */
.list-group-item.active {
background-color: var(--bs-primary) !important;
border-color: var(--bs-primary) !important;
}
`
}
}
})
colors.forEach(name => {
const hex = config[name]
if (hex) {
root.style.setProperty(`--bs-${name}`, hex)
const rgb = Bootstrap._hexToRgb(hex)
if (rgb) root.style.setProperty(`--bs-${name}-rgb`, `${rgb.r}, ${rgb.g}, ${rgb.b}`)
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)
}
},
_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
}
}
export { Bootstrap }
export default Bootstrap
// 挂载到全局
if (typeof globalThis !== 'undefined') {
// 小写变量保持为纯原生或接近原生的引用,供 AI 舒适使用
globalThis.bootstrap = bootstrap;
// 大写变量供需要我们额外增强能力的场景使用
globalThis.Bootstrap = Bootstrap;
}
export default Bootstrap;

View File

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

View File

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