@hyperpackai/hyperui 0.2.0 → 0.3.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.
Files changed (98) hide show
  1. package/README.md +13 -0
  2. package/dist/components/Accordion/index.d.ts +6 -0
  3. package/dist/components/Accordion/index.d.ts.map +1 -1
  4. package/dist/components/Accordion/index.js +65 -9
  5. package/dist/components/Autocomplete/index.d.ts +12 -2
  6. package/dist/components/Autocomplete/index.d.ts.map +1 -1
  7. package/dist/components/Autocomplete/index.js +148 -24
  8. package/dist/components/Backdrop/index.d.ts +2 -1
  9. package/dist/components/Backdrop/index.d.ts.map +1 -1
  10. package/dist/components/Backdrop/index.js +6 -3
  11. package/dist/components/Checkbox/index.d.ts +1 -0
  12. package/dist/components/Checkbox/index.d.ts.map +1 -1
  13. package/dist/components/Checkbox/index.js +6 -2
  14. package/dist/components/DashboardLayout/index.d.ts +13 -0
  15. package/dist/components/DashboardLayout/index.d.ts.map +1 -1
  16. package/dist/components/DashboardLayout/index.js +50 -7
  17. package/dist/components/DataTable/index.d.ts +43 -0
  18. package/dist/components/DataTable/index.d.ts.map +1 -1
  19. package/dist/components/DataTable/index.js +126 -21
  20. package/dist/components/Dialog/index.d.ts +9 -3
  21. package/dist/components/Dialog/index.d.ts.map +1 -1
  22. package/dist/components/Dialog/index.js +46 -30
  23. package/dist/components/Drawer/index.d.ts +11 -3
  24. package/dist/components/Drawer/index.d.ts.map +1 -1
  25. package/dist/components/Drawer/index.js +66 -11
  26. package/dist/components/DropdownMenu/index.d.ts +5 -3
  27. package/dist/components/DropdownMenu/index.d.ts.map +1 -1
  28. package/dist/components/DropdownMenu/index.js +56 -13
  29. package/dist/components/FocusTrap/index.d.ts.map +1 -1
  30. package/dist/components/FocusTrap/index.js +34 -32
  31. package/dist/components/Input/index.d.ts +2 -0
  32. package/dist/components/Input/index.d.ts.map +1 -1
  33. package/dist/components/Input/index.js +18 -4
  34. package/dist/components/Menu/index.d.ts +6 -2
  35. package/dist/components/Menu/index.d.ts.map +1 -1
  36. package/dist/components/Menu/index.js +50 -15
  37. package/dist/components/Modal/index.d.ts +3 -1
  38. package/dist/components/Modal/index.d.ts.map +1 -1
  39. package/dist/components/Modal/index.js +27 -9
  40. package/dist/components/NestedNavbar/index.d.ts +33 -0
  41. package/dist/components/NestedNavbar/index.d.ts.map +1 -0
  42. package/dist/components/NestedNavbar/index.js +435 -0
  43. package/dist/components/NestedSidebar/index.d.ts +48 -0
  44. package/dist/components/NestedSidebar/index.d.ts.map +1 -0
  45. package/dist/components/NestedSidebar/index.js +368 -0
  46. package/dist/components/Popover/index.d.ts +11 -3
  47. package/dist/components/Popover/index.d.ts.map +1 -1
  48. package/dist/components/Popover/index.js +45 -9
  49. package/dist/components/Radio/index.d.ts +26 -1
  50. package/dist/components/Radio/index.d.ts.map +1 -1
  51. package/dist/components/Radio/index.js +61 -2
  52. package/dist/components/Select/index.d.ts +5 -0
  53. package/dist/components/Select/index.d.ts.map +1 -1
  54. package/dist/components/Select/index.js +22 -5
  55. package/dist/components/Sheet/index.d.ts +9 -3
  56. package/dist/components/Sheet/index.d.ts.map +1 -1
  57. package/dist/components/Sheet/index.js +48 -23
  58. package/dist/components/Sidebar/index.d.ts +20 -1
  59. package/dist/components/Sidebar/index.d.ts.map +1 -1
  60. package/dist/components/Sidebar/index.js +285 -8
  61. package/dist/components/SpeedDial/index.d.ts +10 -0
  62. package/dist/components/SpeedDial/index.d.ts.map +1 -1
  63. package/dist/components/SpeedDial/index.js +61 -11
  64. package/dist/components/Switch/index.d.ts +2 -0
  65. package/dist/components/Switch/index.d.ts.map +1 -1
  66. package/dist/components/Switch/index.js +6 -2
  67. package/dist/components/Tabs/index.d.ts +3 -0
  68. package/dist/components/Tabs/index.d.ts.map +1 -1
  69. package/dist/components/Tabs/index.js +47 -8
  70. package/dist/components/TextField/index.d.ts +2 -0
  71. package/dist/components/TextField/index.d.ts.map +1 -1
  72. package/dist/components/TextField/index.js +12 -4
  73. package/dist/components/Textarea/index.d.ts +5 -0
  74. package/dist/components/Textarea/index.d.ts.map +1 -1
  75. package/dist/components/Textarea/index.js +21 -4
  76. package/dist/components/Transition/index.d.ts +14 -0
  77. package/dist/components/Transition/index.d.ts.map +1 -0
  78. package/dist/components/Transition/index.js +49 -0
  79. package/dist/components/TransitionGroup/index.d.ts +16 -0
  80. package/dist/components/TransitionGroup/index.d.ts.map +1 -0
  81. package/dist/components/TransitionGroup/index.js +95 -0
  82. package/dist/components/data.d.ts +81 -16
  83. package/dist/components/data.d.ts.map +1 -1
  84. package/dist/components/data.js +163 -31
  85. package/dist/components/enterprise.d.ts +85 -26
  86. package/dist/components/enterprise.d.ts.map +1 -1
  87. package/dist/components/enterprise.js +211 -36
  88. package/dist/components/index.d.ts +21 -13
  89. package/dist/components/index.d.ts.map +1 -1
  90. package/dist/components/index.js +7 -2
  91. package/dist/portal.d.ts.map +1 -1
  92. package/dist/portal.js +3 -0
  93. package/dist/theme/index.d.ts +5 -6
  94. package/dist/theme/index.d.ts.map +1 -1
  95. package/dist/theme/index.js +30 -0
  96. package/dist/tokens/index.d.ts.map +1 -1
  97. package/dist/tokens/index.js +11 -0
  98. package/package.json +6 -1
