sheer-ui 0.4.0 → 0.4.3

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 (96) hide show
  1. package/package.json +1 -1
  2. package/src/lib/components/accordion/components/accordion-item.svelte +3 -3
  3. package/src/lib/components/accordion/components/accordion.svelte +29 -13
  4. package/src/lib/components/accordion/types.ts +3 -3
  5. package/src/lib/components/avatar/avatar.svelte.ts +34 -37
  6. package/src/lib/components/avatar/components/avatar-fallback.svelte +9 -6
  7. package/src/lib/components/avatar/components/avatar-image.svelte +18 -9
  8. package/src/lib/components/avatar/components/avatar.svelte +20 -16
  9. package/src/lib/components/checkbox/checkbox.svelte.ts +45 -55
  10. package/src/lib/components/checkbox/components/checkbox-group-label.svelte +9 -6
  11. package/src/lib/components/checkbox/components/checkbox-group.svelte +25 -14
  12. package/src/lib/components/checkbox/components/checkbox.svelte +39 -21
  13. package/src/lib/components/combobox/components/combobox.svelte +2 -2
  14. package/src/lib/components/combobox/select/components/select.svelte +2 -2
  15. package/src/lib/components/context-menu/components/context-menu-trigger.svelte +1 -1
  16. package/src/lib/components/date-field/date-field.svelte.ts +2 -3
  17. package/src/lib/components/dialog/components/dialog-close.svelte +13 -8
  18. package/src/lib/components/dialog/components/dialog-content-headless.svelte +14 -11
  19. package/src/lib/components/dialog/components/dialog-description.svelte +9 -6
  20. package/src/lib/components/dialog/components/dialog-overlay.svelte +10 -7
  21. package/src/lib/components/dialog/components/dialog-title.svelte +12 -7
  22. package/src/lib/components/dialog/components/dialog-trigger.svelte +12 -7
  23. package/src/lib/components/dialog/components/dialog.svelte +10 -4
  24. package/src/lib/components/dialog/components/modal-surface.svelte +11 -8
  25. package/src/lib/components/dialog/dialog.svelte.ts +73 -61
  26. package/src/lib/components/drawer/drawer.svelte +8 -3
  27. package/src/lib/components/link-preview/components/link-preview-content.svelte +15 -8
  28. package/src/lib/components/link-preview/components/link-preview-trigger.svelte +9 -6
  29. package/src/lib/components/link-preview/components/link-preview.svelte +19 -11
  30. package/src/lib/components/link-preview/link-preview.svelte.ts +42 -43
  31. package/src/lib/components/menu/components/menu-checkbox-group.svelte +6 -4
  32. package/src/lib/components/menu/components/menu-checkbox-item.svelte +11 -7
  33. package/src/lib/components/menu/menu.svelte.ts +29 -19
  34. package/src/lib/components/menubar/menubar.svelte.ts +7 -6
  35. package/src/lib/components/meter/components/meter.svelte +18 -9
  36. package/src/lib/components/meter/meter.svelte.ts +15 -18
  37. package/src/lib/components/navigation-menu/components/navigation-menu-content.svelte +1 -1
  38. package/src/lib/components/navigation-menu/components/navigation-menu-indicator.svelte +2 -2
  39. package/src/lib/components/navigation-menu/components/navigation-menu-trigger.svelte +1 -1
  40. package/src/lib/components/navigation-menu/navigation-menu.svelte.ts +57 -60
  41. package/src/lib/components/pagination/components/pagination-next-button.svelte +12 -7
  42. package/src/lib/components/pagination/components/pagination-page.svelte +15 -8
  43. package/src/lib/components/pagination/components/pagination-prev-button.svelte +12 -7
  44. package/src/lib/components/pagination/components/pagination.svelte +31 -18
  45. package/src/lib/components/pagination/pagination.svelte.ts +48 -64
  46. package/src/lib/components/pin-input/pin-input.svelte.ts +7 -8
  47. package/src/lib/components/pin-input/usePasswordManager.svelte.ts +2 -2
  48. package/src/lib/components/popover/components/popover-close.svelte +9 -6
  49. package/src/lib/components/popover/components/popover-content.svelte +12 -7
  50. package/src/lib/components/popover/components/popover-trigger.svelte +21 -10
  51. package/src/lib/components/popover/components/popover.svelte +9 -8
  52. package/src/lib/components/popover/popover.svelte.ts +68 -74
  53. package/src/lib/components/progress/components/progress.svelte +18 -9
  54. package/src/lib/components/progress/progress.svelte.ts +14 -17
  55. package/src/lib/components/scroll-area/scroll-area.svelte.ts +9 -10
  56. package/src/lib/components/separator/components/separator.svelte +15 -8
  57. package/src/lib/components/separator/separator.svelte.ts +12 -15
  58. package/src/lib/components/sidebar/sidebar-mobile-surface.svelte +8 -4
  59. package/src/lib/components/slider/components/slider.svelte +2 -1
  60. package/src/lib/components/tabs/components/tabs-content.svelte +12 -7
  61. package/src/lib/components/tabs/components/tabs-list.svelte +9 -6
  62. package/src/lib/components/tabs/components/tabs-trigger.svelte +15 -8
  63. package/src/lib/components/tabs/components/tabs.svelte +28 -17
  64. package/src/lib/components/tabs/tabs.svelte.ts +51 -67
  65. package/src/lib/components/time-field/time-field.svelte.ts +2 -2
  66. package/src/lib/components/toggle/components/toggle.svelte +19 -14
  67. package/src/lib/components/toggle/toggle.svelte.ts +15 -20
  68. package/src/lib/components/toggle-group/components/toggle-group-item.svelte +15 -8
  69. package/src/lib/components/toggle-group/components/toggle-group.svelte +28 -16
  70. package/src/lib/components/toggle-group/toggle-group.svelte.ts +33 -35
  71. package/src/lib/components/toolbar/components/toolbar-group-item.svelte +15 -8
  72. package/src/lib/components/toolbar/components/toolbar-group.svelte +19 -14
  73. package/src/lib/components/toolbar/toolbar.svelte.ts +28 -30
  74. package/src/lib/components/tooltip/components/tooltip-content.svelte +15 -8
  75. package/src/lib/components/tooltip/components/tooltip-provider.svelte +18 -7
  76. package/src/lib/components/tooltip/components/tooltip-trigger.svelte +21 -10
  77. package/src/lib/components/tooltip/components/tooltip.svelte +34 -21
  78. package/src/lib/components/tooltip/tooltip.svelte.ts +101 -125
  79. package/src/lib/internal/dismissible-layer/use-dismissable-layer.svelte.ts +3 -4
  80. package/src/lib/internal/escape-layer/use-escape-layer.svelte.ts +13 -29
  81. package/src/lib/internal/floating-layer/use-floating-layer.svelte.ts +26 -30
  82. package/src/lib/internal/group-value.svelte.ts +11 -10
  83. package/src/lib/internal/native-popover.svelte.ts +6 -6
  84. package/src/lib/internal/{roving-focus-group.ts → roving-focus-group.svelte.ts} +17 -19
  85. package/src/lib/internal/roving-focus-item.svelte.ts +12 -17
  86. package/src/lib/internal/selection.svelte.ts +55 -41
  87. package/src/lib/internal/{state-machine.ts → state-machine.svelte.ts} +6 -8
  88. package/src/lib/internal/timeout-fn.svelte.ts +6 -4
  89. package/src/lib/internal/tools/box.svelte.ts +5 -114
  90. package/src/lib/internal/tools/index.ts +1 -11
  91. package/src/lib/internal/tools/types.ts +0 -3
  92. package/src/lib/internal/typeahead.svelte.ts +9 -13
  93. package/src/lib/internal/types.ts +6 -0
  94. package/src/lib/internal/vaul/use-drawer-content.svelte.ts +2 -2
  95. package/src/lib/internal/vaul/use-drawer-root.svelte.ts +12 -7
  96. package/src/lib/components/accordion/accordion.svelte.ts +0 -43
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sheer-ui",
3
- "version": "0.4.0",
3
+ "version": "0.4.3",
4
4
  "description": "Svelte 5 UI component library with Tailwind CSS",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -1,7 +1,7 @@
