vanilla-jui 1.3.11 → 1.4.1

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 (106) hide show
  1. package/dist/index.d.ts +2921 -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 +457 -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 +0 -45
  26. package/src/css/animation.css +31 -1
  27. package/src/css/avatar.css +7 -33
  28. package/src/css/badge.css +11 -59
  29. package/src/css/breadcrumb.css +9 -32
  30. package/src/css/button.css +23 -224
  31. package/src/css/card.css +1 -1
  32. package/src/css/color.css +863 -1013
  33. package/src/css/color_p3.css +822 -975
  34. package/src/css/components.css +1 -0
  35. package/src/css/drop.css +7 -7
  36. package/src/css/flow.css +1 -1
  37. package/src/css/form.css +1 -1
  38. package/src/css/icon.css +7 -12
  39. package/src/css/index.css +5 -2
  40. package/src/css/loading.css +36 -20
  41. package/src/css/menu.css +1 -1
  42. package/src/css/modal.css +15 -44
  43. package/src/css/offcanvas.css +14 -40
  44. package/src/css/popup.css +4 -4
  45. package/src/css/status.css +0 -2
  46. package/src/css/swiper.css +35 -50
  47. package/src/css/tabs.css +9 -31
  48. package/src/css/tag.css +16 -77
  49. package/src/css/themes/default/accordion.css +62 -0
  50. package/src/css/themes/default/avatar.css +39 -0
  51. package/src/css/themes/default/badge.css +63 -0
  52. package/src/css/themes/default/breadcrumb.css +28 -0
  53. package/src/css/themes/default/button.css +204 -0
  54. package/src/css/themes/default/drop.css +11 -0
  55. package/src/css/themes/default/index.css +16 -0
  56. package/src/css/themes/default/loading.css +23 -0
  57. package/src/css/themes/default/modal.css +40 -0
  58. package/src/css/themes/default/offcanvas.css +13 -0
  59. package/src/css/themes/default/popup.css +7 -0
  60. package/src/css/themes/default/swiper.css +35 -0
  61. package/src/css/themes/default/tabs.css +37 -0
  62. package/src/css/themes/default/tag.css +85 -0
  63. package/src/css/themes/default/tip.css +49 -0
  64. package/src/css/themes/default/toast.css +70 -0
  65. package/src/css/themes/default/tooltip.css +14 -0
  66. package/src/css/themes/index.css +1 -0
  67. package/src/css/tip.css +20 -39
  68. package/src/css/toast.css +18 -80
  69. package/src/css/tokens.css +8 -3
  70. package/src/css/tooltip.css +10 -10
  71. package/src/index.ts +37 -0
  72. package/src/locales/{index.js → index.ts} +1 -2
  73. package/src/types.d.ts +1 -0
  74. package/src/utilities/browser.ts +131 -0
  75. package/src/utilities/cache.ts +57 -0
  76. package/src/utilities/core.ts +614 -0
  77. package/src/utilities/dom.ts +468 -0
  78. package/src/utilities/events.ts +106 -0
  79. package/src/utilities/http.ts +35 -0
  80. package/src/utilities/storage.ts +46 -0
  81. package/src/components/accordion.js +0 -373
  82. package/src/components/components.js +0 -0
  83. package/src/components/drop.js +0 -437
  84. package/src/components/flow.js +0 -1133
  85. package/src/components/form.js +0 -585
  86. package/src/components/menu.js +0 -422
  87. package/src/components/offcanvas.js +0 -332
  88. package/src/components/parabola.js +0 -295
  89. package/src/components/tabs.js +0 -746
  90. package/src/components/theme.js +0 -353
  91. package/src/components/toast.js +0 -329
  92. package/src/components/toc.js +0 -226
  93. package/src/components/tooltip.js +0 -86
  94. package/src/components/validator.js +0 -586
  95. package/src/index.js +0 -45
  96. package/src/locales/index.min.js +0 -1
  97. package/src/utilities/browser.js +0 -111
  98. package/src/utilities/cache.js +0 -159
  99. package/src/utilities/core.js +0 -502
  100. package/src/utilities/debug.js +0 -103
  101. package/src/utilities/dom.js +0 -313
  102. package/src/utilities/events.js +0 -69
  103. package/src/utilities/http.js +0 -30
  104. package/src/utilities/legacy-dom.js +0 -179
  105. package/src/utilities/storage.js +0 -46
  106. /package/src/icons/{index.js → index.ts} +0 -0
