@epoch32/visage 0.0.0-stage → 0.0.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 (138) hide show
  1. package/LICENSE +373 -0
  2. package/README.md +110 -2
  3. package/dist/bundle.dev.js +4875 -0
  4. package/dist/index.js +9633 -0
  5. package/dist/src/api/index.js +4002 -0
  6. package/dist/src/charts/index.js +2717 -0
  7. package/dist/src/devtools/index.js +4677 -0
  8. package/dist/src/dom/index.js +1550 -0
  9. package/dist/src/form/index.js +1415 -0
  10. package/dist/src/index.js +11826 -0
  11. package/dist/src/jsx-runtime/index.js +1630 -0
  12. package/dist/src/query/index.js +2422 -0
  13. package/dist/src/router/index.js +1856 -0
  14. package/dist/src/rpc/index.js +3484 -0
  15. package/dist/src/server/index.js +1173 -0
  16. package/dist/src/signals/index.js +1077 -0
  17. package/dist/src/style/index.js +2340 -0
  18. package/dist/src/template/index.js +1149 -0
  19. package/dist/src/ui/index.js +4155 -0
  20. package/dist/src/virtual/index.js +1955 -0
  21. package/dist/src/vite/index.js +984 -0
  22. package/dist/vite/index.js +707 -0
  23. package/package.json +140 -4
  24. package/src/api/client.ts +371 -0
  25. package/src/api/file-router.ts +183 -0
  26. package/src/api/handler.ts +326 -0
  27. package/src/api/index.ts +71 -0
  28. package/src/api/middleware.ts +54 -0
  29. package/src/api/pragma.ts +155 -0
  30. package/src/api/procedure.ts +172 -0
  31. package/src/api/types.ts +249 -0
  32. package/src/charts/Area.tsx +111 -0
  33. package/src/charts/Axis.tsx +126 -0
  34. package/src/charts/Bar.tsx +86 -0
  35. package/src/charts/CartesianGrid.tsx +72 -0
  36. package/src/charts/Chart.tsx +269 -0
  37. package/src/charts/Cursor.tsx +42 -0
  38. package/src/charts/Legend.tsx +56 -0
  39. package/src/charts/Line.tsx +106 -0
  40. package/src/charts/Pie.tsx +88 -0
  41. package/src/charts/Tooltip.tsx +127 -0
  42. package/src/charts/context.ts +30 -0
  43. package/src/charts/index.ts +14 -0
  44. package/src/charts/path.ts +155 -0
  45. package/src/charts/scale.ts +133 -0
  46. package/src/charts/types.ts +50 -0
  47. package/src/devtools/DomTab.tsx +134 -0
  48. package/src/devtools/GraphInspector.tsx +196 -0
  49. package/src/devtools/GraphTab.tsx +315 -0
  50. package/src/devtools/QueryTab.tsx +200 -0
  51. package/src/devtools/RpcTab.tsx +190 -0
  52. package/src/devtools/SignalsTab.tsx +129 -0
  53. package/src/devtools/component.tsx +243 -0
  54. package/src/devtools/highlighter.ts +134 -0
  55. package/src/devtools/hub.ts +175 -0
  56. package/src/devtools/index.ts +20 -0
  57. package/src/devtools/styles.ts +313 -0
  58. package/src/devtools/types.ts +45 -0
  59. package/src/dom/async.ts +232 -0
  60. package/src/dom/control-flow.ts +353 -0
  61. package/src/dom/index.ts +32 -0
  62. package/src/dom/lazy.ts +82 -0
  63. package/src/dom/render.ts +286 -0
  64. package/src/dom/resource.ts +120 -0
  65. package/src/form/form.ts +218 -0
  66. package/src/form/index.ts +17 -0
  67. package/src/form/schema.ts +299 -0
  68. package/src/index.ts +413 -0
  69. package/src/jsx-dev-runtime/index.ts +1 -0
  70. package/src/jsx-runtime/index.ts +112 -0
  71. package/src/jsx-runtime/jsx.d.ts +278 -0
  72. package/src/query/create-mutation.ts +64 -0
  73. package/src/query/create-query.ts +426 -0
  74. package/src/query/focus-manager.ts +68 -0
  75. package/src/query/index.ts +63 -0
  76. package/src/query/mutation-cache.ts +138 -0
  77. package/src/query/online-manager.ts +67 -0
  78. package/src/query/query-cache.ts +369 -0
  79. package/src/query/query-client.ts +248 -0
  80. package/src/query/types.ts +221 -0
  81. package/src/query/utils.ts +132 -0
  82. package/src/router/index.ts +19 -0
  83. package/src/router/router.tsx +352 -0
  84. package/src/rpc/client.ts +317 -0
  85. package/src/rpc/error.ts +70 -0
  86. package/src/rpc/index.ts +52 -0
  87. package/src/rpc/procedure.ts +88 -0
  88. package/src/rpc/router.ts +105 -0
  89. package/src/rpc/server.ts +511 -0
  90. package/src/rpc/types.ts +328 -0
  91. package/src/server/hydrate.ts +21 -0
  92. package/src/server/index.ts +10 -0
  93. package/src/server/render-to-stream.ts +31 -0
  94. package/src/server/render-to-string.ts +125 -0
  95. package/src/signals/computed.ts +217 -0
  96. package/src/signals/context.ts +89 -0
  97. package/src/signals/effect.ts +161 -0
  98. package/src/signals/index.ts +57 -0
  99. package/src/signals/scope.ts +132 -0
  100. package/src/signals/signal.ts +203 -0
  101. package/src/signals/store.ts +163 -0
  102. package/src/signals/system.ts +124 -0
  103. package/src/signals/types.ts +64 -0
  104. package/src/style/component.tsx +122 -0
  105. package/src/style/create-style.ts +229 -0
  106. package/src/style/cx.ts +75 -0
  107. package/src/style/hash.ts +19 -0
  108. package/src/style/index.ts +8 -0
  109. package/src/style/layout.tsx +406 -0
  110. package/src/style/manager.ts +87 -0
  111. package/src/style/parser.ts +257 -0
  112. package/src/style/scale.ts +237 -0
  113. package/src/template/index.ts +4 -0
  114. package/src/template/template.ts +126 -0
  115. package/src/ui/accordion.tsx +286 -0
  116. package/src/ui/command-dialog.tsx +49 -0
  117. package/src/ui/command.tsx +400 -0
  118. package/src/ui/data-table.tsx +322 -0
  119. package/src/ui/dialog.tsx +362 -0
  120. package/src/ui/dropdown-menu.tsx +381 -0
  121. package/src/ui/icon.tsx +69 -0
  122. package/src/ui/index.ts +138 -0
  123. package/src/ui/layout.tsx +13 -0
  124. package/src/ui/popover.tsx +249 -0
  125. package/src/ui/switch.tsx +114 -0
  126. package/src/ui/tabs.tsx +297 -0
  127. package/src/ui/toast.tsx +280 -0
  128. package/src/ui/tooltip.tsx +262 -0
  129. package/src/ui/utils.ts +110 -0
  130. package/src/virtual/index.ts +12 -0
  131. package/src/virtual/virtual-list.tsx +185 -0
  132. package/src/virtual/virtualizer.ts +274 -0
  133. package/src/vite/hmr.ts +38 -0
  134. package/src/vite/index.ts +3 -0
  135. package/src/vite/lucide.ts +124 -0
  136. package/src/vite/plugin.ts +102 -0
  137. package/src/vite/rpc-middleware.ts +82 -0
  138. package/src/vite/types.ts +65 -0