@@ -1,11 +1,20 @@
1
1
  import { signal } from "@hyperpackai/hyperion";
2
2
  import { injectCSS, cn, h } from "../../theme/index.js";
3
3
  const CSS = `
4
- .hu-speed-dial { position: fixed; z-index: 50; display: flex; flex-direction: column-reverse; align-items: center; gap: var(--hu-space-2); }
4
+ .hu-speed-dial { z-index: 50; display: flex; flex-direction: column-reverse; align-items: center; gap: var(--hu-space-2); }
5
+ .hu-speed-dial--fixed { position: fixed; }
6
+ .hu-speed-dial--absolute { position: absolute; }
7
+ .hu-speed-dial--static { position: static; }
5
8
  .hu-speed-dial--bottom-right { bottom: var(--hu-space-6); right: var(--hu-space-6); }
6
9
  .hu-speed-dial--bottom-left { bottom: var(--hu-space-6); left: var(--hu-space-6); }
7
10
  .hu-speed-dial--top-right { top: var(--hu-space-6); right: var(--hu-space-6); flex-direction: column; }
8
11
  .hu-speed-dial--top-left { top: var(--hu-space-6); left: var(--hu-space-6); flex-direction: column; }
12
+ .hu-speed-dial--static.hu-speed-dial--bottom-right,
13
+ .hu-speed-dial--static.hu-speed-dial--bottom-left,
14
+ .hu-speed-dial--static.hu-speed-dial--top-right,
15
+ .hu-speed-dial--static.hu-speed-dial--top-left {
16
+ inset: auto;
17
+ }
9
18
 
10
19
  .hu-speed-dial__fab {
11
20
  width: 56px; height: 56px; border-radius: 50%; border: none; cursor: pointer;
@@ -19,6 +28,8 @@ const CSS = `
19
28
  .hu-speed-dial__fab--open { transform: rotate(45deg); }
20
29
 
21
30
  .hu-speed-dial__actions { display: flex; flex-direction: column-reverse; gap: var(--hu-space-2); align-items: center; }
31
+ .hu-speed-dial__actions[hidden],
32
+ .hu-speed-dial__backdrop[hidden] { display: none; }
22
33
  .hu-speed-dial--top-right .hu-speed-dial__actions,
23
34
  .hu-speed-dial--top-left .hu-speed-dial__actions { flex-direction: column; }
24
35
 
@@ -50,22 +61,61 @@ const CSS = `
50
61
  `;
