sheer-ui 0.4.2 → 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 (46) hide show
  1. package/package.json +1 -1
  2. package/src/lib/components/avatar/avatar.svelte.ts +34 -37
  3. package/src/lib/components/avatar/components/avatar-fallback.svelte +9 -6
  4. package/src/lib/components/avatar/components/avatar-image.svelte +18 -9
  5. package/src/lib/components/avatar/components/avatar.svelte +20 -16
  6. package/src/lib/components/checkbox/checkbox.svelte.ts +45 -55
  7. package/src/lib/components/checkbox/components/checkbox-group-label.svelte +9 -6
  8. package/src/lib/components/checkbox/components/checkbox-group.svelte +25 -16
  9. package/src/lib/components/checkbox/components/checkbox.svelte +39 -25
  10. package/src/lib/components/dialog/components/dialog-close.svelte +13 -8
  11. package/src/lib/components/dialog/components/dialog-content-headless.svelte +14 -11
  12. package/src/lib/components/dialog/components/dialog-description.svelte +9 -6
  13. package/src/lib/components/dialog/components/dialog-overlay.svelte +10 -7
  14. package/src/lib/components/dialog/components/dialog-title.svelte +12 -7
  15. package/src/lib/components/dialog/components/dialog-trigger.svelte +12 -7
  16. package/src/lib/components/dialog/components/dialog.svelte +10 -4
  17. package/src/lib/components/dialog/components/modal-surface.svelte +11 -8
  18. package/src/lib/components/dialog/dialog.svelte.ts +73 -61
  19. package/src/lib/components/drawer/drawer.svelte +8 -3
  20. package/src/lib/components/link-preview/components/link-preview-content.svelte +15 -8
  21. package/src/lib/components/link-preview/components/link-preview-trigger.svelte +9 -6
  22. package/src/lib/components/link-preview/components/link-preview.svelte +19 -11
  23. package/src/lib/components/link-preview/link-preview.svelte.ts +42 -43
  24. package/src/lib/components/menu/menu.svelte.ts +20 -1
  25. package/src/lib/components/meter/components/meter.svelte +18 -9
  26. package/src/lib/components/meter/meter.svelte.ts +15 -18
  27. package/src/lib/components/popover/components/popover-close.svelte +9 -6
  28. package/src/lib/components/popover/components/popover-content.svelte +12 -7
  29. package/src/lib/components/popover/components/popover-trigger.svelte +21 -10
  30. package/src/lib/components/popover/components/popover.svelte +9 -8
  31. package/src/lib/components/popover/popover.svelte.ts +68 -74
  32. package/src/lib/components/progress/components/progress.svelte +18 -9
  33. package/src/lib/components/progress/progress.svelte.ts +14 -17
  34. package/src/lib/components/separator/components/separator.svelte +15 -8
  35. package/src/lib/components/separator/separator.svelte.ts +12 -15
  36. package/src/lib/components/sidebar/sidebar-mobile-surface.svelte +8 -4
  37. package/src/lib/components/toggle/components/toggle.svelte +19 -14
  38. package/src/lib/components/toggle/toggle.svelte.ts +15 -20
  39. package/src/lib/components/tooltip/components/tooltip-content.svelte +15 -8
  40. package/src/lib/components/tooltip/components/tooltip-provider.svelte +18 -7
  41. package/src/lib/components/tooltip/components/tooltip-trigger.svelte +21 -10
  42. package/src/lib/components/tooltip/components/tooltip.svelte +34 -21
  43. package/src/lib/components/tooltip/tooltip.svelte.ts +96 -99
  44. package/src/lib/internal/escape-layer/use-escape-layer.svelte.ts +13 -29
  45. package/src/lib/internal/group-value.svelte.ts +11 -10
  46. package/src/lib/internal/native-popover.svelte.ts +6 -6
@@ -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 { DialogCloseState } from '../dialog.svelte.js';
5
4
  import type { DialogCloseProps } from '../types.js';
@@ -10,13 +9,19 @@
10
9
  let { children, child, id = createId(uid), ref = $bindable(null), disabled = false, ...restProps }: DialogCloseProps = $props();
