@zvk/ui 0.1.8 → 0.1.9

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 (59) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/README.md +1 -1
  3. package/dist/components/alert-dialog/alert-dialog.d.ts +1 -1
  4. package/dist/components/alert-dialog/alert-dialog.js +18 -10
  5. package/dist/components/combobox/combobox.js +3 -1
  6. package/dist/components/command/command-filter.js +1 -1
  7. package/dist/components/command/command.d.ts +5 -0
  8. package/dist/components/command/command.js +9 -2
  9. package/dist/components/command/index.d.ts +1 -1
  10. package/dist/components/context-menu/context-menu.js +1 -1
  11. package/dist/components/date-range-picker/date-range-picker.d.ts +27 -0
  12. package/dist/components/date-range-picker/date-range-picker.js +193 -0
  13. package/dist/components/date-range-picker/index.d.ts +2 -0
  14. package/dist/components/date-range-picker/index.js +2 -0
  15. package/dist/components/dialog/dialog.d.ts +1 -1
  16. package/dist/components/dialog/dialog.js +19 -14
  17. package/dist/components/dropdown-menu/dropdown-menu.d.ts +2 -2
  18. package/dist/components/dropdown-menu/dropdown-menu.js +24 -19
  19. package/dist/components/file-dropzone/file-dropzone.d.ts +25 -0
  20. package/dist/components/file-dropzone/file-dropzone.js +171 -0
  21. package/dist/components/file-dropzone/index.d.ts +2 -0
  22. package/dist/components/file-dropzone/index.js +2 -0
  23. package/dist/components/form/form.d.ts +16 -1
  24. package/dist/components/form/form.js +13 -2
  25. package/dist/components/form/index.d.ts +1 -1
  26. package/dist/components/hover-card/hover-card.d.ts +1 -1
  27. package/dist/components/hover-card/hover-card.js +12 -3
  28. package/dist/components/index.d.ts +6 -0
  29. package/dist/components/index.js +3 -0
  30. package/dist/components/kbd/index.d.ts +2 -0
  31. package/dist/components/kbd/index.js +1 -0
  32. package/dist/components/kbd/kbd.d.ts +15 -0
  33. package/dist/components/kbd/kbd.js +10 -0
  34. package/dist/components/menubar/menubar.js +1 -1
  35. package/dist/components/popover/popover.d.ts +1 -1
  36. package/dist/components/popover/popover.js +14 -24
  37. package/dist/components/select/select.d.ts +1 -1
  38. package/dist/components/select/select.js +88 -8
  39. package/dist/components/sheet/sheet.d.ts +1 -1
  40. package/dist/components/sheet/sheet.js +17 -12
  41. package/dist/components/table/index.d.ts +1 -1
  42. package/dist/components/table/table.d.ts +37 -0
  43. package/dist/components/table/table.js +30 -2
  44. package/dist/components/toast/index.d.ts +1 -1
  45. package/dist/components/toast/toast.d.ts +18 -0
  46. package/dist/components/toast/toast.js +60 -0
  47. package/dist/components/tooltip/tooltip.d.ts +1 -1
  48. package/dist/components/tooltip/tooltip.js +12 -3
  49. package/dist/internal/floating/transform-origin.d.ts +2 -0
  50. package/dist/internal/floating/transform-origin.js +22 -0
  51. package/dist/internal/presence/index.d.ts +2 -0
  52. package/dist/internal/presence/index.js +2 -0
  53. package/dist/internal/presence/use-presence.d.ts +18 -0
  54. package/dist/internal/presence/use-presence.js +119 -0
  55. package/dist/styles.css +1710 -224
  56. package/dist/tokens/token-types.d.ts +4 -0
  57. package/dist/tokens/tokens.d.ts +41 -5
  58. package/dist/tokens/tokens.js +45 -9
  59. package/package.json +135 -61
@@ -7,7 +7,9 @@ import { cn } from "../../utils/cn.js";
7
7
  import { Portal } from "../../internal/portal/index.js";
8
8
  import { DismissableLayer } from "../../internal/dismissable-layer/index.js";
9
9
  import { useFloatingPosition } from "../../internal/floating/index.js";
10
+ import { getFloatingTransformOrigin } from "../../internal/floating/transform-origin.js";
10
11
  import { placementFromSideAlign } from "../../internal/floating/placement-aliases.js";
12
+ import { usePresence } from "../../internal/presence/index.js";
11
13
  import { Slot } from "../../internal/slot/index.js";