@@ -0,0 +1,249 @@
1
+ import { Portal } from "../dom/control-flow";
2
+ import type { Child } from "../dom/render";
3
+ import { onCleanup, type ReadonlySignal } from "../signals";
4
+ import { createContext, useContext } from "../signals/context";
5
+ import { createControllableSignal, createId, onClickOutside } from "./utils";
6
+
7
+ export interface PopoverContextValue {
8
+ open: ReadonlySignal<boolean>;
9
+ setOpen: (open: boolean) => void;
10
+ triggerRef: { current: HTMLElement | null };
11
+ contentRef: { current: HTMLElement | null };
12
+ contentId: string;
13
+ }
14
+
15
+ export const PopoverContext = createContext<PopoverContextValue | null>(null);
16
+
17
+ export function usePopoverContext(): PopoverContextValue | null {
18
+ return useContext(PopoverContext);
19
+ }
20
+
21
+ export interface PopoverProps {
22
+ open?: () => boolean | undefined;
23
+ defaultOpen?: boolean;
24
+ onOpenChange?: (open: boolean) => void;
25
+ trigger?: Child | (() => Child);
26
+ content?: Child | (() => Child);
27
+ triggerClass?: any;
28
+ contentClass?: any;
29
+ portalled?: boolean;
30
+ children?: Child | (() => Child);
31
+ }
32
+
33
+ export function PopoverRoot(props: PopoverProps): Child {
34
+ const [open, setOpen] = createControllableSignal<boolean>({
35
+ value: props.open,
36
+ defaultValue: props.defaultOpen ?? false,
37
+ onChange: props.onOpenChange
38
+ });
39
+
40
+ const triggerRef: { current: HTMLElement | null } = { current: null };
41
+ const contentRef: { current: HTMLElement | null } = { current: null };
42
+ const contentId = createId("popover-content");
43
+
44
+ const ctx: PopoverContextValue = {
45
+ open,
46
+ setOpen,
47
+ triggerRef,
48
+ contentRef,
49
+ contentId
50
+ };
51
+
52
+ if (props.trigger !== undefined || props.content !== undefined) {
53
+ return PopoverContext.Provider({
54
+ value: ctx,
55
+ children: () => (
56
+ <>
57
+ {props.trigger ? (
58
+ <PopoverTrigger as="span" class={props.triggerClass}>
59
+ {typeof props.trigger === "function" ? props.trigger() : props.trigger}
60
+ </PopoverTrigger>
61
+ ) : null}
62
+ <PopoverContent portalled={props.portalled} class={props.contentClass}>
63
+ {typeof props.content === "function" ? props.content() : props.content}
64
+ {typeof props.children === "function" ? props.children() : props.children}
65
+ </PopoverContent>
66
+ </>
67
+ )
68
+ });
69
+ }
70
+
71
+ return PopoverContext.Provider({
72
+ value: ctx,
73
+ children: props.children
74
+ });
75
+ }
76
+
77
+ export interface PopoverTriggerProps {
78
+ as?: string;
79
+ class?: any;
80
+ style?: any;
81
+ onClick?: (e: MouseEvent) => void;
82
+ children: Child | (() => Child);
83
+ [key: string]: any;
84
+ }
85
+
86
+ export function PopoverTrigger(props: PopoverTriggerProps): Child {
87
+ const ctx = useContext(PopoverContext);
88
+
89
+ const handleClick = (e: MouseEvent) => {
90
+ if (props.onClick) props.onClick(e);
91
+ if (!e.defaultPrevented && ctx) {
92
+ ctx.setOpen(!ctx.open());
93
+ }
94
+ };
95
+
96
+ const { as: Tag = "button", children, onClick, class: className, style, ...rest } = props;
97
+
98
+ return (
99
+ <Tag
100
+ ref={(el: HTMLElement | null) => {
101
+ if (ctx) ctx.triggerRef.current = el;
102
+ }}
103
+ type={Tag === "button" ? "button" : undefined}
104
+ aria-haspopup="dialog"
105
+ aria-expanded={() => ctx?.open() ?? false}
106
+ aria-controls={ctx?.contentId}
107
+ data-state={() => (ctx?.open() ? "open" : "closed")}
108
+ class={className}
109
+ style={style}
110
+ onClick={handleClick}
111
+ {...rest}
112
+ >
113
+ {children}
114
+ </Tag>
115
+ );
116
+ }
117
+
118
+ export interface PopoverPortalProps {
119
+ mount?: HTMLElement | null;
120
+ children: Child | (() => Child);
121
+ }
122
+
123
+ export function PopoverPortal(props: PopoverPortalProps): Child {
124
+ return <Portal mount={props.mount}>{props.children}</Portal>;
125
+ }
126
+
127
+ export interface PopoverContentProps {
128
+ as?: string;
129
+ portalled?: boolean;
130
+ mount?: HTMLElement | null;
131
+ class?: any;
132
+ style?: any;
133
+ onKeyDown?: (e: KeyboardEvent) => void;
134
+ children: Child | (() => Child);
135
+ [key: string]: any;
136
+ }
137
+
138
+ export function PopoverContent(props: PopoverContentProps): Child {
139
+ const ctx = useContext(PopoverContext);
140
+
141
+ const handleKeyDown = (e: KeyboardEvent) => {
142
+ if (props.onKeyDown) props.onKeyDown(e);
143
+ if (e.defaultPrevented) return;
144
+
145
+ if (e.key === "Escape" && ctx) {
146
+ ctx.setOpen(false);
147
+ ctx.triggerRef.current?.focus();
148
+ e.stopPropagation();
149
+ }
150
+ };
151
+
152
+ const handleClick = (e: MouseEvent) => {
153
+ if (props.onClick) props.onClick(e);
154
+ if (e.defaultPrevented) return;
155
+ const target = e.target as HTMLElement | null;
156
+ if (target?.closest("[data-popover-close]")) {
157
+ ctx?.setOpen(false);
158
+ }
159
+ };
160
+
161
+ const {
162
+ as: Tag = "div",
163
+ children,
164
+ onKeyDown,
165
+ onClick: _onClick,
166
+ class: className,
167
+ style,
168
+ portalled = true,
169
+ mount,
170
+ ...rest
171
+ } = props;
172
+
173
+ const content = (
174
+ <Tag
175
+ ref={(el: HTMLElement | null) => {
176
+ if (ctx) {
177
+ ctx.contentRef.current = el;
178
+ if (el) {
179
+ const cleanup = onClickOutside([ctx.triggerRef.current, el], () => {
180
+ if (ctx.open()) {
181
+ ctx.setOpen(false);
182
+ }
183
+ });
184
+ onCleanup(cleanup);
185
+ }
186
+ }
187
+ }}
188
+ id={ctx?.contentId}
189
+ role="dialog"
190
+ data-state={() => (ctx?.open() ? "open" : "closed")}
191
+ class={className}
192
+ style={() => ({
193
+ display: ctx?.open() ? undefined : "none",
194
+ ...style
195
+ })}
196
+ onKeyDown={handleKeyDown}
197
+ onClick={handleClick}
198
+ tabIndex={-1}
199
+ {...rest}
200
+ >
201
+ {children}
202
+ </Tag>
203
+ );
204
+
205
+ return portalled ? <Portal mount={mount}>{content}</Portal> : content;
206
+ }
207
+
208
+ export interface PopoverCloseProps {
209
+ as?: string;
210
+ class?: any;
211
+ style?: any;
212
+ onClick?: (e: MouseEvent) => void;
213
+ children: Child | (() => Child);
214
+ [key: string]: any;
215
+ }
216
+
217
+ export function PopoverClose(props: PopoverCloseProps): Child {
218
+ const ctx = useContext(PopoverContext);
219
+
220
+ const handleClick = (e: MouseEvent) => {
221
+ if (props.onClick) props.onClick(e);
222
+ if (!e.defaultPrevented && ctx) {
223
+ ctx.setOpen(false);
224
+ }
225
+ };
226
+
227
+ const { as: Tag = "button", children, onClick, class: className, style, ...rest } = props;
228
+
229
+ return (
230
+ <Tag
231
+ type={Tag === "button" ? "button" : undefined}
232
+ data-popover-close=""
233
+ class={className}
234
+ style={style}
235
+ onClick={handleClick}
236
+ {...rest}
237
+ >
238
+ {children}
239
+ </Tag>
240
+ );
241
+ }
242
+
243
+ export const Popover = Object.assign(PopoverRoot, {
244
+ Root: PopoverRoot,
245
+ Trigger: PopoverTrigger,
246
+ Portal: PopoverPortal,
247
+ Content: PopoverContent,
248
+ Close: PopoverClose
249
+ });
@@ -0,0 +1,114 @@
1
+ import type { Child } from "../dom/render";
2
+ import { createControllableSignal } from "./utils";
3
+
4
+ export interface SwitchProps {
5
+ checked?: () => boolean | undefined;
6
+ defaultChecked?: boolean;
7
+ onCheckedChange?: (checked: boolean) => void;
8
+ disabled?: boolean | (() => boolean);
9
+ required?: boolean;
10
+ name?: string;
11
+ value?: string;
12
+ thumbClass?: any;
13
+ thumbStyle?: any;
14
+ as?: string;
15
+ class?: any;
16
+ style?: any;
17
+ children?: Child | (() => Child);
18
+ onClick?: (e: MouseEvent) => void;
19
+ onKeyDown?: (e: KeyboardEvent) => void;
20
+ [key: string]: any;
21
+ }
22
+
23
+ export function SwitchRoot(props: SwitchProps): Child {
24
+ const [checked, setChecked] = createControllableSignal<boolean>({
25
+ value: props.checked,
26
+ defaultValue: props.defaultChecked ?? false,
27
+ onChange: props.onCheckedChange
28
+ });
29
+
30
+ const toggle = () => {
31
+ const isDisabled =
32
+ typeof props.disabled === "function"
33
+ ? (props.disabled as () => boolean)()
34
+ : Boolean(props.disabled);
35
+ if (isDisabled) return;
36
+ setChecked(!checked());
37
+ };
38
+
39
+ const handleClick = (e: MouseEvent) => {
40
+ if (props.onClick) props.onClick(e);
41
+ if (!e.defaultPrevented) {
42
+ toggle();
43
+ }
44
+ };
45
+
46
+ const handleKeyDown = (e: KeyboardEvent) => {
47
+ if (props.onKeyDown) props.onKeyDown(e);
48
+ if (e.key === "Enter" || e.key === " ") {
49
+ e.preventDefault();
50
+ toggle();
51
+ }
52
+ };
53
+
54
+ const {
55
+ checked: _c,
56
+ defaultChecked: _dc,
57
+ onCheckedChange: _occ,
58
+ disabled,
59
+ required,
60
+ name,
61
+ value,
62
+ thumbClass,
63
+ thumbStyle,
64
+ as: Tag = "button",
65
+ children,
66
+ onClick,
67
+ onKeyDown,
68
+ class: className,
69
+ style,
70
+ ...rest
71
+ } = props;
72
+
73
+ return (
74
+ <Tag
75
+ type={Tag === "button" ? "button" : undefined}
76
+ role="switch"
77
+ aria-checked={checked}
78
+ data-state={() => (checked() ? "checked" : "unchecked")}
79
+ disabled={disabled}
80
+ class={className}
81
+ style={style}
82
+ onClick={handleClick}
83
+ onKeyDown={handleKeyDown}
84
+ {...rest}
85
+ >
86
+ {children !== undefined ? (
87
+ typeof children === "function" ? (
88
+ children()
89
+ ) : (
90
+ children
91
+ )
92
+ ) : (
93
+ <SwitchThumb class={thumbClass} style={thumbStyle} />
94
+ )}
95
+ </Tag>
96
+ );
97
+ }
98
+
99
+ export interface SwitchThumbProps {
100
+ as?: string;
101
+ class?: any;
102
+ style?: any;
103
+ [key: string]: any;
104
+ }
105
+
106
+ export function SwitchThumb(props: SwitchThumbProps): Child {
107
+ const { as: Tag = "span", class: className, style, ...rest } = props;
108
+ return <Tag class={className} style={style} {...rest} />;
109
+ }
110
+
111
+ export const Switch = Object.assign(SwitchRoot, {
112
+ Root: SwitchRoot,
113
+ Thumb: SwitchThumb
114
+ });
@@ -0,0 +1,297 @@
1
+ import { Show } from "../dom/control-flow";
2
+ import type { Child } from "../dom/render";
3
+ import type { ReadonlySignal } from "../signals";
4
+ import { createContext, useContext } from "../signals/context";
5
+ import { createControllableSignal, createId } from "./utils";
6
+
7
+ export interface TabsContextValue {
8
+ value: ReadonlySignal<string>;
9
+ setValue: (value: string) => void;
10
+ orientation: "horizontal" | "vertical";
11
+ activationMode: "automatic" | "manual";
12
+ baseId: string;
13
+ }
14
+
15
+ export const TabsContext = createContext<TabsContextValue | null>(null);
16
+
17
+ export function useTabsContext(): TabsContextValue | null {
18
+ return useContext(TabsContext);
19
+ }
20
+
21
+ export interface TabItem {
22
+ value: string;
23
+ label: Child | (() => Child);
24
+ icon?: Child | (() => Child);
25
+ content?: Child | (() => Child);
26
+ disabled?: boolean | (() => boolean);
27
+ }
28
+
29
+ export interface TabsProps {
30
+ value?: () => string | undefined;
31
+ defaultValue?: string;
32
+ onValueChange?: (value: string) => void;
33
+ orientation?: "horizontal" | "vertical";
34
+ activationMode?: "automatic" | "manual";
35
+ items?: TabItem[];
36
+ listClass?: any;
37
+ triggerClass?: any;
38
+ contentClass?: any;
39
+ class?: any;
40
+ style?: any;
41
+ as?: string;
42
+ children?: Child | (() => Child);
43
+ [key: string]: any;
44
+ }
45
+
46
+ export function TabsRoot(props: TabsProps): Child {
47
+ const [value, setValue] = createControllableSignal<string>({
48
+ value: props.value,
49
+ defaultValue:
50
+ props.defaultValue ?? (props.items && props.items.length > 0 ? props.items[0]?.value : ""),
51
+ onChange: props.onValueChange
52
+ });
53
+
54
+ const baseId = createId("tabs");
55
+ const orientation = props.orientation ?? "horizontal";
56
+ const activationMode = props.activationMode ?? "automatic";
57
+
58
+ const ctx: TabsContextValue = {
59
+ value,
60
+ setValue,
61
+ orientation,
62
+ activationMode,
63
+ baseId
64
+ };
65
+
66
+ const {
67
+ as: Tag = "div",
68
+ value: _v,
69
+ defaultValue: _dv,
70
+ onValueChange: _ovc,
71
+ orientation: _ori,
72
+ activationMode: _am,
73
+ items,
74
+ listClass,
75
+ triggerClass,
76
+ contentClass,
77
+ children,
78
+ class: className,
79
+ style,
80
+ ...rest
81
+ } = props;
82
+
83
+ return TabsContext.Provider({
84
+ value: ctx,
85
+ children: () => (
86
+ <Tag data-orientation={orientation} class={className} style={style} {...rest}>
87
+ {items ? (
88
+ <>
89
+ <TabsList class={listClass}>
90
+ {items.map((item) => (
91
+ <TabsTrigger value={item.value} disabled={item.disabled} class={triggerClass}>
92
+ {item.icon ? (
93
+ <span class="tab-icon" style={{ display: "inline-flex", alignItems: "center" }}>
94
+ {typeof item.icon === "function" ? item.icon() : item.icon}
95
+ </span>
96
+ ) : null}
97
+ {typeof item.label === "function" ? item.label() : item.label}
98
+ </TabsTrigger>
99
+ ))}
100
+ </TabsList>
101
+ {items.map((item) => (
102
+ <TabsContent value={item.value} class={contentClass}>
103
+ {typeof item.content === "function" ? item.content() : item.content}
104
+ </TabsContent>
105
+ ))}
106
+ </>
107
+ ) : null}
108
+ {typeof children === "function" ? children() : children}
109
+ </Tag>
110
+ )
111
+ });
112
+ }
113
+
114
+ export interface TabsListProps {
115
+ as?: string;
116
+ class?: any;
117
+ style?: any;
118
+ onKeyDown?: (e: KeyboardEvent) => void;
119
+ children: Child | (() => Child);
120
+ [key: string]: any;
121
+ }
122
+
123
+ export function TabsList(props: TabsListProps): Child {
124
+ const ctx = useContext(TabsContext);
125
+
126
+ const handleKeyDown = (e: KeyboardEvent) => {
127
+ if (props.onKeyDown) props.onKeyDown(e);
128
+ if (e.defaultPrevented) return;
129
+
130
+ const target = e.target as HTMLElement | null;
131
+ if (target?.getAttribute("role") !== "tab") return;
132
+
133
+ const list = target.closest('[role="tablist"]');
134
+ if (!list) return;
135
+
136
+ const tabs = Array.from(list.querySelectorAll<HTMLElement>('[role="tab"]:not([disabled])'));
137
+ const index = tabs.indexOf(target);
138
+ if (index === -1) return;
139
+
140
+ let nextIndex = index;
141
+ const isHorizontal = (ctx?.orientation ?? "horizontal") === "horizontal";
142
+
143
+ const prevKey = isHorizontal ? "ArrowLeft" : "ArrowUp";
144
+ const nextKey = isHorizontal ? "ArrowRight" : "ArrowDown";
145
+
146
+ if (e.key === prevKey) {
147
+ nextIndex = index === 0 ? tabs.length - 1 : index - 1;
148
+ e.preventDefault();
149
+ } else if (e.key === nextKey) {
150
+ nextIndex = index === tabs.length - 1 ? 0 : index + 1;
151
+ e.preventDefault();
152
+ } else if (e.key === "Home") {
153
+ nextIndex = 0;
154
+ e.preventDefault();
155
+ } else if (e.key === "End") {
156
+ nextIndex = tabs.length - 1;
157
+ e.preventDefault();
158
+ }
159
+
160
+ if (nextIndex !== index) {
161
+ const nextTab = tabs[nextIndex]!;
162
+ nextTab.focus();
163
+ if (ctx?.activationMode === "automatic") {
164
+ const val = nextTab.getAttribute("data-value");
165
+ if (val) ctx.setValue(val);
166
+ }
167
+ }
168
+ };
169
+
170
+ const { as: Tag = "div", children, onKeyDown, class: className, style, ...rest } = props;
171
+
172
+ return (
173
+ <Tag
174
+ role="tablist"
175
+ aria-orientation={ctx?.orientation ?? "horizontal"}
176
+ class={className}
177
+ style={style}
178
+ onKeyDown={handleKeyDown}
179
+ {...rest}
180
+ >
181
+ {children}
182
+ </Tag>
183
+ );
184
+ }
185
+
186
+ export interface TabsTriggerProps {
187
+ value: string;
188
+ disabled?: boolean | (() => boolean);
189
+ as?: string;
190
+ class?: any;
191
+ style?: any;
192
+ onClick?: (e: MouseEvent) => void;
193
+ children: Child | (() => Child);
194
+ [key: string]: any;
195
+ }
196
+
197
+ export function TabsTrigger(props: TabsTriggerProps): Child {
198
+ const ctx = useContext(TabsContext);
199
+ const isSelected = () => (ctx ? ctx.value() === props.value : false);
200
+
201
+ const handleClick = (e: MouseEvent) => {
202
+ if (props.onClick) props.onClick(e);
203
+ if (!e.defaultPrevented && ctx) {
204
+ ctx.setValue(props.value);
205
+ }
206
+ };
207
+
208
+ const {
209
+ value,
210
+ disabled,
211
+ as: Tag = "button",
212
+ children,
213
+ onClick,
214
+ class: className,
215
+ style,
216
+ ...rest
217
+ } = props;
218
+
219
+ return (
220
+ <Tag
221
+ id={ctx ? `${ctx.baseId}-trigger-${value}` : undefined}
222
+ type={Tag === "button" ? "button" : undefined}
223
+ role="tab"
224
+ aria-selected={isSelected}
225
+ aria-controls={ctx ? `${ctx.baseId}-panel-${value}` : undefined}
226
+ data-state={() => (isSelected() ? "active" : "inactive")}
227
+ data-value={value}
228
+ tabIndex={() => (isSelected() ? 0 : -1)}
229
+ disabled={disabled}
230
+ class={className}
231
+ style={style}
232
+ onClick={handleClick}
233
+ {...rest}
234
+ >
235
+ {children}
236
+ </Tag>
237
+ );
238
+ }
239
+
240
+ export interface TabsContentProps {
241
+ value: string;
242
+ forceMount?: boolean;
243
+ as?: string;
244
+ class?: any;
245
+ style?: any;
246
+ children: Child | (() => Child);
247
+ [key: string]: any;
248
+ }
249
+
250
+ export function TabsContent(props: TabsContentProps): Child {
251
+ const ctx = useContext(TabsContext);
252
+ const isSelected = () => (ctx ? ctx.value() === props.value : false);
253
+
254
+ const { value, forceMount, as: Tag = "div", children, class: className, style, ...rest } = props;
255
+
256
+ if (forceMount) {
257
+ return (
258
+ <Tag
259
+ id={ctx ? `${ctx.baseId}-panel-${value}` : undefined}
260
+ role="tabpanel"
261
+ aria-labelledby={ctx ? `${ctx.baseId}-trigger-${value}` : undefined}
262
+ data-state={() => (isSelected() ? "active" : "inactive")}
263
+ hidden={() => !isSelected()}
264
+ tabIndex={0}
265
+ class={className}
266
+ style={style}
267
+ {...rest}
268
+ >
269
+ {children}
270
+ </Tag>
271
+ );
272
+ }
273
+
274
+ return (
275
+ <Show when={isSelected}>
276
+ <Tag
277
+ id={ctx ? `${ctx.baseId}-panel-${value}` : undefined}
278
+ role="tabpanel"
279
+ aria-labelledby={ctx ? `${ctx.baseId}-trigger-${value}` : undefined}
280
+ data-state="active"
281
+ tabIndex={0}
282
+ class={className}
283
+ style={style}
284
+ {...rest}
285
+ >
286
+ {children}
287
+ </Tag>
288
+ </Show>
289
+ );
290
+ }
291
+
292
+ export const Tabs = Object.assign(TabsRoot, {
293
+ Root: TabsRoot,
294
+ List: TabsList,
295
+ Trigger: TabsTrigger,
296
+ Content: TabsContent
297
+ });