11
10
 
12
11
  const closeState = DialogCloseState.create({
13
- variant: boxWith(() => 'close'),
14
- id: boxWith(() => id),
15
- ref: boxWith(
16
- () => ref,
17
- (v) => (ref = v),
18
- ),
19
- disabled: boxWith(() => Boolean(disabled)),
12
+ variant: 'close',
13
+ get id() {
14
+ return id;
15
+ },
16
+ get ref() {
17
+ return ref;
18
+ },
19
+ set ref(v) {
20
+ ref = v;
21
+ },
22
+ get disabled() {
23
+ return Boolean(disabled);
24
+ },
20
25
  });
21
26
 
22
27
  const mergedProps = $derived(mergeProps({ 'data-slot': 'dialog-close' }, restProps, closeState.props));
@@ -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 { DialogContentState } from '../dialog.svelte.js';
5
4
  import type { DialogContentProps } from '../types.js';
@@ -35,17 +34,21 @@
35
34
  }: DialogContentProps = $props();
36
35
 
37
36
  const contentState = DialogContentState.create({
38
- id: boxWith(() => id),
39
- ref: boxWith(
40
- () => ref,
41
- (v) => (ref = v),
42
- ),
37
+ get id() {
38
+ return id;
39
+ },
40
+ get ref() {
41
+ return ref;
42
+ },
43
+ set ref(v) {
44
+ ref = v;
45
+ },
43
46
  });
44
47
 
45
48
  // Closed state, on this element only (DialogContentState.props is shared with the native
46
49
  // modal surface). The scroll lock's body styles land a tick after opening, after vaul's
47
50
  // open effect has snapshotted them, as they did when the mount gate flipped a flush later.
48
- const mergedProps = $derived(mergeProps(restProps, contentState.props, { style: contentState.root.cell.open ? {} : { visibility: 'hidden' } }));
51
+ const mergedProps = $derived(mergeProps(restProps, contentState.props, { style: contentState.root.opts.open ? {} : { visibility: 'hidden' } }));
49
52
 
50
53
  const escapeAttachment = escapeKeydownAttachment({
51
54
  escapeKeydownBehavior: () => restProps.escapeKeydownBehavior ?? 'close',
@@ -54,7 +57,7 @@
54
57
  if (e.defaultPrevented) return;
55
58
  contentState.root.handleClose();
56
59
  },
57
- enabled: () => contentState.root.cell.open,
60
+ enabled: () => contentState.root.opts.open,
58
61
  });
59
62
 
60
63
  // Dialog content historically dropped the dismissible focus-capture props (its default-slot
@@ -68,12 +71,12 @@
68
71
  contentState.root.handleClose();
69
72
  },
70
73
  onFocusOutside: () => restProps.onFocusOutside,
71
- enabled: () => contentState.root.cell.open,
74
+ enabled: () => contentState.root.opts.open,
72
75
  });
73
76
 
74
77
  // Flags the open surface for the CSS text-selection guard in ui.css.
75
78
  const textSelectionGuard = $derived(
76
- contentState.root.cell.open && (restProps.preventOverflowTextSelection ?? true) ? { 'data-text-selection-guard': '' } : {},
79
+ contentState.root.opts.open && (restProps.preventOverflowTextSelection ?? true) ? { 'data-text-selection-guard': '' } : {},
77
80
  );
78
81
 
79
82
  // Spans `present` (open through the settled exit), the window element lifecycle used to
@@ -84,7 +87,7 @@
84
87
  });
85
88
 
86
89
  const focusScope = createFocusScopeProps({
87
- enabled: () => contentState.root.cell.open,
90
+ enabled: () => contentState.root.opts.open,
88
91
  trap: () => trapFocus,
89
92
  loop: () => true,
90
93
  onCloseAutoFocus: () => onCloseAutoFocus,
@@ -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 { DialogDescriptionState } from '../dialog.svelte.js';
5
4
  import type { DialogDescriptionProps } from '../types.js';
@@ -10,11 +9,15 @@
10
9
  let { id = createId(uid), children, child, ref = $bindable(null), ...restProps }: DialogDescriptionProps = $props();
11
10
 
12
11
  const descriptionState = DialogDescriptionState.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, descriptionState.props));
