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
@@ -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
 
@@ -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 { LinkPreviewContentProps } from '../types.js';
5
4
  import { LinkPreviewContentState } from '../link-preview.svelte.js';
@@ -25,13 +24,21 @@
25
24
  }: LinkPreviewContentProps = $props();
26
25
 
27
26
  const contentState = LinkPreviewContentState.create({
28
- id: boxWith(() => id),
29
- ref: boxWith(
30
- () => ref,
31
- (v) => (ref = v),
32
- ),
33
- onInteractOutside: boxWith(() => onInteractOutside),
34
- onEscapeKeydown: boxWith(() => onEscapeKeydown),
27
+ get id() {
28
+ return id;
29
+ },
30
+ get onInteractOutside() {
31
+ return onInteractOutside;
32
+ },
33
+ get onEscapeKeydown() {
34
+ return onEscapeKeydown;
35
+ },
36
+ get ref() {
37
+ return ref;
38
+ },
39
+ set ref(v) {
40
+ ref = v;
41
+ },
35
42
  });
36
43
 
37
44
  const lifecycleProps = useNativePopoverLifecycle(contentState);
@@ -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 { LinkPreviewTriggerProps } from '../types.js';
5
4
  import { LinkPreviewTriggerState } from '../link-preview.svelte.js';
@@ -10,11 +9,15 @@
10
9
  let { ref = $bindable(null), id = createId(uid), child, children, ...restProps }: LinkPreviewTriggerProps = $props();
11
10
 
12
11
  const triggerState = LinkPreviewTriggerState.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({ 'data-slot': 'hover-card-trigger' }, restProps, triggerState.props));
@@ -1,5 +1,4 @@
1
1
  <script lang="ts">
2
- import { boxWith } from '../../../internal/tools/index.js';
3
2
  import { OpenCell } from '../../../internal/open-cell.svelte.js';
4
3
  import type { LinkPreviewRootProps } from '../types.js';
5
4
  import { LinkPreviewRootState } from '../link-preview.svelte.js';
@@ -14,20 +13,29 @@
14
13
  children,
15
14
  }: LinkPreviewRootProps = $props();
16
15
 
17
- // Cell over the source prop; the engine keeps its boxed-open interface (vendored,
18
- // ADR 0006) — the box is a bridge over the cell, so the cell owns `open`.
16
+ // Cell over the source prop; the cell owns `open`, the engine reads and writes it through it.
19
17
  // svelte-ignore state_referenced_locally
20
18
  const cell = givenCell ?? new OpenCell(() => open);
21
19
 
22
20
  LinkPreviewRootState.create({
23
- disabled: boxWith(() => disabled),
24
- open: boxWith(
25
- () => cell.open,
26
- (v) => (cell.open = v),
27
- ),
28
- openDelay: boxWith(() => openDelay),
29
- closeDelay: boxWith(() => closeDelay),
30
- onOpenChangeComplete: boxWith(() => onOpenChangeComplete),
21
+ get open() {
22
+ return cell.open;
23
+ },
24
+ set open(v) {
25
+ cell.open = v;
26
+ },
27
+ get disabled() {
28
+ return disabled;
29
+ },
30
+ get openDelay() {
31
+ return openDelay;
32
+ },
33
+ get closeDelay() {
34
+ return closeDelay;
35
+ },
36
+ get onOpenChangeComplete() {
37
+ return onOpenChangeComplete;
38
+ },
31
39
  });
32
40
  </script>
33
41
 
@@ -1,9 +1,9 @@
1
- import { attachRef, DOMContext, type ReadableBoxedValues, type WritableBoxedValues } from '../../internal/tools/index.js';
1
+ import { attachRef, DOMContext } from '../../internal/tools/index.js';
2
2
  import { on } from 'svelte/events';
3
3
  import { createContext, onDestroy, untrack } from 'svelte';
4
4
  import { createBitsAttrs, boolToStr, getDataOpenClosed } from '../../internal/attrs.js';
5
5
  import { isElement } from '../../internal/tools/utils/dom.js';
6
- import type { BitsFocusEvent, BitsPointerEvent, OnChangeFn, RefAttachment, WithRefOpts } from '../../internal/types.js';
6
+ import type { BitsFocusEvent, BitsPointerEvent, OnChangeFn, RefAttachment, RefOpts } from '../../internal/types.js';
7
7
  import { getTabbableCandidates } from '../../internal/tabbable.js';
