vanilla-jui 1.3.10 → 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 (90) 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/accordion.css +2 -2
  26. package/src/css/animation.css +31 -1
  27. package/src/css/breadcrumb.css +1 -1
  28. package/src/css/button.css +5 -5
  29. package/src/css/card.css +7 -7
  30. package/src/css/color.css +865 -1028
  31. package/src/css/color_p3.css +871 -1036
  32. package/src/css/components.css +1 -0
  33. package/src/css/drop.css +1 -1
  34. package/src/css/flow.css +1 -1
  35. package/src/css/form.css +21 -21
  36. package/src/css/gradient.css +17 -4
  37. package/src/css/icon.css +17 -1
  38. package/src/css/index.css +3 -3
  39. package/src/css/layout.css +1 -1
  40. package/src/css/menu.css +27 -27
  41. package/src/css/modal.css +10 -27
  42. package/src/css/offcanvas.css +2 -2
  43. package/src/css/pagination.css +1 -1
  44. package/src/css/popup.css +2 -2
  45. package/src/css/swiper.css +12 -12
  46. package/src/css/tabs.css +3 -2
  47. package/src/css/tag.css +3 -4
  48. package/src/css/theme-palette.css +6 -6
  49. package/src/css/tip.css +1 -1
  50. package/src/css/toast.css +8 -8
  51. package/src/css/tokens.css +197 -226
  52. package/src/css/toolbar.css +3 -3
  53. package/src/css/tooltip.css +1 -1
  54. package/src/index.ts +37 -0
  55. package/src/locales/{index.js → index.ts} +1 -2
  56. package/src/types.d.ts +1 -0
  57. package/src/utilities/browser.ts +131 -0
  58. package/src/utilities/cache.ts +57 -0
  59. package/src/utilities/core.ts +614 -0
  60. package/src/utilities/dom.ts +468 -0
  61. package/src/utilities/events.ts +106 -0
  62. package/src/utilities/http.ts +35 -0
  63. package/src/utilities/storage.ts +46 -0
  64. package/src/components/accordion.js +0 -373
  65. package/src/components/drop.js +0 -437
  66. package/src/components/flow.js +0 -1133
  67. package/src/components/form.js +0 -585
  68. package/src/components/menu.js +0 -422
  69. package/src/components/offcanvas.js +0 -332
  70. package/src/components/parabola.js +0 -295
  71. package/src/components/tabs.js +0 -746
  72. package/src/components/theme.js +0 -353
  73. package/src/components/toast.js +0 -329
  74. package/src/components/toc.js +0 -226
  75. package/src/components/tooltip.js +0 -86
  76. package/src/components/validator.js +0 -586
  77. package/src/css/toast.min.css +0 -1
  78. package/src/index.js +0 -45
  79. package/src/locales/index.min.js +0 -1
  80. package/src/utilities/browser.js +0 -111
  81. package/src/utilities/cache.js +0 -159
  82. package/src/utilities/core.js +0 -502
  83. package/src/utilities/debug.js +0 -103
  84. package/src/utilities/dom.js +0 -313
  85. package/src/utilities/events.js +0 -69
  86. package/src/utilities/http.js +0 -30
  87. package/src/utilities/legacy-dom.js +0 -179
  88. package/src/utilities/storage.js +0 -46
  89. /package/src/{components/components.js → css/themes/index.css} +0 -0
  90. /package/src/icons/{index.js → index.ts} +0 -0