1
1
  <script lang="ts">
2
2
  import { join } from 'overrule';
3
3
  import { createSettleRunner } from '../../../internal/animations-settled.svelte.js';
4
- import { useAccordion } from '../accordion.svelte.js';
4
+ import { useAccordion } from './accordion.svelte';
5
5
  import type { AccordionItemProps } from '../types.js';
6
6
 
7
7
  let { value, disabled = false, class: className, ref = $bindable(null), children, ...restProps }: AccordionItemProps = $props();
@@ -46,12 +46,12 @@
46
46
  const summary = (e.target as HTMLElement).closest('summary');
47
47
  if (!summary || summary.closest('details') !== e.currentTarget) return;
48
48
  e.preventDefault();
49
- accordion.report(value, !open);
49
+ accordion.value = accordion.with(value, !open);
50
50
  }}
51
51
  ontoggle={(e) => {
52
52
  // Native toggles that bypass the click path (find-in-page, hash reveal).
53
53
  if (e.currentTarget.open === rendered) return;
54
- accordion.report(value, e.currentTarget.open);
54
+ accordion.value = accordion.with(value, e.currentTarget.open);
55
55
  }}
56
56
  bind:this={ref}>
57
57
  {@render children?.()}
@@ -1,21 +1,37 @@
1
+ <script lang="ts" module>
2
+ import { createContext } from 'svelte';
3
+ import { SelectionValue, emptySelection } from '../../../internal/selection.svelte.js';
4
+
5
+ export const [useAccordion, setAccordion] = createContext<SelectionValue>();
6
+ </script>
7
+
1
8
  <script lang="ts">