12
14
  const defaultPositioning = {
13
15
  sideOffset: 0,
@@ -15,6 +17,7 @@ const defaultPositioning = {
15
17
  collisionPadding: 0,
16
18
  matchTriggerWidth: false
17
19
  };
20
+ const popoverExitDurationMs = 120;
18
21
  const PopoverContext = React.createContext(null);
19
22
  function usePopoverContext(calledBy) {
20
23
  const context = React.useContext(PopoverContext);
@@ -32,26 +35,6 @@ function mergeRef(ref, node) {
32
35
  ref.current = node;
33
36
  }
34
37
  }
35
- function getTransformOrigin(placement) {
36
- const [side, align = "center"] = placement.split("-");
37
- const anchor = side === "top" ? "bottom" : side === "bottom" ? "top" : side === "left" ? "right" : "left";
38
- if (side === "top" || side === "bottom") {
39
- if (align === "start") {
40
- return `${anchor} left`;
41
- }
42
- if (align === "end") {
43
- return `${anchor} right`;
44
- }
45
- return `${anchor} center`;
46
- }
47
- if (align === "start") {
48
- return `${anchor} top`;
49
- }
50
- if (align === "end") {
51
- return `${anchor} bottom`;
52
- }
53
- return `${anchor} center`;
54
- }
55
38
  function getPlacementParts(placement) {
56
39
  const [side, align = "center"] = placement.split("-");
57
40
  return { side, align };
@@ -132,10 +115,13 @@ function PopoverTrigger({ asChild = false, className, disabled, onClick, ref, ty
132
115
  }
133
116
  return (_jsx("button", { ...props, ref: handleRef, type: type, disabled: disabled, className: cn("zvk-ui-popover__trigger", className), "aria-expanded": context.open, "aria-controls": context.contentId, "aria-haspopup": "dialog", "data-state": context.open ? "open" : "closed", onClick: composeEventHandlers(onClick, handleClick) }));
134
117
  }
135
- function PopoverContent({ align, alignOffset = defaultPositioning.alignOffset, className, container, forceMount = false, id, disableEscapeKeyDown = false, disableOutsidePointerDown = false, ref, placement, side, sideOffset = 0, collisionPadding = 0, matchTriggerWidth = false, style, ...props }) {
118
+ function PopoverContent({ align, alignOffset = defaultPositioning.alignOffset, className, container, forceMount = false, id, disableEscapeKeyDown = false, disableOutsidePointerDown = false, onAnimationEnd, ref, placement, side, sideOffset = 0, collisionPadding = 0, matchTriggerWidth = false, style, ...props }) {
136
119
  const context = usePopoverContext("Popover.Content");
137
120
  const { defaultContentId, setContentId } = context;
138
- const transformOrigin = getTransformOrigin(context.floatingPlacement);
121
+ const presence = usePresence({ open: context.open, forceMount, exitDurationMs: popoverExitDurationMs });
122
+ const wasOpenRef = React.useRef(context.open);
123
+ const isClosing = wasOpenRef.current && !context.open;
124
+ const transformOrigin = getFloatingTransformOrigin(context.floatingPlacement);
139
125
  const placementParts = getPlacementParts(context.floatingPlacement);
140
126
  const contentId = id ?? defaultContentId;
141
127
  const handleRef = React.useCallback((node) => {
@@ -153,6 +139,9 @@ function PopoverContent({ align, alignOffset = defaultPositioning.alignOffset, c
153
139
  setContentId(defaultContentId);
154
140
  };
155
141
  }, [contentId, defaultContentId, setContentId]);
142
+ React.useEffect(() => {
143
+ wasOpenRef.current = context.open;
144
+ }, [context.open]);
156
145
  const close = () => {
157
146
  context.setOpen(false);
158
147
  };
@@ -176,10 +165,11 @@ function PopoverContent({ align, alignOffset = defaultPositioning.alignOffset, c
176
165
  context.updateContentPositioning(defaultPositioning);
177
166
  };
178
167
  }, [align, alignOffset, context.updateContentPositioning, collisionPadding, matchTriggerWidth, placement, side, sideOffset]);
179
- if (!forceMount && !context.open) {
168
+ if (!presence.present) {
180
169
  return null;
181
170
  }
182
- const content = (_jsx("div", { ...props, ref: handleRef, id: contentId, role: "dialog", className: cn("zvk-ui-popover__content", className), "data-align": placementParts.align, "data-side": placementParts.side, "data-state": context.open ? "open" : "closed", hidden: context.open ? undefined : true, style: contentStyle }));
171
+ const hidden = presence.inert && !isClosing && presence.motionState !== "exiting" ? true : undefined;
172
+ const content = (_jsx("div", { ...props, ref: handleRef, "aria-hidden": presence.inert ? true : undefined, id: contentId, inert: presence.inert ? true : undefined, role: "dialog", className: cn("zvk-ui-popover__content", className), "data-align": placementParts.align, "data-side": placementParts.side, "data-state": presence.state, hidden: hidden, onAnimationEnd: composeEventHandlers(onAnimationEnd, presence.onExitComplete), style: contentStyle }));
183
173
  if (context.open &&
184
174
  props["aria-label"] === undefined &&
185
175
  props["aria-labelledby"] === undefined &&
@@ -53,7 +53,7 @@ export interface SelectClearProps extends React.ButtonHTMLAttributes<HTMLButtonE
53
53
  declare function SelectRoot({ children, className, defaultOpen, defaultValue, disabled, invalid, name, onOpenChange, onValueChange, open: openProp, placeholder, placement, required, size, value, ...props }: SelectProps): React.JSX.Element;
54
54
  declare function SelectTrigger({ children, className, disabled, onClick, onKeyDown, ref, type, ...props }: SelectTriggerProps): React.JSX.Element;
55
55
  declare function SelectValue({ className, placeholder, ref, ...props }: SelectValueProps): React.JSX.Element;
56
- declare function SelectContent({ children, className, collisionPadding, container, forceMount, matchTriggerWidth, onKeyDown, ref, sideOffset, style, ...props }: SelectContentProps): React.JSX.Element | null;
56
+ declare function SelectContent({ children, className, collisionPadding, container, forceMount, matchTriggerWidth, onAnimationEnd, onKeyDown, ref, sideOffset, style, ...props }: SelectContentProps): React.JSX.Element | null;
57
57
  declare function SelectItem({ children, className, disabled, onClick, onKeyDown, ref, value, ...props }: SelectItemProps): React.JSX.Element;
58
58
  declare function SelectGroup({ children, className, ref, ...props }: SelectGroupProps): React.JSX.Element;
59
59
  declare function SelectLabel({ className, ref, ...props }: SelectLabelProps): React.JSX.Element;
@@ -7,6 +7,8 @@ import { cn } from "../../utils/cn.js";
7
7
  import { Portal } from "../../internal/portal/index.js";
8
8
  import { DismissableLayer } from "../../internal/dismissable-layer/index.js";
9
9
  import { useFloatingPosition } from "../../internal/floating/index.js";
10
+ import { placementParts } from "../../internal/floating/placement-aliases.js";
11
+ import { usePresence } from "../../internal/presence/index.js";
10
12
  const nativeSelectStyle = {
11
13
  position: "absolute",
12
14
  width: 1,
@@ -20,6 +22,8 @@ const defaultContentPositioning = {
20
22
  collisionPadding: 0,
21
23
  matchTriggerWidth: true
22
24
  };
25
+ const selectExitDurationMs = 120;
26
+ const selectTypeaheadTimeoutMs = 700;
23
27
  const SelectContext = React.createContext(null);
24
28
  function useSelectContext(calledBy) {
25
29
  const context = React.useContext(SelectContext);
@@ -43,6 +47,18 @@ function composeRefs(...refs) {
43
47
  function getTextLabel(node) {
44
48
  return node;
45
49
  }
50
+ function getTypeaheadLabel(node, fallback) {
51
+ if (typeof node === "string" || typeof node === "number") {
52
+ return String(node);
53
+ }
54
+ if (Array.isArray(node)) {
55
+ return node.map((child) => getTypeaheadLabel(child, "")).join(" ").trim() || fallback;
56
+ }
57
+ if (React.isValidElement(node)) {
58
+ return getTypeaheadLabel(node.props.children, fallback);
59
+ }
60
+ return fallback;
61
+ }
46
62
  function getNativeOptionLabel(label, value) {
47
63
  if (typeof label === "string" || typeof label === "number") {
48
64
  return String(label);
@@ -70,6 +86,30 @@ function collectNativeOptions(children) {
70
86
  visit(children);
71
87
  return options;
72
88
  }
89
+ function isPrintableTypeaheadKey(event) {
90
+ return event.key.length === 1 && !event.altKey && !event.ctrlKey && !event.metaKey;
91
+ }
92
+ function getTypeaheadSearch(buffer) {
93
+ const normalized = buffer.toLocaleLowerCase();
94
+ if (normalized.length > 1 && normalized.split("").every((character) => character === normalized[0])) {
95
+ return normalized[0] ?? "";
96
+ }
97
+ return normalized;
98
+ }
99
+ function findTypeaheadMatch(items, search, activeElement) {
100
+ if (items.length === 0 || search.length === 0) {
101
+ return undefined;
102
+ }
103
+ const activeIndex = items.findIndex((item) => item.element === activeElement);
104
+ const startIndex = activeIndex >= 0 ? activeIndex + 1 : 0;
105
+ for (let offset = 0; offset < items.length; offset += 1) {
106
+ const item = items[(startIndex + offset) % items.length];
107
+ if (item?.labelText.toLocaleLowerCase().startsWith(search)) {
108
+ return item;
109
+ }
110
+ }
111
+ return undefined;
112
+ }
73
113
  function SelectRoot({ children, className, defaultOpen = false, defaultValue = null, disabled, invalid, name, onOpenChange, onValueChange, open: openProp, placeholder, placement = "bottom-start", required, size = "md", value, ...props }) {
74
114
  const instanceId = React.useId();
75
115
  const triggerId = `${instanceId}-trigger`;
@@ -77,6 +117,8 @@ function SelectRoot({ children, className, defaultOpen = false, defaultValue = n
77
117
  const triggerRef = React.useRef(null);
78
118
  const itemRegistry = React.useRef(new Map());
79
119
  const itemOrder = React.useRef([]);
120
+ const typeaheadBuffer = React.useRef("");
121
+ const typeaheadTimer = React.useRef(null);
80
122
  const wasOpenRef = React.useRef(false);
81
123
  const [open, setOpen] = useControllableState({
82
124
  ...(openProp !== undefined ? { value: openProp } : {}),
@@ -122,6 +164,26 @@ function SelectRoot({ children, className, defaultOpen = false, defaultValue = n
122
164
  setCurrentValue(nextValue);
123
165
  setOpen(false);
124
166
  }, [setCurrentValue, setOpen]);
167
+ const resetTypeahead = React.useCallback(() => {
168
+ typeaheadBuffer.current = "";
169
+ if (typeaheadTimer.current !== null) {
170
+ clearTimeout(typeaheadTimer.current);
171
+ typeaheadTimer.current = null;
172
+ }
173
+ }, []);
174
+ const focusTypeaheadMatch = React.useCallback((key) => {
175
+ typeaheadBuffer.current += key;
176
+ if (typeaheadTimer.current !== null) {
177
+ clearTimeout(typeaheadTimer.current);
178
+ }
179
+ typeaheadTimer.current = setTimeout(resetTypeahead, selectTypeaheadTimeoutMs);
180
+ const match = findTypeaheadMatch(getEnabledItems(), getTypeaheadSearch(typeaheadBuffer.current), document.activeElement);
181
+ if (!match?.element) {
182
+ return false;
183
+ }
184
+ match.element.focus();
185
+ return true;
186
+ }, [getEnabledItems, resetTypeahead]);
125
187
  const orderedItems = React.useMemo(() => getOrderedItems(), [getOrderedItems, itemsVersion]);
126
188
  const selectedLabel = React.useMemo(() => orderedItems.find((item) => item.value === currentValue)?.label ??
127
189
  nativeOptions.find((item) => item.value === currentValue)?.label, [currentValue, nativeOptions, orderedItems]);
@@ -133,13 +195,17 @@ function SelectRoot({ children, className, defaultOpen = false, defaultValue = n
133
195
  if (wasOpenRef.current && !open) {
134
196
  triggerRef.current?.focus();
135
197
  }
198
+ if (!open) {
199
+ resetTypeahead();
200
+ }
136
201
  wasOpenRef.current = open;
137
- }, [open]);
202
+ }, [open, resetTypeahead]);
138
203
  React.useEffect(() => {
139
204
  if (!required || currentValue !== null) {
140
205
  setNativeInvalid(false);
141
206
  }
142
207
  }, [currentValue, required]);
208
+ React.useEffect(() => resetTypeahead, [resetTypeahead]);
143
209
  const handleNativeInvalid = React.useCallback((event) => {
144
210
  event.preventDefault();
145
211
  setNativeInvalid(true);
@@ -147,6 +213,7 @@ function SelectRoot({ children, className, defaultOpen = false, defaultValue = n
147
213
  }, []);
148
214
  const context = React.useMemo(() => ({
149
215
  contentId,
216
+ focusTypeaheadMatch,
150
217
  ...(disabled !== undefined ? { disabled } : {}),
151
218
  getEnabledItems,
152
219
  ...(resolvedInvalid ? { invalid: true } : {}),
@@ -164,8 +231,9 @@ function SelectRoot({ children, className, defaultOpen = false, defaultValue = n
164
231
  setOpen,
165
232
  referenceRef: floating.referenceRef,
166
233
  floatingRef: floating.floatingRef,
234
+ floatingPlacement: floating.placement,
167
235
  floatingStyle: floating.floatingStyle
168
- }), [contentId, currentValue, disabled, floating.floatingRef, floating.floatingStyle, floating.referenceRef, getEnabledItems, open, placeholder, registerItem, required, resolvedInvalid, selectValue, selectedLabel, setOpen, triggerId, unregisterItem]);
236
+ }), [contentId, currentValue, disabled, floating.floatingRef, floating.floatingStyle, floating.placement, floating.referenceRef, focusTypeaheadMatch, getEnabledItems, open, placeholder, registerItem, required, resolvedInvalid, selectValue, selectedLabel, setOpen, triggerId, unregisterItem]);
169
237
  return (_jsx(SelectContext.Provider, { value: context, children: _jsxs("div", { ...props, className: cn("zvk-ui-select", className), "data-disabled": disabled ? "true" : undefined, "data-invalid": resolvedInvalid ? "true" : undefined, "data-required": required ? "true" : undefined, "data-size": size, "data-state": open ? "open" : "closed", children: [name ? (_jsxs("select", { "aria-hidden": "true", disabled: disabled, name: name, onChange: () => undefined, onInvalid: handleNativeInvalid, required: required, style: nativeSelectStyle, tabIndex: -1, value: nativeValue, children: [_jsx("option", { value: "" }), nativeOptions.map((item) => (_jsx("option", { disabled: item.disabled, value: item.value, children: item.label }, item.value)))] })) : null, children] }) }));
170
238
  }
171
239
  function focusItem(items, index) {
@@ -190,7 +258,7 @@ function SelectTrigger({ children, className, disabled, onClick, onKeyDown, ref,
190
258
  focusItem(items, selectedIndex >= 0 ? selectedIndex : 0);
191
259
  });
192
260
  }, [context]);
193
- return (_jsx("button", { ...props, ref: composeRefs(ref, context.triggerRef, context.referenceRef), "aria-controls": context.contentId, "aria-expanded": context.open ? "true" : "false", "aria-haspopup": "listbox", "aria-invalid": context.invalid ? "true" : undefined, "aria-required": context.required ? "true" : undefined, className: cn("zvk-ui-select__trigger", className), "data-disabled": isDisabled ? "true" : undefined, "data-invalid": context.invalid ? "true" : undefined, "data-state": context.open ? "open" : "closed", disabled: isDisabled, id: context.triggerId, onClick: composeEventHandlers(onClick, () => {
261
+ return (_jsxs("button", { ...props, ref: composeRefs(ref, context.triggerRef, context.referenceRef), "aria-controls": context.contentId, "aria-expanded": context.open ? "true" : "false", "aria-haspopup": "listbox", "aria-invalid": context.invalid ? "true" : undefined, "aria-required": context.required ? "true" : undefined, className: cn("zvk-ui-select__trigger", className), "data-disabled": isDisabled ? "true" : undefined, "data-invalid": context.invalid ? "true" : undefined, "data-state": context.open ? "open" : "closed", disabled: isDisabled, id: context.triggerId, onClick: composeEventHandlers(onClick, () => {
194
262
  if (!isDisabled) {
195
263
  context.setOpen(!context.open);
196
264
  }
@@ -206,15 +274,19 @@ function SelectTrigger({ children, className, disabled, onClick, onKeyDown, ref,
206
274
  event.preventDefault();
207
275
  openAndFocus("last");
208
276
  }
209
- }), type: type, children: children }));
277
+ }), type: type, children: [children, _jsx("span", { className: "zvk-ui-select__indicator", "aria-hidden": "true" })] }));
210
278
  }
211
279
  function SelectValue({ className, placeholder, ref, ...props }) {
212
280
  const context = useSelectContext("Select.Value");
213
281
  const displayPlaceholder = context.selectedLabel === undefined;
214
282
  return (_jsx("span", { ...props, ref: ref, className: cn("zvk-ui-select__value", className), "data-placeholder": displayPlaceholder ? "true" : undefined, children: displayPlaceholder ? placeholder ?? context.placeholder : context.selectedLabel }));
215
283
  }
216
- function SelectContent({ children, className, collisionPadding = defaultContentPositioning.collisionPadding, container, forceMount = false, matchTriggerWidth = defaultContentPositioning.matchTriggerWidth, onKeyDown, ref, sideOffset = defaultContentPositioning.sideOffset, style, ...props }) {
284
+ function SelectContent({ children, className, collisionPadding = defaultContentPositioning.collisionPadding, container, forceMount = false, matchTriggerWidth = defaultContentPositioning.matchTriggerWidth, onAnimationEnd, onKeyDown, ref, sideOffset = defaultContentPositioning.sideOffset, style, ...props }) {
217
285
  const context = useSelectContext("Select.Content");
286
+ const presence = usePresence({ open: context.open, forceMount, exitDurationMs: selectExitDurationMs });
287
+ const wasOpenRef = React.useRef(context.open);
288
+ const isClosing = wasOpenRef.current && !context.open;
289
+ const resolvedPlacement = placementParts(context.floatingPlacement);
218
290
  React.useEffect(() => {
219
291
  context.updateContentPositioning({
220
292
  sideOffset,
@@ -223,6 +295,9 @@ function SelectContent({ children, className, collisionPadding = defaultContentP
223
295
  });
224
296
  return () => context.updateContentPositioning(defaultContentPositioning);
225
297
  }, [collisionPadding, context, matchTriggerWidth, sideOffset]);
298
+ React.useEffect(() => {
299
+ wasOpenRef.current = context.open;
300
+ }, [context.open]);
226
301
  const moveFocus = React.useCallback((direction) => {
227
302
  const items = context.getEnabledItems();
228
303
  const currentIndex = items.findIndex((item) => item.element === document.activeElement);
@@ -232,10 +307,11 @@ function SelectContent({ children, className, collisionPadding = defaultContentP
232
307
  const items = context.getEnabledItems();
233
308
  focusItem(items, end ? items.length - 1 : 0);
234
309
  }, [context]);
235
- if (!context.open && !forceMount) {
310
+ if (!presence.present) {
236
311
  return null;
237
312
  }
238
- const content = (_jsx("div", { ...props, ref: composeRefs(ref, context.floatingRef), "aria-labelledby": context.triggerId, className: cn("zvk-ui-select__content", className), "data-state": context.open ? "open" : "closed", hidden: context.open ? undefined : true, id: context.contentId, onKeyDown: composeEventHandlers(onKeyDown, (event) => {
313
+ const hidden = presence.inert && !isClosing && presence.motionState !== "exiting" ? true : undefined;
314
+ const content = (_jsx("div", { ...props, ref: composeRefs(ref, context.floatingRef), "aria-hidden": presence.inert ? true : undefined, "aria-labelledby": context.triggerId, className: cn("zvk-ui-select__content", className), "data-align": resolvedPlacement.align, "data-side": resolvedPlacement.side, "data-state": presence.state, hidden: hidden, id: context.contentId, inert: presence.inert ? true : undefined, onAnimationEnd: composeEventHandlers(onAnimationEnd, presence.onExitComplete), onKeyDown: composeEventHandlers(onKeyDown, (event) => {
239
315
  if (event.key === "ArrowDown") {
240
316
  event.preventDefault();
241
317
  moveFocus("next");
@@ -255,11 +331,14 @@ function SelectContent({ children, className, collisionPadding = defaultContentP
255
331
  if (event.key === "Escape") {
256
332
  context.setOpen(false);
257
333
  }
334
+ if (isPrintableTypeaheadKey(event) && context.focusTypeaheadMatch(event.key)) {
335
+ event.preventDefault();
336
+ }
258
337
  }), role: "listbox", style: { ...context.floatingStyle, ...style }, tabIndex: -1, children: children }));
259
338
  if (!context.open) {
260
339
  return _jsx(Portal, { ...(container === undefined ? {} : { container }), children: content });
261
340
  }
262
- return (_jsx(Portal, { ...(container === undefined ? {} : { container }), children: _jsx(DismissableLayer, { open: true, onDismiss: () => context.setOpen(false), children: content }) }));
341
+ return (_jsx(Portal, { ...(container === undefined ? {} : { container }), children: _jsx(DismissableLayer, { open: context.open, onDismiss: () => context.setOpen(false), children: content }) }));
263
342
  }
264
343
  function SelectItem({ children, className, disabled = false, onClick, onKeyDown, ref, value, ...props }) {
265
344
  const context = useSelectContext("Select.Item");
@@ -272,6 +351,7 @@ function SelectItem({ children, className, disabled = false, onClick, onKeyDown,
272
351
  element: itemRef.current,
273
352
  id: itemId,
274
353
  label: getTextLabel(children),
354
+ labelText: getTypeaheadLabel(children, value),
275
355
  value
276
356
  });
277
357
  return () => context.unregisterItem(itemId);
@@ -37,7 +37,7 @@ export interface SheetCloseProps extends React.ButtonHTMLAttributes<HTMLButtonEl
37
37
  }
38
38
  declare function SheetRoot({ children, className, container, defaultOpen, disableEscapeKeyDown, disableOutsidePointerDown, onOpenChange, open: openProp, ref, side, ...props }: SheetProps): React.JSX.Element;
39
39
  declare function SheetTrigger({ asChild, className, disabled, onClick, ref, type, ...props }: SheetTriggerProps): React.JSX.Element;
40
- declare function SheetContent({ children, className, forceMount, ref, ...props }: SheetContentProps): React.JSX.Element | null;
40
+ declare function SheetContent({ children, className, forceMount, onAnimationEnd, onTransitionEnd, ref, ...props }: SheetContentProps): React.JSX.Element | null;
41
41
  declare function SheetHeader({ className, ref, ...props }: SheetHeaderProps): React.JSX.Element;
42
42
  declare function SheetTitle({ className, ref, ...props }: SheetTitleProps): React.JSX.Element;
43
43
  declare function SheetDescription({ className, ref, ...props }: SheetDescriptionProps): React.JSX.Element;
@@ -6,6 +6,7 @@ import { cn } from "../../utils/cn.js";
6
6
  import { useControllableState } from "../../hooks/use-controllable-state.js";
7
7
  import { DismissableLayer } from "../../internal/dismissable-layer/index.js";
8
8
  import { FocusScope } from "../../internal/focus/index.js";
9
+ import { usePresence } from "../../internal/presence/index.js";
9
10
  import { Portal } from "../../internal/portal/index.js";
10
11
  import { lockScroll, unlockScroll } from "../../internal/scroll-lock/index.js";
11
12
  import { Slot } from "../../internal/slot/index.js";
@@ -29,6 +30,7 @@ function composeRefs(...refs) {
29
30
  }
30
31
  };
31
32
  }
33
+ const sheetExitDurationMs = 240;
32
34
  function SheetRoot({ children, className, container, defaultOpen = false, disableEscapeKeyDown = false, disableOutsidePointerDown = false, onOpenChange, open: openProp, ref, side = "right", ...props }) {
33
35
  const [open, setOpen] = useControllableState({
34
36
  ...(openProp !== undefined ? { value: openProp } : {}),
@@ -47,13 +49,6 @@ function SheetRoot({ children, className, container, defaultOpen = false, disabl
47
49
  setDescribedBy(id);
48
50
  return () => setDescribedBy((current) => (current === id ? undefined : current));
49
51
  }, []);
50
- React.useLayoutEffect(() => {
51
- if (!open) {
52
- return;
53
- }
54
- lockScroll();
55
- return () => unlockScroll();
56
- }, [open]);
57
52
  return (_jsx(SheetContext.Provider, { value: {
58
53
  close: () => setOpen(false),
59
54
  ...(container === undefined ? {} : { container }),
@@ -84,16 +79,26 @@ function SheetTrigger({ asChild = false, className, disabled, onClick, ref, type
84
79
  }
85
80
  return (_jsx("button", { ...props, ref: composeRefs(ref, triggerRef), type: type, disabled: disabled, "aria-controls": contentId, "aria-expanded": open ? "true" : "false", className: cn("zvk-ui-sheet__trigger", className), "data-state": open ? "open" : "closed", onClick: composeEventHandlers(onClick, handleClick) }));
86
81
  }
87
- function SheetContent({ children, className, forceMount = false, ref, ...props }) {
82
+ function SheetContent({ children, className, forceMount = false, onAnimationEnd, onTransitionEnd, ref, ...props }) {
88
83
  const { close, container, contentId, describedBy, disableEscapeKeyDown, disableOutsidePointerDown, labelledBy, open, side } = useSheetContext("Sheet.Content");
89
- if (!open && !forceMount) {
84
+ const presence = usePresence({ open, forceMount, exitDurationMs: sheetExitDurationMs });
85
+ const hidden = !open && forceMount && presence.motionState === "idle";
86
+ const shouldLockScroll = presence.present && (!presence.inert || presence.motionState === "exiting");
87
+ React.useLayoutEffect(() => {
88
+ if (!shouldLockScroll) {
89
+ return;
90
+ }
91
+ lockScroll();
92
+ return () => unlockScroll();
93
+ }, [shouldLockScroll]);
94
+ if (!presence.present) {
90
95
  return null;
91
96
  }
92
- const content = (_jsx("div", { ...props, ref: ref, id: contentId, role: "dialog", "aria-modal": "true", "aria-describedby": describedBy, "aria-labelledby": labelledBy, className: cn("zvk-ui-sheet__content", className), "data-side": side, "data-state": open ? "open" : "closed", hidden: open ? undefined : true, children: children }));
97
+ const content = (_jsx("div", { ...props, ref: ref, id: contentId, role: "dialog", "aria-hidden": presence.inert ? true : undefined, "aria-modal": open ? true : undefined, "aria-describedby": describedBy, "aria-labelledby": labelledBy, className: cn("zvk-ui-sheet__content", className), "data-motion-state": presence.motionState, "data-side": side, "data-state": presence.state, hidden: hidden ? true : undefined, inert: presence.inert ? true : undefined, onAnimationEnd: composeEventHandlers(onAnimationEnd, presence.onExitComplete, { checkDefaultPrevented: false }), onTransitionEnd: composeEventHandlers(onTransitionEnd, presence.onExitComplete, { checkDefaultPrevented: false }), children: children }));
93
98
  if (!open) {
94
- return (_jsxs(Portal, { ...(container === undefined ? {} : { container }), children: [_jsx("div", { "aria-hidden": "true", className: "zvk-ui-sheet__overlay", hidden: true }), content] }));
99
+ return (_jsxs(Portal, { ...(container === undefined ? {} : { container }), children: [_jsx("div", { "aria-hidden": "true", className: "zvk-ui-sheet__overlay", "data-state": presence.state, hidden: hidden ? true : undefined }), content] }));
95
100
  }
96
- return (_jsxs(Portal, { ...(container === undefined ? {} : { container }), children: [_jsx("div", { "aria-hidden": "true", className: "zvk-ui-sheet__overlay" }), _jsx(DismissableLayer, { open: open, disableEscapeKeyDown: disableEscapeKeyDown, disableOutsidePointerDown: disableOutsidePointerDown, onDismiss: close, children: _jsx(FocusScope, { active: open, trapped: true, children: content }) })] }));
101
+ return (_jsxs(Portal, { ...(container === undefined ? {} : { container }), children: [_jsx("div", { "aria-hidden": "true", className: "zvk-ui-sheet__overlay", "data-state": presence.state }), _jsx(DismissableLayer, { open: open, disableEscapeKeyDown: disableEscapeKeyDown, disableOutsidePointerDown: disableOutsidePointerDown, onDismiss: close, children: _jsx(FocusScope, { active: open, trapped: true, children: content }) })] }));
97
102
  }
98
103
  function SheetHeader({ className, ref, ...props }) {
99
104
  return _jsx("div", { ...props, ref: ref, className: cn("zvk-ui-sheet__header", className) });
@@ -1,2 +1,2 @@
1
1
  export { Table } from "./table.js";
2
- export type { TableBodyProps, TableCaptionProps, TableCellProps, TableContainerProps, TableFooterProps, TableHeadProps, TableHeaderProps, TableProps, TableRowProps } from "./table.js";
2
+ export type { TableBodyProps, TableCaptionProps, TableCellProps, TableContainerProps, TableFooterProps, TableHeadProps, TableHeaderProps, TableProps, TableRowProps, TableSelectionCellProps, TableSortButtonProps, TableStateRowProps, TableToolbarActionsProps, TableToolbarProps, TableToolbarTitleProps } from "./table.js";
@@ -28,6 +28,31 @@ export interface TableHeadProps extends React.ThHTMLAttributes<HTMLTableHeaderCe
28
28
  export interface TableCellProps extends React.TdHTMLAttributes<HTMLTableCellElement> {
29
29
  ref?: React.Ref<HTMLTableCellElement>;
30
30
  }
31
+ export interface TableToolbarProps extends React.HTMLAttributes<HTMLDivElement> {
32
+ ref?: React.Ref<HTMLDivElement>;
33
+ }
34
+ export interface TableToolbarTitleProps extends React.HTMLAttributes<HTMLHeadingElement> {
35
+ ref?: React.Ref<HTMLHeadingElement>;
36
+ }
37
+ export interface TableToolbarActionsProps extends React.HTMLAttributes<HTMLDivElement> {
38
+ ref?: React.Ref<HTMLDivElement>;
39
+ }
40
+ export interface TableSelectionCellProps extends Omit<React.TdHTMLAttributes<HTMLTableCellElement>, "children"> {
41
+ checked: boolean;
42
+ disabled?: boolean;
43
+ onCheckedChange?: (checked: boolean) => void;
44
+ ref?: React.Ref<HTMLTableCellElement>;
45
+ "aria-label": string;
46
+ }
47
+ export interface TableSortButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
48
+ direction?: "ascending" | "descending" | "none";
49
+ ref?: React.Ref<HTMLButtonElement>;
50
+ }
51
+ export interface TableStateRowProps extends React.HTMLAttributes<HTMLTableRowElement> {
52
+ colSpan: number;
53
+ tone?: "empty" | "loading" | "error";
54
+ ref?: React.Ref<HTMLTableRowElement>;
55
+ }
31
56
  declare function TableRoot({ className, density, ref, variant, ...props }: TableProps): React.JSX.Element;
32
57
  declare function TableCaption({ className, ref, ...props }: TableCaptionProps): React.JSX.Element;
33
58
  declare function TableHeader({ className, ref, ...props }: TableHeaderProps): React.JSX.Element;
@@ -36,6 +61,12 @@ declare function TableFooter({ className, ref, ...props }: TableFooterProps): Re
36
61
  declare function TableRow({ className, ref, ...props }: TableRowProps): React.JSX.Element;
37
62
  declare function TableHead({ className, ref, scope, ...props }: TableHeadProps): React.JSX.Element;
38
63
  declare function TableCell({ className, ref, ...props }: TableCellProps): React.JSX.Element;
64
+ declare function TableToolbar({ className, ref, ...props }: TableToolbarProps): React.JSX.Element;
65
+ declare function TableToolbarTitle({ className, ref, ...props }: TableToolbarTitleProps): React.JSX.Element;
66
+ declare function TableToolbarActions({ className, ref, ...props }: TableToolbarActionsProps): React.JSX.Element;
67
+ declare function TableSelectionCell({ checked, className, disabled, onCheckedChange, ref, "aria-label": ariaLabel, ...props }: TableSelectionCellProps): React.JSX.Element;
68
+ declare function TableSortButton({ children, className, direction, ref, type, ...props }: TableSortButtonProps): React.JSX.Element;
69
+ declare function TableStateRow({ children, className, colSpan, ref, tone, ...props }: TableStateRowProps): React.JSX.Element;
39
70
  declare function TableContainer({ className, ref, ...props }: TableContainerProps): React.JSX.Element;
40
71
  export declare const Table: typeof TableRoot & {
41
72
  Container: typeof TableContainer;
@@ -46,5 +77,11 @@ export declare const Table: typeof TableRoot & {
46
77
  Row: typeof TableRow;
47
78
  Head: typeof TableHead;
48
79
  Cell: typeof TableCell;
80
+ Toolbar: typeof TableToolbar;
81
+ ToolbarTitle: typeof TableToolbarTitle;
82
+ ToolbarActions: typeof TableToolbarActions;
83
+ SelectionCell: typeof TableSelectionCell;
84
+ SortButton: typeof TableSortButton;
85
+ StateRow: typeof TableStateRow;
49
86
  };
50
87
  export {};
@@ -1,5 +1,6 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import * as React from "react";
3
+ import { Checkbox } from "../checkbox/checkbox.js";
3
4
  import { cn } from "../../utils/cn.js";
4
5
  function TableRoot({ className, density = "default", ref, variant = "plain", ...props }) {
5
6
  return (_jsx("table", { ...props, ref: ref, className: cn("zvk-ui-table", className), "data-density": density, "data-variant": variant }));
@@ -25,6 +26,27 @@ function TableHead({ className, ref, scope = "col", ...props }) {
25
26
  function TableCell({ className, ref, ...props }) {
26
27
  return _jsx("td", { ...props, ref: ref, className: cn("zvk-ui-table__cell", className) });
27
28
  }
29
+ function TableToolbar({ className, ref, ...props }) {
30
+ return _jsx("div", { ...props, ref: ref, className: cn("zvk-ui-table__toolbar", className) });
31
+ }
32
+ function TableToolbarTitle({ className, ref, ...props }) {
33
+ return _jsx("h3", { ...props, ref: ref, className: cn("zvk-ui-table__toolbar-title", className) });
34
+ }
35
+ function TableToolbarActions({ className, ref, ...props }) {
36
+ return _jsx("div", { ...props, ref: ref, className: cn("zvk-ui-table__toolbar-actions", className) });
37
+ }
38
+ function TableSelectionCell({ checked, className, disabled, onCheckedChange, ref, "aria-label": ariaLabel, ...props }) {
39
+ return (_jsx("td", { ...props, ref: ref, className: cn("zvk-ui-table__cell", "zvk-ui-table__selection-cell", className), "data-disabled": disabled ? "true" : undefined, children: _jsx(Checkbox, { "aria-label": ariaLabel, checked: checked, className: "zvk-ui-table__selection-checkbox", disabled: disabled, onChange: (event) => onCheckedChange?.(event.currentTarget.checked) }) }));
40
+ }
41
+ function TableSortButton({ children, className, direction = "none", ref, type = "button", ...props }) {
42
+ const directionLabel = direction === "ascending" ? "sorted ascending" : direction === "descending" ? "sorted descending" : "not sorted";
43
+ const visibleDirection = direction === "ascending" ? "Asc" : direction === "descending" ? "Desc" : "Sort";
44
+ const accessibleLabel = props["aria-label"] ?? (typeof children === "string" ? `${children} ${directionLabel}` : undefined);
45
+ return (_jsxs("button", { ...props, "aria-label": accessibleLabel, ref: ref, className: cn("zvk-ui-table__sort-button", className), "data-direction": direction, type: type, children: [_jsx("span", { className: "zvk-ui-table__sort-label", children: children }), _jsxs("span", { className: "zvk-ui-sr-only", children: [" ", directionLabel] }), _jsx("span", { "aria-hidden": "true", className: "zvk-ui-table__sort-direction", children: visibleDirection })] }));
46
+ }
47
+ function TableStateRow({ children, className, colSpan, ref, tone = "empty", ...props }) {
48
+ return (_jsx("tr", { ...props, ref: ref, className: cn("zvk-ui-table__row", "zvk-ui-table__state-row", className), "data-state": tone, children: _jsx("td", { className: "zvk-ui-table__cell zvk-ui-table__state-cell", colSpan: colSpan, children: children }) }));
49
+ }
28
50
  function TableContainer({ className, ref, ...props }) {
29
51
  return _jsx("div", { ...props, ref: ref, className: cn("zvk-ui-table-container", className) });
30
52
  }
@@ -36,5 +58,11 @@ export const Table = Object.assign(TableRoot, {
36
58
  Footer: TableFooter,
37
59
  Row: TableRow,
38
60
  Head: TableHead,
39
- Cell: TableCell
61
+ Cell: TableCell,
62
+ Toolbar: TableToolbar,
63
+ ToolbarTitle: TableToolbarTitle,
64
+ ToolbarActions: TableToolbarActions,
65
+ SelectionCell: TableSelectionCell,
66
+ SortButton: TableSortButton,
67
+ StateRow: TableStateRow
40
68
  });
@@ -1,2 +1,2 @@
1
1
  export { createToastController, toast, Toast, Toaster, ToastProvider, ToastViewport, useToast } from "./toast.js";
2
- export type { ToasterPosition, ToasterProps, ToastActionInput, ToastActionProps, ToastCloseProps, ToastController, ToastInput, ToastOptions, ToastPlacement, ToastProviderProps, ToastProps, ToastTextProps, ToastTone, ToastViewportProps } from "./toast.js";
2
+ export type { ToasterPosition, ToasterProps, ToastActionInput, ToastActionProps, ToastCloseProps, ToastController, ToastInput, ToastOptions, ToastPlacement, ToastPromiseMessages, ToastPromiseOptions, ToastProviderProps, ToastProps, ToastTextProps, ToastTone, ToastUpdateOptions, ToastUpdateType, ToastViewportProps } from "./toast.js";
@@ -17,12 +17,30 @@ export type ToastOptions = {
17
17
  action?: ToastActionInput;
18
18
  cancel?: ToastActionInput;
19
19
  };
20
+ export type ToastUpdateType = "default" | "success" | "error" | "warning" | "info";
21
+ export type ToastUpdateOptions = Omit<ToastOptions, "id"> & {
22
+ type?: ToastUpdateType;
23
+ message?: ToastInput;
24
+ };
25
+ export type ToastPromiseMessages<T> = {
26
+ loading: ToastInput;
27
+ success: ToastInput | ((value: T) => ToastInput);
28
+ error: ToastInput | ((error: unknown) => ToastInput);
29
+ };
30
+ export type ToastPromiseOptions<T> = ToastPromiseMessages<T> & {
31
+ loadingOptions?: ToastOptions;
32
+ successOptions?: Omit<ToastUpdateOptions, "message" | "type">;
33
+ errorOptions?: Omit<ToastUpdateOptions, "message" | "type">;
34
+ };
20
35
  export type ToastController = {
21
36
  (input: ToastInput, options?: ToastOptions): string;
22
37
  success: (input: ToastInput, options?: ToastOptions) => string;
23
38
  error: (input: ToastInput, options?: ToastOptions) => string;
24
39
  warning: (input: ToastInput, options?: ToastOptions) => string;
25
40
  info: (input: ToastInput, options?: ToastOptions) => string;
41
+ loading: (input: ToastInput, options?: ToastOptions) => string;
42
+ update: (id: string, options: ToastUpdateOptions) => string;
43
+ promise: <T>(promise: Promise<T>, options: ToastPromiseOptions<T>) => Promise<T>;
26
44
  dismiss: (id?: string) => void;
27
45
  };
28
46
  export interface ToastViewportProps extends React.HTMLAttributes<HTMLDivElement> {
@@ -45,6 +45,18 @@ function toastTypeFromTone(tone) {
45
45
  }
46
46
  return tone;
47
47
  }
48
+ function toastToneFromUpdateType(type) {
49
+ if (type === "success" || type === "warning" || type === "info") {
50
+ return type;
51
+ }
52
+ if (type === "error") {
53
+ return "destructive";
54
+ }
55
+ return "neutral";
56
+ }
57
+ function resolveToastPromiseMessage(message, value) {
58
+ return typeof message === "function" ? message(value) : message;
59
+ }
48
60
  function isToastActionObject(input) {
49
61
  return typeof input === "object" && input !== null && !React.isValidElement(input) && "label" in input && "onClick" in input;
50
62
  }
@@ -89,6 +101,54 @@ export function createToastController() {
89
101
  controller.error = (input, options) => show("destructive", input, options);
90
102
  controller.warning = (input, options) => show("warning", input, options);
91
103
  controller.info = (input, options) => show("info", input, options);
104
+ controller.loading = (input, options) => show("neutral", input, { ...options, duration: options?.duration ?? Infinity });
105
+ controller.update = (id, options) => {
106
+ const existingRecord = records.find((record) => record.id === id);
107
+ if (!existingRecord) {
108
+ return id;
109
+ }
110
+ const { message, type, ...toastOptions } = options;
111
+ const { title, description } = message === undefined
112
+ ? {
113
+ title: existingRecord.title,
114
+ description: toastOptions.description ?? existingRecord.description
115
+ }
116
+ : resolveToastContent(message, toastOptions);
117
+ toastVersionCounter += 1;
118
+ const nextRecord = {
119
+ id,
120
+ tone: type === undefined ? existingRecord.tone : toastToneFromUpdateType(type),
121
+ duration: toastOptions.duration ?? DEFAULT_TOAST_DURATION,
122
+ version: toastVersionCounter,
123
+ ...(title !== undefined ? { title } : {}),
124
+ ...(description !== undefined ? { description } : {}),
125
+ ...(toastOptions.action !== undefined ? { action: toastOptions.action } : {}),
126
+ ...(toastOptions.cancel !== undefined ? { cancel: toastOptions.cancel } : {})
127
+ };
128
+ records = records.map((record) => (record.id === id ? nextRecord : record));
129
+ notify();
130
+ return id;
131
+ };
132
+ controller.promise = async (promise, options) => {
133
+ const id = controller.loading(options.loading, options.loadingOptions);
134
+ try {
135
+ const value = await promise;
136
+ controller.update(id, {
137
+ ...options.successOptions,
138
+ type: "success",
139
+ message: resolveToastPromiseMessage(options.success, value)
140
+ });
141
+ return value;
142
+ }
143
+ catch (error) {
144
+ controller.update(id, {
145
+ ...options.errorOptions,
146
+ type: "error",
147
+ message: resolveToastPromiseMessage(options.error, error)
148
+ });
149
+ throw error;
150
+ }
151
+ };
92
152
  controller.dismiss = (id) => {
93
153
  records = id ? records.filter((toast) => toast.id !== id) : [];
94
154
  notify();
@@ -41,7 +41,7 @@ export interface TooltipContentProps extends React.HTMLAttributes<HTMLSpanElemen
41
41
  declare function TooltipProvider({ children, delayDuration }: TooltipProviderProps): React.JSX.Element;
42
42
  declare function TooltipRoot({ children, defaultOpen, delay, disabled, onOpenChange, open: openProp, placement }: TooltipRootProps): React.JSX.Element;
43
43
  declare function TooltipTrigger({ children, asChild: _asChild }: TooltipTriggerProps): React.ReactElement<unknown, string | React.JSXElementConstructor<any>>;
44
- declare function TooltipContent({ align, alignOffset, children, className, collisionPadding, container, forceMount, placement, ref, side, sideOffset, style, ...props }: TooltipContentProps): React.JSX.Element | null;
44
+ declare function TooltipContent({ align, alignOffset, children, className, collisionPadding, container, forceMount, onAnimationEnd, placement, ref, side, sideOffset, style, ...props }: TooltipContentProps): React.JSX.Element | null;
45
45
  declare function TooltipWrapper({ children, content, delay, disabled, placement }: TooltipProps): React.JSX.Element;
46
46
  export declare const Tooltip: typeof TooltipWrapper & {
47
47
  Content: typeof TooltipContent;