vanilla-jui 1.3.11 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/dist/index.d.ts +2927 -0
  2. package/dist/index.js +1 -1
  3. package/dist/index.umd.js +1 -1
  4. package/dist/style.css +1 -1
  5. package/package.json +4 -3
  6. package/src/components/accordion.ts +522 -0
  7. package/src/components/drop.ts +497 -0
  8. package/src/components/flow.ts +1579 -0
  9. package/src/components/form.ts +953 -0
  10. package/src/components/{icons.js → icons.ts} +24 -11
  11. package/src/components/menu.ts +615 -0
  12. package/src/components/{modal.js → modal.ts} +560 -237
  13. package/src/components/offcanvas.ts +496 -0
  14. package/src/components/{pagination.js → pagination.ts} +251 -76
  15. package/src/components/parabola.ts +335 -0
  16. package/src/components/{sticky.js → sticky.ts} +123 -98
  17. package/src/components/{swiper.js → swiper.ts} +510 -232
  18. package/src/components/tabs.ts +984 -0
  19. package/src/components/theme.ts +437 -0
  20. package/src/components/toast.ts +371 -0
  21. package/src/components/toc.ts +385 -0
  22. package/src/components/tooltip.ts +179 -0
  23. package/src/components/validator.ts +596 -0
  24. package/src/core/{Component.js → Component.ts} +134 -27
  25. package/src/css/animation.css +31 -1
  26. package/src/css/card.css +1 -1
  27. package/src/css/color.css +863 -1013
  28. package/src/css/color_p3.css +822 -975
  29. package/src/css/components.css +1 -0
  30. package/src/css/form.css +1 -1
  31. package/src/css/icon.css +17 -1
  32. package/src/css/index.css +2 -2
  33. package/src/css/menu.css +1 -1
  34. package/src/css/tabs.css +1 -0
  35. package/src/css/tokens.css +1 -1
  36. package/src/index.ts +37 -0
  37. package/src/locales/{index.js → index.ts} +1 -2
  38. package/src/types.d.ts +1 -0
  39. package/src/utilities/browser.ts +131 -0
  40. package/src/utilities/cache.ts +57 -0
  41. package/src/utilities/core.ts +614 -0
  42. package/src/utilities/dom.ts +468 -0
  43. package/src/utilities/events.ts +106 -0
  44. package/src/utilities/http.ts +35 -0
  45. package/src/utilities/storage.ts +46 -0
  46. package/src/components/accordion.js +0 -373
  47. package/src/components/drop.js +0 -437
  48. package/src/components/flow.js +0 -1133
  49. package/src/components/form.js +0 -585
  50. package/src/components/menu.js +0 -422
  51. package/src/components/offcanvas.js +0 -332
  52. package/src/components/parabola.js +0 -295
  53. package/src/components/tabs.js +0 -746
  54. package/src/components/theme.js +0 -353
  55. package/src/components/toast.js +0 -329
  56. package/src/components/toc.js +0 -226
  57. package/src/components/tooltip.js +0 -86
  58. package/src/components/validator.js +0 -586
  59. package/src/index.js +0 -45
  60. package/src/locales/index.min.js +0 -1
  61. package/src/utilities/browser.js +0 -111
  62. package/src/utilities/cache.js +0 -159
  63. package/src/utilities/core.js +0 -502
  64. package/src/utilities/debug.js +0 -103
  65. package/src/utilities/dom.js +0 -313
  66. package/src/utilities/events.js +0 -69
  67. package/src/utilities/http.js +0 -30
  68. package/src/utilities/legacy-dom.js +0 -179
  69. package/src/utilities/storage.js +0 -46
  70. /package/src/{components/components.js → css/themes/index.css} +0 -0
  71. /package/src/icons/{index.js → index.ts} +0 -0