@@ -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 { DialogOverlayState } from '../dialog.svelte.js';
5
4
  import type { DialogOverlayProps } from '../types.js';
@@ -10,16 +9,20 @@
10
9
  let { id = createId(uid), child, children, ref = $bindable(null), ...restProps }: DialogOverlayProps = $props();
11
10
 
12
11
  const overlayState = DialogOverlayState.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
  // Always mounted; closed is visibility:hidden here, and the adapter's exit (the drawer's fadeOut
21
24
  // keyframe, the sheet's overlay-surface transition) holds it visible while it plays.
22
- const mergedProps = $derived(mergeProps(restProps, overlayState.props, { style: overlayState.root.cell.open ? {} : { visibility: 'hidden' } }));
25
+ const mergedProps = $derived(mergeProps(restProps, overlayState.props, { style: overlayState.root.opts.open ? {} : { visibility: 'hidden' } }));
23
26
  </script>
24
27
 
25
28
  {#if child}
@@ -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 { DialogTitleState } from '../dialog.svelte.js';
5
4
  import type { DialogTitleProps } from '../types.js';
@@ -10,12 +9,18 @@
10
9
  let { id = createId(uid), ref = $bindable(null), child, children, level = 2, ...restProps }: DialogTitleProps = $props();
11
10
 
12
11
  const titleState = DialogTitleState.create({
13
- id: boxWith(() => id),
14
- level: boxWith(() => level),
15
- ref: boxWith(
16
- () => ref,
17
- (v) => (ref = v),
18
- ),
12
+ get id() {
13
+ return id;
14
+ },
15
+ get level() {
16
+ return level;
17
+ },
18
+ get ref() {
19
+ return ref;
20
+ },
21
+ set ref(v) {
22
+ ref = v;
23
+ },
19
24
  });
20
25
 
21
26
  const mergedProps = $derived(mergeProps(restProps, titleState.props));
@@ -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 { DialogTriggerState } from '../dialog.svelte.js';
5
4
  import type { DialogTriggerProps } from '../types.js';
@@ -10,12 +9,18 @@
10
9
  let { id = createId(uid), ref = $bindable(null), children, child, disabled = false, ...restProps }: DialogTriggerProps = $props();
11
10
 
12
11
  const triggerState = DialogTriggerState.create({
13
- id: boxWith(() => id),
14
- ref: boxWith(
15
- () => ref,
16
- (v) => (ref = v),
17
- ),
18
- disabled: boxWith(() => Boolean(disabled)),
12
+ get id() {
13
+ return id;
14
+ },
15
+ get ref() {
16
+ return ref;
17
+ },
18
+ set ref(v) {
19
+ ref = v;
20
+ },
21
+ get disabled() {
22
+ return Boolean(disabled);
23
+ },
19
24
  });
20
25
 
21
26
  const mergedProps = $derived(mergeProps(restProps, triggerState.props));
@@ -1,5 +1,4 @@
1
1
  <script lang="ts">
2
- import { boxWith } from '../../../internal/tools/index.js';
3
2
  import { DialogRootState, DialogState } from '../dialog.svelte.js';
4
3
  import type { DialogRootProps } from '../types.js';
5
4
 
@@ -12,9 +11,16 @@
12
11
  // svelte-ignore state_referenced_locally
13
12
  const dialog = state ?? new DialogState(() => open);
14
13
  DialogRootState.create({
15
- variant: boxWith(() => 'dialog'),
16
- cell: dialog,
17
- onOpenChangeComplete: boxWith(() => onOpenChangeComplete),
14
+ variant: 'dialog',
15
+ get open() {
16
+ return dialog.open;
17
+ },
18
+ set open(v) {
19
+ dialog.open = v;
20
+ },
21
+ get onOpenChangeComplete() {
22
+ return onOpenChangeComplete;
23
+ },
18
24
  });
19
25
  </script>
20
26
 
@@ -1,6 +1,5 @@
1
1
  <script lang="ts">
2
2
  import type { HTMLDialogAttributes } from 'svelte/elements';
3
- import { boxWith } from '../../../internal/tools/index.js';
4
3
  import { mergeProps } from '../../../internal/merge-props.js';
5
4
  import { DialogContentState } from '../dialog.svelte.js';
6
5
  import type { DialogContentProps, DialogPortalProps } from '../types.js';
@@ -84,11 +83,15 @@
84
83
  } = $props();
