vanilla-jui 1.5.19 → 1.5.21

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vanilla-jui",
3
- "version": "1.5.19",
3
+ "version": "1.5.21",
4
4
  "private": false,
5
5
  "description": "A lightweight, reactive, and fully customizable vanilla JS UI library.",
6
6
  "keywords": [
@@ -13,8 +13,12 @@ import {
13
13
  type FunctionalComponent,
14
14
  defineComponent,
15
15
  } from '../core/component.ts';
16
- import { joinClasses } from '../utilities/class-name.ts';
17
- import { asRenderable, type RenderableContent, all } from '../utilities/dom.ts';
16
+ import {
17
+ all,
18
+ asRenderable,
19
+ joinClasses,
20
+ type RenderableContent,
21
+ } from '../utilities/dom.ts';
18
22
  import { randomId } from '../utilities/id.ts';
19
23
  import { isPlainObject } from '../utilities/object.ts';
20
24
  import {
@@ -16,7 +16,7 @@ import {
16
16
  } from '../core/component.ts';
17
17
  import locales from '../locales/index.ts';
18
18
  import { icon } from '../primitives/icons.ts';
19
- import { joinClasses } from '../utilities/class-name.ts';
19
+ import { joinClasses } from '../utilities/dom.ts';
20
20
  import { createEventManager } from '../utilities/events.ts';
21
21
  import { randomId } from '../utilities/id.ts';
22
22
  import {
@@ -16,10 +16,10 @@ import locales from '../locales/index.ts';
16
16
  import { icon } from '../primitives/icons.ts';
17
17
  import { createLoading } from '../primitives/loading.ts';
18
18
  import { createPopup } from '../primitives/popup.ts';
19
- import { joinClasses } from '../utilities/class-name.ts';
20
19
  import {
21
20
  all,
22
21
  asRenderable,
22
+ joinClasses,
23
23
  q,
24
24
  type RenderableContent,
25
25
  } from '../utilities/dom.ts';
@@ -12,7 +12,7 @@ import {
12
12
  defineComponent,
13
13
  } from '../core/component.ts';
14
14
  import { icon } from '../primitives/icons.ts';
15
- import { joinClasses } from '../utilities/class-name.ts';
15
+ import { joinClasses } from '../utilities/dom.ts';
16
16
  import { isPlainObject } from '../utilities/object.ts';
17
17
  import {
18
18
  type ResolveSchema,
@@ -13,10 +13,10 @@ import {
13
13
  } from '../core/component.ts';
14
14
  import { icon } from '../primitives/icons.ts';
15
15
  import { createLoading } from '../primitives/loading.ts';
16
- import { joinClasses } from '../utilities/class-name.ts';
17
16
  import {
18
17
  all,
19
18
  asRenderable,
19
+ joinClasses,
20
20
  q,
21
21
  type RenderableContent,
22
22
  } from '../utilities/dom.ts';
@@ -14,8 +14,11 @@ import {
14
14
  defineComponent,
15
15
  } from '../core/component.ts';
16
16
  import { createLoading } from '../primitives/loading.ts';
17
- import { joinClasses } from '../utilities/class-name.ts';
18
- import { asRenderable, type RenderableContent } from '../utilities/dom.ts';
17
+ import {
18
+ asRenderable,
19
+ joinClasses,
20
+ type RenderableContent,
21
+ } from '../utilities/dom.ts';
19
22
  import { createEventManager } from '../utilities/events.ts';
20
23
  import { randomId } from '../utilities/id.ts';
21
24
  import { createElementRef, createKeyedElementRefs } from '../utilities/refs.ts';
@@ -58,7 +61,7 @@ export type TabContent =
58
61
 
59
62
  export interface TabItem extends Record<string, unknown> {
60
63
  name?: string;
61
- title: Exclude<RenderableContent, (...args: never[]) => unknown>;
64
+ title: RenderableContent<TabsPanelContext>;
62
65
  content: TabContent;
63
66
  cache?: boolean;
64
67
  ttl?: number;
@@ -123,8 +126,6 @@ type TabsBase = FunctionalComponent<
123
126
 
124
127
  export type Tabs = TabsBase & {
125
128
  readonly current: TabsCurrent;
126
- readonly activeIndex: number;
127
- readonly disabledNames: string[];
128
129
  };
129
130
 
130
131
  type PointerDragEvent = MouseEvent | TouchEvent;
@@ -519,8 +520,18 @@ export function createTabs(input: TabsProps = {}): Tabs {
519
520
  }) as HTMLElement;
520
521
  };
521
522
 
522
- const tabView = (item: () => TabItem, _index: () => number): HTMLElement => {
523
+ const tabView = (item: () => TabItem, index: () => number): HTMLElement => {
523
524
  const name = item().name as string;
525
+ const title = (): RenderableContent<TabsPanelContext> => {
526
+ const value = item().title;
527
+ const context: TabsPanelContext = {
528
+ tabs: instance,
529
+ item: item(),
530
+ index: index(),
531
+ name,
532
+ };
533
+ return typeof value === 'function' ? value(context) : value;
534
+ };
524
535
  return jsx('div', {
525
536
  className: props.className.tab,
526
537
  'data-tabs-tab': name,
@@ -528,7 +539,7 @@ export function createTabs(input: TabsProps = {}): Tabs {
528
539
  'aria-selected': () => (isActive(name) ? 'true' : 'false'),
529
540
  'aria-disabled': () => (isDisabled(name) ? 'true' : 'false'),
530
541
  ref: tabs.bind(name),
531
- children: () => asRenderable(item().title),
542
+ children: () => asRenderable(title()),
532
543
  }) as HTMLElement;
533
544
  };
534
545
 
@@ -652,8 +663,6 @@ export function createTabs(input: TabsProps = {}): Tabs {
652
663
 
653
664
  Object.defineProperties(instance, {
654
665
  current: { enumerable: true, get: current },
655
- activeIndex: { enumerable: true, get: () => current().index },
656
- disabledNames: { enumerable: true, get: disabledNames },
657
666
  });
658
667
 
659
668
  return instance;
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  For,
3
- createMemo,
4
3
  createDeepStore,
4
+ createMemo,
5
5
  flushSync,
6
6
  jsx,
7
7
  } from 'vanilla-signal';
@@ -10,8 +10,12 @@ import {
10
10
  type FunctionalComponent,
11
11
  defineComponent,
12
12
  } from '../core/component.ts';
13
- import { joinClasses } from '../utilities/class-name.ts';
14
- import { type DOMReference, all, requireContainer } from '../utilities/dom.ts';
13
+ import {
14
+ type DOMReference,
15
+ all,
16
+ joinClasses,
17
+ requireContainer,
18
+ } from '../utilities/dom.ts';
15
19
  import { createEventManager } from '../utilities/events.ts';
16
20
  import { randomId } from '../utilities/id.ts';
17
21
  import { type ResolveSchema, resolveProps } from '../utilities/types.ts';
@@ -28,10 +28,6 @@
28
28
  border-right: var(--accordion-border);
29
29
  }
30
30
 
31
- .j-accordion .accordion-header[aria-expanded='true'] {
32
- border-bottom: 0;
33
- }
34
-
35
31
  .j-accordion[data-direction='horizontal']
36
32
  .accordion-header[aria-expanded='true'] {
37
33
  border-right: 0;
@@ -41,10 +37,6 @@
41
37
  --accordion-bg: var(--ui-surface-hover);
42
38
  }
43
39
 
44
- .j-accordion .accordion-header[aria-expanded='false']:nth-last-child(2) {
45
- border-bottom: 0;
46
- }
47
-
48
40
  .j-accordion[data-direction='horizontal']
49
41
  .accordion-header[aria-expanded='false']:nth-last-child(2) {
50
42
  border-right: 0;
@@ -99,6 +91,9 @@
99
91
  flex: 1;
100
92
  }
101
93
 
94
+ .j-accordion .accordion-header[aria-expanded='true'],
95
+ .j-accordion .accordion-header[aria-expanded='false']:nth-last-child(2),
96
+ .j-accordion .accordion-panel[aria-hidden='true'],
102
97
  .j-accordion .accordion-panel:last-child {
103
98
  border-bottom: 0;
104
99
  }
@@ -341,7 +341,7 @@
341
341
  position: relative;
342
342
  margin-block: 1rem;
343
343
  border-radius: var(--radius-md);
344
- border: 1px solid var(--content-border);
344
+ border: 1px solid var(--content-border-subtle);
345
345
  overflow: auto;
346
346
  scrollbar-width: thin;
347
347
  scrollbar-color: var(--ui-border) transparent;
@@ -356,10 +356,9 @@
356
356
  .j-code-editor .code-header {
357
357
  display: flex;
358
358
  align-items: center;
359
- justify-content: space-between;
360
- gap: 12px;
359
+ gap: 40px;
361
360
  padding: 0.5rem 1rem 0;
362
- font-size: 0.78rem;
361
+ font-size: 12px;
363
362
  color: var(--ui-fg-soft);
364
363
  }
365
364
 
@@ -159,6 +159,12 @@
159
159
  height: fit-content;
160
160
  }
161
161
 
162
+ .group {
163
+ display: flex;
164
+ gap: var(--group-gap, 0.5rem);
165
+ flex-wrap: wrap;
166
+ }
167
+
162
168
  /* 行容器 */
163
169
  .flex-container {
164
170
  display: flex;
package/src/css/tabs.css CHANGED
@@ -56,7 +56,7 @@
56
56
  display: none;
57
57
  margin: var(--panel-list-item-margin);
58
58
  position: relative;
59
- min-height: inherit;
59
+ min-height: 22px;
60
60
  }
61
61
 
62
62
  .j-tabs .panel-list .panel-item[aria-hidden='false'] {
@@ -4,7 +4,7 @@
4
4
  --tab-list-item-margin: 0;
5
5
  --tab-list-font-weight: var(--font-medium);
6
6
  --tab-wrap-border: 1px solid var(--ui-border-subtle);
7
- --panel-list-padding: var(--s2) var(--s3);
7
+ --panel-list-padding: var(--s3);
8
8
  --panel-list-margin: 0;
9
9
  --panel-list-item-margin: 0;
10
10
  }
@@ -1,7 +1,7 @@
1
1
  @layer themes {
2
2
  .j-tag {
3
3
  --tag-gap: var(--space);
4
- --tag-fg: var(--ui-fg);
4
+ --tag-fg: var(--ui-fg-muted);
5
5
  --tag-bg: var(--ui-bg);
6
6
  --tag-font-size: var(--font-size-sm);
7
7
  --tag-font-weight: var(--font-normal);
@@ -19,7 +19,7 @@
19
19
  --tag-padding: 0 var(--s2);
20
20
  }
21
21
 
22
- .j-tag.is-contrast {
22
+ .j-tag.is-reverse {
23
23
  --tag-fg: var(--ui-bg);
24
24
  --tag-bg: var(--ui-fg);
25
25
  }
@@ -73,7 +73,7 @@
73
73
  color: var(--ui-bg-subtle);
74
74
  }
75
75
 
76
- .j-tag.is-contrast .el-icon[data-action='close']:hover {
76
+ .j-tag.is-reverse .el-icon[data-action='close']:hover {
77
77
  color: var(--ui-fg);
78
78
  background: var(--ui-bg);
79
79
  }
@@ -153,14 +153,14 @@ export function createParabola(input: ParabolaProps = {}): ParabolaInstance {
153
153
  insert(document.body, root);
154
154
  };
155
155
  const removeRootIfIdle = (): void => {
156
- if (!runtime.destroyed || balls.size > 0) return;
156
+ if (balls.size > 0) return;
157
157
  root?.remove();
158
158
  root = null;
159
159
  };
160
160
  const removeBall = (ball: HTMLElement, notify: boolean): void => {
161
161
  if (!balls.has(ball)) return;
162
162
  const frame = frames.get(ball);
163
- if (frame) cancelAnimationFrame(frame);
163
+ if (frame !== undefined) cancelAnimationFrame(frame);
164
164
  frames.delete(ball);
165
165
  ball.remove();
166
166
  balls.delete(ball);
@@ -342,6 +342,5 @@ export function createParabola(input: ParabolaProps = {}): ParabolaInstance {
342
342
  removeRootIfIdle();
343
343
  },
344
344
  };
345
- createRoot();
346
345
  return parabola;
347
346
  }
@@ -10,8 +10,7 @@ import {
10
10
  import { t } from 'vanilla-signal-i18n';
11
11
 
12
12
  import locales from '../locales/index.ts';
13
- import { joinClasses } from '../utilities/class-name.ts';
14
- import { all } from '../utilities/dom.ts';
13
+ import { all, joinClasses } from '../utilities/dom.ts';
15
14
  import { createEventManager } from '../utilities/events.ts';
16
15
  import { isPlainObject } from '../utilities/object.ts';
17
16
 
@@ -11,8 +11,7 @@ import { t } from 'vanilla-signal-i18n';
11
11
  import locales from '../locales/index.ts';
12
12
  import { icon } from '../primitives/icons.ts';
13
13
  import { createLoading } from '../primitives/loading.ts';
14
- import { joinClasses } from '../utilities/class-name.ts';
15
- import { q } from '../utilities/dom.ts';
14
+ import { joinClasses, q } from '../utilities/dom.ts';
16
15
  import { listen } from '../utilities/events.ts';
17
16
  import { randomId } from '../utilities/id.ts';
18
17
  import { timer } from '../utilities/timer.ts';
@@ -52,6 +51,7 @@ export interface ToastThemeOptions extends ToastClassNameOptions {
52
51
 
53
52
  export interface ToastOptions extends ToastThemeOptions {
54
53
  duration?: number;
54
+ once?: boolean;
55
55
  loading?: MaybeAccessor<boolean>;
56
56
  text?: {
57
57
  loading?: string;
@@ -61,6 +61,7 @@ export interface ToastOptions extends ToastThemeOptions {
61
61
  }
62
62
 
63
63
  export interface ToastConfirmProps extends ToastThemeOptions {
64
+ once?: boolean;
64
65
  text?: {
65
66
  close?: string;
66
67
  confirm?: string;
@@ -98,6 +99,7 @@ const timers = new Set<string>();
98
99
  const disposers = new Map<HTMLElement, () => void>();
99
100
  const animations = new Map<HTMLElement, Animation>();
100
101
  const operations = new Map<HTMLElement, number>();
102
+ const singletonToasts = new Map<'show' | 'confirm', HTMLElement>();
101
103
  let classNames = DEFAULT_CLASS_NAMES;
102
104
 
103
105
  function mergeClassNames(value?: ToastClassNameConfig): ToastClassNames {
@@ -116,6 +118,30 @@ function addToastDisposer(element: HTMLElement, dispose: () => void): void {
116
118
  });
117
119
  }
118
120
 
121
+ function getSingletonToast(kind: 'show' | 'confirm'): HTMLElement | null {
122
+ const toast = singletonToasts.get(kind);
123
+ if (!toast) return null;
124
+ if (!toast.isConnected || toast.getAttribute('data-mount') === 'false') {
125
+ singletonToasts.delete(kind);
126
+ return null;
127
+ }
128
+ return toast;
129
+ }
130
+
131
+ function setSingletonToast(
132
+ kind: 'show' | 'confirm',
133
+ toast: HTMLElement,
134
+ once: boolean
135
+ ): void {
136
+ if (once) singletonToasts.set(kind, toast);
137
+ }
138
+
139
+ function clearSingletonToast(toast: HTMLElement): void {
140
+ for (const [kind, current] of singletonToasts) {
141
+ if (current === toast) singletonToasts.delete(kind);
142
+ }
143
+ }
144
+
119
145
  function isToastLoading(options: ToastOptions): boolean {
120
146
  return !!access(options.loading ?? false);
121
147
  }
@@ -230,6 +256,7 @@ function removeToastElement(element: HTMLElement): void {
230
256
  element.remove();
231
257
  disposers.get(element)?.();
232
258
  disposers.delete(element);
259
+ clearSingletonToast(element);
233
260
  const container = q<HTMLElement>('[data-toast-container]');
234
261
  if (container && container.children.length === 0) container.remove();
235
262
  }
@@ -343,9 +370,15 @@ function hide(toast: HTMLElement | null | undefined): void {
343
370
  }
344
371
 
345
372
  function show(message = '', options: ToastOptions = {}): HTMLElement {
373
+ const once = options.once ?? false;
374
+ if (once) {
375
+ const toast = getSingletonToast('show');
376
+ if (toast) return toast;
377
+ }
346
378
  validateParam('message', message, 'string', 'Toast.show');
347
379
  const duration = options.duration ?? 3000;
348
380
  const theme = options.theme || 'info';
381
+ validateParam('once', once, 'boolean', 'Toast.show');
349
382
  validateParam('duration', duration, TOAST_DURATION_RULE, 'Toast.show');
350
383
  validateParam('theme', theme, TOAST_THEME_RULE, 'Toast.show');
351
384
 
@@ -381,6 +414,7 @@ function show(message = '', options: ToastOptions = {}): HTMLElement {
381
414
  () => insert(getOrCreateContainer(names), toast),
382
415
  theme === 'error' ? 'assertive' : 'polite'
383
416
  );
417
+ setSingletonToast('show', toast, once);
384
418
  addToastDisposer(
385
419
  toast,
386
420
  listen(toast, 'click', () => void closeByUser(toast, options))
@@ -416,10 +450,16 @@ function lite(
416
450
  }
417
451
 
418
452
  function confirm(message = '', props: ToastConfirmProps = {}): HTMLElement {
453
+ const once = props.once ?? true;
454
+ if (once) {
455
+ const toast = getSingletonToast('confirm');
456
+ if (toast) return toast;
457
+ }
419
458
  validateParam('message', message, 'string', 'Toast.confirm');
420
459
  const closeText = props.text?.close || s('Close');
421
460
  const confirmText = props.text?.confirm || s('Confirm');
422
461
  const theme = props.theme || 'info';
462
+ validateParam('once', once, 'boolean', 'Toast.confirm');
423
463
  validateParam('theme', theme, TOAST_THEME_RULE, 'Toast.confirm');
424
464
  const names = resolveClassNames(props);
425
465
  const id = randomId();
@@ -469,6 +509,7 @@ function confirm(message = '', props: ToastConfirmProps = {}): HTMLElement {
469
509
  q<HTMLButtonElement>('[data-toast-button="confirm"]', element)?.focus();
470
510
  }
471
511
  );
512
+ setSingletonToast('confirm', element, once);
472
513
  return element;
473
514
  }
474
515
 
@@ -480,6 +521,7 @@ function clearAll(): void {
480
521
  for (const animation of animations.values()) animation.cancel();
481
522
  animations.clear();
482
523
  operations.clear();
524
+ singletonToasts.clear();
483
525
  q<HTMLElement>('[data-toast-container]')?.remove();
484
526
  q<HTMLElement>('[data-toast-lite]')?.remove();
485
527
  }
@@ -1,7 +1,6 @@
1
1
  import { jsx } from 'vanilla-signal';
2
2
 
3
- import { joinClasses } from '../utilities/class-name.ts';
4
- import { type DOMReference } from '../utilities/dom.ts';
3
+ import { type DOMReference, joinClasses } from '../utilities/dom.ts';
5
4
  import { type ResolveSchema, resolveProps } from '../utilities/types.ts';
6
5
  import { createDrop } from './drop.ts';
7
6
 
@@ -23,6 +23,7 @@ export type QueryContext = Document | DocumentFragment | Element;
23
23
  export type CleanupFunction = () => void;
24
24
  export type LazyRenderTarget = string | Element;
25
25
  export type LazyRenderCallback = () => void;
26
+ export type ClassNameToken = string | false | null | undefined;
26
27
  export type RenderableContent<TContext = unknown> =
27
28
  | Node
28
29
  | string
@@ -39,6 +40,10 @@ export function asRenderable<TContext>(
39
40
  return value as Renderable;
40
41
  }
41
42
 
43
+ export function joinClasses(...tokens: ClassNameToken[]): string {
44
+ return tokens.filter(Boolean).join(' ');
45
+ }
46
+
42
47
  export interface LazyRenderOptions {
43
48
  threshold?: number | number[];
44
49
  rootMargin?: string;
@@ -299,11 +304,6 @@ export function all<TElement extends Element = Element>(
299
304
  export function lazyRender(
300
305
  target: LazyRenderTarget,
301
306
  renderCallback: LazyRenderCallback,
302
- options?: LazyRenderOptions
303
- ): CleanupFunction;
304
- export function lazyRender(
305
- target: unknown,
306
- renderCallback: LazyRenderCallback,
307
307
  options: LazyRenderOptions = {}
308
308
  ): CleanupFunction {
309
309
  if (typeof renderCallback !== 'function') {
@@ -1,7 +1,11 @@
1
1
  import { jsx } from 'vanilla-signal';
2
2
 
3
- import { joinClasses } from '../utilities/class-name.ts';
4
- import { type DOMReference, all, resolveElement } from '../utilities/dom.ts';
3
+ import {
4
+ type DOMReference,
5
+ all,
6
+ joinClasses,
7
+ resolveElement,
8
+ } from '../utilities/dom.ts';
5
9
  import { createEventManager } from '../utilities/events.ts';
6
10
  import { isPlainObject } from '../utilities/object.ts';
7
11
  import {
@@ -1,5 +0,0 @@
1
- export type ClassNameToken = string | false | null | undefined;
2
-
3
- export function joinClasses(...tokens: ClassNameToken[]): string {
4
- return tokens.filter(Boolean).join(' ');
5
- }