vanilla-jui 1.3.8 → 1.3.10

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 (77) hide show
  1. package/dist/index.js +1 -1
  2. package/dist/index.umd.js +1 -1
  3. package/dist/style.css +1 -1
  4. package/package.json +3 -2
  5. package/src/components/accordion.js +373 -0
  6. package/src/components/components.js +0 -0
  7. package/src/components/drop.js +437 -0
  8. package/src/components/flow.js +1133 -0
  9. package/src/components/form.js +585 -0
  10. package/src/components/icons.js +83 -0
  11. package/src/components/menu.js +422 -0
  12. package/src/components/modal.js +1174 -0
  13. package/src/components/offcanvas.js +332 -0
  14. package/src/components/pagination.js +536 -0
  15. package/src/components/parabola.js +295 -0
  16. package/src/components/sticky.js +271 -0
  17. package/src/components/swiper.js +1123 -0
  18. package/src/components/tabs.js +746 -0
  19. package/src/components/theme.js +353 -0
  20. package/src/components/toast.js +329 -0
  21. package/src/components/toc.js +226 -0
  22. package/src/components/tooltip.js +86 -0
  23. package/src/components/validator.js +586 -0
  24. package/src/core/Component.js +248 -0
  25. package/src/css/accordion.css +72 -0
  26. package/src/css/animation.css +177 -0
  27. package/src/css/avatar.css +62 -0
  28. package/src/css/badge.css +66 -0
  29. package/src/css/breadcrumb.css +106 -0
  30. package/src/css/button.css +296 -0
  31. package/src/css/card.css +151 -0
  32. package/src/css/color.css +1176 -0
  33. package/src/css/color_p3.css +1184 -0
  34. package/src/css/components.css +26 -0
  35. package/src/css/content.css +716 -0
  36. package/src/css/divider.css +9 -0
  37. package/src/css/drop.css +28 -0
  38. package/src/css/flow.css +86 -0
  39. package/src/css/font.css +23 -0
  40. package/src/css/form.css +919 -0
  41. package/src/css/gradient.css +211 -0
  42. package/src/css/icon.css +63 -0
  43. package/src/css/index.css +12 -0
  44. package/src/css/layout.css +782 -0
  45. package/src/css/loading.css +38 -0
  46. package/src/css/menu.css +564 -0
  47. package/src/css/modal.css +111 -0
  48. package/src/css/offcanvas.css +104 -0
  49. package/src/css/pagination.css +25 -0
  50. package/src/css/popup.css +69 -0
  51. package/src/css/skeleton.css +76 -0
  52. package/src/css/status.css +56 -0
  53. package/src/css/swiper.css +160 -0
  54. package/src/css/table.css +52 -0
  55. package/src/css/tabs.css +95 -0
  56. package/src/css/tag.css +98 -0
  57. package/src/css/theme-palette.css +59 -0
  58. package/src/css/tip.css +84 -0
  59. package/src/css/toast.css +154 -0
  60. package/src/css/toast.min.css +1 -0
  61. package/src/css/toc.css +38 -0
  62. package/src/css/tokens.css +1324 -0
  63. package/src/css/toolbar.css +115 -0
  64. package/src/css/tooltip.css +19 -0
  65. package/src/icons/index.js +30 -0
  66. package/src/index.js +45 -0
  67. package/src/locales/index.js +45 -0
  68. package/src/locales/index.min.js +1 -0
  69. package/src/utilities/browser.js +111 -0
  70. package/src/utilities/cache.js +159 -0
  71. package/src/utilities/core.js +502 -0
  72. package/src/utilities/debug.js +103 -0
  73. package/src/utilities/dom.js +313 -0
  74. package/src/utilities/events.js +69 -0
  75. package/src/utilities/http.js +30 -0
  76. package/src/utilities/legacy-dom.js +179 -0
  77. package/src/utilities/storage.js +46 -0
@@ -0,0 +1,332 @@
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
+ }