51
62
  export function SpeedDial(props) {
52
63
  injectCSS("hu-speed-dial", CSS);
53
- const { actions, direction = "bottom-right", backdrop, ariaLabel } = props;
54
- const isOpen = signal(false);
55
- const toggle = () => { isOpen.value = !isOpen.value; };
56
- const close = () => { isOpen.value = false; };
57
- return h("div", { class: cn("hu-speed-dial", `hu-speed-dial--${direction}`), "aria-label": ariaLabel }, backdrop && isOpen.value && h("div", { class: "hu-speed-dial__backdrop", onClick: close }), isOpen.value && h("div", { class: "hu-speed-dial__actions" }, ...actions.map((action, i) => h("div", {
64
+ const { actions, direction = "bottom-right", position = "fixed", backdrop, closeOnBackdrop = true, closeOnEscape = true, ariaLabel } = props;
65
+ const uncontrolledOpen = signal(props.defaultOpen ?? false);
66
+ const isSignalOpen = typeof props.open === "object" && props.open != null && "peek" in props.open;
67
+ const isControlled = typeof props.open === "boolean" || isSignalOpen;
68
+ const isOpen = () => {
69
+ if (typeof props.open === "boolean")
70
+ return props.open;
71
+ if (isSignalOpen)
72
+ return props.open.value;
73
+ return uncontrolledOpen.value;
74
+ };
75
+ const setOpen = (next, reason = "programmatic") => {
76
+ if (!isControlled)
77
+ uncontrolledOpen.value = next;
78
+ if (isSignalOpen)
79
+ props.open.value = next;
80
+ props.onOpenChange?.(next, reason);
81
+ if (!next)
82
+ props.onClose?.(reason);
83
+ };
84
+ const toggle = () => { setOpen(!isOpen(), "toggle"); };
85
+ const close = (reason = "programmatic") => { setOpen(false, reason); };
86
+ return h("div", {
87
+ class: cn("hu-speed-dial", `hu-speed-dial--${position}`, `hu-speed-dial--${direction}`),
88
+ "aria-label": ariaLabel,
89
+ onKeyDown: closeOnEscape ? (event) => {
90
+ if (event.key === "Escape" && isOpen())
91
+ close("escape");
92
+ } : undefined
93
+ }, backdrop && h("div", {
94
+ class: "hu-speed-dial__backdrop",
95
+ hidden: () => !isOpen(),
96
+ onClick: closeOnBackdrop ? () => close("backdrop") : undefined
97
+ }), h("div", {
98
+ class: "hu-speed-dial__actions",
99
+ hidden: () => !isOpen(),
100
+ "aria-hidden": () => String(!isOpen())
101
+ }, ...actions.map((action, i) => h("div", {
58
102
  class: "hu-speed-dial__action-wrap",
59
103
  style: `animation-delay:${i * 40}ms`
60
104
  }, h("button", {
61
105
  class: "hu-speed-dial__action",
62
- onClick: () => { action.onClick(); close(); },
106
+ onClick: () => {
107
+ if (action.disabled)
108
+ return;
109
+ action.onClick();
110
+ close("action");
111
+ },
63
112
  disabled: action.disabled,
64
113
  "aria-label": action.label
65
114
  }, action.icon), h("span", { class: "hu-speed-dial__action-tooltip" }, action.label)))), h("button", {
66
- class: cn("hu-speed-dial__fab", isOpen.value && "hu-speed-dial__fab--open"),
115
+ type: "button",
116
+ class: () => cn("hu-speed-dial__fab", isOpen() && "hu-speed-dial__fab--open"),
67
117
  onClick: toggle,
68
- "aria-haspopup": "true",
69
- "aria-expanded": isOpen.value
70
- }, isOpen.value && props.openIcon ? props.openIcon : (props.icon ?? "+")));
118
+ "aria-haspopup": "menu",
119
+ "aria-expanded": () => String(isOpen())
120
+ }, () => isOpen() && props.openIcon ? props.openIcon : (props.icon ?? "+")));
71
121
  }
@@ -1,6 +1,7 @@
1
1
  import { type VNode } from "../../theme/index.js";
2
2
  export interface SwitchProps {
3
3
  checked?: boolean;
4
+ defaultChecked?: boolean;
4
5
  disabled?: boolean;
5
6
  size?: "sm" | "md" | "lg";
6
7
  label?: string;
@@ -8,6 +9,7 @@ export interface SwitchProps {
8
9
  name?: string;
9
10
  class?: string;
10
11
  onChange?: (e: Event) => void;
12
+ onCheckedChange?: (checked: boolean, e: Event) => void;
11
13
  }
12
14
  export declare function Switch(props: SwitchProps): VNode;
13
15
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/Switch/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,KAAK,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAmCpE,MAAM,WAAW,WAAW;IAC1B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,CAAC,CAAC,EAAE,KAAK,KAAK,IAAI,CAAC;CAC/B;AAED,wBAAgB,MAAM,CAAC,KAAK,EAAE,WAAW,GAAG,KAAK,CAWhD"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/Switch/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,KAAK,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAmCpE,MAAM,WAAW,WAAW;IAC1B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,CAAC,CAAC,EAAE,KAAK,KAAK,IAAI,CAAC;IAC9B,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,KAAK,KAAK,IAAI,CAAC;CACxD;AAED,wBAAgB,MAAM,CAAC,KAAK,EAAE,WAAW,GAAG,KAAK,CAehD"}
@@ -33,6 +33,10 @@ const SWITCH_CSS = `
33
33
  `;
34
34
  export function Switch(props) {
35
35
  injectCSS("hu-switch", SWITCH_CSS);
36
- const { label, disabled, size = "md", id, ...rest } = props;
37
- return h("label", { class: cn("hu-switch-wrap", disabled && "hu-switch-wrap--disabled", size !== "md" && `hu-switch--${size}`, props.class) }, h("input", { ...rest, type: "checkbox", id, disabled, class: "hu-switch-input", role: "switch" }), h("span", { class: "hu-switch-track" }, h("span", { class: "hu-switch-thumb" })), label && h("span", { class: "hu-switch-label" }, label));
36
+ const { label, disabled, size = "md", id, onChange, onCheckedChange, ...rest } = props;
37
+ const handleChange = (event) => {
38
+ onChange?.(event);
39
+ onCheckedChange?.(event.target.checked, event);
40
+ };
41
+ return h("label", { class: cn("hu-switch-wrap", disabled && "hu-switch-wrap--disabled", size !== "md" && `hu-switch--${size}`, props.class) }, h("input", { ...rest, type: "checkbox", id, disabled, onChange: handleChange, class: "hu-switch-input", role: "switch" }), h("span", { class: "hu-switch-track" }, h("span", { class: "hu-switch-thumb" })), label && h("span", { class: "hu-switch-label" }, label));
38
42
  }
@@ -10,10 +10,13 @@ export interface TabItem {
10
10
  }
11
11
  export interface TabsProps {
12
12
  items: TabItem[];
13
+ value?: string | Signal<string>;
14
+ defaultValue?: string;
13
15
  defaultTab?: string;
14
16
  activeTab?: string | Signal<string>;
15
17
  variant?: "underline" | "pill";
16
18
  onChange?: (id: string) => void;
19
+ onValueChange?: (id: string) => void;
17
20
  class?: string;
18
21
  }
19
22
  export declare function Tabs(props: TabsProps): VNode;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/Tabs/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,KAAK,KAAK,EAAE,MAAM,sBAAsB,CAAC;AACpE,OAAO,EAAU,KAAK,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAoC5D,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,OAAO,EAAE,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;IACpC,OAAO,CAAC,EAAE,WAAW,GAAG,MAAM,CAAC;IAC/B,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,IAAI,CAAC,KAAK,EAAE,SAAS,GAAG,KAAK,CAyC5C"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/Tabs/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,KAAK,KAAK,EAAE,MAAM,sBAAsB,CAAC;AACpE,OAAO,EAAU,KAAK,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAoC5D,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,OAAO,EAAE,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;IAChC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;IACpC,OAAO,CAAC,EAAE,WAAW,GAAG,MAAM,CAAC;IAC/B,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,aAAa,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,IAAI,CAAC,KAAK,EAAE,SAAS,GAAG,KAAK,CAsE5C"}
@@ -35,22 +35,53 @@ const TABS_CSS = `
35
35
  `;
36
36
  export function Tabs(props) {
37
37
  injectCSS("hu-tabs", TABS_CSS);
38
- const { items, variant = "underline", onChange } = props;
39
- const uncontrolledActive = signal(props.defaultTab ?? items[0]?.id ?? "");
40
- const controlled = typeof props.activeTab === "object" && props.activeTab !== null && "peek" in props.activeTab;
41
- const getActive = () => controlled
42
- ? props.activeTab.value
43
- : props.activeTab ?? uncontrolledActive.value;
38
+ const { items, variant = "underline", onChange, onValueChange } = props;
39
+ const uncontrolledActive = signal(props.defaultValue ?? props.defaultTab ?? items[0]?.id ?? "");
40
+ const controlledSignal = getSignalProp(props.value) ?? getSignalProp(props.activeTab);
41
+ const controlledValue = typeof props.value === "string" ? props.value : typeof props.activeTab === "string" ? props.activeTab : undefined;
42
+ const controlled = controlledSignal != null || controlledValue !== undefined;
43
+ const enabledItems = () => items.filter((item) => !item.disabled);
44
+ const getActive = () => controlledSignal?.value ?? controlledValue ?? uncontrolledActive.value;
44
45
  const selectTab = (id) => {
45
- if (!controlled && props.activeTab === undefined)
46
+ if (!controlled)
46
47
  uncontrolledActive.value = id;
48
+ if (controlledSignal)
49
+ controlledSignal.value = id;
47
50
  onChange?.(id);
51
+ onValueChange?.(id);
48
52
  };
49
- return h("div", { class: cn("hu-tabs", props.class) }, h("div", { class: cn("hu-tabs-list", variant === "pill" && "hu-tabs-list--pill"), role: "tablist" }, ...items.map((item) => h("button", {
53
+ const focusTab = (id, event) => {
54
+ const root = event.currentTarget;
55
+ const next = root.querySelector(`#tab-${cssEscape(id)}`);
56
+ next?.focus();
57
+ selectTab(id);
58
+ };
59
+ const handleKeyDown = (event) => {
60
+ const enabled = enabledItems();
61
+ if (enabled.length === 0)
62
+ return;
63
+ const current = getActive();
64
+ const currentIndex = Math.max(0, enabled.findIndex((item) => item.id === current));
65
+ let nextIndex = currentIndex;
66
+ if (event.key === "ArrowRight" || event.key === "ArrowDown")
67
+ nextIndex = (currentIndex + 1) % enabled.length;
68
+ else if (event.key === "ArrowLeft" || event.key === "ArrowUp")
69
+ nextIndex = (currentIndex - 1 + enabled.length) % enabled.length;
70
+ else if (event.key === "Home")
71
+ nextIndex = 0;
72
+ else if (event.key === "End")
73
+ nextIndex = enabled.length - 1;
74
+ else
75
+ return;
76
+ event.preventDefault();
77
+ focusTab(enabled[nextIndex].id, event);
78
+ };
79
+ return h("div", { class: cn("hu-tabs", props.class) }, h("div", { class: cn("hu-tabs-list", variant === "pill" && "hu-tabs-list--pill"), role: "tablist", onKeyDown: handleKeyDown }, ...items.map((item) => h("button", {
50
80
  key: item.id, id: `tab-${item.id}`, role: "tab",
51
81
  class: "hu-tabs-trigger",
52
82
  "aria-selected": () => getActive() === item.id ? "true" : "false",
53
83
  "aria-controls": `tabpanel-${item.id}`,
84
+ tabindex: () => getActive() === item.id ? "0" : "-1",
54
85
  disabled: item.disabled,
55
86
  onClick: () => selectTab(item.id)
56
87
  }, item.icon && h("span", { "aria-hidden": "true" }, item.icon), item.label, item.badge !== undefined && h("span", { class: "hu-tabs-badge" }, item.badge)))), ...items.map((item) => h("div", {
@@ -60,3 +91,11 @@ export function Tabs(props) {
60
91
  "aria-labelledby": `tab-${item.id}`
61
92
  }, () => getActive() === item.id ? item.content : null)));
62
93
  }
94
+ function getSignalProp(value) {
95
+ return typeof value === "object" && value !== null && "peek" in value ? value : undefined;
96
+ }
97
+ function cssEscape(value) {
98
+ return typeof CSS !== "undefined" && typeof CSS.escape === "function"
99
+ ? CSS.escape(value)
100
+ : value.replace(/["\\#.;?+*~':!^$[\]()=>|/@]/g, "\\$&");
101
+ }
@@ -26,6 +26,8 @@ export interface TextFieldProps {
26
26
  name?: string;
27
27
  autoFocus?: boolean;
28
28
  autoComplete?: string;
29
+ "aria-label"?: string;
30
+ "aria-describedby"?: string;
29
31
  onChange?: (value: string, e: Event) => void;
30
32
  onBlur?: (e: FocusEvent) => void;
31
33
  onFocus?: (e: FocusEvent) => void;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/TextField/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,KAAK,KAAK,EAAE,MAAM,sBAAsB,CAAC;AA0CpE,MAAM,MAAM,gBAAgB,GAAG,UAAU,GAAG,QAAQ,GAAG,UAAU,CAAC;AAClE,MAAM,MAAM,aAAa,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAE/C,MAAM,WAAW,cAAc;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,gBAAgB,CAAC;IAC3B,IAAI,CAAC,EAAE,aAAa,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IACzB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC,EAAE,KAAK,KAAK,IAAI,CAAC;IAC7C,MAAM,CAAC,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,IAAI,CAAC;IACjC,OAAO,CAAC,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,IAAI,CAAC;IAClC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,cAAc,GAAG,KAAK,CAyDtD"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/TextField/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,KAAK,KAAK,EAAE,MAAM,sBAAsB,CAAC;AA0CpE,MAAM,MAAM,gBAAgB,GAAG,UAAU,GAAG,QAAQ,GAAG,UAAU,CAAC;AAClE,MAAM,MAAM,aAAa,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAE/C,MAAM,WAAW,cAAc;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,gBAAgB,CAAC;IAC3B,IAAI,CAAC,EAAE,aAAa,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IACzB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC,EAAE,KAAK,KAAK,IAAI,CAAC;IAC7C,MAAM,CAAC,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,IAAI,CAAC;IACjC,OAAO,CAAC,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,IAAI,CAAC;IAClC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAID,wBAAgB,SAAS,CAAC,KAAK,EAAE,cAAc,GAAG,KAAK,CAiEtD"}
@@ -38,25 +38,33 @@ const TEXTFIELD_CSS = `
38
38
  .hu-textfield--sm .hu-textfield__input { min-height: 34px; padding: 6px 10px; font-size: var(--hu-font-size-xs); border-radius: var(--hu-radius); }
39
39
  .hu-textfield--lg .hu-textfield__input { min-height: 46px; padding: 11px 14px; font-size: var(--hu-font-size-base); }
40
40
  `;
41
+ let textFieldId = 0;
41
42
  export function TextField(props) {
42
43
  injectCSS("hu-textfield", TEXTFIELD_CSS);
43
- const { label, placeholder, value, type = "text", variant = "outlined", size = "md", helperText, error, success, required, disabled, readOnly, multiline, rows = 3, startIcon, endIcon, maxLength, id, name, autoFocus, autoComplete, onChange, onBlur, onFocus } = props;
44
+ const { label, placeholder, value, type = "text", variant = "outlined", size = "md", helperText, error, success, required, disabled, readOnly, multiline, rows = 3, startIcon, endIcon, maxLength, name, autoFocus, autoComplete, onChange, onBlur, onFocus } = props;
45
+ const id = props.id ?? (label || helperText || error ? `hu-textfield-${++textFieldId}` : undefined);
44
46
  const errorMsg = typeof error === "string" ? error : undefined;
45
47
  const hasError = !!error;
46
48
  const hasStartIcon = startIcon != null;
47
49
  const hasEndIcon = endIcon != null;
50
+ const helperId = helperText && !errorMsg ? `${id}-helper` : undefined;
51
+ const errorId = errorMsg ? `${id}-error` : undefined;
52
+ const describedBy = [props["aria-describedby"], errorId, helperId].filter(Boolean).join(" ") || undefined;
48
53
  const inputProps = {
49
54
  class: cn("hu-textfield__input", hasStartIcon && "hu-textfield__input--has-start", hasEndIcon && "hu-textfield__input--has-end"),
50
55
  id, name, placeholder, disabled, required, "readonly": readOnly,
51
56
  autofocus: autoFocus, autocomplete: autoComplete,
52
57
  maxlength: maxLength,
58
+ "aria-invalid": hasError ? "true" : undefined,
59
+ "aria-describedby": describedBy,
60
+ "aria-label": props["aria-label"],
53
61
  onInput: onChange ? (e) => onChange(e.target.value, e) : undefined,
54
62
  onBlur, onFocus
55
63
  };
56
64
  if (value !== undefined)
57
65
  inputProps["value"] = value;
58
- if (props.defaultValue !== undefined)
59
- inputProps["value"] = props.defaultValue;
66
+ if (value === undefined && props.defaultValue !== undefined)
67
+ inputProps["defaultValue"] = props.defaultValue;
60
68
  const inputEl = multiline
61
69
  ? h("textarea", { ...inputProps, rows })
62
70
  : h("input", { ...inputProps, type });
@@ -65,5 +73,5 @@ export function TextField(props) {
65
73
  }, label && h("label", {
66
74
  class: cn("hu-textfield__label", required && "hu-textfield__label--required"),
67
75
  for: id
68
- }, label), h("div", { class: "hu-textfield__input-wrap" }, startIcon && h("span", { class: "hu-textfield__start-icon" }, startIcon), inputEl, endIcon && h("span", { class: "hu-textfield__end-icon" }, endIcon)), (helperText || errorMsg) && h("span", { class: "hu-textfield__helper" }, errorMsg ?? helperText), maxLength && h("span", { class: "hu-textfield__char-count" }, `${(value ?? "").length}/${maxLength}`));
76
+ }, label), h("div", { class: "hu-textfield__input-wrap" }, startIcon && h("span", { class: "hu-textfield__start-icon" }, startIcon), inputEl, endIcon && h("span", { class: "hu-textfield__end-icon" }, endIcon)), errorMsg && h("span", { id: errorId, class: "hu-textfield__helper", role: "alert" }, errorMsg), !errorMsg && helperText && h("span", { id: helperId, class: "hu-textfield__helper" }, helperText), maxLength && h("span", { class: "hu-textfield__char-count" }, `${(value ?? "").length}/${maxLength}`));
69
77
  }
@@ -1,6 +1,7 @@
1
1
  import { type VNode } from "../../theme/index.js";
2
2
  export interface TextareaProps {
3
3
  value?: string;
4
+ defaultValue?: string;
4
5
  placeholder?: string;
5
6
  disabled?: boolean;
6
7
  readOnly?: boolean;
@@ -14,9 +15,13 @@ export interface TextareaProps {
14
15
  name?: string;
15
16
  maxLength?: number;
16
17
  class?: string;
18
+ "aria-label"?: string;
19
+ "aria-describedby"?: string;
17
20
  onInput?: (e: InputEvent) => void;
18
21
  onChange?: (e: Event) => void;
22
+ onValueChange?: (value: string, e: Event) => void;
19
23
  onBlur?: (e: FocusEvent) => void;
24
+ onFocus?: (e: FocusEvent) => void;
20
25
  }
21
26
  export declare function Textarea(props: TextareaProps): VNode;
22
27
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/Textarea/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,KAAK,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAqBpE,MAAM,WAAW,aAAa;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,IAAI,CAAC;IAClC,QAAQ,CAAC,EAAE,CAAC,CAAC,EAAE,KAAK,KAAK,IAAI,CAAC;IAC9B,MAAM,CAAC,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,IAAI,CAAC;CAClC;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,aAAa,GAAG,KAAK,CAapD"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/Textarea/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,KAAK,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAsBpE,MAAM,WAAW,aAAa;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,OAAO,CAAC,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,IAAI,CAAC;IAClC,QAAQ,CAAC,EAAE,CAAC,CAAC,EAAE,KAAK,KAAK,IAAI,CAAC;IAC9B,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC,EAAE,KAAK,KAAK,IAAI,CAAC;IAClD,MAAM,CAAC,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,IAAI,CAAC;IACjC,OAAO,CAAC,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,IAAI,CAAC;CACnC;AAID,wBAAgB,QAAQ,CAAC,KAAK,EAAE,aAAa,GAAG,KAAK,CAyCpD"}
@@ -16,12 +16,29 @@ const TEXTAREA_CSS = `
16
16
  .hu-textarea--error { border-color: var(--hu-error) !important; }
17
17
  .hu-textarea--error:focus { box-shadow: 0 0 0 3px color-mix(in srgb, var(--hu-error) 18%, transparent); }
18
18
  .hu-textarea--no-resize { resize: none; }
19
+ .hu-textarea__count { font-size: var(--hu-font-size-xs); color: var(--hu-text-2); text-align: right; }
19
20
  `;
21
+ let textareaId = 0;
20
22
  export function Textarea(props) {
21
23
  injectCSS("hu-textarea", TEXTAREA_CSS);
22
- const { error, helper, label, resize = true, id, ...rest } = props;
24
+ const { error, helper, label, resize = true, value, defaultValue, onInput, onChange, onValueChange, class: className, ...rest } = props;
25
+ const id = props.id ?? (label || helper || error ? `hu-textarea-${++textareaId}` : undefined);
26
+ const helperId = helper && !error ? `${id}-helper` : undefined;
27
+ const errorId = error ? `${id}-error` : undefined;
28
+ const describedBy = [props["aria-describedby"], errorId, helperId].filter(Boolean).join(" ") || undefined;
29
+ const handleInput = (event) => {
30
+ onInput?.(event);
31
+ onValueChange?.(event.target.value, event);
32
+ };
23
33
  return h("div", { class: "hu-input-wrap" }, label && Label({ for: id, required: props.required, children: label }), h("textarea", {
24
- ...rest, id,
25
- class: cn("hu-textarea", error && "hu-textarea--error", !resize && "hu-textarea--no-resize", props.class)
26
- }), error && h("span", { class: "hu-input-error-msg", role: "alert" }, error), !error && helper && h("span", { class: "hu-input-helper" }, helper));
34
+ ...rest,
35
+ id,
36
+ value,
37
+ defaultValue: value === undefined ? defaultValue : undefined,
38
+ onInput: handleInput,
39
+ onChange,
40
+ "aria-invalid": error ? "true" : undefined,
41
+ "aria-describedby": describedBy,
42
+ class: cn("hu-textarea", error && "hu-textarea--error", !resize && "hu-textarea--no-resize", className)
43
+ }), error && h("span", { id: errorId, class: "hu-input-error-msg", role: "alert" }, error), !error && helper && h("span", { id: helperId, class: "hu-input-helper" }, helper), props.maxLength && h("span", { class: "hu-textarea__count" }, `${(value ?? defaultValue ?? "").length}/${props.maxLength}`));
27
44
  }
@@ -0,0 +1,14 @@
1
+ import { type Signal, type TransitionOptions } from "@hyperpackai/hyperion";
2
+ import { type VNode } from "../../theme/index.js";
3
+ export interface TransitionProps extends TransitionOptions {
4
+ show: boolean | Signal<boolean>;
5
+ class?: string;
6
+ children?: unknown;
7
+ }
8
+ /**
9
+ * Mounts/unmounts `children` based on `show`, applying enter/leave CSS
10
+ * classes (Vue-style: `*-from` / `*-active` / `*-to`) around the transition.
11
+ * Override the class names via props to plug in custom animations.
12
+ */
13
+ export declare function Transition(props: TransitionProps): VNode;
14
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/Transition/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiC,KAAK,MAAM,EAAE,KAAK,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC3G,OAAO,EAAoB,KAAK,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAUpE,MAAM,WAAW,eAAgB,SAAQ,iBAAiB;IACxD,IAAI,EAAE,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;IAChC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,eAAe,GAAG,KAAK,CAgDxD"}
@@ -0,0 +1,49 @@
1
+ import { signal, effect, useTransition } from "@hyperpackai/hyperion";
2
+ import { injectCSS, cn, h } from "../../theme/index.js";
3
+ const CSS = `
4
+ .hu-transition-enter-active, .hu-transition-leave-active {
5
+ transition: opacity var(--hu-duration) var(--hu-ease), transform var(--hu-duration) var(--hu-ease);
6
+ }
7
+ .hu-transition-enter-from, .hu-transition-leave-to { opacity: 0; transform: scale(0.96); }
8
+ .hu-transition-enter-to, .hu-transition-leave-from { opacity: 1; transform: scale(1); }
9
+ `;
10
+ /**
11
+ * Mounts/unmounts `children` based on `show`, applying enter/leave CSS
12
+ * classes (Vue-style: `*-from` / `*-active` / `*-to`) around the transition.
13
+ * Override the class names via props to plug in custom animations.
14
+ */
15
+ export function Transition(props) {
16
+ injectCSS("hu-transition", CSS);
17
+ const { enterClass = "hu-transition-enter-from", enterActiveClass = "hu-transition-enter-active", enterToClass = "hu-transition-enter-to", leaveClass = "hu-transition-leave-from", leaveActiveClass = "hu-transition-leave-active", leaveToClass = "hu-transition-leave-to" } = props;
18
+ const { state, shouldRender } = useTransition(props.show, props);
19
+ // Drives the dynamic part of the `class` attribute. Kept as its own signal
20
+ // (rather than mutating `classList` from a side-effect) so it composes
21
+ // cleanly with `props.class` through the reactive `class` getter below —
22
+ // applyProp re-sets the whole `class` attribute on every change, which
23
+ // would otherwise wipe out classes toggled imperatively.
24
+ const transitionClass = signal("");
25
+ effect(() => {
26
+ switch (state.value) {
27
+ case "entering":
28
+ transitionClass.value = cn(enterClass, enterActiveClass);
29
+ requestAnimationFrame(() => {
30
+ transitionClass.value = cn(enterActiveClass, enterToClass);
31
+ });
32
+ break;
33
+ case "leaving":
34
+ transitionClass.value = cn(leaveClass, leaveActiveClass);
35
+ requestAnimationFrame(() => {
36
+ transitionClass.value = cn(leaveActiveClass, leaveToClass);
37
+ });
38
+ break;
39
+ default:
40
+ transitionClass.value = "";
41
+ break;
42
+ }
43
+ });
44
+ return (() => shouldRender.value
45
+ ? h("div", {
46
+ class: () => cn(props.class, transitionClass.value)
47
+ }, props.children)
48
+ : null);
49
+ }
@@ -0,0 +1,16 @@
1
+ import { type Signal } from "@hyperpackai/hyperion";
2
+ import { type VNode } from "../../theme/index.js";
3
+ export interface TransitionGroupProps<T> {
4
+ each: T[] | Signal<T[]>;
5
+ key: (item: T, index: number) => string | number;
6
+ children: (item: T, index: number) => unknown;
7
+ duration?: number;
8
+ tag?: string;
9
+ class?: string;
10
+ }
11
+ /**
12
+ * Animates list items in/out with enter/leave CSS classes and applies a
13
+ * best-effort FLIP transform to items that moved on reorder.
14
+ */
15
+ export declare function TransitionGroup<T>(props: TransitionGroupProps<T>): VNode;
16
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/TransitionGroup/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,KAAK,MAAM,EAAE,MAAM,uBAAuB,CAAC;AACzE,OAAO,EAAoB,KAAK,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAsBpE,MAAM,WAAW,oBAAoB,CAAC,CAAC;IACrC,IAAI,EAAE,CAAC,EAAE,GAAG,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC;IACxB,GAAG,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,GAAG,MAAM,CAAC;IACjD,QAAQ,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC;IAC9C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,KAAK,EAAE,oBAAoB,CAAC,CAAC,CAAC,GAAG,KAAK,CA4FxE"}
@@ -0,0 +1,95 @@
1
+ import { signal, effect, For } from "@hyperpackai/hyperion";
2
+ import { injectCSS, cn, h } from "../../theme/index.js";
3
+ const CSS = `
4
+ .hu-transition-group-item {
5
+ transition: opacity var(--hu-duration) var(--hu-ease), transform var(--hu-duration) var(--hu-ease);
6
+ }
7
+ .hu-transition-group-item-enter-from,
8
+ .hu-transition-group-item-leave-to {
9
+ opacity: 0;
10
+ transform: scale(0.96);
11
+ }
12
+ `;
13
+ /**
14
+ * Animates list items in/out with enter/leave CSS classes and applies a
15
+ * best-effort FLIP transform to items that moved on reorder.
16
+ */
17
+ export function TransitionGroup(props) {
18
+ injectCSS("hu-transition-group", CSS);
19
+ const duration = props.duration ?? 150;
20
+ const entries = signal([]);
21
+ const elements = new Map();
22
+ // JSX delivers a single function child as `[fn]` (see `For`'s same handling).
23
+ const childrenFn = (Array.isArray(props.children) ? props.children[0] : props.children);
24
+ effect(() => {
25
+ const list = Array.isArray(props.each) ? props.each : props.each.value;
26
+ const current = entries.peek();
27
+ const nextKeys = list.map((item, i) => props.key(item, i));
28
+ const nextKeySet = new Set(nextKeys);
29
+ // FLIP: capture positions before the DOM updates.
30
+ const rectsBefore = new Map();
31
+ for (const [key, el] of elements) {
32
+ if (el)
33
+ rectsBefore.set(key, el.getBoundingClientRect());
34
+ }
35
+ const updated = [];
36
+ // Items removed from `each`: animate out, then drop after `duration`.
37
+ for (const entry of current) {
38
+ if (!nextKeySet.has(entry.key)) {
39
+ if (entry.status.peek() !== "leaving") {
40
+ entry.status.value = "leaving";
41
+ setTimeout(() => {
42
+ entries.value = entries.peek().filter((e) => e.key !== entry.key);
43
+ elements.delete(entry.key);
44
+ }, duration);
45
+ }
46
+ updated.push(entry);
47
+ }
48
+ }
49
+ // Items present in the new list: reuse existing entries or create new ones.
50
+ list.forEach((item, index) => {
51
+ const key = nextKeys[index];
52
+ const existing = current.find((e) => e.key === key);
53
+ if (existing) {
54
+ existing.item = item;
55
+ existing.index = index;
56
+ updated.push(existing);
57
+ }
58
+ else {
59
+ const status = signal("entering");
60
+ updated.push({ key, item, index, status });
61
+ requestAnimationFrame(() => { status.value = "entered"; });
62
+ }
63
+ });
64
+ entries.value = updated;
65
+ // FLIP: animate items whose position changed due to reordering.
66
+ requestAnimationFrame(() => {
67
+ for (const [key, el] of elements) {
68
+ if (!el)
69
+ continue;
70
+ const before = rectsBefore.get(key);
71
+ if (!before)
72
+ continue;
73
+ const after = el.getBoundingClientRect();
74
+ const dx = before.left - after.left;
75
+ const dy = before.top - after.top;
76
+ if (dx || dy) {
77
+ el.style.transition = "none";
78
+ el.style.transform = `translate(${dx}px, ${dy}px)`;
79
+ requestAnimationFrame(() => {
80
+ el.style.transition = `transform ${duration}ms var(--hu-ease, ease)`;
81
+ el.style.transform = "";
82
+ });
83
+ }
84
+ }
85
+ });
86
+ });
87
+ return h(props.tag ?? "div", { class: cn("hu-transition-group", props.class) }, For({
88
+ each: entries,
89
+ key: (entry) => entry.key,
90
+ children: (entry) => h("div", {
91
+ ref: (el) => { elements.set(entry.key, el); },
92
+ class: () => cn("hu-transition-group-item", entry.status.value === "entering" && "hu-transition-group-item-enter-from", entry.status.value === "leaving" && "hu-transition-group-item-leave-to")
93
+ }, childrenFn(entry.item, entry.index))
94
+ }));
95
+ }