2
- import { AccordionState, setAccordion } from '../accordion.svelte.js';
9
+ import { repairBindable } from '../../../internal/tools/index.js';
3
10
  import type { AccordionRootProps } from '../types.js';
4
11
 
5
- let {
6
- type = 'single',
7
- value = $bindable(type === 'single' ? '' : []),
8
- ref = $bindable(null),
9
- children,
10
- ...restProps
11
- }: AccordionRootProps = $props();
12
+ let { type = 'single', value = $bindable(), ref = $bindable(null), children, ...restProps }: AccordionRootProps = $props();
13
+
14
+ // Mode is fixed at mount, like the selection groups: `value` keeps the shape it was declared with.
15
+ // svelte-ignore state_referenced_locally
16
+ const valueType = type;
17
+
18
+ repairBindable(
19
+ () => value,
20
+ () => {
21
+ if (value === undefined) value = emptySelection(valueType);
22
+ },
23
+ );
12
24
 
25
+ // Each item is a `<details>`; this holds the open set (bindable `value`) and single-type
26
+ // exclusivity — not the native `name` attribute, which would unrender a force-closed sibling
27
+ // before its closing transition can play. Items intercept summary clicks and report intent
28
+ // here; the grid-track animation lives in ui.css.
13
29
  setAccordion(
14
- new AccordionState({
15
- type: () => type,
16
- value: () => value ?? (type === 'single' ? '' : []),
17
- setValue: (v) => (value = v),
18
- }),
30
+ new SelectionValue(
31
+ valueType,
32
+ () => value ?? emptySelection(valueType),
33
+ (v) => (value = v),
34
+ ),
19
35
  );
20
36
  </script>
21
37
 
@@ -1,10 +1,10 @@
1
1
  import type { HTMLAttributes, SvelteHTMLElements } from 'svelte/elements';
2
2
  import type { WithElementRef } from '../../internal/utils.js';
3
- import type { AccordionType } from './accordion.svelte.js';
3
+ import type { SelectionType } from '../../internal/selection.svelte.js';
4
4
 
5
5
  export type AccordionRootProps = WithElementRef<HTMLAttributes<HTMLDivElement>, HTMLDivElement> & {
6
- /** 'single' groups the items' `<details>` by name so the browser enforces one-open. */
7
- type?: AccordionType;
6
+ /** 'single' keeps one item open: opening an item closes the open one. Fixed at mount. */
7
+ type?: SelectionType;
8
8
  /** The open item value(s) — string for single, string[] for multiple. Bindable. */
9
9
  value?: string | string[];
10
10
  };
@@ -1,8 +1,8 @@
1
1
  import { createContext, untrack } from 'svelte';
2
- import { DOMContext, type ReadableBox, type WritableBox, attachRef, type ReadableBoxedValues } from '../../internal/tools/index.js';
2
+ import { DOMContext, attachRef } from '../../internal/tools/index.js';
3
3
  import type { HTMLImgAttributes } from 'svelte/elements';
4
4
  import type { AvatarImageLoadingStatus } from './types.js';
5
- import type { RefAttachment, WithRefOpts } from '../../internal/types.js';
5
+ import type { RefAttachment, RefOpts } from '../../internal/types.js';
6
6
  import { createBitsAttrs } from '../../internal/attrs.js';
7
7
 
