@lumiose/ui 0.1.2 → 0.2.0

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.
@@ -18,6 +18,7 @@ import {
18
18
  import defineAccordion from "./accordion.script.ts?client";
19
19
  import type { LuAccordion } from "./accordion.script.ts";
20
20
  import { batched } from "../batched.ts";
21
+ import { notInside } from "../not-inside.ts";
21
22
 
22
23
  declare module "lumiose/jsx-runtime" {
23
24
  namespace JSX {
@@ -119,13 +120,13 @@ const HeaderContext = createContext<AccordionHeaderProps>();
119
120
 
120
121
  function useAccordion(): AccordionContextValue {
121
122
  const context = useContext(AccordionContext);
122
- if (!context) throw new Error("Accordion parts must be inside <Accordion.Root>.");
123
+ if (!context) throw notInside("Accordion parts", "<Accordion.Root>");
123
124
  return context;
124
125
  }
125
126
 
126
127
  function useItem(): ItemContextValue {
127
128
  const context = useContext(ItemContext);
128
- if (!context) throw new Error("Accordion parts must be inside an <Accordion.Item>.");
129
+ if (!context) throw notInside("Accordion parts", "an <Accordion.Item>");
129
130
  return context;
130
131
  }
131
132
 
@@ -7,6 +7,7 @@
7
7
  import { createContext, effect, ref, untrack, useContext, useId } from "lumiose";
8
8
  import collapsibleScript from "./collapsible.script.ts?client";
9
9
  import { batched } from "../batched.ts";
10
+ import { notInside } from "../not-inside.ts";
10
11
 
11
12
  /**
12
13
  * The `<details>` props Root passes on (`class`, `style`, `name`, `data-*`, `aria-*`, events),
@@ -54,7 +55,7 @@ const CollapsibleContext = createContext<CollapsibleContextValue>();
54
55
 
55
56
  function useCollapsible(): CollapsibleContextValue {
56
57
  const context = useContext(CollapsibleContext);
57
- if (!context) throw new Error("Collapsible parts must be inside <Collapsible.Root>.");
58
+ if (!context) throw notInside("Collapsible parts", "<Collapsible.Root>");
58
59
  return context;
59
60
  }
60
61
 
@@ -34,7 +34,9 @@ function panelOf(details: HTMLDetailsElement): HTMLElement | undefined {
34
34
 
35
35
  /** The variables' prefix: an accordion item is a `<details>` right inside `<lu-accordion>`. */
36
36
  const prefix = (details: HTMLDetailsElement) =>
37
- details.parentElement?.localName === "lu-accordion" ? "--lu-accordion-panel" : "--lu-collapsible-panel";
37
+ details.parentElement?.localName === "lu-accordion"
38
+ ? "--lu-accordion-panel"
39
+ : "--lu-collapsible-panel";
38
40
 
39
41
  function measure(panel: HTMLElement, name: string) {
40
42
  panel.style.setProperty(`${name}-height`, `${panel.scrollHeight}px`);
@@ -28,6 +28,7 @@ import { onFormReset } from "../form/reset.ts";
28
28
  import { anchorName, placement, withStyle, type PlacementProps } from "../placement/placement.ts";
29
29
  import { ComboboxContext, register, useCombobox, type ComboboxContextValue } from "./context.ts";
30
30
  import { batched } from "../batched.ts";
31
+ import { notInside } from "../not-inside.ts";
31
32
 
32
33
  declare module "lumiose/jsx-runtime" {
33
34
  namespace JSX {
@@ -489,7 +490,7 @@ function GroupElement({
489
490
 
490
491
  function GroupLabel({ children, ...rest }: ComboboxGroupLabelProps) {
491
492
  const group = useContext(GroupContext);
492
- if (!group) throw new Error("Combobox.GroupLabel must be inside <Combobox.Group>.");
493
+ if (!group) throw notInside("Combobox.GroupLabel", "<Combobox.Group>");
493
494
  register(group.labels);
494
495
  return (
495
496
  <div role="presentation" {...rest} id={group.labelId}>
@@ -1,6 +1,7 @@
1
1
  // The context the Combobox parts share, with Autocomplete's Root (TASK-377): the same parts work
2
2
  // under either Root, which says how the input behaves.
3
3
  import { createContext, onCleanup, useContext, type Signal } from "lumiose";
4
+ import { notInside } from "../not-inside.ts";
4
5
 
5
6
  export interface ComboboxContextValue {
6
7
  /** The element's id; the parts' ids and the anchor name derive from it. */
@@ -35,7 +36,7 @@ export const ComboboxContext = createContext<ComboboxContextValue>();
35
36
 
36
37
  export function useCombobox(): ComboboxContextValue {
37
38
  const context = useContext(ComboboxContext);
38
- if (!context) throw new Error("Combobox parts must be inside <Combobox.Root>.");
39
+ if (!context) throw notInside("Combobox parts", "<Combobox.Root>");
39
40
  return context;
40
41
  }
41
42
 
@@ -20,6 +20,7 @@ import { anchorName } from "../placement/placement.ts";
20
20
  import contextMenuScript from "./context-menu.script.ts?client";
21
21
  import type { ContextMenuElement } from "./context-menu.script.ts";
22
22
  import { batched } from "../batched.ts";
23
+ import { notInside } from "../not-inside.ts";
23
24
 
24
25
  declare module "lumiose/jsx-runtime" {
25
26
  namespace JSX {
@@ -72,7 +73,7 @@ const ContextMenuContext = createContext<ContextMenuContextValue>();
72
73
 
73
74
  function useContextMenu(): ContextMenuContextValue {
74
75
  const context = useContext(ContextMenuContext);
75
- if (!context) throw new Error("ContextMenu parts must be inside <ContextMenu.Root>.");
76
+ if (!context) throw notInside("ContextMenu parts", "<ContextMenu.Root>");
76
77
  return context;
77
78
  }
78
79
 
@@ -17,6 +17,7 @@ import {
17
17
  } from "lumiose";
18
18
  import dialogScript from "./dialog.script.ts?client";
19
19
  import { batched } from "../batched.ts";
20
+ import { notInside } from "../not-inside.ts";
20
21
 
21
22
  /**
22
23
  * `Dialog.Root` renders no element: it holds the dialog's state and the ids its parts share.
@@ -69,7 +70,7 @@ const DialogContext = createContext<DialogContextValue>();
69
70
 
70
71
  function useDialog(): DialogContextValue {
71
72
  const context = useContext(DialogContext);
72
- if (!context) throw new Error("Dialog parts must be inside <Dialog.Root>.");
73
+ if (!context) throw notInside("Dialog parts", "<Dialog.Root>");
73
74
  return context;
74
75
  }
75
76
 
@@ -13,6 +13,7 @@ import {
13
13
  type DialogTriggerProps,
14
14
  } from "../dialog/dialog.tsx";
15
15
  import drawerScript from "./drawer.script.ts?client";
16
+ import { notInside } from "../not-inside.ts";
16
17
 
17
18
  /** The direction a swipe closes the drawer in, and the edge of the window it sits against. */
18
19
  export type DrawerSwipeDirection = "up" | "down" | "left" | "right";
@@ -68,7 +69,7 @@ function Root(props: DrawerRootProps) {
68
69
 
69
70
  function Popup({ children, ...rest }: DrawerPopupProps) {
70
71
  const drawer = useContext(DrawerContext);
71
- if (!drawer) throw new Error("Drawer parts must be inside <Drawer.Root>.");
72
+ if (!drawer) throw notInside("Drawer parts", "<Drawer.Root>");
72
73
  return (
73
74
  <Dialog.Popup {...rest} data-swipe-direction={drawer.direction()}>
74
75
  {children}
@@ -32,6 +32,7 @@ import fieldScript from "./field.script.ts?client";
32
32
  import { listenToForm, reportValidity } from "../form/native.ts";
33
33
  import { Input, type InputProps } from "../input/input.tsx";
34
34
  import { batched } from "../batched.ts";
35
+ import { notInside } from "../not-inside.ts";
35
36
 
36
37
  declare module "lumiose/jsx-runtime" {
37
38
  namespace JSX {
@@ -373,7 +374,7 @@ function Validity({ children }: FieldValidityProps) {
373
374
 
374
375
  function useFieldPart(part: string): FieldContextValue {
375
376
  const field = useContext(FieldContext);
376
- if (!field) throw new Error(`Field.${part} must be inside <Field.Root>.`);
377
+ if (!field) throw notInside(`Field.${part}`, "<Field.Root>");
377
378
  return field;
378
379
  }
379
380
 
package/dist/index.ts CHANGED
@@ -259,3 +259,20 @@ export type {
259
259
  NavigationMenuRootProps,
260
260
  NavigationMenuTriggerProps,
261
261
  } from "./navigation-menu/navigation-menu.tsx";
262
+ export {
263
+ createVirtualizer,
264
+ VirtualGrid,
265
+ VirtualList,
266
+ VirtualTable,
267
+ } from "./virtualizer/virtual.tsx";
268
+ export type {
269
+ Virtualizer,
270
+ VirtualizerOptions,
271
+ VirtualizerScrollOptions,
272
+ VirtualizerValue,
273
+ VirtualGridProps,
274
+ VirtualItem,
275
+ VirtualListProps,
276
+ VirtualProps,
277
+ VirtualTableProps,
278
+ } from "./virtualizer/virtual.tsx";
@@ -22,6 +22,7 @@ import menuScript from "./menu.script.ts?client";
22
22
  import { anchorName, placement, withStyle, type Align, type Side } from "../placement/placement.ts";
23
23
  import { OUTSIDE_TOOLBAR, ToolbarContext } from "../toolbar/context.ts";
24
24
  import { batched } from "../batched.ts";
25
+ import { notInside } from "../not-inside.ts";
25
26
 
26
27
  declare module "lumiose/jsx-runtime" {
27
28
  namespace JSX {
@@ -190,7 +191,7 @@ const RadioContext = createContext<{ value: () => string | undefined }>();
190
191
 
191
192
  function useMenu(): MenuContextValue {
192
193
  const context = useContext(MenuContext);
193
- if (!context) throw new Error("Menu parts must be inside <Menu.Root>.");
194
+ if (!context) throw notInside("Menu parts", "<Menu.Root>");
194
195
  return context;
195
196
  }
196
197
 
@@ -417,7 +418,7 @@ function Group(props: MenuGroupProps) {
417
418
 
418
419
  function GroupLabel({ children, ...rest }: MenuGroupLabelProps) {
419
420
  const group = useContext(GroupContext);
420
- if (!group) throw new Error("Menu.GroupLabel must be inside <Menu.Group> or <Menu.RadioGroup>.");
421
+ if (!group) throw notInside("Menu.GroupLabel", "<Menu.Group> or <Menu.RadioGroup>");
421
422
  register(group.labels);
422
423
  return (
423
424
  <div role="presentation" {...rest} id={group.labelId}>
@@ -467,7 +468,7 @@ function RadioItem({
467
468
  }: MenuRadioItemProps) {
468
469
  useMenu();
469
470
  const radio = useContext(RadioContext);
470
- if (!radio) throw new Error("Menu.RadioItem must be inside <Menu.RadioGroup>.");
471
+ if (!radio) throw notInside("Menu.RadioItem", "<Menu.RadioGroup>");
471
472
  return (
472
473
  <button
473
474
  type="button"
@@ -6,6 +6,7 @@
6
6
  import { createContext, useContext, useId } from "lumiose";
7
7
  import { withStyle } from "../placement/placement.ts";
8
8
  import { formatValue, percent } from "../progress/value.ts";
9
+ import { notInside } from "../not-inside.ts";
9
10
 
10
11
  interface MeterContextValue {
11
12
  id: string;
@@ -52,7 +53,7 @@ export type MeterValueProps = Omit<JSX.IntrinsicElements["span"], "children"> &
52
53
 
53
54
  function useMeter(part: string): MeterContextValue {
54
55
  const context = useContext(MeterContext);
55
- if (!context) throw new Error(`Meter.${part} must be inside <Meter.Root>.`);
56
+ if (!context) throw notInside(`Meter.${part}`, "<Meter.Root>");
56
57
  return context;
57
58
  }
58
59
 
@@ -16,6 +16,7 @@ import type {
16
16
  NavigationMenuValueChangeDetail,
17
17
  } from "./navigation-menu.script.ts";
18
18
  import { batched } from "../batched.ts";
19
+ import { notInside } from "../not-inside.ts";
19
20
 
20
21
  declare module "lumiose/jsx-runtime" {
21
22
  namespace JSX {
@@ -100,7 +101,7 @@ const ItemContext = createContext<ItemContextValue>();
100
101
 
101
102
  function useItem(part: string): ItemContextValue {
102
103
  const item = useContext(ItemContext);
103
- if (!item) throw new Error(`NavigationMenu.${part} must be inside <NavigationMenu.Item>.`);
104
+ if (!item) throw notInside(`NavigationMenu.${part}`, "<NavigationMenu.Item>");
104
105
  return item;
105
106
  }
106
107
 
@@ -0,0 +1,13 @@
1
+ // The error a compound component's part throws when the Root (or another part) it needs is not
2
+ // above it. The case that is hard to see is an island in between (TASK-550): a part passed to an
3
+ // island as a slot renders in the page, where the Root the island renders around the slot is not.
4
+
5
+ /** `subject must be inside container.`, with the island hint. */
6
+ export function notInside(subject: string, container: string): Error {
7
+ return new Error(
8
+ `${subject} must be inside ${container}. If this part looks inside, check for an island in between: ` +
9
+ "a part passed to an island as a slot (children or a JSX prop) renders in the page, not inside " +
10
+ "the components the island renders around it. Pass the data as props and render the parts " +
11
+ "inside the island.",
12
+ );
13
+ }
@@ -25,6 +25,7 @@ import { formatNumber, formatter } from "./format.ts";
25
25
  import type { NumberFieldElement } from "./number-field.script.ts";
26
26
  import numberFieldScript from "./number-field.script.ts?client";
27
27
  import { batched } from "../batched.ts";
28
+ import { notInside } from "../not-inside.ts";
28
29
 
29
30
  declare module "lumiose/jsx-runtime" {
30
31
  namespace JSX {
@@ -165,7 +166,7 @@ const NumberFieldContext = createContext<NumberFieldContextValue>();
165
166
 
166
167
  function useNumberField(part: string): NumberFieldContextValue {
167
168
  const context = useContext(NumberFieldContext);
168
- if (!context) throw new Error(`NumberField.${part} must be inside <NumberField.Root>.`);
169
+ if (!context) throw notInside(`NumberField.${part}`, "<NumberField.Root>");
169
170
  return context;
170
171
  }
171
172
 
@@ -13,6 +13,7 @@ import { onFormReset } from "../form/reset.ts";
13
13
  import type { OTPFieldElement, OTPFieldValueDetail } from "./otp-field.script.ts";
14
14
  import otpFieldScript from "./otp-field.script.ts?client";
15
15
  import { batched } from "../batched.ts";
16
+ import { notInside } from "../not-inside.ts";
16
17
 
17
18
  declare module "lumiose/jsx-runtime" {
18
19
  namespace JSX {
@@ -145,7 +146,7 @@ const OTPFieldContext = createContext<OTPFieldContextValue>();
145
146
 
146
147
  function useOTPField(part: string): OTPFieldContextValue {
147
148
  const context = useContext(OTPFieldContext);
148
- if (!context) throw new Error(`OTPField.${part} must be inside <OTPField.Root>.`);
149
+ if (!context) throw notInside(`OTPField.${part}`, "<OTPField.Root>");
149
150
  return context;
150
151
  }
151
152
 
@@ -157,7 +157,8 @@ export function hoverIntent(source: HoverSource): HoverIntent {
157
157
  const { popup, x, y } = corridor;
158
158
  // Arrived (the popup's `pointerover` cancelled the close), or closed already.
159
159
  if (!closing.has(popup)) corridor = undefined;
160
- else if (toward(popup.getBoundingClientRect(), x, y, event.clientX, event.clientY)) close(popup);
160
+ else if (toward(popup.getBoundingClientRect(), x, y, event.clientX, event.clientY))
161
+ close(popup);
161
162
  else corridor = undefined;
162
163
  });
163
164
 
@@ -28,6 +28,7 @@ import popoverScript from "./popover.script.ts?client";
28
28
  import popoverHoverScript from "./popover-hover.script.ts?client";
29
29
  import { OUTSIDE_TOOLBAR, ToolbarContext } from "../toolbar/context.ts";
30
30
  import { batched } from "../batched.ts";
31
+ import { notInside } from "../not-inside.ts";
31
32
 
32
33
  /**
33
34
  * `Popover.Root` renders no element: it holds the ids its parts share. `bind:open={signal}`
@@ -97,7 +98,7 @@ const PopoverContext = createContext<PopoverContextValue>();
97
98
 
98
99
  function usePopover(): PopoverContextValue {
99
100
  const context = useContext(PopoverContext);
100
- if (!context) throw new Error("Popover parts must be inside <Popover.Root>.");
101
+ if (!context) throw notInside("Popover parts", "<Popover.Root>");
101
102
  return context;
102
103
  }
103
104
 
@@ -174,13 +175,16 @@ function Trigger({
174
175
  ? { command: "show-modal", commandfor: popover.popupId, "aria-expanded": "false" as const }
175
176
  : { popovertarget: popover.popupId };
176
177
  // A function, so hover opening and its delays follow the props: the script reads them from
177
- // the trigger at each hover (TASK-477).
178
- const hover = () =>
179
- openOnHover && !popover.modal && !off
180
- ? { "data-open-on-hover": "", "data-delay": delay, "data-close-delay": closeDelay }
181
- : {};
182
- // Only a hover trigger loads the hover timing on a server page (TASK-541).
183
- if (untrack(() => openOnHover)) popoverHoverScript();
178
+ // the trigger at each hover (TASK-477). Only a hover trigger loads the hover timing (TASK-541),
179
+ // on a server page or in an island, once `openOnHover` is on: the call is conditional, so an
180
+ // island loads the script when it is made (TASK-551).
181
+ const hover = () => {
182
+ if (openOnHover && !popover.modal && !off) {
183
+ popoverHoverScript();
184
+ return { "data-open-on-hover": "", "data-delay": delay, "data-close-delay": closeDelay };
185
+ }
186
+ return {};
187
+ };
184
188
  return (
185
189
  <button
186
190
  type="button"
@@ -8,6 +8,7 @@ import { createContext, effect, ref, useContext, useId } from "lumiose";
8
8
  import { anchorName, placement, withStyle, type PlacementProps } from "../placement/placement.ts";
9
9
  import previewCardScript from "./preview-card.script.ts?client";
10
10
  import { batched } from "../batched.ts";
11
+ import { notInside } from "../not-inside.ts";
11
12
 
12
13
  /**
13
14
  * `PreviewCard.Root` renders no element: it holds the ids its parts share. `bind:open={signal}`
@@ -44,7 +45,7 @@ const PreviewCardContext = createContext<PreviewCardContextValue>();
44
45
 
45
46
  function usePreviewCard(part: string): PreviewCardContextValue {
46
47
  const context = useContext(PreviewCardContext);
47
- if (!context) throw new Error(`PreviewCard.${part} must be inside <PreviewCard.Root>.`);
48
+ if (!context) throw notInside(`PreviewCard.${part}`, "<PreviewCard.Root>");
48
49
  return context;
49
50
  }
50
51
 
@@ -6,6 +6,7 @@
6
6
  import { createContext, useContext, useId } from "lumiose";
7
7
  import { withStyle } from "../placement/placement.ts";
8
8
  import { formatValue, percent } from "./value.ts";
9
+ import { notInside } from "../not-inside.ts";
9
10
 
10
11
  interface ProgressContextValue {
11
12
  id: string;
@@ -52,7 +53,7 @@ export type ProgressValueProps = Omit<JSX.IntrinsicElements["span"], "children">
52
53
 
53
54
  function useProgress(part: string): ProgressContextValue {
54
55
  const context = useContext(ProgressContext);
55
- if (!context) throw new Error(`Progress.${part} must be inside <Progress.Root>.`);
56
+ if (!context) throw notInside(`Progress.${part}`, "<Progress.Root>");
56
57
  return context;
57
58
  }
58
59
 
@@ -8,6 +8,7 @@ import checkboxScript from "../checkbox/checkbox.script.ts?client";
8
8
  import { describedBy, labelledBy, useField, useFormValues } from "../field/context.ts";
9
9
  import { onFormReset } from "../form/reset.ts";
10
10
  import { batched } from "../batched.ts";
11
+ import { notInside } from "../not-inside.ts";
11
12
 
12
13
  interface RadioGroupContextValue {
13
14
  name: () => string;
@@ -137,7 +138,7 @@ export function RadioGroup({
137
138
 
138
139
  function Root({ value, ...rest }: RadioRootProps) {
139
140
  const group = useContext(RadioGroupContext);
140
- if (!group) throw new Error("Radio.Root must be inside <RadioGroup>.");
141
+ if (!group) throw notInside("Radio.Root", "<RadioGroup>");
141
142
  return (
142
143
  <input
143
144
  required={group.required() || undefined}
@@ -19,6 +19,7 @@ import selectScript from "./select.script.ts?client";
19
19
  import { fieldProps, useField, useFormValues } from "../field/context.ts";
20
20
  import { onFormReset } from "../form/reset.ts";
21
21
  import { batched } from "../batched.ts";
22
+ import { notInside } from "../not-inside.ts";
22
23
 
23
24
  declare module "lumiose/jsx-runtime" {
24
25
  namespace JSX {
@@ -149,14 +150,19 @@ function Value(props: SelectValueProps) {
149
150
 
150
151
  function Item({ children, value, label, ...rest }: SelectItemProps) {
151
152
  const select = useContext(SelectContext);
152
- if (!select) throw new Error("Select parts must be inside <Select.Root>.");
153
- // Only items with a label need the script.
154
- if (untrack(() => label) !== undefined) selectScript();
153
+ if (!select) throw notInside("Select parts", "<Select.Root>");
154
+ // Only items with a label need the script. Read where the attribute is, so a label that appears
155
+ // after an island's first render still loads it (TASK-552; the call is conditional, so an island
156
+ // loads the script when it is made, TASK-551).
157
+ const labelled = () => {
158
+ if (label !== undefined) selectScript();
159
+ return label;
160
+ };
155
161
  return (
156
162
  <option
157
163
  aria-label={label}
158
164
  {...rest}
159
- label={label}
165
+ label={labelled()}
160
166
  value={value}
161
167
  selected={select.initial === value || undefined}
162
168
  >
@@ -14,6 +14,7 @@ import { formatValues } from "./format.ts";
14
14
  import type { SliderElement } from "./slider.script.ts";
15
15
  import sliderScript from "./slider.script.ts?client";
16
16
  import { batched } from "../batched.ts";
17
+ import { notInside } from "../not-inside.ts";
17
18
 
18
19
  declare module "lumiose/jsx-runtime" {
19
20
  namespace JSX {
@@ -156,7 +157,7 @@ const SliderContext = createContext<SliderContextValue>();
156
157
 
157
158
  function useSlider(part: string): SliderContextValue {
158
159
  const context = useContext(SliderContext);
159
- if (!context) throw new Error(`Slider.${part} must be inside <Slider.Root>.`);
160
+ if (!context) throw notInside(`Slider.${part}`, "<Slider.Root>");
160
161
  return context;
161
162
  }
162
163
 
@@ -20,6 +20,7 @@ import { withStyle } from "../placement/placement.ts";
20
20
  import defineTabs from "./tabs.script.ts?client";
21
21
  import type { LuTabs } from "./tabs.script.ts";
22
22
  import { batched } from "../batched.ts";
23
+ import { notInside } from "../not-inside.ts";
23
24
 
24
25
  declare module "lumiose/jsx-runtime" {
25
26
  namespace JSX {
@@ -114,7 +115,7 @@ const TabsContext = createContext<TabsContextValue>();
114
115
 
115
116
  function useTabs(): TabsContextValue {
116
117
  const context = useContext(TabsContext);
117
- if (!context) throw new Error("Tabs parts must be inside <Tabs.Root>.");
118
+ if (!context) throw notInside("Tabs parts", "<Tabs.Root>");
118
119
  return context;
119
120
  }
120
121
 
@@ -17,6 +17,7 @@ import toastScript from "./toast.script.ts?client";
17
17
  import { CLOSE_COMMAND } from "./command.ts";
18
18
  import type { LuToastViewport } from "./toast.script.ts";
19
19
  import { batched } from "../batched.ts";
20
+ import { notInside } from "../not-inside.ts";
20
21
 
21
22
  declare module "lumiose/jsx-runtime" {
22
23
  namespace JSX {
@@ -255,7 +256,7 @@ const ToastItemContext = createContext<ToastItemContextValue>();
255
256
 
256
257
  function useToastItem(): ToastItemContextValue {
257
258
  const context = useContext(ToastItemContext);
258
- if (!context) throw new Error("Toast parts must be inside <Toast.Root>.");
259
+ if (!context) throw notInside("Toast parts", "<Toast.Root>");
259
260
  return context;
260
261
  }
261
262
 
@@ -14,6 +14,7 @@ import { Separator, type SeparatorProps } from "../separator/separator.tsx";
14
14
  import { ToolbarContext, type ToolbarContextValue } from "./context.ts";
15
15
  import type { ToolbarElement } from "./toolbar.script.ts";
16
16
  import toolbarScript from "./toolbar.script.ts?client";
17
+ import { notInside } from "../not-inside.ts";
17
18
 
18
19
  declare module "lumiose/jsx-runtime" {
19
20
  namespace JSX {
@@ -63,7 +64,7 @@ export type ToolbarSeparatorProps = SeparatorProps;
63
64
 
64
65
  function useToolbar(part: string): ToolbarContextValue {
65
66
  const context = useContext(ToolbarContext);
66
- if (!context) throw new Error(`Toolbar.${part} must be inside <Toolbar.Root>.`);
67
+ if (!context) throw notInside(`Toolbar.${part}`, "<Toolbar.Root>");
67
68
  return context;
68
69
  }
69
70
 
@@ -9,6 +9,7 @@ import { createContext, effect, ref, useContext, useId } from "lumiose";
9
9
  import { anchorName, placement, withStyle, type PlacementProps } from "../placement/placement.ts";
10
10
  import tooltipScript from "./tooltip.script.ts?client";
11
11
  import { batched } from "../batched.ts";
12
+ import { notInside } from "../not-inside.ts";
12
13
 
13
14
  /**
14
15
  * `Tooltip.Root` renders no element: it holds the ids its parts share. `bind:open={signal}`
@@ -62,7 +63,7 @@ const TooltipContext = createContext<TooltipContextValue>();
62
63
 
63
64
  function useTooltip(): TooltipContextValue {
64
65
  const context = useContext(TooltipContext);
65
- if (!context) throw new Error("Tooltip parts must be inside <Tooltip.Root>.");
66
+ if (!context) throw notInside("Tooltip parts", "<Tooltip.Root>");
66
67
  return context;
67
68
  }
68
69