@@ -1,585 +0,0 @@
1
- import {
2
- createDeepStore,
3
- createRoot,
4
- flushSync,
5
- jsx,
6
- onCleanup,
7
- render,
8
- } from 'vanilla-signal';
9
-
10
- import Component from '../core/Component.js';
11
- import {
12
- hasOwn,
13
- isPlainObject,
14
- randomId,
15
- resolveProps,
16
- validateParam,
17
- } from '../utilities/core.js';
18
- import {
19
- isRenderableContent,
20
- normalizeContentNodes,
21
- requireContainer,
22
- requireRenderDOM,
23
- } from '../utilities/dom.js';
24
- import { Validator } from './validator.js';
25
-
26
- const DEFAULT_BUTTONS = [
27
- { type: 'submit', text: 'Submit', theme: 'primary', action: 'submit' },
28
- { type: 'reset', text: 'Reset', theme: 'ghost', action: 'reset' },
29
- ];
30
-
31
- const FORM_PROPS_SCHEMA = {
32
- id: {
33
- default: null,
34
- types: ['string', 'null'],
35
- normalize: (value) => {
36
- if (typeof value === 'string')
37
- return value.trim() ? value.trim() : randomId();
38
- if (value == null) return randomId();
39
- return value;
40
- },
41
- },
42
- vertical: { default: true, type: 'boolean' },
43
- itemVertical: { default: true, type: 'boolean' },
44
- style: { default: '', types: ['string', 'object', 'null'] },
45
- fields: { default: [], type: 'array' },
46
- buttons: {
47
- default: DEFAULT_BUTTONS,
48
- types: ['boolean', 'array'],
49
- normalize: (value) => {
50
- if (value === false) return [];
51
- if (value === true) return cloneButtons(DEFAULT_BUTTONS);
52
- if (Array.isArray(value)) return cloneButtons(value);
53
- return value;
54
- },
55
- },
56
- validator: { default: {}, type: 'object' },
57
- onSubmit: { default: null, types: ['function', 'null'] },
58
- onReset: { default: null, types: ['function', 'null'] },
59
- };
60
-
61
- const FORM_UPDATE_RULE = {
62
- validate: (value) => isPlainObject(value),
63
- message: 'expects a props object.',
64
- };
65
-
66
- function cloneOptions(options) {
67
- if (!Array.isArray(options)) return options;
68
- return options.map((option) =>
69
- option && typeof option === 'object' ? { ...option } : option
70
- );
71
- }
72
-
73
- function cloneFields(fields) {
74
- if (!Array.isArray(fields)) return [];
75
- return fields.map((field) => ({
76
- ...field,
77
- options: cloneOptions(field.options),
78
- }));
79
- }
80
-
81
- function cloneButtons(buttons) {
82
- if (!Array.isArray(buttons)) return [];
83
- return buttons.map((button) => ({ ...button }));
84
- }
85
-
86
- function normalizeOption(option) {
87
- if (option && typeof option === 'object') return option;
88
- return { value: option, text: option };
89
- }
90
-
91
- function contentView(content, context) {
92
- if (!isRenderableContent(content)) return null;
93
- return normalizeContentNodes(content, context);
94
- }
95
-
96
- function fieldIsRequired(field, rules) {
97
- return !!field.required || !!rules?.[field.name]?.required;
98
- }
99
-
100
- function resolveClassName(...parts) {
101
- return parts.filter(Boolean).join(' ');
102
- }
103
-
104
- function setElementStyle(element, style) {
105
- element.removeAttribute('style');
106
- if (!style) return;
107
- if (typeof style === 'string') {
108
- element.style.cssText = style;
109
- return;
110
- }
111
- if (!isPlainObject(style)) return;
112
- for (const [key, value] of Object.entries(style)) {
113
- if (value == null) continue;
114
- const name = key.startsWith('--')
115
- ? key
116
- : key.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`);
117
- element.style.setProperty(name, String(value));
118
- }
119
- }
120
-
121
- function cloneValidator(validator) {
122
- const source = isPlainObject(validator) ? validator : {};
123
- return {
124
- ...source,
125
- rules: isPlainObject(source.rules) ? { ...source.rules } : {},
126
- messages: isPlainObject(source.messages) ? { ...source.messages } : {},
127
- };
128
- }
129
-
130
- export class Form extends Component {
131
- constructor(input = {}, container = false) {
132
- const props = resolveProps(input, FORM_PROPS_SCHEMA, 'Form.props');
133
- super(props);
134
-
135
- this.dom.container = container;
136
- this.dom.fields = new Map();
137
- this.validator = null;
138
- this.cleanup.view = null;
139
-
140
- this.cache = {
141
- initial: this.cloneProps(props),
142
- fieldIds: new Map(),
143
- };
144
-
145
- this.state = createDeepStore({
146
- ...this.cloneProps(props),
147
- submitting: false,
148
- data: null,
149
- });
150
- }
151
-
152
- onInit() {
153
- this.mount(this.dom.container);
154
- }
155
-
156
- build(container = this.dom.container) {
157
- if (this.runtime.destroyed)
158
- throw new Error('Form.build: instance destroyed');
159
- if (this.cleanup.view) return this;
160
-
161
- requireRenderDOM('Form');
162
- this.dom.container =
163
- container === false
164
- ? document.createDocumentFragment()
165
- : requireContainer(container, 'Form.container');
166
- this.init(this.props);
167
- return this;
168
- }
169
-
170
- mount(container = this.dom.container) {
171
- if (this.cleanup.view) return this;
172
-
173
- this.cleanup.view = createRoot((dispose) => {
174
- const viewDispose = render(() => this.view(), container);
175
- onCleanup(viewDispose);
176
- return dispose;
177
- });
178
-
179
- return this;
180
- }
181
-
182
- view() {
183
- return jsx('form', {
184
- className: () =>
185
- resolveClassName(
186
- 'j-form',
187
- this.state.vertical ? 'is-vertical' : 'is-horizontal',
188
- this.state.itemVertical ? 'is-item-vertical' : 'is-item-horizontal'
189
- ),
190
- id: () => this.state.id,
191
- style: () => this.state.style,
192
- ref: (element) => {
193
- const changed = this.root !== element;
194
- this.root = element;
195
- setElementStyle(element, this.state.style);
196
- if (changed || !this.validator) this.syncValidator();
197
- },
198
- onSubmit: (event) => this.handleSubmit(event),
199
- onReset: (event) => this.handleReset(event),
200
- children: () => [
201
- ...this.state.fields.map((field, index) =>
202
- this.fieldView(field, index)
203
- ),
204
- this.buttonsView(),
205
- ],
206
- });
207
- }
208
-
209
- fieldView(field, index) {
210
- const id = this.resolveFieldId(field, index);
211
- const hidden = field.type === 'hidden';
212
-
213
- return jsx('div', {
214
- className: 'form-item',
215
- style: { display: hidden ? 'none' : '' },
216
- children: [
217
- this.labelView(field, id),
218
- jsx('div', {
219
- className: 'form-control',
220
- children: [
221
- this.controlView(field, id, index),
222
- field.help
223
- ? jsx('div', { className: 'help-block', children: field.help })
224
- : null,
225
- ],
226
- }),
227
- ],
228
- });
229
- }
230
-
231
- labelView(field, id) {
232
- if (field.label === false || field.label === undefined) return null;
233
-
234
- return jsx('label', {
235
- className: resolveClassName(
236
- 'item-label',
237
- fieldIsRequired(field, this.state.validator?.rules) ? 'is-required' : ''
238
- ),
239
- for: id,
240
- children: field.label,
241
- });
242
- }
243
-
244
- controlView(field, id, index) {
245
- switch (field.type) {
246
- case 'textarea':
247
- return this.textareaView(field, id);
248
- case 'select':
249
- return this.selectView(field, id);
250
- case 'radio':
251
- return this.choiceGroupView(field, id, 'radio');
252
- case 'checkbox':
253
- return Array.isArray(field.options)
254
- ? this.choiceGroupView(field, id, 'checkbox')
255
- : this.inputView(field, id);
256
- case 'switch':
257
- return this.switchView(field, id);
258
- case 'custom':
259
- return contentView(field.content, { form: this, field, index });
260
- default:
261
- return this.inputView(field, id);
262
- }
263
- }
264
-
265
- inputView(field, id) {
266
- const type = field.type || 'text';
267
- const props = this.controlProps(field, id, {
268
- type,
269
- className: field.className || 'j-input',
270
- autocomplete: field.autocomplete || this.autoComplete(type),
271
- value: field.value ?? '',
272
- });
273
-
274
- if (field.checked !== undefined) props.checked = !!field.checked;
275
- return jsx('input', props);
276
- }
277
-
278
- textareaView(field, id) {
279
- return jsx(
280
- 'textarea',
281
- this.controlProps(field, id, {
282
- className: field.className || 'j-textarea',
283
- autocomplete: field.autocomplete,
284
- value: field.value ?? '',
285
- })
286
- );
287
- }
288
-
289
- selectView(field, id) {
290
- const value = field.value;
291
- return jsx(
292
- 'select',
293
- this.controlProps(field, id, {
294
- className: field.className || 'j-select',
295
- autocomplete: field.autocomplete || 'off',
296
- multiple: !!field.multiple,
297
- children: (field.options || []).map((option) => {
298
- const item = normalizeOption(option);
299
- return jsx('option', {
300
- value: item.value ?? '',
301
- disabled: !!item.disabled,
302
- selected: this.isSelected(value, item.value),
303
- children: item.text ?? item.label ?? item.value ?? '',
304
- });
305
- }),
306
- })
307
- );
308
- }
309
-
310
- choiceGroupView(field, id, type) {
311
- const direction = field.vertical ? 'vertical' : 'horizontal';
312
- const className = resolveClassName(
313
- type === 'radio' ? 'j-radio' : 'j-checkbox',
314
- `is-${direction}`,
315
- field.group ? 'is-group' : '',
316
- field.size ? `is-${field.size}` : ''
317
- );
318
-
319
- return jsx('div', {
320
- className,
321
- children: (field.options || []).map((option, optionIndex) => {
322
- const item = normalizeOption(option);
323
- const optionId = `${id}_${optionIndex}`;
324
- return jsx('label', {
325
- className: type === 'radio' ? 'radio-label' : '',
326
- for: optionId,
327
- children: [
328
- jsx('input', {
329
- type,
330
- id: optionId,
331
- name: field.name,
332
- value: item.value ?? '',
333
- checked:
334
- type === 'radio'
335
- ? this.isSelected(field.value, item.value)
336
- : this.isChecked(field.value, item.value, item.checked),
337
- disabled: !!item.disabled || !!field.disabled,
338
- required: !!field.required,
339
- }),
340
- jsx('span', {
341
- className: type === 'radio' ? 'radio-text' : '',
342
- children: item.text ?? item.label ?? item.value ?? '',
343
- }),
344
- ],
345
- });
346
- }),
347
- });
348
- }
349
-
350
- switchView(field, id) {
351
- return jsx('label', {
352
- className: resolveClassName(
353
- 'j-switch',
354
- field.variant ? `is-${field.variant}` : 'is-default',
355
- field.size ? `is-${field.size}` : 'is-md'
356
- ),
357
- for: id,
358
- children: [
359
- jsx('input', {
360
- type: 'checkbox',
361
- id,
362
- name: field.name,
363
- value: field.value ?? 'on',
364
- checked: !!field.checked,
365
- disabled: !!field.disabled,
366
- required: !!field.required,
367
- }),
368
- jsx('span', { className: 'switch-slider' }),
369
- ],
370
- });
371
- }
372
-
373
- buttonsView() {
374
- if (!this.state.buttons.length) return null;
375
-
376
- return jsx('div', {
377
- className: 'form-buttons',
378
- children: this.state.buttons.map((button) =>
379
- jsx('button', {
380
- type: button.type || 'button',
381
- className: resolveClassName(
382
- 'j-button',
383
- button.theme ? `is-${button.theme}` : '',
384
- button.className || ''
385
- ),
386
- 'data-action': button.action || button.type || 'button',
387
- disabled: () => !!this.state.submitting || !!button.disabled,
388
- children: button.text ?? button.label ?? '',
389
- })
390
- ),
391
- });
392
- }
393
-
394
- controlProps(field, id, extra = {}) {
395
- return {
396
- ...extra,
397
- name: field.name,
398
- id,
399
- placeholder: field.placeholder || '',
400
- required: !!field.required,
401
- disabled: !!field.disabled,
402
- readonly: !!field.readonly,
403
- ref: (element) => {
404
- if (field.name) this.dom.fields.set(field.name, element);
405
- },
406
- };
407
- }
408
-
409
- resolveFieldId(field, index) {
410
- if (field.id) return field.id;
411
-
412
- const key = field.name || index;
413
- if (!this.cache.fieldIds.has(key)) {
414
- this.cache.fieldIds.set(key, `${this.state.id}_field_${index}`);
415
- }
416
- return this.cache.fieldIds.get(key);
417
- }
418
-
419
- syncValidator() {
420
- if (!this.root) return;
421
- const options = {
422
- ...this.state.validator,
423
- onSubmit: null,
424
- };
425
-
426
- if (this.validator?.root === this.root) {
427
- this.validator.options = options;
428
- return;
429
- }
430
-
431
- this.validator?.destroy();
432
- this.validator = new Validator(this.root, options, false);
433
- }
434
-
435
- validate() {
436
- if (!this.validator) return true;
437
- return this.validator.validate();
438
- }
439
-
440
- reset() {
441
- this.validator?.reset();
442
- flushSync(() => {
443
- this.state.data = null;
444
- });
445
- return this;
446
- }
447
-
448
- async handleSubmit(event) {
449
- event.preventDefault();
450
- if (this.state.submitting) return;
451
- if (!this.validate()) return;
452
-
453
- const data = this.collectData();
454
- flushSync(() => {
455
- this.state.submitting = true;
456
- this.state.data = data;
457
- });
458
-
459
- try {
460
- await Promise.resolve(this.state.onSubmit?.(data, this));
461
- } finally {
462
- if (this.state) {
463
- flushSync(() => {
464
- this.state.submitting = false;
465
- });
466
- }
467
- }
468
- }
469
-
470
- handleReset(event) {
471
- this.validator?.reset();
472
- flushSync(() => {
473
- this.state.data = null;
474
- });
475
- this.state.onReset?.(event, this);
476
- }
477
-
478
- collectData() {
479
- if (!this.root) return {};
480
- return this.collectFormData(this.root);
481
- }
482
-
483
- collectFormData(form) {
484
- const data = {};
485
- const formData = new FormData(form);
486
-
487
- for (const [key, value] of formData.entries()) {
488
- if (hasOwn(data, key)) {
489
- data[key] = Array.isArray(data[key])
490
- ? [...data[key], value]
491
- : [data[key], value];
492
- } else {
493
- data[key] = value;
494
- }
495
- }
496
-
497
- return data;
498
- }
499
-
500
- requestSubmit() {
501
- if (!this.root) return this;
502
- if (typeof this.root.requestSubmit === 'function') {
503
- this.root.requestSubmit();
504
- return this;
505
- }
506
-
507
- const event = new Event('submit', { bubbles: true, cancelable: true });
508
- this.root.dispatchEvent(event);
509
- return this;
510
- }
511
-
512
- update(patch = {}) {
513
- validateParam('props', patch, FORM_UPDATE_RULE, 'Form.update');
514
- const nextProps = resolveProps(
515
- Object.assign({}, this.props, patch),
516
- FORM_PROPS_SCHEMA,
517
- 'Form.props'
518
- );
519
-
520
- this.props = this.cloneProps(nextProps);
521
- this.cache.fieldIds.clear();
522
- super.update(patch);
523
-
524
- flushSync(() => {
525
- for (const [key, value] of Object.entries(this.cloneProps(nextProps))) {
526
- this.state[key] = value;
527
- }
528
- });
529
- return this;
530
- }
531
-
532
- setFields(fields) {
533
- validateParam('fields', fields, 'array', 'Form.setFields');
534
- return this.update({ fields });
535
- }
536
-
537
- resetFields() {
538
- return this.setFields(this.cache.initial.fields);
539
- }
540
-
541
- cloneProps(props) {
542
- return {
543
- ...props,
544
- fields: cloneFields(props.fields),
545
- buttons: cloneButtons(props.buttons),
546
- validator: cloneValidator(props.validator),
547
- };
548
- }
549
-
550
- autoComplete(type) {
551
- switch (type) {
552
- case 'password':
553
- return 'current-password';
554
- case 'email':
555
- return 'email';
556
- default:
557
- return 'on';
558
- }
559
- }
560
-
561
- isSelected(value, optionValue) {
562
- if (Array.isArray(value))
563
- return value.map(String).includes(String(optionValue));
564
- return value == optionValue;
565
- }
566
-
567
- isChecked(value, optionValue, checked) {
568
- if (checked !== undefined) return !!checked;
569
- if (Array.isArray(value))
570
- return value.map(String).includes(String(optionValue));
571
- return value === true || String(value) === String(optionValue);
572
- }
573
-
574
- onDestroy() {
575
- this.validator?.destroy();
576
- this.validator = null;
577
- this.cleanup.view?.();
578
- this.cleanup.view = null;
579
- this.cache = { initial: null, fieldIds: null };
580
- }
581
- }
582
-
583
- export function createForm(props = {}, container = false) {
584
- return new Form(props, container);
585
- }