@@ -1,332 +0,0 @@
1
- import { createDeepStore, flushSync, jsx } from 'vanilla-signal';
2
-
3
- import Component from '../core/Component.js';
4
- import { randomId, resolveProps, timer } from '../utilities/core.js';
5
- import {
6
- createLoading,
7
- isRenderableContent,
8
- normalizeContentNodes,
9
- requireRenderDOM,
10
- } from '../utilities/dom.js';
11
-
12
- const OFFCANVAS_PROPS_SCHEMA = {
13
- content: {
14
- default: '',
15
- validate: isRenderableContent,
16
- message: 'expects string, Node, array, function or null.',
17
- },
18
- overlay: { default: true, type: 'boolean' },
19
- filter: { default: true, type: 'boolean' },
20
- cache: { default: false, type: 'boolean' },
21
- ttl: { default: 0, type: 'number' },
22
- direction: {
23
- default: 'left',
24
- type: 'string',
25
- enum: ['top', 'right', 'bottom', 'left'],
26
- },
27
- animation: {
28
- default: 'slide',
29
- type: 'string',
30
- enum: ['slide', 'push', 'none'],
31
- },
32
- bgClose: { default: true, type: 'boolean' },
33
- escClose: { default: true, type: 'boolean' },
34
- id: {
35
- default: null,
36
- types: ['string', 'null'],
37
- normalize: (value) => {
38
- if (typeof value === 'string')
39
- return value.trim() ? value.trim() : randomId();
40
- if (value == null) return randomId();
41
- return value;
42
- },
43
- },
44
- onShow: { default: null, types: ['function', 'null'] },
45
- onShown: { default: null, types: ['function', 'null'] },
46
- onHide: { default: null, types: ['function', 'null'] },
47
- onHidden: { default: null, types: ['function', 'null'] },
48
- };
49
-
50
- function normalizeTtl(ttl) {
51
- return typeof ttl === 'number' && ttl > 0 ? ttl : 0;
52
- }
53
-
54
- /**
55
- * 侧滑面板组件,继承 Component。
56
- *
57
- * 适用于侧边菜单、筛选面板、移动端抽屉等场景。
58
- */
59
- export class Offcanvas extends Component {
60
- /**
61
- * 创建侧滑面板实例。
62
- * @param {object} [input={}] 面板配置。
63
- */
64
- constructor(input = {}) {
65
- requireRenderDOM('Offcanvas');
66
-
67
- const props = resolveProps(input, OFFCANVAS_PROPS_SCHEMA, 'Offcanvas');
68
- super(props);
69
-
70
- this._overlay = null;
71
- this.runtime.cache = {
72
- content: null,
73
- hasContent: false,
74
- updatedAt: 0,
75
- };
76
- this.runtime.contentLoadId = 0;
77
-
78
- this.state = createDeepStore({ visible: false, loading: false });
79
-
80
- this.onInit(props);
81
- }
82
-
83
- /**
84
- * 初始化组件。
85
- * @private
86
- */
87
- onInit(props) {
88
- if (props.overlay) this._overlay = this._buildOverlay(props);
89
- this.root = this._buildRoot(props);
90
- }
91
-
92
- _buildOverlay(props) {
93
- return jsx('div', {
94
- className: 'j-offcanvas-overlay',
95
- style: props.filter ? { backdropFilter: 'blur(2px)' } : {},
96
- });
97
- }
98
-
99
- _buildRoot(props) {
100
- const content = jsx('div', {
101
- className: 'offcanvas-content',
102
- });
103
- this.dom.content = content;
104
-
105
- if (typeof props.content !== 'function') {
106
- content.append(...normalizeContentNodes(props.content, this));
107
- }
108
-
109
- return jsx('div', {
110
- className: `j-offcanvas is-${props.direction} is-${props.animation}`,
111
- id: props.id,
112
- children: content,
113
- });
114
- }
115
-
116
- _isCacheValid() {
117
- if (!this.props.cache || !this.runtime.cache.hasContent) return false;
118
-
119
- const ttl = normalizeTtl(this.props.ttl);
120
- return !ttl || Date.now() - this.runtime.cache.updatedAt <= ttl;
121
- }
122
-
123
- _renderContent(content) {
124
- if (!this.dom.content) return;
125
-
126
- this.dom.content.textContent = '';
127
- this.dom.content.append(...normalizeContentNodes(content, this));
128
- }
129
-
130
- async _loadContent() {
131
- const { content, cache } = this.props;
132
- if (typeof content !== 'function') {
133
- this.runtime.contentLoadId += 1;
134
- flushSync(() => {
135
- this.state.loading = false;
136
- });
137
- return;
138
- }
139
-
140
- if (this._isCacheValid()) {
141
- this.runtime.contentLoadId += 1;
142
- this._renderContent(this.runtime.cache.content);
143
- flushSync(() => {
144
- this.state.loading = false;
145
- });
146
- return;
147
- }
148
-
149
- const loadId = ++this.runtime.contentLoadId;
150
- flushSync(() => {
151
- this.state.loading = true;
152
- });
153
- this.dom.content.textContent = '';
154
- this.dom.content.appendChild(createLoading());
155
-
156
- try {
157
- const result = await Promise.resolve(content(this));
158
-
159
- if (this.runtime.destroyed || loadId !== this.runtime.contentLoadId) {
160
- return;
161
- }
162
-
163
- if (cache) {
164
- this.runtime.cache.content = result;
165
- this.runtime.cache.hasContent = true;
166
- this.runtime.cache.updatedAt = Date.now();
167
- }
168
- this._renderContent(result);
169
- } finally {
170
- if (!this.runtime.destroyed && loadId === this.runtime.contentLoadId) {
171
- flushSync(() => {
172
- this.state.loading = false;
173
- });
174
- }
175
- }
176
- }
177
-
178
- _bindEvents() {
179
- this.unbindEvents();
180
- const { bgClose, escClose } = this.props;
181
-
182
- if (this._overlay && bgClose) {
183
- this.cleanup.events.on('overlay', this._overlay, 'click', () => {
184
- void this.hide();
185
- });
186
- }
187
- if (escClose) {
188
- this.cleanup.events.on('esc', document, 'keydown', (e) => {
189
- if (e.key === 'Escape' && this.state.visible) void this.hide();
190
- });
191
- }
192
- this.cleanup.events.on('close', this.root, 'click', (e) => {
193
- const action = e.target.dataset.action;
194
- if (action === 'close' || action === 'cancel') void this.hide();
195
- });
196
- }
197
-
198
- unbindEvents() {
199
- this.cleanup?.events.clear();
200
- }
201
-
202
- /**
203
- * 将面板挂载到页面并触发展示动画。
204
- * @private
205
- */
206
- _render() {
207
- const { overlay, animation, direction, id } = this.props;
208
- const b = document.body;
209
-
210
- if (overlay) b.appendChild(this._overlay);
211
- b.appendChild(this.root);
212
-
213
- b.style.overflow = 'hidden';
214
- if (animation === 'push') {
215
- b.classList.add('offcanvas-push-body');
216
- }
217
-
218
- timer.start(`oc-show-${id}`, 10, () => {
219
- if (!this.root) return;
220
- if (this._overlay) this._overlay.classList.add('is-active');
221
- if (animation === 'push') {
222
- b.classList.add(`offcanvas-push-${direction}`);
223
- }
224
- this.root.classList.add('is-active');
225
- });
226
- }
227
-
228
- /**
229
- * 触发隐藏动画并移除 DOM。
230
- * @private
231
- */
232
- _remove() {
233
- const { animation, direction, id } = this.props;
234
- const b = document.body;
235
-
236
- if (this._overlay) this._overlay.classList.remove('is-active');
237
- this.root.classList.remove('is-active');
238
-
239
- if (animation === 'push') {
240
- b.classList.remove(`offcanvas-push-${direction}`);
241
- }
242
-
243
- timer.start(`oc-remove-${id}`, 100, () => {
244
- this._overlay?.parentNode?.removeChild(this._overlay);
245
- this.root?.parentNode?.removeChild(this.root);
246
- if (animation === 'push') {
247
- b.classList.remove('offcanvas-push-body');
248
- }
249
- b.style.overflow = '';
250
- });
251
- }
252
-
253
- /**
254
- * 展示侧滑面板。
255
- * @returns {Promise<void>}
256
- */
257
- async show() {
258
- if (this.runtime.destroyed || this.state.visible) return;
259
-
260
- const { onShow, onShown, id } = this.props;
261
- if (onShow) await Promise.resolve(onShow());
262
-
263
- this._render();
264
- this._bindEvents();
265
-
266
- flushSync(() => {
267
- this.state.visible = true;
268
- });
269
-
270
- await this._loadContent();
271
-
272
- timer.start(`oc-shown-${id}`, 300, () => {
273
- if (this.runtime.destroyed) return;
274
- if (onShown) onShown();
275
- });
276
- }
277
-
278
- /**
279
- * 隐藏侧滑面板。
280
- * @returns {Promise<void>}
281
- */
282
- async hide() {
283
- if (this.runtime.destroyed || !this.state.visible) return;
284
-
285
- const { onHide, onHidden } = this.props;
286
- if (onHide) await Promise.resolve(onHide());
287
-
288
- this.runtime.contentLoadId += 1;
289
- flushSync(() => {
290
- this.state.loading = false;
291
- });
292
-
293
- this.unbindEvents();
294
- this._remove();
295
-
296
- flushSync(() => {
297
- this.state.visible = false;
298
- });
299
-
300
- if (onHidden) onHidden();
301
- }
302
-
303
- /**
304
- * 销毁当前侧滑面板实例。
305
- */
306
- onDestroy() {
307
- const { id } = this.props;
308
-
309
- timer.cancel(`oc-show-${id}`);
310
- timer.cancel(`oc-remove-${id}`);
311
- timer.cancel(`oc-shown-${id}`);
312
-
313
- if (this.state.visible) this.unbindEvents();
314
-
315
- this.root?.classList.remove('is-active');
316
- this.root?.parentNode?.removeChild(this.root);
317
- this._overlay?.parentNode?.removeChild(this._overlay);
318
-
319
- const b = document.body;
320
- b.style.overflow = '';
321
- b.classList.remove(
322
- 'offcanvas-push-body',
323
- `offcanvas-push-${this.props.direction}`
324
- );
325
-
326
- this._overlay = null;
327
- }
328
- }
329
-
330
- export function createOffcanvas(options = {}) {
331
- return new Offcanvas(options);
332
- }
@@ -1,295 +0,0 @@
1
- import { jsx } from 'vanilla-signal';
2
-
3
- import { resolveProps, timer } from '../utilities/core.js';
4
- import { isNode, requireRenderDOM, resolveElement } from '../utilities/dom.js';
5
-
6
- const ELEMENT_REF_RULE = {
7
- validate: (value) =>
8
- value == null ||
9
- typeof value === 'string' ||
10
- Array.isArray(value) ||
11
- isNode(value),
12
- message: 'expects Element, Node, selector or JSX node.',
13
- };
14
-
15
- const PARABOLA_OPTIONS_SCHEMA = {
16
- ball: {
17
- default: {
18
- color: 'var(--primary, #3e63dd)',
19
- size: '10px',
20
- },
21
- type: 'object',
22
- validate: (value) =>
23
- value &&
24
- Object.keys(value).length === 2 &&
25
- typeof value.color === 'string' &&
26
- typeof value.size === 'string',
27
- message:
28
- 'expects an object with two string properties: "color" and "size".',
29
- },
30
- from: { default: null, ...ELEMENT_REF_RULE },
31
- to: { default: null, ...ELEMENT_REF_RULE },
32
- direction: {
33
- default: 'center',
34
- type: 'string',
35
- enum: ['center', 'top-right', 'top-left', 'bottom-right', 'bottom-left'],
36
- },
37
- showDelay: { default: 0, type: 'number' },
38
- hideDelay: { default: 0, type: 'number' },
39
- onShow: { default: null, types: ['function', 'null'] },
40
- onHidden: { default: null, types: ['function', 'null'] },
41
- };
42
-
43
- /**
44
- * @typedef {object} ParabolaBallOptions
45
- * @property {string} color 小球颜色。
46
- * @property {string} size 小球尺寸,例如 `10px`。
47
- */
48
-
49
- /**
50
- * @typedef {object} ParabolaOptions
51
- * @property {ParabolaBallOptions} [ball] 小球样式配置。
52
- * @property {Element|Node|string|Array} from 起点元素、选择器或 JSX/h 返回节点。
53
- * @property {Element|Node|string|Array} to 终点元素、选择器或 JSX/h 返回节点。
54
- * @property {"center"|"top-right"|"top-left"|"bottom-right"|"bottom-left"} [direction="center"] 起点取样位置。
55
- * @property {number} [showDelay=0] 开始动画前的延迟,单位毫秒。
56
- * @property {number} [hideDelay=0] 预留隐藏延迟配置。
57
- * @property {Function|null} [onShow] 动画开始前回调。
58
- * @property {Function|null} [onHidden] 动画结束或销毁后回调。
59
- */
60
-
61
- /**
62
- * 抛物线动画组件。
63
- *
64
- * 用于实现“加入购物车”等从一个元素飞向另一个元素的小球动画。
65
- */
66
- export class Parabola {
67
- /**
68
- * 创建抛物线动画实例。
69
- * @param {ParabolaOptions} options 动画配置。
70
- */
71
- constructor(options) {
72
- requireRenderDOM('Parabola');
73
-
74
- this.options = resolveProps(
75
- options,
76
- PARABOLA_OPTIONS_SCHEMA,
77
- 'Parabola.options'
78
- );
79
- this._init(this.options);
80
- }
81
-
82
- /**
83
- * 初始化动画状态并创建小球。
84
- * @private
85
- * @param {ParabolaOptions} options 已归一化配置。
86
- * @returns {void}
87
- */
88
- _init(options) {
89
- this.hidden = false;
90
-
91
- this._ball = null;
92
- this._animationId = null;
93
-
94
- const fromEl = resolveElement(options.from, 'Parabola.from');
95
- const toEl = resolveElement(options.to, 'Parabola.to');
96
-
97
- this._fromEl = fromEl;
98
- this._toEl = toEl;
99
-
100
- this._createBall(options);
101
- }
102
-
103
- /**
104
- * 创建动画小球节点。
105
- * @private
106
- * @param {ParabolaOptions} options 已归一化配置。
107
- * @returns {void}
108
- */
109
- _createBall(options) {
110
- if (this.hidden || this._ball) return;
111
-
112
- const { color, size } = options.ball;
113
-
114
- this._ball = jsx('div', {
115
- className: 'parabola-ball',
116
- style: {
117
- backgroundColor: color,
118
- width: size,
119
- height: size,
120
- position: 'fixed',
121
- borderRadius: '50%',
122
- transform: 'translate(-50%, -50%)',
123
- pointerEvents: 'none',
124
- zIndex: '9999',
125
- opacity: '1',
126
- },
127
- });
128
- document.body.appendChild(this._ball);
129
- }
130
-
131
- /**
132
- * 计算动画起点和终点坐标。
133
- * @private
134
- * @param {ParabolaOptions} options 已归一化配置。
135
- * @returns {{startX:number,startY:number,endX:number,endY:number}|null}
136
- */
137
- _calculatePath(options) {
138
- const fromRect = this._fromEl?.getBoundingClientRect();
139
- const toRect = this._toEl?.getBoundingClientRect();
140
-
141
- if (!fromRect || !toRect) return null;
142
-
143
- const { left: fL, top: fT, width: fW, height: fH } = fromRect;
144
- const { left: tL, top: tT, width: tW, height: tH } = toRect;
145
-
146
- let startX, startY;
147
- switch (options.direction) {
148
- case 'top-left':
149
- startX = fL + fW * 0.2;
150
- startY = fT + fH * 0.2;
151
- break;
152
- case 'top-right':
153
- startX = fL + fW * 0.8;
154
- startY = fT + fH * 0.2;
155
- break;
156
- case 'bottom-left':
157
- startX = fL + fW * 0.2;
158
- startY = fT + fH * 0.8;
159
- break;
160
- case 'bottom-right':
161
- startX = fL + fW * 0.8;
162
- startY = fT + fH * 0.8;
163
- break;
164
- default: // 'center'
165
- startX = fL + fW / 2;
166
- startY = fT + fH / 2;
167
- }
168
-
169
- const endX = tL + tW / 2;
170
- const endY = tT + tH / 2;
171
-
172
- return { startX, startY, endX, endY };
173
- }
174
-
175
- /**
176
- * 三次缓出曲线。
177
- * @private
178
- * @param {number} t 动画进度,范围 0 到 1。
179
- * @returns {number}
180
- */
181
- _easeOutCubic(t) {
182
- return 1 - (1 - t) ** 3;
183
- }
184
-
185
- /**
186
- * 执行抛物线动画。
187
- * @private
188
- * @param {number} startX 起点 x 坐标。
189
- * @param {number} startY 起点 y 坐标。
190
- * @param {number} endX 终点 x 坐标。
191
- * @param {number} endY 终点 y 坐标。
192
- * @param {number} [duration=800] 动画时长,单位毫秒。
193
- * @returns {void}
194
- */
195
- _animate(startX, startY, endX, endY, duration = 800) {
196
- if (this.hidden) return;
197
-
198
- const startTime = performance.now();
199
- const deltaX = endX - startX;
200
- const deltaY = endY - startY;
201
- const peakOffset = -100;
202
-
203
- const step = (currentTime) => {
204
- if (this.hidden) return;
205
-
206
- const elapsed = currentTime - startTime;
207
- const progress = Math.min(elapsed / duration, 1);
208
- const eased = this._easeOutCubic(progress);
209
-
210
- const currentX = startX + deltaX * eased;
211
- const currentY =
212
- startY + peakOffset * Math.sin(Math.PI * eased) + deltaY * eased;
213
-
214
- const opacity = 1 - progress;
215
-
216
- this._ball.style.left = `${currentX}px`;
217
- this._ball.style.top = `${currentY}px`;
218
- this._ball.style.opacity = `${opacity}`;
219
- this._ball.style.transform = `translate(-50%, -50%) scale(${1 - eased * 0.3})`;
220
-
221
- if (progress < 1) {
222
- this._animationId = requestAnimationFrame(step);
223
- } else {
224
- this.destroy(); // 动画结束,直接销毁
225
- }
226
- };
227
-
228
- this._animationId = requestAnimationFrame(step);
229
- }
230
-
231
- /**
232
- * 开始播放动画。
233
- * @returns {Promise<boolean>} 成功开始动画时返回 true;元素缺失或已销毁时返回 false。
234
- */
235
- async show() {
236
- if (this.hidden) return false;
237
-
238
- return new Promise((resolve) => {
239
- timer.start('show', this.options.showDelay, () => {
240
- if (this.hidden) {
241
- resolve(false);
242
- return;
243
- }
244
-
245
- const path = this._calculatePath(this.options);
246
- if (!path) {
247
- this.destroy();
248
- resolve(false);
249
- return;
250
- }
251
-
252
- this._ball.style.left = `${path.startX}px`;
253
- this._ball.style.top = `${path.startY}px`;
254
-
255
- if (this.options.onShow) this.options.onShow();
256
-
257
- this._animate(path.startX, path.startY, path.endX, path.endY);
258
- resolve(true);
259
- });
260
- });
261
- }
262
-
263
- /**
264
- * 销毁动画实例并清理 DOM、定时器和动画帧。
265
- * @returns {void}
266
- */
267
- destroy() {
268
- if (this.hidden) return;
269
-
270
- this._ball?.remove();
271
-
272
- timer.cancel('show');
273
- if (this._animationId) cancelAnimationFrame(this._animationId);
274
-
275
- this._ball = null;
276
- this._fromEl = null;
277
- this._toEl = null;
278
-
279
- this.hidden = true;
280
-
281
- if (this.options.onHidden) this.options.onHidden();
282
- }
283
-
284
- /**
285
- * show 的语义化别名。
286
- * @returns {Promise<boolean>}
287
- */
288
- start() {
289
- return this.show();
290
- }
291
- }
292
-
293
- export function createParabola(options = {}) {
294
- return new Parabola(options);
295
- }