85
84
 
86
85
  const contentState = DialogContentState.create({
87
- id: boxWith(() => id),
88
- ref: boxWith(
89
- () => ref,
90
- (v) => (ref = v),
91
- ),
86
+ get id() {
87
+ return id;
88
+ },
89
+ get ref() {
90
+ return ref;
91
+ },
92
+ set ref(v) {
93
+ ref = v;
94
+ },
92
95
  });
93
96
 
94
97
  // Bridge the shared root `open` to the native top-layer API and mirror native dismissal back
@@ -99,7 +102,7 @@
99
102
  // `contentNode` (set by DialogContentState) and fires it once the <dialog>'s animations
100
103
  // settle (useOpenChangeComplete) — a listener here would double-fire it.
101
104
  const controllerAttachment = nativeDialogControllerAttachment({
102
- open: () => contentState.root.cell.open,
105
+ open: () => contentState.root.opts.open,
103
106
  onClose: () => contentState.root.handleClose(),
104
107
  // the controller takes this as a plain, non-reactive value — fixed per adapter at mount
105
108
  // svelte-ignore state_referenced_locally
@@ -113,7 +116,7 @@
113
116
 
114
117
  // The <dialog> persists across open/close, so the lock gates on the cell's open, not element lifecycle.
115
118
  const scrollLock = scrollLockAttachment({
116
- enabled: () => contentState.root.cell.open && preventScroll,
119
+ enabled: () => contentState.root.opts.open && preventScroll,
117
120
  restoreScrollDelay: () => restoreScrollDelay,
118
121
  });
119
122
 
@@ -1,7 +1,7 @@
1
- import { attachRef, type ReadableBoxedValues } from '../../internal/tools/index.js';
1
+ import { attachRef } from '../../internal/tools/index.js';
2
2
  import { createContext, onDestroy, untrack } from 'svelte';
3
3
  import { createBitsAttrs, boolToStr, getDataOpenClosed, boolToEmptyStrOrUndef } from '../../internal/attrs.js';
4
- import type { BitsKeyboardEvent, BitsMouseEvent, OnChangeFn, RefAttachment, WithRefOpts } from '../../internal/types.js';
4
+ import type { BitsKeyboardEvent, BitsMouseEvent, OnChangeFn, RefAttachment, RefOpts } from '../../internal/types.js';
5
5
  import { kbd } from '../../internal/kbd.js';
6
6
  import { useOpenChangeComplete } from '../../internal/animations-settled.svelte.js';
7
7
 
@@ -19,12 +19,10 @@ const [getDialogRoot, setDialogRoot, hasDialogRoot] = createContext<DialogRootSt
19
19
  import { OpenCell as DialogState } from '../../internal/open-cell.svelte.js';
20
20
  export { DialogState };
21
21
 
22
- interface DialogRootStateOpts
23
- extends ReadableBoxedValues<{
24
- variant: DialogVariant;
25
- onOpenChangeComplete: OnChangeFn<boolean>;
26
- }> {
27
- cell: DialogState;
22
+ interface DialogRootStateOpts {
23
+ readonly variant: DialogVariant;
24
+ readonly onOpenChangeComplete: OnChangeFn<boolean>;
25
+ open: boolean;
28
26
  }
29
27
 
30
28
  export class DialogRootState {
@@ -34,7 +32,6 @@ export class DialogRootState {
34
32
  }
35
33
 
36
34
  readonly opts: DialogRootStateOpts;
37
- readonly cell: DialogState;
38
35
  triggerNode = $state<HTMLElement | null>(null);
39
36
  contentNode = $state<HTMLElement | null>(null);
40
37
  descriptionNode = $state<HTMLElement | null>(null);
@@ -42,33 +39,32 @@ export class DialogRootState {
42
39
  triggerId = $state<string | undefined>(undefined);
43
40
  titleState = $state<DialogTitleState | null>(null);
44
41
  descriptionState = $state<DialogDescriptionState | null>(null);
45
- readonly titleId = $derived.by(() => this.titleState?.opts.id.current);
46
- readonly descriptionId = $derived.by(() => this.descriptionState?.opts.id.current);
42
+ readonly titleId = $derived.by(() => this.titleState?.opts.id);
43
+ readonly descriptionId = $derived.by(() => this.descriptionState?.opts.id);
47
44
  cancelNode = $state<HTMLElement | null>(null);
48
45
  nestedOpenCount = $state(0);
49
46
  readonly depth: number;
50
47
  readonly parent: DialogRootState | null;
51
48
  readonly #completion: { readonly pending: boolean };
52
49
  /** Rendered: open, or closing with the exit still settling — the window the headless content's scroll lock covers. */
53
- readonly present = $derived.by(() => this.cell.open || this.#completion.pending);
50
+ readonly present = $derived.by(() => this.opts.open || this.#completion.pending);
54
51
 
55
52
  constructor(opts: DialogRootStateOpts, parent: DialogRootState | null) {
56
53
  this.opts = opts;
57
- this.cell = opts.cell;
58
54
  this.parent = parent;
59
55
  this.depth = parent ? parent.depth + 1 : 0;
60
56
  this.handleOpen = this.handleOpen.bind(this);
61
57
  this.handleClose = this.handleClose.bind(this);
62
58
 
63
59
  this.#completion = useOpenChangeComplete(
64
- () => this.cell.open,
60
+ () => this.opts.open,
65
61
  () => this.contentNode,
66
- (isOpen) => this.opts.onOpenChangeComplete.current(isOpen),
62
+ (isOpen) => this.opts.onOpenChangeComplete(isOpen),
67
63
  );
68
64
 
69
65
  let started = false;
70
66
  $effect(() => {
71
- const isOpen = this.cell.open;
67
+ const isOpen = this.opts.open;
72
68
  if (!started) {
73
69
  started = true;
74
70
  return;
@@ -84,22 +80,22 @@ export class DialogRootState {
84
80
  });
85
81
 
86
82
  onDestroy(() => {
87
- if (this.cell.open) {
83
+ if (this.opts.open) {
88
84
  this.parent?.decrementNested();
89
85
  }
90
86
  });
91
87
  }
92
88
 
93
89
  handleOpen() {
94
- this.cell.open = true;
90
+ this.opts.open = true;
95
91
  }
96
92
 
97
93
  handleClose() {
98
- this.cell.open = false;
94
+ this.opts.open = false;
99
95
  }
100
96
 
101
97
  getBitsAttr: typeof dialogAttrs.getAttr = (part) => {
102
- return dialogAttrs.getAttr(part, this.opts.variant.current);
98
+ return dialogAttrs.getAttr(part, this.opts.variant);
103
99
  };
104
100
 
105
101
  incrementNested() {
@@ -116,12 +112,14 @@ export class DialogRootState {
116
112
  readonly sharedProps = $derived.by(
117
113
  () =>
118
114
  ({
119
- 'data-state': getDataOpenClosed(this.cell.open),
115
+ 'data-state': getDataOpenClosed(this.opts.open),
120
116
  }) as const,
121
117
  );
122
118
  }
123
119
 
124
- interface DialogTriggerStateOpts extends WithRefOpts, ReadableBoxedValues<{ disabled: boolean }> {}
120
+ interface DialogTriggerStateOpts extends RefOpts {
121
+ readonly disabled: boolean;
122
+ }
125
123
 
126
124
  export class DialogTriggerState {
127
125
  static create(opts: DialogTriggerStateOpts) {
@@ -135,22 +133,25 @@ export class DialogTriggerState {
135
133
  constructor(opts: DialogTriggerStateOpts, root: DialogRootState) {
136
134
  this.opts = opts;
137
135
  this.root = root;
138
- this.attachment = attachRef(this.opts.ref, (v) => {
139
- this.root.triggerNode = v;
140
- this.root.triggerId = v?.id;
141
- });
136
+ this.attachment = attachRef<HTMLElement>(
137
+ (v) => (opts.ref = v),
138
+ (v) => {
139
+ this.root.triggerNode = v;
140
+ this.root.triggerId = v?.id;
141
+ },
142
+ );
142
143
  this.onclick = this.onclick.bind(this);
143
144
  this.onkeydown = this.onkeydown.bind(this);
144
145
  }
145
146
 
146
147
  onclick(e: BitsMouseEvent) {
147
- if (this.opts.disabled.current) return;
148
+ if (this.opts.disabled) return;
148
149
  if (e.button > 0) return;
149
150
  this.root.handleOpen();
150
151
  }
151
152
 
152
153
  onkeydown(e: BitsKeyboardEvent) {
153
- if (this.opts.disabled.current) return;
154
+ if (this.opts.disabled) return;
154
155
  if (e.key === kbd.SPACE || e.key === kbd.ENTER) {
155
156
  e.preventDefault();
156
157
  this.root.handleOpen();
@@ -160,21 +161,24 @@ export class DialogTriggerState {
160
161
  readonly props = $derived.by(
161
162
  () =>
162
163
  ({
163
- id: this.opts.id.current,
164
+ id: this.opts.id,
164
165
  'aria-haspopup': 'dialog',
165
- 'aria-expanded': boolToStr(this.root.cell.open),
166
+ 'aria-expanded': boolToStr(this.root.opts.open),
166
167
  'aria-controls': this.root.contentId,
167
168
  [this.root.getBitsAttr('trigger')]: '',
168
169
  onkeydown: this.onkeydown,
169
170
  onclick: this.onclick,
170
- disabled: this.opts.disabled.current ? true : undefined,
171
+ disabled: this.opts.disabled ? true : undefined,
171
172
  ...this.root.sharedProps,
172
173
  ...this.attachment,
173
174
  }) as const,
174
175
  );
175
176
  }
176
177
 
177
- interface DialogCloseStateOpts extends WithRefOpts, ReadableBoxedValues<{ variant: 'action' | 'cancel' | 'close'; disabled: boolean }> {}
178
+ interface DialogCloseStateOpts extends RefOpts {
179
+ readonly variant: 'action' | 'cancel' | 'close';
180
+ readonly disabled: boolean;
181
+ }
178
182
 
179
183
  export class DialogCloseState {
180
184
  static create(opts: DialogCloseStateOpts) {
@@ -188,19 +192,19 @@ export class DialogCloseState {
188
192
  constructor(opts: DialogCloseStateOpts, root: DialogRootState) {
189
193
  this.opts = opts;
190
194
  this.root = root;
191
- this.attachment = attachRef(this.opts.ref);
195
+ this.attachment = attachRef<HTMLElement>((v) => (opts.ref = v));
192
196
  this.onclick = this.onclick.bind(this);
193
197
  this.onkeydown = this.onkeydown.bind(this);
194
198
  }
195
199
 
196
200
  onclick(e: BitsMouseEvent) {
197
- if (this.opts.disabled.current) return;
201
+ if (this.opts.disabled) return;
198
202
  if (e.button > 0) return;
199
203
  this.root.handleClose();
200
204
  }
201
205
 
202
206
  onkeydown(e: BitsKeyboardEvent) {
203
- if (this.opts.disabled.current) return;
207
+ if (this.opts.disabled) return;
204
208
  if (e.key === kbd.SPACE || e.key === kbd.ENTER) {
205
209
  e.preventDefault();
206
210
  this.root.handleClose();
@@ -210,11 +214,11 @@ export class DialogCloseState {
210
214
  readonly props = $derived.by(
211
215
  () =>
212
216
  ({
213
- id: this.opts.id.current,
214
- [this.root.getBitsAttr(this.opts.variant.current)]: '',
217
+ id: this.opts.id,
218
+ [this.root.getBitsAttr(this.opts.variant)]: '',
215
219
  onclick: this.onclick,
216
220
  onkeydown: this.onkeydown,
217
- disabled: this.opts.disabled.current ? true : undefined,
221
+ disabled: this.opts.disabled ? true : undefined,
218
222
  tabindex: 0,
219
223
  ...this.root.sharedProps,
220
224
  ...this.attachment,
@@ -222,7 +226,9 @@ export class DialogCloseState {
222
226
  );
223
227
  }
224
228
 
225
- interface DialogTitleStateOpts extends WithRefOpts, ReadableBoxedValues<{ level: 1 | 2 | 3 | 4 | 5 | 6 }> {}
229
+ interface DialogTitleStateOpts extends RefOpts {
230
+ readonly level: 1 | 2 | 3 | 4 | 5 | 6;
231
+ }
226
232
 
227
233
  export class DialogTitleState {
228
234
  static create(opts: DialogTitleStateOpts) {
@@ -237,15 +243,15 @@ export class DialogTitleState {
237
243
  this.opts = opts;
238
244
  this.root = root;
239
245
  this.root.titleState = this;
240
- this.attachment = attachRef(this.opts.ref);
246
+ this.attachment = attachRef<HTMLElement>((v) => (opts.ref = v));
241
247
  }
242
248
 
243
249
  readonly props = $derived.by(
244
250
  () =>
245
251
  ({
246
- id: this.opts.id.current,
252
+ id: this.opts.id,
247
253
  role: 'heading',
248
- 'aria-level': this.opts.level.current,
254
+ 'aria-level': this.opts.level,
249
255
  [this.root.getBitsAttr('title')]: '',
250
256
  ...this.root.sharedProps,
251
257
  ...this.attachment,
@@ -253,7 +259,7 @@ export class DialogTitleState {
253
259
  );
254
260
  }
255
261
 
256
- interface DialogDescriptionStateOpts extends WithRefOpts {}
262
+ interface DialogDescriptionStateOpts extends RefOpts {}
257
263
 
258
264
  export class DialogDescriptionState {
259
265
  static create(opts: DialogDescriptionStateOpts) {
@@ -268,15 +274,18 @@ export class DialogDescriptionState {
268
274
  this.opts = opts;
269
275
  this.root = root;
270
276
  this.root.descriptionState = this;
271
- this.attachment = attachRef(this.opts.ref, (v) => {
272
- this.root.descriptionNode = v;
273
- });
277
+ this.attachment = attachRef<HTMLElement>(
278
+ (v) => (opts.ref = v),
279
+ (v) => {
280
+ this.root.descriptionNode = v;
281
+ },
282
+ );
274
283
  }
275
284
 
276
285
  readonly props = $derived.by(
277
286
  () =>
278
287
  ({
279
- id: this.opts.id.current,
288
+ id: this.opts.id,
280
289
  [this.root.getBitsAttr('description')]: '',
281
290
  ...this.root.sharedProps,
282
291
  ...this.attachment,
@@ -284,7 +293,7 @@ export class DialogDescriptionState {
284
293
  );
285
294
  }
286
295
 
287
- interface DialogContentStateOpts extends WithRefOpts {}
296
+ interface DialogContentStateOpts extends RefOpts {}
288
297
 
289
298
  export class DialogContentState {
290
299
  static create(opts: DialogContentStateOpts) {
@@ -298,26 +307,29 @@ export class DialogContentState {
298
307
  constructor(opts: DialogContentStateOpts, root: DialogRootState) {
299
308
  this.opts = opts;
300
309
  this.root = root;
301
- this.attachment = attachRef(this.opts.ref, (v) => {
302
- this.root.contentNode = v;
303
- this.root.contentId = v?.id;
304
- });
310
+ this.attachment = attachRef<HTMLElement>(
311
+ (v) => (opts.ref = v),
312
+ (v) => {
313
+ this.root.contentNode = v;
314
+ this.root.contentId = v?.id;
315
+ },
316
+ );
305
317
  }
306
318
 
307
- readonly snippetProps = $derived.by(() => ({ open: this.root.cell.open }));
319
+ readonly snippetProps = $derived.by(() => ({ open: this.root.opts.open }));
308
320
 
309
321
  readonly props = $derived.by(
310
322
  () =>
311
323
  ({
312
- id: this.opts.id.current,
313
- role: this.root.opts.variant.current === 'alert-dialog' ? 'alertdialog' : 'dialog',
324
+ id: this.opts.id,
325
+ role: this.root.opts.variant === 'alert-dialog' ? 'alertdialog' : 'dialog',
314
326
  'aria-modal': 'true',
315
327
  'aria-describedby': this.root.descriptionId,
316
328
  'aria-labelledby': this.root.titleId,
317
329
  [this.root.getBitsAttr('content')]: '',
318
330
  style: {
319
331
  pointerEvents: 'auto',
320
- outline: this.root.opts.variant.current === 'alert-dialog' ? 'none' : undefined,
332
+ outline: this.root.opts.variant === 'alert-dialog' ? 'none' : undefined,
321
333
  '--bits-dialog-depth': this.root.depth,
322
334
  '--bits-dialog-nested-count': this.root.nestedOpenCount,
323
335
  // CSS containment isolates style/layout calculations from the rest of the page,
@@ -325,7 +337,7 @@ export class DialogContentState {
325
337
  // Paint is omitted so tooltips/selects can render outside dialog bounds.
326
338
  contain: 'layout style',
327
339
  },
328
- tabindex: this.root.opts.variant.current === 'alert-dialog' ? -1 : undefined,
340
+ tabindex: this.root.opts.variant === 'alert-dialog' ? -1 : undefined,
329
341
  'data-nested-open': boolToEmptyStrOrUndef(this.root.nestedOpenCount > 0),
330
342
  'data-nested': boolToEmptyStrOrUndef(this.root.parent !== null),
331
343
  ...this.root.sharedProps,
@@ -334,7 +346,7 @@ export class DialogContentState {
334
346
  );
335
347
  }
336
348
 
337
- interface DialogOverlayStateOpts extends WithRefOpts {}
349
+ interface DialogOverlayStateOpts extends RefOpts {}
338
350
 
339
351
  export class DialogOverlayState {
340
352
  static create(opts: DialogOverlayStateOpts) {
@@ -347,15 +359,15 @@ export class DialogOverlayState {
347
359
  constructor(opts: DialogOverlayStateOpts, root: DialogRootState) {
348
360
  this.opts = opts;
349
361
  this.root = root;
350
- this.attachment = attachRef(this.opts.ref);
362
+ this.attachment = attachRef<HTMLElement>((v) => (opts.ref = v));
351
363
  }
352
364
 
353
- readonly snippetProps = $derived.by(() => ({ open: this.root.cell.open }));
365
+ readonly snippetProps = $derived.by(() => ({ open: this.root.opts.open }));
354
366
 
355
367
  readonly props = $derived.by(
356
368
  () =>
357
369
  ({
358
- id: this.opts.id.current,
370
+ id: this.opts.id,
359
371
  [this.root.getBitsAttr('overlay')]: '',
360
372
  style: {
361
373
  pointerEvents: 'auto',
@@ -118,9 +118,14 @@
118
118
  });
119
119
 
120
120
  DialogRootState.create({
121
- variant: boxWith(() => 'dialog'),
122
- cell: dialogCell,
123
- onOpenChangeComplete: boxWith(() => handleOpenChangeComplete),
121
+ variant: 'dialog',
122
+ get open() {
123
+ return dialogCell.open;
124
+ },
125
+ set open(v) {
126
+ dialogCell.open = v;
127
+ },
128
+ onOpenChangeComplete: handleOpenChangeComplete,
124
129
  });
125
130
  </script>
126
131