@@ -1,373 +0,0 @@
1
- import {
2
- bindAttr,
3
- bindClass,
4
- createDeepStore,
5
- createRoot,
6
- flushSync,
7
- insert,
8
- jsx,
9
- } from 'vanilla-signal';
10
-
11
- import Component from '../core/Component.js';
12
- import { randomId, resolveProps, validateParam } from '../utilities/core.js';
13
- import {
14
- isRenderableContent,
15
- normalizeContentNodes,
16
- requireContainer,
17
- requireRenderDOM,
18
- } from '../utilities/dom.js';
19
- import { icon } from './icons.js';
20
-
21
- const ACCORDION_PROPS_SCHEMA = {
22
- id: {
23
- default: null,
24
- types: ['string', 'null'],
25
- normalize: (value) => {
26
- if (typeof value === 'string')
27
- return value.trim() ? value.trim() : randomId();
28
- if (value == null) return randomId();
29
- return value;
30
- },
31
- },
32
- active: { default: 0, types: ['number', 'string', 'array', 'null'] },
33
- collapsible: { default: false, type: 'boolean' },
34
- multiple: { default: false, type: 'boolean' },
35
- onChange: { default: null, types: ['function', 'null'] },
36
- items: { default: [], type: 'array' },
37
- };
38
-
39
- const ACCORDION_ITEMS_RULE = {
40
- type: 'array',
41
- validate: (value) => value.length > 0,
42
- message: 'expects a non-empty array.',
43
- };
44
-
45
- const ACCORDION_ACTIVE_RULE = {
46
- types: ['number', 'string', 'array', 'null'],
47
- validate: (value) => {
48
- if (value == null) return true;
49
- if (Array.isArray(value)) {
50
- return value.every(
51
- (item) => typeof item === 'number' || typeof item === 'string'
52
- );
53
- }
54
- if (typeof value === 'number') {
55
- return Number.isInteger(value) && value >= 0;
56
- }
57
- return value.trim().length > 0;
58
- },
59
- message: 'expects a positive number, string, array or null.',
60
- };
61
-
62
- function cloneItems(items) {
63
- return Array.isArray(items) ? items.map((item) => ({ ...item })) : [];
64
- }
65
-
66
- function normalizeItems(items) {
67
- validateParam('items', items, ACCORDION_ITEMS_RULE, 'Accordion');
68
-
69
- return items.map((item) => {
70
- if (!item || typeof item !== 'object' || Array.isArray(item)) {
71
- throw new Error('Accordion: item expects an object.');
72
- }
73
- if (item.name != null && typeof item.name !== 'string') {
74
- throw new Error('Accordion: item name expects a string.');
75
- }
76
- if (!isRenderableContent(item.title)) {
77
- throw new Error(
78
- 'Accordion: item title expects string, Node, array, function or null.'
79
- );
80
- }
81
- if (!isRenderableContent(item.content)) {
82
- throw new Error(
83
- 'Accordion: item content expects string, Node, array, function or null.'
84
- );
85
- }
86
- return { ...item, name: item.name || randomId() };
87
- });
88
- }
89
-
90
- /**
91
- * 轻量手风琴组件,继承 Component。
92
- *
93
- * DOM 创建一次,通过 createEffect 细粒度更新 class/ARIA。
94
- */
95
- export class Accordion extends Component {
96
- /**
97
- * @param {Element|Node|string|Array} container 挂载容器(元素、选择器或 JSX/h 返回节点)。
98
- * @param {object} [input={}] 手风琴配置。
99
- */
100
- constructor(container, input = {}) {
101
- requireRenderDOM('Accordion');
102
-
103
- const el = requireContainer(container, 'Accordion');
104
-
105
- const props = resolveProps(input, ACCORDION_PROPS_SCHEMA, 'Accordion');
106
- super(props);
107
-
108
- this.dom.container = el;
109
- this.dom.headers = [];
110
- this.dom.panels = [];
111
-
112
- this.state = createDeepStore({
113
- activeNames: [],
114
- current: { index: null, name: null },
115
- });
116
-
117
- try {
118
- this.onInit(props);
119
- } catch (error) {
120
- this.destroy();
121
- throw error;
122
- }
123
- }
124
-
125
- onInit(props) {
126
- this.root = this.buildRoot(props);
127
- this.buildItems(props);
128
- this.syncActiveNames(this.resolveActiveNames(props.active));
129
- this.bindEvents();
130
- }
131
-
132
- buildRoot(props) {
133
- return jsx('div', { className: 'j-accordion', id: props.id });
134
- }
135
-
136
- /**
137
- * 创建 DOM 节点(一次性)。
138
- * @private
139
- */
140
- buildItems(props) {
141
- const items = normalizeItems(props.items);
142
- const fragment = document.createDocumentFragment();
143
-
144
- this.dom.headers = [];
145
- this.dom.panels = [];
146
-
147
- items.forEach((item, index) => {
148
- const name = item.name || randomId();
149
- const headerId = `${props.id}_header_${index}`;
150
- const panelId = `${props.id}_panel_${index}`;
151
-
152
- const header = jsx('div', {
153
- className: 'accordion-header',
154
- 'data-item': name,
155
- id: headerId,
156
- role: 'button',
157
- tabindex: '0',
158
- 'aria-controls': panelId,
159
- children: [
160
- jsx('span', {
161
- className: 'header-title',
162
- children: this.contentView(item.title, item, index, 'title'),
163
- }),
164
- jsx('span', {
165
- className: 'header-arrow',
166
- 'aria-hidden': 'true',
167
- children: icon('arrow-down'),
168
- }),
169
- ],
170
- });
171
-
172
- const panel = jsx('div', {
173
- className: 'accordion-panel',
174
- id: panelId,
175
- role: 'region',
176
- 'aria-labelledby': headerId,
177
- children: jsx('div', {
178
- className: 'panel-content',
179
- children: this.contentView(item.content, item, index, 'content'),
180
- }),
181
- });
182
-
183
- this.dom.headers.push(header);
184
- this.dom.panels.push(panel);
185
- fragment.append(header, panel);
186
- });
187
-
188
- this.root.append(fragment);
189
-
190
- this.cleanup.bindings?.();
191
- this.cleanup.bindings = createRoot((dispose) => {
192
- this.dom.headers.forEach((header) => {
193
- const name = header.dataset.item;
194
- bindClass(header, 'is-active', () =>
195
- this.state.activeNames.includes(name)
196
- );
197
- bindAttr(header, 'aria-expanded', () =>
198
- this.state.activeNames.includes(name)
199
- );
200
- });
201
- this.dom.panels.forEach((panel, i) => {
202
- const name = this.dom.headers[i]?.dataset.item;
203
- bindClass(panel, 'is-active', () =>
204
- this.state.activeNames.includes(name)
205
- );
206
- bindAttr(panel, 'aria-hidden', () =>
207
- this.state.activeNames.includes(name) ? 'false' : 'true'
208
- );
209
- bindAttr(panel, 'hidden', () => !this.state.activeNames.includes(name));
210
- });
211
- return dispose;
212
- });
213
- }
214
-
215
- contentView(content, item, index, type) {
216
- return normalizeContentNodes(content, {
217
- accordion: this,
218
- item,
219
- index,
220
- type,
221
- active: false,
222
- });
223
- }
224
-
225
- resolveActiveNames(active) {
226
- if (active == null) return [];
227
- const values = Array.isArray(active) ? active : [active];
228
- const names = [];
229
- for (const value of values) {
230
- const index = this.getIndex(value);
231
- if (index < 0 || index >= this.dom.headers.length) continue;
232
- names.push(this.dom.headers[index].dataset.item || String(index));
233
- if (!this.props.multiple) break;
234
- }
235
- return Array.from(new Set(names));
236
- }
237
-
238
- syncActiveNames(names) {
239
- const firstName = names[0] || null;
240
- const index = firstName ? this.getIndex(firstName) : null;
241
- flushSync(() => {
242
- this.state.activeNames = names;
243
- this.state.current = { index, name: firstName };
244
- });
245
- }
246
-
247
- bindEvents() {
248
- this.unbindEvents();
249
-
250
- this.cleanup.events.on('click', this.root, 'click', (event) => {
251
- const header = event.target.closest('.accordion-header');
252
- if (!header) return;
253
- void this.active(header.dataset.item);
254
- });
255
-
256
- this.cleanup.events.on('keydown', this.root, 'keydown', (event) => {
257
- if (event.key !== 'Enter' && event.key !== ' ') return;
258
- const header = event.target.closest('.accordion-header');
259
- if (!header) return;
260
- event.preventDefault();
261
- void this.active(header.dataset.item);
262
- });
263
- }
264
-
265
- unbindEvents() {
266
- this.cleanup.events.clear();
267
- }
268
-
269
- isActive(name) {
270
- return this.state.activeNames.includes(name);
271
- }
272
-
273
- assertActive(method) {
274
- if (this.runtime.destroyed) {
275
- throw new Error(`Accordion.${method}: instance has been destroyed.`);
276
- }
277
- }
278
-
279
- async activateItem(val, fireEvent = true) {
280
- const index = this.getIndex(val);
281
- if (index < 0 || index >= this.dom.headers.length) return;
282
-
283
- const headerEl = this.dom.headers[index];
284
- const panelEl = this.dom.panels[index];
285
- const name = headerEl.dataset.item || String(index);
286
- const active = this.isActive(name);
287
-
288
- if (active && !this.props.multiple && !this.props.collapsible) return;
289
-
290
- let nextNames;
291
- if (this.props.multiple) {
292
- nextNames = active
293
- ? this.state.activeNames.filter((n) => n !== name)
294
- : [...this.state.activeNames, name];
295
- } else if (active) {
296
- nextNames = this.props.collapsible ? [] : this.state.activeNames;
297
- } else {
298
- nextNames = [name];
299
- }
300
-
301
- this.syncActiveNames(nextNames);
302
-
303
- if (fireEvent && this.props.onChange) {
304
- await Promise.resolve(
305
- this.props.onChange(index, name, headerEl, panelEl, this)
306
- );
307
- }
308
- }
309
-
310
- getIndex(val) {
311
- if (typeof val === 'number') return val;
312
- if (typeof val === 'string') {
313
- return this.dom.headers.findIndex(
314
- (header) => header.dataset.item === val
315
- );
316
- }
317
- return -1;
318
- }
319
-
320
- /**
321
- * 激活指定面板。
322
- * @param {number|string} val 面板索引或 `data-item` 名称。
323
- */
324
- async active(val) {
325
- this.assertActive('active');
326
- await this.activateItem(val, true);
327
- }
328
-
329
- /**
330
- * 将组件挂载到构造器指定的容器中。
331
- */
332
- build() {
333
- this.assertActive('build');
334
- insert(this.dom.container, () => this.root);
335
- }
336
-
337
- /**
338
- * 动态替换全部面板条目。
339
- * @param {AccordionItem[]} items 新面板配置。
340
- * @param {number|string|Array<number|string>|null} [active=0] 替换后默认激活项。
341
- */
342
- setItems(items, active = 0) {
343
- this.assertActive('setItems');
344
- validateParam('items', items, ACCORDION_ITEMS_RULE, 'Accordion.setItems');
345
- validateParam(
346
- 'active',
347
- active,
348
- ACCORDION_ACTIVE_RULE,
349
- 'Accordion.setItems'
350
- );
351
-
352
- this.props.items = cloneItems(normalizeItems(items));
353
- this.props.active = active;
354
-
355
- this.root.textContent = '';
356
- this.buildItems(this.props);
357
- this.syncActiveNames(this.resolveActiveNames(active));
358
- this.bindEvents();
359
- }
360
-
361
- onDestroy() {
362
- this.unbindEvents();
363
- this.cleanup.bindings?.();
364
- this.cleanup.bindings = null;
365
- if (this.root?.parentNode) {
366
- this.root.parentNode.removeChild(this.root);
367
- }
368
- }
369
- }
370
-
371
- export function createAccordion(container, input = {}) {
372
- return new Accordion(container, input);
373
- }