8
8
  const avatarAttrs = createBitsAttrs({
@@ -14,9 +14,9 @@ type CrossOrigin = HTMLImgAttributes['crossorigin'];
14
14
  type ReferrerPolicy = HTMLImgAttributes['referrerpolicy'];
15
15
  type AvatarImageSrc = string | null | undefined;
16
16
 
17
- interface AvatarRootStateOpts extends WithRefOpts {
18
- delayMs: ReadableBox<number>;
19
- loadingStatus: WritableBox<AvatarImageLoadingStatus>;
17
+ interface AvatarRootStateOpts extends RefOpts {
18
+ readonly delayMs: number;
19
+ loadingStatus: AvatarImageLoadingStatus;
20
20
  }
21
21
 
22
22
  const [getAvatarRoot, setAvatarRoot] = createContext<AvatarRootState>();
@@ -32,13 +32,13 @@ export class AvatarRootState {
32
32
 
33
33
  constructor(opts: AvatarRootStateOpts) {
34
34
  this.opts = opts;
35
- this.domContext = new DOMContext(this.opts.ref);
35
+ this.domContext = new DOMContext(() => opts.ref);
36
36
  this.loadImage = this.loadImage.bind(this);
37
- this.attachment = attachRef(this.opts.ref);
37
+ this.attachment = attachRef<HTMLElement>((v) => (opts.ref = v));
38
38
  }
39
39
 
40
40
  loadImage(src: string, crossorigin?: CrossOrigin, referrerPolicy?: ReferrerPolicy) {
41
- if (this.opts.loadingStatus.current === 'loaded') return;
41
+ if (this.opts.loadingStatus === 'loaded') return;
42
42
  let imageTimerId: number;
43
43
  const image = new Image();
44
44
 
@@ -46,14 +46,14 @@ export class AvatarRootState {
46
46
  if (crossorigin !== undefined) image.crossOrigin = crossorigin;
47
47
  if (referrerPolicy) image.referrerPolicy = referrerPolicy;
48
48
 
49
- this.opts.loadingStatus.current = 'loading';
49
+ this.opts.loadingStatus = 'loading';
50
50
  image.onload = () => {
51
51
  imageTimerId = this.domContext.setTimeout(() => {
52
- this.opts.loadingStatus.current = 'loaded';
53
- }, this.opts.delayMs.current);
52
+ this.opts.loadingStatus = 'loaded';
53
+ }, this.opts.delayMs);
54
54
  };
55
55
  image.onerror = () => {
56
- this.opts.loadingStatus.current = 'error';
56
+ this.opts.loadingStatus = 'error';
57
57
  };
58
58
  return () => {
59
59
  if (!imageTimerId) return;
@@ -64,22 +64,19 @@ export class AvatarRootState {
64
64
  props = $derived.by(
65
65
  () =>
66
66
  ({
67
- id: this.opts.id.current,
67
+ id: this.opts.id,
68
68
  [avatarAttrs.root]: '',
69
- 'data-status': this.opts.loadingStatus.current,
69
+ 'data-status': this.opts.loadingStatus,
70
70
  ...this.attachment,
71
71
  }) as const,
72
72
  );
73
73
  }
74
74
 
75
- interface AvatarImageStateOpts
76
- extends
77
- WithRefOpts,
78
- ReadableBoxedValues<{
79
- src: AvatarImageSrc;
80
- crossOrigin: CrossOrigin;
81
- referrerPolicy: ReferrerPolicy;
82
- }> {}
75
+ interface AvatarImageStateOpts extends RefOpts {
76
+ readonly src: AvatarImageSrc;
77
+ readonly crossOrigin: CrossOrigin;
78
+ readonly referrerPolicy: ReferrerPolicy;
79
+ }
83
80
 
84
81
  export class AvatarImageState {
85
82
  static create(opts: AvatarImageStateOpts) {
@@ -92,17 +89,17 @@ export class AvatarImageState {
92
89
  constructor(opts: AvatarImageStateOpts, root: AvatarRootState) {
93
90
  this.opts = opts;
94
91
  this.root = root;
95
- this.attachment = attachRef(this.opts.ref);
92
+ this.attachment = attachRef<HTMLElement>((v) => (opts.ref = v));
96
93
 
97
94
  $effect.pre(() => {
98
- const src = this.opts.src.current;
99
- const crossOrigin = this.opts.crossOrigin.current;
95
+ const src = this.opts.src;
96
+ const crossOrigin = this.opts.crossOrigin;
100
97
  untrack(() => {
101
98
  if (!src) {
102
- this.root.opts.loadingStatus.current = 'error';
99
+ this.root.opts.loadingStatus = 'error';
103
100
  return;
104
101
  }
105
- this.root.loadImage(src, crossOrigin, this.opts.referrerPolicy.current);
102
+ this.root.loadImage(src, crossOrigin, this.opts.referrerPolicy);
106
103
  });
107
104
  });
108
105
  }
@@ -110,21 +107,21 @@ export class AvatarImageState {
110
107
  readonly props = $derived.by(
111
108
  () =>
112
109
  ({
113
- id: this.opts.id.current,
110
+ id: this.opts.id,
114
111
  style: {
115
- display: this.root.opts.loadingStatus.current === 'loaded' ? 'block' : 'none',
112
+ display: this.root.opts.loadingStatus === 'loaded' ? 'block' : 'none',
116
113
  },
117
- 'data-status': this.root.opts.loadingStatus.current,
114
+ 'data-status': this.root.opts.loadingStatus,
118
115
  [avatarAttrs.image]: '',
119
- src: this.opts.src.current,
120
- crossorigin: this.opts.crossOrigin.current,
121
- referrerpolicy: this.opts.referrerPolicy.current,
116
+ src: this.opts.src,
117
+ crossorigin: this.opts.crossOrigin,
118
+ referrerpolicy: this.opts.referrerPolicy,
122
119
  ...this.attachment,
123
120
  }) as const,
124
121
  );
125
122
  }
126
123
 
127
- interface AvatarFallbackStateOpts extends WithRefOpts {}
124
+ interface AvatarFallbackStateOpts extends RefOpts {}
128
125
  export class AvatarFallbackState {
129
126
  static create(opts: AvatarFallbackStateOpts) {
130
127
  return new AvatarFallbackState(opts, getAvatarRoot());
@@ -137,16 +134,16 @@ export class AvatarFallbackState {
137
134
  constructor(opts: AvatarFallbackStateOpts, root: AvatarRootState) {
138
135
  this.opts = opts;
139
136
  this.root = root;
140
- this.attachment = attachRef(this.opts.ref);
137
+ this.attachment = attachRef<HTMLElement>((v) => (opts.ref = v));
141
138
  }
142
139
 
143
- readonly style = $derived.by(() => (this.root.opts.loadingStatus.current === 'loaded' ? { display: 'none' } : undefined));
140
+ readonly style = $derived.by(() => (this.root.opts.loadingStatus === 'loaded' ? { display: 'none' } : undefined));
144
141
 
145
142
  readonly props = $derived.by(
146
143
  () =>
147
144
  ({
148
145
  style: this.style,
149
- 'data-status': this.root.opts.loadingStatus.current,
146
+ 'data-status': this.root.opts.loadingStatus,
150
147
  [avatarAttrs.fallback]: '',
151
148
  ...this.attachment,
152
149
  }) as const,
@@ -1,5 +1,4 @@
1
1
  <script lang="ts">
2
- import { boxWith } from '../../../internal/tools/index.js';
3
2
  import { mergeProps } from '../../../internal/merge-props.js';
4
3
  import type { AvatarFallbackProps } from '../types.js';
5
4
  import { AvatarFallbackState } from '../avatar.svelte.js';
@@ -10,11 +9,15 @@
10
9
  let { children, child, id = createId(uid), ref = $bindable(null), ...restProps }: AvatarFallbackProps = $props();
11
10
 
12
11
  const fallbackState = AvatarFallbackState.create({
13
- id: boxWith(() => id),
14
- ref: boxWith(
15
- () => ref,
16
- (v) => (ref = v),
17
- ),
12
+ get id() {
13
+ return id;
14
+ },
15
+ get ref() {
16
+ return ref;
17
+ },
18
+ set ref(v) {
19
+ ref = v;
20
+ },
18
21
  });
19
22
 
20
23
  const mergedProps = $derived(
@@ -1,5 +1,4 @@
1
1
  <script lang="ts">
2
- import { boxWith } from '../../../internal/tools/index.js';
3
2
  import { mergeProps } from '../../../internal/merge-props.js';
4
3
  import type { AvatarImageProps } from '../types.js';
5
4
  import { AvatarImageState } from '../avatar.svelte.js';
@@ -18,14 +17,24 @@
18
17
  }: AvatarImageProps = $props();
19
18
 
20
19
  const imageState = AvatarImageState.create({
21
- src: boxWith(() => src),
22
- id: boxWith(() => id),
23
- ref: boxWith(
24
- () => ref,
25
- (v) => (ref = v),
26
- ),
27
- crossOrigin: boxWith(() => crossorigin),
28
- referrerPolicy: boxWith(() => referrerpolicy),
20
+ get src() {
21
+ return src;
22
+ },
23
+ get id() {
24
+ return id;
25
+ },
26
+ get crossOrigin() {
27
+ return crossorigin;
28
+ },
29
+ get referrerPolicy() {
30
+ return referrerpolicy;
31
+ },
32
+ get ref() {
33
+ return ref;
34
+ },
35
+ set ref(v) {
36
+ ref = v;
37
+ },
29
38
  });
30
39
 
31
40
  const mergedProps = $derived(
@@ -1,5 +1,4 @@
1
1
  <script lang="ts">
2
- import { boxWith } from '../../../internal/tools/index.js';
3
2
  import { mergeProps } from '../../../internal/merge-props.js';
4
3
  import type { AvatarRootProps } from '../types.js';
5
4
  import { AvatarRootState } from '../avatar.svelte.js';
@@ -19,21 +18,26 @@
19
18
  }: AvatarRootProps = $props();
20
19
 
21
20
  const rootState = AvatarRootState.create({
22
- delayMs: boxWith(() => delayMs),
23
- loadingStatus: boxWith(
24
- () => loadingStatus,
25
- (v) => {
26
- if (loadingStatus !== v) {
27
- loadingStatus = v;
28
- onLoadingStatusChange?.(v);
29
- }
30
- },
31
- ),
32
- id: boxWith(() => id),
33
- ref: boxWith(
34
- () => ref,
35
- (v) => (ref = v),
36
- ),
21
+ get delayMs() {
22
+ return delayMs;
23
+ },
24
+ get loadingStatus() {
25
+ return loadingStatus;
26
+ },
27
+ set loadingStatus(v) {
28
+ if (loadingStatus === v) return;
29
+ loadingStatus = v;
30
+ onLoadingStatusChange?.(v);
31
+ },
32
+ get id() {
33
+ return id;
34
+ },
35
+ get ref() {
36
+ return ref;
37
+ },
38
+ set ref(v) {
39
+ ref = v;
40
+ },
37
41
  });
38
42
 
39
43
  const mergedProps = $derived(
@@ -1,8 +1,8 @@
1
1
  import { createContext } from 'svelte';
2
2
  import { joinGroup } from '../../internal/group-value.svelte.js';
3
- import { attachRef, type ReadableBoxedValues, type WritableBoxedValues } from '../../internal/tools/index.js';
3
+ import { attachRef } from '../../internal/tools/index.js';
4
4
  import type { HTMLButtonAttributes } from 'svelte/elements';
5
- import type { BitsKeyboardEvent, BitsMouseEvent, RefAttachment, WithRefOpts } from '../../internal/types.js';
5
+ import type { BitsKeyboardEvent, BitsMouseEvent, RefAttachment, RefOpts } from '../../internal/types.js';
6
6
  import { boolToStr, createBitsAttrs, getAriaChecked, boolToEmptyStrOrUndef } from '../../internal/attrs.js';
7
7
  import { kbd } from '../../internal/kbd.js';
8
8
 
@@ -11,17 +11,12 @@ const checkboxAttrs = createBitsAttrs({
11
11
  parts: ['root', 'group', 'group-label', 'input'],
12
12
  });
13
13
 
14
- interface CheckboxGroupStateOpts
15
- extends
16
- WithRefOpts,
17
- ReadableBoxedValues<{
18
- disabled: boolean;
19
- required: boolean;
20
- readonly: boolean;
21
- }>,
22
- WritableBoxedValues<{
23
- value: string[];
24
- }> {}
14
+ interface CheckboxGroupStateOpts extends RefOpts {
15
+ readonly disabled: boolean;
16
+ readonly required: boolean;
17
+ readonly readonly: boolean;
18
+ value: string[];
19
+ }
25
20
 
26
21
  const [getCheckboxGroup, setCheckboxGroup, hasCheckboxGroup] = createContext<CheckboxGroupState>();
27
22
 
@@ -33,27 +28,27 @@ export class CheckboxGroupState {
33
28
  readonly opts: CheckboxGroupStateOpts;
34
29
  readonly attachment: RefAttachment;
35
30
  labelState = $state<CheckboxGroupLabelState | null>(null);
36
- readonly labelId = $derived.by(() => this.labelState?.opts.id.current);
31
+ readonly labelId = $derived.by(() => this.labelState?.opts.id);
37
32
 
38
33
  constructor(opts: CheckboxGroupStateOpts) {
39
34
  this.opts = opts;
40
- this.attachment = attachRef(this.opts.ref);
35
+ this.attachment = attachRef<HTMLElement>((v) => (opts.ref = v));
41
36
  }
42
37
 
43
38
  readonly props = $derived.by(
44
39
  () =>
45
40
  ({
46
- id: this.opts.id.current,
41
+ id: this.opts.id,
47
42
  role: 'group',
48
43
  'aria-labelledby': this.labelId,
49
- 'data-disabled': boolToEmptyStrOrUndef(this.opts.disabled.current),
44
+ 'data-disabled': boolToEmptyStrOrUndef(this.opts.disabled),
50
45
  [checkboxAttrs.group]: '',
51
46
  ...this.attachment,
52
47
  }) as const,
53
48
  );
54
49
  }
55
50
 
56
- interface CheckboxGroupLabelStateOpts extends WithRefOpts {}
51
+ interface CheckboxGroupLabelStateOpts extends RefOpts {}
57
52
 
58
53
  export class CheckboxGroupLabelState {
59
54
  static create(opts: CheckboxGroupLabelStateOpts) {
@@ -68,14 +63,14 @@ export class CheckboxGroupLabelState {
68
63
  this.opts = opts;
69
64
  this.group = group;
70
65
  this.group.labelState = this;
71
- this.attachment = attachRef(this.opts.ref);
66
+ this.attachment = attachRef<HTMLElement>((v) => (opts.ref = v));
72
67
  }
73
68
 
74
69
  readonly props = $derived.by(
75
70
  () =>
76
71
  ({
77
- id: this.opts.id.current,
78
- 'data-disabled': boolToEmptyStrOrUndef(this.group.opts.disabled.current),
72
+ id: this.opts.id,
73
+ 'data-disabled': boolToEmptyStrOrUndef(this.group.opts.disabled),
79
74
  [checkboxAttrs['group-label']]: '',
80
75
  ...this.attachment,
81
76
  }) as const,
@@ -84,20 +79,15 @@ export class CheckboxGroupLabelState {
84
79
 
85
80
  const [, setCheckboxRoot] = createContext<CheckboxRootState>();
86
81
 
87
- interface CheckboxRootStateOpts
88
- extends
89
- WithRefOpts,
90
- ReadableBoxedValues<{
91
- disabled: boolean;
92
- required: boolean;
93
- readonly: boolean;
94
- value: string | undefined;
95
- type: HTMLButtonAttributes['type'];
96
- }>,
97
- WritableBoxedValues<{
98
- checked: boolean;
99
- indeterminate: boolean;
100
- }> {}
82
+ interface CheckboxRootStateOpts extends RefOpts {
83
+ readonly disabled: boolean;
84
+ readonly required: boolean;
85
+ readonly readonly: boolean;
86
+ readonly value: string | undefined;
87
+ readonly type: HTMLButtonAttributes['type'];
88
+ checked: boolean;
89
+ indeterminate: boolean;
90
+ }
101
91
 
102
92
  export class CheckboxRootState {
103
93
  static create(opts: CheckboxRootStateOpts) {
@@ -109,33 +99,33 @@ export class CheckboxRootState {
109
99
  readonly group: CheckboxGroupState | null;
110
100
  readonly groupChecked: () => boolean | undefined;
111
101
  readonly trueRequired = $derived.by(() => {
112
- if (this.group && this.group.opts.required.current) return true;
113
- return this.opts.required.current;
102
+ if (this.group && this.group.opts.required) return true;
103
+ return this.opts.required;
114
104
  });
115
105
  readonly trueDisabled = $derived.by(() => {
116
- if (this.group && this.group.opts.disabled.current) return true;
117
- return this.opts.disabled.current;
106
+ if (this.group && this.group.opts.disabled) return true;
107
+ return this.opts.disabled;
118
108
  });
119
109
  readonly trueReadonly = $derived.by(() => {
120
- if (this.group && this.group.opts.readonly.current) return true;
121
- return this.opts.readonly.current;
110
+ if (this.group && this.group.opts.readonly) return true;
111
+ return this.opts.readonly;
122
112
  });
123
113
  readonly attachment: RefAttachment;
124
114
 
125
115
  constructor(opts: CheckboxRootStateOpts, group: CheckboxGroupState | null) {
126
116
  this.opts = opts;
127
117
  this.group = group;
128
- this.attachment = attachRef(this.opts.ref);
118
+ this.attachment = attachRef<HTMLElement>((v) => (opts.ref = v));
129
119
  this.onkeydown = this.onkeydown.bind(this);
130
120
  this.onclick = this.onclick.bind(this);
131
- this.groupChecked = joinGroup(group, opts);
121
+ this.groupChecked = joinGroup(group?.opts ?? null, opts);
132
122
  }
133
123
 
134
124
  onkeydown(e: BitsKeyboardEvent) {
135
125
  if (this.trueDisabled || this.trueReadonly) return;
136
126
  if (e.key === kbd.ENTER) {
137
127
  e.preventDefault();
138
- if (this.opts.type.current === 'submit') {
128
+ if (this.opts.type === 'submit') {
139
129
  const form = e.currentTarget.closest('form');
140
130
  form?.requestSubmit();
141
131
  }
@@ -148,17 +138,17 @@ export class CheckboxRootState {
148
138
  }
149
139
 
150
140
  #toggle() {
151
- if (this.opts.indeterminate.current) {
152
- this.opts.indeterminate.current = false;
153
- this.opts.checked.current = true;
141
+ if (this.opts.indeterminate) {
142
+ this.opts.indeterminate = false;
143
+ this.opts.checked = true;
154
144
  } else {
155
- this.opts.checked.current = !this.opts.checked.current;
145
+ this.opts.checked = !this.opts.checked;
156
146
  }
157
147
  }
158
148
 
159
149
  onclick(e: BitsMouseEvent) {
160
150
  if (this.trueDisabled || this.trueReadonly) return;
161
- if (this.opts.type.current === 'submit') {
151
+ if (this.opts.type === 'submit') {
162
152
  this.#toggle();
163
153
  return;
164
154
  }
@@ -167,23 +157,23 @@ export class CheckboxRootState {
167
157
  }
168
158
 
169
159
  readonly snippetProps = $derived.by(() => ({
170
- checked: this.opts.checked.current,
171
- indeterminate: this.opts.indeterminate.current,
160
+ checked: this.opts.checked,
161
+ indeterminate: this.opts.indeterminate,
172
162
  }));
173
163
 
174
164
  readonly props = $derived.by(
175
165
  () =>
176
166
  ({
177
- id: this.opts.id.current,
167
+ id: this.opts.id,
178
168
  role: 'checkbox',
179
- type: this.opts.type.current,
169
+ type: this.opts.type,
180
170
  disabled: this.trueDisabled,
181
- 'aria-checked': getAriaChecked(this.opts.checked.current, this.opts.indeterminate.current),
171
+ 'aria-checked': getAriaChecked(this.opts.checked, this.opts.indeterminate),
182
172
  'aria-required': boolToStr(this.trueRequired),
183
173
  'aria-readonly': boolToStr(this.trueReadonly),
184
174
  'data-disabled': boolToEmptyStrOrUndef(this.trueDisabled),
185
175
  'data-readonly': boolToEmptyStrOrUndef(this.trueReadonly),
186
- 'data-state': getCheckboxDataState(this.opts.checked.current, this.opts.indeterminate.current),
176
+ 'data-state': getCheckboxDataState(this.opts.checked, this.opts.indeterminate),
187
177
  [checkboxAttrs.root]: '',
188
178
  //
189
179
  onclick: this.onclick,
@@ -1,5 +1,4 @@
1
1
  <script lang="ts">
2
- import { boxWith } from '../../../internal/tools/index.js';
3
2
  import { mergeProps } from '../../../internal/merge-props.js';
4
3
  import type { CheckboxGroupLabelProps } from '../types.js';
5
4
  import { CheckboxGroupLabelState } from '../checkbox.svelte.js';
@@ -10,11 +9,15 @@
10
9
  let { ref = $bindable(null), id = createId(uid), child, children, ...restProps }: CheckboxGroupLabelProps = $props();
11
10
 
12
11
  const labelState = CheckboxGroupLabelState.create({
13
- id: boxWith(() => id),
14
- ref: boxWith(
15
- () => ref,
16
- (v) => (ref = v),
17
- ),
12
+ get id() {
13
+ return id;
14
+ },
15
+ get ref() {
16
+ return ref;
17
+ },
18
+ set ref(v) {
19
+ ref = v;
20
+ },
18
21
  });
19
22
 
20
23
  const mergedProps = $derived(mergeProps(restProps, labelState.props));
@@ -1,5 +1,4 @@
1
1
  <script lang="ts">
2
- import { bindableWith, boxWith } from '../../../internal/tools/index.js';
3
2
  import { mergeProps } from '../../../internal/merge-props.js';
4
3
  import type { CheckboxGroupProps } from '../types.js';
5
4
  import { CheckboxGroupState } from '../checkbox.svelte.js';
@@ -21,19 +20,31 @@
21
20
  }: CheckboxGroupProps = $props();
22
21
 
23
22
  const groupState = CheckboxGroupState.create({
24
- id: boxWith(() => id),
25
- ref: bindableWith(
26
- () => ref,
27
- (v) => (ref = v),
28
- ),
29
- disabled: boxWith(() => Boolean(disabled)),
30
- required: boxWith(() => Boolean(required)),
31
- readonly: boxWith(() => Boolean(readonly)),
32
- value: bindableWith(
33
- () => $state.snapshot(value),
34
- (v) => (value = $state.snapshot(v)),
35
- (v) => onValueChange(v),
36
- ),
23
+ get id() {
24
+ return id;
25
+ },
26
+ get disabled() {
27
+ return Boolean(disabled);
28
+ },
29
+ get required() {
30
+ return Boolean(required);
31
+ },
32
+ get readonly() {
33
+ return Boolean(readonly);
34
+ },
35
+ get value() {
36
+ return $state.snapshot(value);
37
+ },
38
+ set value(v) {
39
+ value = $state.snapshot(v);
40
+ onValueChange(v);
41
+ },
42
+ get ref() {
43
+ return ref;
44
+ },
45
+ set ref(v) {
46
+ ref = v;
47
+ },
37
48
  });
38
49
 
39
50
  const mergedProps = $derived(mergeProps(restProps, groupState.props));