8
8
  import { SafePolygon } from '../../internal/safe-polygon.svelte.js';
9
9
  import { createEffectTimeout } from '../../internal/timeout-fn.svelte.js';
@@ -15,17 +15,13 @@ const linkPreviewAttrs = createBitsAttrs({
15
15
 
16
16
  const [getLinkPreviewRoot, setLinkPreviewRoot] = createContext<LinkPreviewRootState>();
17
17
 
18
- interface LinkPreviewRootStateOpts
19
- extends
20
- WritableBoxedValues<{
21
- open: boolean;
22
- }>,
23
- ReadableBoxedValues<{
24
- disabled: boolean;
25
- openDelay: number;
26
- closeDelay: number;
27
- onOpenChangeComplete: OnChangeFn<boolean>;
28
- }> {}
18
+ interface LinkPreviewRootStateOpts {
19
+ open: boolean;
20
+ readonly disabled: boolean;
21
+ readonly openDelay: number;
22
+ readonly closeDelay: number;
23
+ readonly onOpenChangeComplete: OnChangeFn<boolean>;
24
+ }
29
25
 
30
26
  export class LinkPreviewRootState {
31
27
  static create(opts: LinkPreviewRootStateOpts) {
@@ -41,17 +37,17 @@ export class LinkPreviewRootState {
41
37
  triggerNode = $state<HTMLElement | null>(null);
42
38
  domContext: DOMContext = new DOMContext(() => null);
43
39
  #openTimer = createEffectTimeout(() => {
44
- this.opts.open.current = true;
45
- }, () => this.opts.openDelay.current);
40
+ this.opts.open = true;
41
+ }, () => this.opts.openDelay);
46
42
  #closeTimer = createEffectTimeout(() => {
47
- this.opts.open.current = false;
48
- }, () => this.opts.closeDelay.current);
43
+ this.opts.open = false;
44
+ }, () => this.opts.closeDelay);
49
45
 
50
46
  constructor(opts: LinkPreviewRootStateOpts) {
51
47
  this.opts = opts;
52
48
 
53
49
  $effect(() => {
54
- const isOpen = this.opts.open.current;
50
+ const isOpen = this.opts.open;
55
51
  return untrack(() => {
56
52
  if (!isOpen) {
57
53
  this.hasSelection = false;
@@ -99,13 +95,13 @@ export class LinkPreviewRootState {
99
95
 
100
96
  handleOpen() {
101
97
  this.clearTimeout();
102
- if (this.opts.open.current || this.opts.disabled.current) return;
98
+ if (this.opts.open || this.opts.disabled) return;
103
99
  this.#openTimer.start();
104
100
  }
105
101
 
106
102
  immediateClose() {
107
103
  this.clearTimeout();
108
- this.opts.open.current = false;
104
+ this.opts.open = false;
109
105
  }
110
106
 
111
107
  handleClose() {
@@ -117,7 +113,7 @@ export class LinkPreviewRootState {
117
113
  }
118
114
  }
119
115
 
120
- interface LinkPreviewTriggerStateOpts extends WithRefOpts {}
116
+ interface LinkPreviewTriggerStateOpts extends RefOpts {}
121
117
 
122
118
  export class LinkPreviewTriggerState {
123
119
  static create(opts: LinkPreviewTriggerStateOpts) {
@@ -131,8 +127,11 @@ export class LinkPreviewTriggerState {
131
127
  constructor(opts: LinkPreviewTriggerStateOpts, root: LinkPreviewRootState) {
132
128
  this.opts = opts;
133
129
  this.root = root;
134
- this.attachment = attachRef(this.opts.ref, (v) => (this.root.triggerNode = v));
135
- this.root.domContext = new DOMContext(opts.ref);
130
+ this.attachment = attachRef<HTMLElement>(
131
+ (v) => (opts.ref = v),
132
+ (v) => (this.root.triggerNode = v),
133
+ );
134
+ this.root.domContext = new DOMContext(() => opts.ref);
136
135
  this.onpointerenter = this.onpointerenter.bind(this);
137
136
  this.onpointerleave = this.onpointerleave.bind(this);
138
137
  this.onfocus = this.onfocus.bind(this);
@@ -147,7 +146,7 @@ export class LinkPreviewTriggerState {
147
146
  onpointerleave(e: BitsPointerEvent) {
148
147
  if (e.pointerType === 'touch') return;
149
148
  // The content is always mounted (native popover, display toggled), so open is the only gate.
150
- if (!this.root.opts.open.current) {
149
+ if (!this.root.opts.open) {
151
150
  this.root.immediateClose();
152
151
  }
153
152
  }
@@ -164,10 +163,10 @@ export class LinkPreviewTriggerState {
164
163
  readonly props = $derived.by(
165
164
  () =>
166
165
  ({
167
- id: this.opts.id.current,
166
+ id: this.opts.id,
168
167
  'aria-haspopup': 'dialog',
169
- 'aria-expanded': boolToStr(this.root.opts.open.current),
170
- 'data-state': getDataOpenClosed(this.root.opts.open.current),
168
+ 'aria-expanded': boolToStr(this.root.opts.open),
169
+ 'data-state': getDataOpenClosed(this.root.opts.open),
171
170
  'aria-controls': this.root.contentNode?.id,
172
171
  role: 'button',
173
172
  [linkPreviewAttrs.trigger]: '',
@@ -180,13 +179,10 @@ export class LinkPreviewTriggerState {
180
179
  );
181
180
  }
182
181
 
183
- interface LinkPreviewContentStateOpts
184
- extends
185
- WithRefOpts,
186
- ReadableBoxedValues<{
187
- onInteractOutside: (e: PointerEvent) => void;
188
- onEscapeKeydown: (e: KeyboardEvent) => void;
189
- }> {}
182
+ interface LinkPreviewContentStateOpts extends RefOpts {
183
+ readonly onInteractOutside: (e: PointerEvent) => void;
184
+ readonly onEscapeKeydown: (e: KeyboardEvent) => void;
185
+ }
190
186
 
191
187
  export class LinkPreviewContentState {
192
188
  static create(opts: LinkPreviewContentStateOpts) {
@@ -200,16 +196,19 @@ export class LinkPreviewContentState {
200
196
  constructor(opts: LinkPreviewContentStateOpts, root: LinkPreviewRootState) {
201
197
  this.opts = opts;
202
198
  this.root = root;
203
- this.attachment = attachRef(this.opts.ref, (v) => (this.root.contentNode = v));
204
- this.root.domContext = new DOMContext(opts.ref);
199
+ this.attachment = attachRef<HTMLElement>(
200
+ (v) => (opts.ref = v),
201
+ (v) => (this.root.contentNode = v),
202
+ );
203
+ this.root.domContext = new DOMContext(() => opts.ref);
205
204
  this.onpointerdown = this.onpointerdown.bind(this);
206
205
  this.onpointerenter = this.onpointerenter.bind(this);
207
206
  this.onfocusout = this.onfocusout.bind(this);
208
207
 
209
208
  new SafePolygon({
210
209
  triggerNode: () => this.root.triggerNode,
211
- contentNode: () => this.opts.ref.current,
212
- enabled: () => this.root.opts.open.current,
210
+ contentNode: () => this.opts.ref,
211
+ enabled: () => this.root.opts.open,
213
212
  onPointerExit: () => {
214
213
  this.root.handleClose();
215
214
  },
@@ -241,25 +240,25 @@ export class LinkPreviewContentState {
241
240
  }
242
241
 
243
242
  onInteractOutside = (e: PointerEvent) => {
244
- this.opts.onInteractOutside.current(e);
243
+ this.opts.onInteractOutside(e);
245
244
  if (e.defaultPrevented) return;
246
245
  this.root.handleClose();
247
246
  };
248
247
 
249
248
  onEscapeKeydown = (e: KeyboardEvent) => {
250
- this.opts.onEscapeKeydown.current?.(e);
249
+ this.opts.onEscapeKeydown?.(e);
251
250
  if (e.defaultPrevented) return;
252
251
  this.root.handleClose();
253
252
  };
254
253
 
255
- readonly snippetProps = $derived.by(() => ({ open: this.root.opts.open.current }));
254
+ readonly snippetProps = $derived.by(() => ({ open: this.root.opts.open }));
256
255
 
257
256
  readonly props = $derived.by(
258
257
  () =>
259
258
  ({
260
- id: this.opts.id.current,
259
+ id: this.opts.id,
261
260
  tabindex: -1,
262
- 'data-state': getDataOpenClosed(this.root.opts.open.current),
261
+ 'data-state': getDataOpenClosed(this.root.opts.open),
263
262
  [linkPreviewAttrs.content]: '',
264
263
  onpointerdown: this.onpointerdown,
265
264
  onpointerenter: this.onpointerenter,