aeon-ui-engine 1.5.1 → 1.8.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.
- package/CHANGELOG.md +112 -0
- package/package.json +2 -2
- package/packages/cli/node_modules/.bin/vitest +2 -2
- package/packages/core/node_modules/.bin/vitest +2 -2
- package/packages/core/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
- package/packages/panda/styled-system/styles.css +0 -4
- package/packages/primitives/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
- package/packages/react/dist/index.d.ts +29 -4
- package/packages/react/dist/index.js +85 -28
- package/packages/react/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
- package/packages/react/src/dialog/dialog.test.tsx +27 -0
- package/packages/react/src/dialog/index.tsx +11 -3
- package/packages/react/src/floating/FloatingPositioner.tsx +11 -1
- package/packages/react/src/menu/index.tsx +8 -3
- package/packages/react/src/popover/index.tsx +25 -4
- package/packages/react/src/slider/index.tsx +13 -6
- package/packages/schemas/dist/index.js +3 -1
- package/packages/schemas/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
- package/packages/schemas/prompts/statecharts.md +1 -1
- package/packages/schemas/registry/machines/slider.json +18 -1
- package/packages/tree/README.md +29 -0
- package/packages/tree/dist/index.d.ts +97 -0
- package/packages/tree/dist/index.js +107 -0
- package/packages/tree/node_modules/.bin/esbuild +21 -0
- package/packages/tree/node_modules/.bin/jiti +21 -0
- package/packages/tree/node_modules/.bin/tsc +21 -0
- package/packages/tree/node_modules/.bin/tsserver +21 -0
- package/packages/tree/node_modules/.bin/tsup +21 -0
- package/packages/tree/node_modules/.bin/tsup-node +21 -0
- package/packages/tree/node_modules/.bin/tsx +21 -0
- package/packages/tree/node_modules/.bin/vite +21 -0
- package/packages/tree/node_modules/.bin/vitest +21 -0
- package/packages/tree/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
- package/packages/tree/package.json +42 -0
- package/packages/tree/src/exclusive-action.test.ts +54 -0
- package/packages/tree/src/exclusive-action.ts +104 -0
- package/packages/tree/src/index.ts +7 -0
- package/packages/tree/src/interpret.tsx +45 -0
- package/packages/tree/src/totality.ts +20 -0
- package/packages/tree/src/widget.ts +31 -0
- package/packages/tree/tsconfig.json +8 -0
- package/packages/tree/vitest.config.ts +8 -0
- package/scripts/vite-aliases.mjs +3 -0
|
@@ -51,16 +51,21 @@ const Root = forwardRef<HTMLDivElement, MenuRootProps>(function MenuRoot(
|
|
|
51
51
|
ref,
|
|
52
52
|
) {
|
|
53
53
|
const [snapshot, send] = useAeonMachine(popoverMachine, { input: { open: defaultOpen } })
|
|
54
|
-
const
|
|
54
|
+
const machineOpen = snapshot.context.open
|
|
55
|
+
const resolvedOpen = open ?? machineOpen
|
|
55
56
|
const rootRef = useRef<HTMLDivElement>(null)
|
|
56
57
|
const triggerRef = useRef<HTMLButtonElement>(null)
|
|
57
58
|
const positionerRef = useRef<HTMLDivElement>(null)
|
|
58
59
|
const triggerId = useId()
|
|
59
60
|
const menuId = useId()
|
|
60
61
|
|
|
62
|
+
/** Machine intent, so a controlled owner still hears its own trigger. */
|
|
63
|
+
const reportedOpen = useRef(machineOpen)
|
|
61
64
|
useEffect(() => {
|
|
62
|
-
|
|
63
|
-
|
|
65
|
+
if (reportedOpen.current === machineOpen) return
|
|
66
|
+
reportedOpen.current = machineOpen
|
|
67
|
+
onOpenChange?.(machineOpen)
|
|
68
|
+
}, [machineOpen, onOpenChange])
|
|
64
69
|
|
|
65
70
|
useEffect(() => {
|
|
66
71
|
if (open !== undefined) send({ type: 'SET_OPEN', open })
|
|
@@ -53,16 +53,21 @@ const Root = forwardRef<HTMLDivElement, PopoverRootProps>(function PopoverRoot(
|
|
|
53
53
|
const [snapshot, send] = useAeonMachine(popoverMachine, {
|
|
54
54
|
input: { open: defaultOpen },
|
|
55
55
|
})
|
|
56
|
-
const
|
|
56
|
+
const machineOpen = snapshot.context.open
|
|
57
|
+
const resolvedOpen = open ?? machineOpen
|
|
57
58
|
const rootRef = useRef<HTMLDivElement>(null)
|
|
58
59
|
const triggerRef = useRef<HTMLButtonElement>(null)
|
|
59
60
|
const positionerRef = useRef<HTMLDivElement>(null)
|
|
60
61
|
const triggerId = useId()
|
|
61
62
|
const contentId = useId()
|
|
62
63
|
|
|
64
|
+
/** Machine intent, so a controlled owner still hears its own trigger. */
|
|
65
|
+
const reportedOpen = useRef(machineOpen)
|
|
63
66
|
useEffect(() => {
|
|
64
|
-
|
|
65
|
-
|
|
67
|
+
if (reportedOpen.current === machineOpen) return
|
|
68
|
+
reportedOpen.current = machineOpen
|
|
69
|
+
onOpenChange?.(machineOpen)
|
|
70
|
+
}, [machineOpen, onOpenChange])
|
|
66
71
|
|
|
67
72
|
useEffect(() => {
|
|
68
73
|
if (open !== undefined) send({ type: 'SET_OPEN', open })
|
|
@@ -132,6 +137,10 @@ export interface PopoverPositionerProps extends HTMLAttributes<HTMLDivElement> {
|
|
|
132
137
|
/** Mount panel in document.body (default true). */
|
|
133
138
|
portalled?: boolean
|
|
134
139
|
placement?: AnchorPlacement
|
|
140
|
+
/** Align to this region rather than the trigger. See `FloatingPositioner`. */
|
|
141
|
+
anchorRef?: React.RefObject<HTMLElement | null>
|
|
142
|
+
/** Size the panel to the anchor, so it spans the region it belongs to. */
|
|
143
|
+
matchAnchorWidth?: boolean
|
|
135
144
|
/** Gap between trigger and panel (default 4). */
|
|
136
145
|
gutter?: number
|
|
137
146
|
/** Stack level for the portalled surface (default 50). */
|
|
@@ -140,7 +149,17 @@ export interface PopoverPositionerProps extends HTMLAttributes<HTMLDivElement> {
|
|
|
140
149
|
}
|
|
141
150
|
|
|
142
151
|
const Positioner = forwardRef<HTMLDivElement, PopoverPositionerProps>(function PopoverPositioner(
|
|
143
|
-
{
|
|
152
|
+
{
|
|
153
|
+
portalled = true,
|
|
154
|
+
placement = 'bottom-start',
|
|
155
|
+
anchorRef,
|
|
156
|
+
matchAnchorWidth = false,
|
|
157
|
+
gutter,
|
|
158
|
+
zIndex,
|
|
159
|
+
children,
|
|
160
|
+
className,
|
|
161
|
+
...rest
|
|
162
|
+
},
|
|
144
163
|
ref,
|
|
145
164
|
) {
|
|
146
165
|
const { open, triggerRef, positionerRef } = usePopoverCtx()
|
|
@@ -153,6 +172,8 @@ const Positioner = forwardRef<HTMLDivElement, PopoverPositionerProps>(function P
|
|
|
153
172
|
part={popoverAnatomy.positioner}
|
|
154
173
|
triggerRef={triggerRef}
|
|
155
174
|
positionerRef={positionerRef}
|
|
175
|
+
anchorRef={anchorRef}
|
|
176
|
+
matchAnchorWidth={matchAnchorWidth}
|
|
156
177
|
portalled={portalled}
|
|
157
178
|
placement={placement}
|
|
158
179
|
gutter={gutter}
|
|
@@ -16,13 +16,15 @@ import {
|
|
|
16
16
|
import { useAeonMachine } from '../hooks/use-aeon-machine.js'
|
|
17
17
|
import { mergeProps } from '../utils/merge-props.js'
|
|
18
18
|
|
|
19
|
-
interface SliderContextValue {
|
|
19
|
+
export interface SliderContextValue {
|
|
20
20
|
value: number
|
|
21
21
|
min: number
|
|
22
22
|
max: number
|
|
23
23
|
step: number
|
|
24
24
|
disabled: boolean
|
|
25
25
|
dragging: boolean
|
|
26
|
+
/** Nested region value from the machine: `interaction.idle` | `interaction.dragging`. */
|
|
27
|
+
interaction: 'idle' | 'dragging'
|
|
26
28
|
send: ReturnType<typeof useAeonMachine<typeof sliderMachine>>[1]
|
|
27
29
|
onValueChange?: (value: number) => void
|
|
28
30
|
/** Snap, store and report a user-intended value. */
|
|
@@ -33,7 +35,11 @@ interface SliderContextValue {
|
|
|
33
35
|
|
|
34
36
|
const SliderCtx = createContext<SliderContextValue | null>(null)
|
|
35
37
|
|
|
36
|
-
|
|
38
|
+
/**
|
|
39
|
+
* Read the slider's live machine state from inside `Slider.Root`.
|
|
40
|
+
* `interaction` is the nested region value — the same thing the snapshot holds.
|
|
41
|
+
*/
|
|
42
|
+
export function useSliderContext() {
|
|
37
43
|
const ctx = useContext(SliderCtx)
|
|
38
44
|
if (!ctx) throw new Error('Slider parts must be used within Slider.Root')
|
|
39
45
|
return ctx
|
|
@@ -144,6 +150,7 @@ const Root = forwardRef<HTMLDivElement, SliderRootProps>(function SliderRoot(
|
|
|
144
150
|
step,
|
|
145
151
|
disabled,
|
|
146
152
|
dragging,
|
|
153
|
+
interaction: (dragging ? 'dragging' : 'idle') as 'idle' | 'dragging',
|
|
147
154
|
send,
|
|
148
155
|
onValueChange,
|
|
149
156
|
commit,
|
|
@@ -187,7 +194,7 @@ const Track = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(functio
|
|
|
187
194
|
{ onPointerDown, ...rest },
|
|
188
195
|
ref,
|
|
189
196
|
) {
|
|
190
|
-
const { disabled, send, setValueFromClientX, trackRef } =
|
|
197
|
+
const { disabled, send, setValueFromClientX, trackRef } = useSliderContext()
|
|
191
198
|
|
|
192
199
|
return (
|
|
193
200
|
<div
|
|
@@ -217,7 +224,7 @@ const Range = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(functio
|
|
|
217
224
|
{ style, ...rest },
|
|
218
225
|
ref,
|
|
219
226
|
) {
|
|
220
|
-
const { value, min, max } =
|
|
227
|
+
const { value, min, max } = useSliderContext()
|
|
221
228
|
return (
|
|
222
229
|
<div
|
|
223
230
|
ref={ref}
|
|
@@ -233,7 +240,7 @@ const Thumb = forwardRef<HTMLSpanElement, HTMLAttributes<HTMLSpanElement>>(funct
|
|
|
233
240
|
ref,
|
|
234
241
|
) {
|
|
235
242
|
const { value, min, max, step, disabled, dragging, send, commit, setValueFromClientX } =
|
|
236
|
-
|
|
243
|
+
useSliderContext()
|
|
237
244
|
|
|
238
245
|
return (
|
|
239
246
|
<span
|
|
@@ -295,7 +302,7 @@ const ValueText = forwardRef<HTMLSpanElement, HTMLAttributes<HTMLSpanElement>>(f
|
|
|
295
302
|
props,
|
|
296
303
|
ref,
|
|
297
304
|
) {
|
|
298
|
-
const { value } =
|
|
305
|
+
const { value } = useSliderContext()
|
|
299
306
|
return (
|
|
300
307
|
<span ref={ref} {...mergeProps(partOnlyAttrs(sliderAnatomy.valueText), props)}>
|
|
301
308
|
{props.children ?? value}
|
|
@@ -926,7 +926,9 @@ When generating Aeon UI, constrain props/parts/states to @aeon-ui/schemas catalo
|
|
|
926
926
|
Import headless from @aeon-ui/react or styled from @aeon-ui/ui.
|
|
927
927
|
Install: npm install aeon-ui-engine react react-dom xstate @xstate/react
|
|
928
928
|
CSS: import 'aeon-ui-engine/aeon.css'
|
|
929
|
-
|
|
929
|
+
Link: postinstall + npx aeon-ui link (node_modules/@aeon-ui/*)
|
|
930
|
+
Next.js: transpilePackages for @aeon-ui/* + "use client"; Vite plugin optional
|
|
931
|
+
Vue/Solid/Svelte: thin adapters only \u2014 React is the full catalog
|
|
930
932
|
|
|
931
933
|
${STATECHART_PROMPTS.async}
|
|
932
934
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":"4.1.8","results":[[":src/index.test.ts",{"duration":
|
|
1
|
+
{"version":"4.1.8","results":[[":src/index.test.ts",{"duration":42.299125000000004,"failed":false}],[":src/aeon-to-schema.test.ts",{"duration":7.255999999999972,"failed":false}]]}
|
|
@@ -34,7 +34,7 @@ Rule: Hidden input stays in sync with snapshot for forms.
|
|
|
34
34
|
|
|
35
35
|
## Slider statechart
|
|
36
36
|
Machine: sliderMachine — idle | dragging; value/min/max/step in context.
|
|
37
|
-
Events: SET_VALUE, STEP, POINTER_DOWN, POINTER_UP, HOME, END
|
|
37
|
+
Events: SET_VALUE, STEP, POINTER_DOWN, POINTER_UP, HOME, END.
|
|
38
38
|
Rule: Disabled blocks SET_VALUE / drag; project idle vs dragging on data-aeon-state.
|
|
39
39
|
|
|
40
40
|
## Content region statechart
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
"id": "slider",
|
|
3
3
|
"initial": "interaction",
|
|
4
4
|
"events": [
|
|
5
|
+
"SET_BOUNDS",
|
|
6
|
+
"SET_DISABLED",
|
|
5
7
|
"SET_VALUE",
|
|
6
8
|
"STEP",
|
|
7
9
|
"HOME",
|
|
@@ -14,6 +16,8 @@
|
|
|
14
16
|
"id": "slider",
|
|
15
17
|
"type": "compound",
|
|
16
18
|
"events": [
|
|
19
|
+
"SET_BOUNDS",
|
|
20
|
+
"SET_DISABLED",
|
|
17
21
|
"SET_VALUE",
|
|
18
22
|
"STEP",
|
|
19
23
|
"HOME",
|
|
@@ -21,7 +25,18 @@
|
|
|
21
25
|
"POINTER_DOWN",
|
|
22
26
|
"POINTER_UP"
|
|
23
27
|
],
|
|
24
|
-
"transitions": [
|
|
28
|
+
"transitions": [
|
|
29
|
+
{
|
|
30
|
+
"event": "SET_BOUNDS",
|
|
31
|
+
"target": [],
|
|
32
|
+
"guarded": false
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"event": "SET_DISABLED",
|
|
36
|
+
"target": [],
|
|
37
|
+
"guarded": false
|
|
38
|
+
}
|
|
39
|
+
],
|
|
25
40
|
"children": [
|
|
26
41
|
{
|
|
27
42
|
"key": "interaction",
|
|
@@ -110,6 +125,8 @@
|
|
|
110
125
|
"states": {
|
|
111
126
|
"slider": {
|
|
112
127
|
"events": [
|
|
128
|
+
"SET_BOUNDS",
|
|
129
|
+
"SET_DISABLED",
|
|
113
130
|
"SET_VALUE",
|
|
114
131
|
"STEP",
|
|
115
132
|
"HOME",
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# @aeon-ui/tree
|
|
2
|
+
|
|
3
|
+
Widget trees for Aeon. A widget is configuration — immutable data describing one face of a chart — interpreted into DOM that carries `data-aeon-scope`, `data-aeon-part`, and `data-aeon-state`.
|
|
4
|
+
|
|
5
|
+
This is Flutter's Widget and Element layers only. There is no RenderObject (layout stays CSS) and no `StatefulWidget` (the chart is the only state). A tree that stored `busy` itself would be the boolean twin the philosophy rules out.
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import { exclusiveActionWidget, interpret, uncoveredStates, EXCLUSIVE_ACTION_STATES } from '@aeon-ui/tree'
|
|
9
|
+
|
|
10
|
+
const tree = exclusiveActionWidget({
|
|
11
|
+
scope: 'settings-change-password',
|
|
12
|
+
part: 'change-password-form',
|
|
13
|
+
state, // snapshot token
|
|
14
|
+
busy,
|
|
15
|
+
error,
|
|
16
|
+
body: { type: 'host', id: 'body' },
|
|
17
|
+
idleLabel: 'Update password',
|
|
18
|
+
pendingLabel: 'Updating…',
|
|
19
|
+
onSubmit,
|
|
20
|
+
confirm: confirm !== null,
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
uncoveredStates(EXCLUSIVE_ACTION_STATES, (s) => exclusiveActionWidget({ ...args, state: s }))
|
|
24
|
+
// [] — every state of the chart has a face
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
`interpret(tree, hosts)` is the element layer. Hosts are where an existing compound (a field, a `Prompt`) fills a slot the tree names. Product CSS restyles the anatomy attributes; class names are passed in by the app, not invented here.
|
|
28
|
+
|
|
29
|
+
`exclusiveActionWidget` is the one face shared by every "confirm, then await" region: `idle → confirming → busy → idle | failure`.
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A widget is configuration, the way a Flutter `Widget` is: immutable data
|
|
5
|
+
* describing one face of a chart. It holds no lifecycle. The only state it
|
|
6
|
+
* may name is a snapshot already produced by a machine (`data-aeon-state`).
|
|
7
|
+
*
|
|
8
|
+
* Callbacks are event bindings (what to send), not state. There is no
|
|
9
|
+
* equivalent of `StatefulWidget` or `setState` — a second copy of the chart
|
|
10
|
+
* living in the tree would be the boolean twin this layer exists to remove.
|
|
11
|
+
*/
|
|
12
|
+
type Widget = {
|
|
13
|
+
type: 'text';
|
|
14
|
+
text: string;
|
|
15
|
+
} | {
|
|
16
|
+
type: 'host';
|
|
17
|
+
id: string;
|
|
18
|
+
} | {
|
|
19
|
+
type: 'el';
|
|
20
|
+
tag: 'div' | 'form' | 'button' | 'p' | 'span';
|
|
21
|
+
scope?: string;
|
|
22
|
+
part?: string;
|
|
23
|
+
/** Chart snapshot token. Projected onto `data-aeon-state`. */
|
|
24
|
+
state?: string;
|
|
25
|
+
className?: string;
|
|
26
|
+
buttonType?: 'button' | 'submit';
|
|
27
|
+
disabled?: boolean;
|
|
28
|
+
role?: string;
|
|
29
|
+
onSubmit?: (event: {
|
|
30
|
+
preventDefault(): void;
|
|
31
|
+
}) => void;
|
|
32
|
+
onClick?: () => void;
|
|
33
|
+
children: Widget[];
|
|
34
|
+
};
|
|
35
|
+
declare const text: (value: string) => Widget;
|
|
36
|
+
declare const host: (id: string) => Widget;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The chart every "click, maybe confirm, await, succeed or fail" region
|
|
40
|
+
* shares. One region, four faces — not a `busy` boolean beside the markup.
|
|
41
|
+
*/
|
|
42
|
+
declare const EXCLUSIVE_ACTION_STATES: readonly ["idle", "confirming", "busy", "failure"];
|
|
43
|
+
type ExclusiveActionState = (typeof EXCLUSIVE_ACTION_STATES)[number];
|
|
44
|
+
type ExclusiveActionInput = {
|
|
45
|
+
scope: string;
|
|
46
|
+
part: string;
|
|
47
|
+
/** Active snapshot token, usually `stateToAttr(snapshot.value)`. */
|
|
48
|
+
state: string;
|
|
49
|
+
busy: boolean;
|
|
50
|
+
error: string | null;
|
|
51
|
+
className?: string;
|
|
52
|
+
tag?: 'form' | 'div';
|
|
53
|
+
/** Panel-specific fields. A host, so the tree stays generic. */
|
|
54
|
+
body: Widget;
|
|
55
|
+
idleLabel: string;
|
|
56
|
+
pendingLabel: string;
|
|
57
|
+
primaryClassName?: string;
|
|
58
|
+
primaryDisabled?: boolean;
|
|
59
|
+
onSubmit?: (event: {
|
|
60
|
+
preventDefault(): void;
|
|
61
|
+
}) => void;
|
|
62
|
+
secondary?: {
|
|
63
|
+
label: string;
|
|
64
|
+
className?: string;
|
|
65
|
+
disabled?: boolean;
|
|
66
|
+
onClick: () => void;
|
|
67
|
+
};
|
|
68
|
+
actionsClassName?: string;
|
|
69
|
+
/** When set, the `confirm` host is mounted. The app renders its Prompt there. */
|
|
70
|
+
confirm?: boolean;
|
|
71
|
+
};
|
|
72
|
+
/**
|
|
73
|
+
* One widget for an exclusive mutation. The root carries the chart state;
|
|
74
|
+
* the primary label and the disabled set are functions of `busy`, and the
|
|
75
|
+
* failure copy is a child that exists only when `error` is set.
|
|
76
|
+
*/
|
|
77
|
+
declare function exclusiveActionWidget(input: ExclusiveActionInput): Widget;
|
|
78
|
+
|
|
79
|
+
type WidgetHosts = Record<string, () => ReactNode>;
|
|
80
|
+
/**
|
|
81
|
+
* Element layer: turn a widget tree into DOM. Anatomy comes from
|
|
82
|
+
* `@aeon-ui/core`, so product CSS restyles `data-aeon-*` and nothing else
|
|
83
|
+
* is introduced. Hosts are the escape for existing compounds (Prompt, a
|
|
84
|
+
* password field) — the tree names the slot, the app supplies the compound.
|
|
85
|
+
*/
|
|
86
|
+
declare function interpret(widget: Widget, hosts: WidgetHosts): ReactNode;
|
|
87
|
+
|
|
88
|
+
/** Every `data-aeon-state` token a tree would project. */
|
|
89
|
+
declare function widgetStates(widget: Widget): string[];
|
|
90
|
+
/**
|
|
91
|
+
* States the chart names that the projection never puts on the tree.
|
|
92
|
+
* Totality (PHILOSOPHY.md §2): if the machine has the state, the face for
|
|
93
|
+
* that state must carry it. A missing name is a face the UI forgot.
|
|
94
|
+
*/
|
|
95
|
+
declare function uncoveredStates(states: readonly string[], project: (state: string) => Widget): string[];
|
|
96
|
+
|
|
97
|
+
export { EXCLUSIVE_ACTION_STATES, type ExclusiveActionInput, type ExclusiveActionState, type Widget, type WidgetHosts, exclusiveActionWidget, host, interpret, text, uncoveredStates, widgetStates };
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
// src/widget.ts
|
|
2
|
+
var text = (value) => ({ type: "text", text: value });
|
|
3
|
+
var host = (id) => ({ type: "host", id });
|
|
4
|
+
|
|
5
|
+
// src/exclusive-action.ts
|
|
6
|
+
var EXCLUSIVE_ACTION_STATES = ["idle", "confirming", "busy", "failure"];
|
|
7
|
+
function exclusiveActionWidget(input) {
|
|
8
|
+
const children = [input.body];
|
|
9
|
+
if (input.error) {
|
|
10
|
+
children.push({
|
|
11
|
+
type: "el",
|
|
12
|
+
tag: "p",
|
|
13
|
+
part: "error",
|
|
14
|
+
className: "error",
|
|
15
|
+
role: "alert",
|
|
16
|
+
children: [text(input.error)]
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
const actions = [
|
|
20
|
+
{
|
|
21
|
+
type: "el",
|
|
22
|
+
tag: "button",
|
|
23
|
+
part: "primary",
|
|
24
|
+
buttonType: input.tag === "div" ? "button" : "submit",
|
|
25
|
+
disabled: input.busy || input.primaryDisabled,
|
|
26
|
+
className: input.primaryClassName,
|
|
27
|
+
onClick: input.tag === "div" ? () => input.onSubmit?.({ preventDefault() {
|
|
28
|
+
} }) : void 0,
|
|
29
|
+
children: [text(input.busy ? input.pendingLabel : input.idleLabel)]
|
|
30
|
+
}
|
|
31
|
+
];
|
|
32
|
+
if (input.secondary) {
|
|
33
|
+
actions.push({
|
|
34
|
+
type: "el",
|
|
35
|
+
tag: "button",
|
|
36
|
+
part: "secondary",
|
|
37
|
+
buttonType: "button",
|
|
38
|
+
disabled: input.busy || input.secondary.disabled,
|
|
39
|
+
className: input.secondary.className,
|
|
40
|
+
onClick: input.secondary.onClick,
|
|
41
|
+
children: [text(input.secondary.label)]
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
children.push({
|
|
45
|
+
type: "el",
|
|
46
|
+
tag: "div",
|
|
47
|
+
part: "actions",
|
|
48
|
+
className: input.actionsClassName,
|
|
49
|
+
state: input.state,
|
|
50
|
+
children: actions
|
|
51
|
+
});
|
|
52
|
+
if (input.confirm) children.push(host("confirm"));
|
|
53
|
+
return {
|
|
54
|
+
type: "el",
|
|
55
|
+
tag: input.tag ?? "form",
|
|
56
|
+
scope: input.scope,
|
|
57
|
+
part: input.part,
|
|
58
|
+
state: input.state,
|
|
59
|
+
className: input.className,
|
|
60
|
+
onSubmit: input.tag === "div" ? void 0 : input.onSubmit,
|
|
61
|
+
children
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// src/interpret.tsx
|
|
66
|
+
import { partOnlyAttrs, scopeAttrs } from "@aeon-ui/core";
|
|
67
|
+
import { createElement, Fragment } from "react";
|
|
68
|
+
function interpret(widget, hosts) {
|
|
69
|
+
if (widget.type === "text") return widget.text;
|
|
70
|
+
if (widget.type === "host") return hosts[widget.id]?.() ?? null;
|
|
71
|
+
const attrs = widget.scope ? scopeAttrs(widget.scope, widget.part ?? "root", { state: widget.state }) : widget.part ? partOnlyAttrs(widget.part, { state: widget.state }) : widget.state ? { "data-aeon-state": widget.state } : {};
|
|
72
|
+
const props = {
|
|
73
|
+
...attrs,
|
|
74
|
+
className: widget.className,
|
|
75
|
+
role: widget.role,
|
|
76
|
+
disabled: widget.disabled || void 0,
|
|
77
|
+
type: widget.tag === "button" ? widget.buttonType ?? "button" : void 0,
|
|
78
|
+
onSubmit: widget.tag === "form" && widget.onSubmit ? (event) => widget.onSubmit?.(event) : void 0,
|
|
79
|
+
onClick: widget.onClick
|
|
80
|
+
};
|
|
81
|
+
return createElement(
|
|
82
|
+
widget.tag,
|
|
83
|
+
props,
|
|
84
|
+
...widget.children.map(
|
|
85
|
+
(child, index) => createElement(Fragment, { key: index }, interpret(child, hosts))
|
|
86
|
+
)
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// src/totality.ts
|
|
91
|
+
function widgetStates(widget) {
|
|
92
|
+
if (widget.type !== "el") return [];
|
|
93
|
+
const own = widget.state ? [widget.state] : [];
|
|
94
|
+
return [...own, ...widget.children.flatMap(widgetStates)];
|
|
95
|
+
}
|
|
96
|
+
function uncoveredStates(states, project) {
|
|
97
|
+
return states.filter((state) => !widgetStates(project(state)).includes(state));
|
|
98
|
+
}
|
|
99
|
+
export {
|
|
100
|
+
EXCLUSIVE_ACTION_STATES,
|
|
101
|
+
exclusiveActionWidget,
|
|
102
|
+
host,
|
|
103
|
+
interpret,
|
|
104
|
+
text,
|
|
105
|
+
uncoveredStates,
|
|
106
|
+
widgetStates
|
|
107
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
|
3
|
+
|
|
4
|
+
case `uname` in
|
|
5
|
+
*CYGWIN*|*MINGW*|*MSYS*)
|
|
6
|
+
if command -v cygpath > /dev/null 2>&1; then
|
|
7
|
+
basedir=`cygpath -w "$basedir"`
|
|
8
|
+
fi
|
|
9
|
+
;;
|
|
10
|
+
esac
|
|
11
|
+
|
|
12
|
+
if [ -z "$NODE_PATH" ]; then
|
|
13
|
+
export NODE_PATH="/Users/brandoncryderman/AeonUI/node_modules/.pnpm/esbuild@0.28.0/node_modules/esbuild/bin/node_modules:/Users/brandoncryderman/AeonUI/node_modules/.pnpm/esbuild@0.28.0/node_modules/esbuild/node_modules:/Users/brandoncryderman/AeonUI/node_modules/.pnpm/esbuild@0.28.0/node_modules:/Users/brandoncryderman/AeonUI/node_modules/.pnpm/node_modules"
|
|
14
|
+
else
|
|
15
|
+
export NODE_PATH="/Users/brandoncryderman/AeonUI/node_modules/.pnpm/esbuild@0.28.0/node_modules/esbuild/bin/node_modules:/Users/brandoncryderman/AeonUI/node_modules/.pnpm/esbuild@0.28.0/node_modules/esbuild/node_modules:/Users/brandoncryderman/AeonUI/node_modules/.pnpm/esbuild@0.28.0/node_modules:/Users/brandoncryderman/AeonUI/node_modules/.pnpm/node_modules:$NODE_PATH"
|
|
16
|
+
fi
|
|
17
|
+
if [ -x "$basedir/node" ]; then
|
|
18
|
+
exec "$basedir/node" "$basedir/../../../../node_modules/.pnpm/esbuild@0.28.0/node_modules/esbuild/bin/esbuild" "$@"
|
|
19
|
+
else
|
|
20
|
+
exec node "$basedir/../../../../node_modules/.pnpm/esbuild@0.28.0/node_modules/esbuild/bin/esbuild" "$@"
|
|
21
|
+
fi
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
|
3
|
+
|
|
4
|
+
case `uname` in
|
|
5
|
+
*CYGWIN*|*MINGW*|*MSYS*)
|
|
6
|
+
if command -v cygpath > /dev/null 2>&1; then
|
|
7
|
+
basedir=`cygpath -w "$basedir"`
|
|
8
|
+
fi
|
|
9
|
+
;;
|
|
10
|
+
esac
|
|
11
|
+
|
|
12
|
+
if [ -z "$NODE_PATH" ]; then
|
|
13
|
+
export NODE_PATH="/Users/brandoncryderman/AeonUI/node_modules/.pnpm/jiti@2.7.0/node_modules/jiti/lib/node_modules:/Users/brandoncryderman/AeonUI/node_modules/.pnpm/jiti@2.7.0/node_modules/jiti/node_modules:/Users/brandoncryderman/AeonUI/node_modules/.pnpm/jiti@2.7.0/node_modules:/Users/brandoncryderman/AeonUI/node_modules/.pnpm/node_modules"
|
|
14
|
+
else
|
|
15
|
+
export NODE_PATH="/Users/brandoncryderman/AeonUI/node_modules/.pnpm/jiti@2.7.0/node_modules/jiti/lib/node_modules:/Users/brandoncryderman/AeonUI/node_modules/.pnpm/jiti@2.7.0/node_modules/jiti/node_modules:/Users/brandoncryderman/AeonUI/node_modules/.pnpm/jiti@2.7.0/node_modules:/Users/brandoncryderman/AeonUI/node_modules/.pnpm/node_modules:$NODE_PATH"
|
|
16
|
+
fi
|
|
17
|
+
if [ -x "$basedir/node" ]; then
|
|
18
|
+
exec "$basedir/node" "$basedir/../../../../node_modules/.pnpm/jiti@2.7.0/node_modules/jiti/lib/jiti-cli.mjs" "$@"
|
|
19
|
+
else
|
|
20
|
+
exec node "$basedir/../../../../node_modules/.pnpm/jiti@2.7.0/node_modules/jiti/lib/jiti-cli.mjs" "$@"
|
|
21
|
+
fi
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
|
3
|
+
|
|
4
|
+
case `uname` in
|
|
5
|
+
*CYGWIN*|*MINGW*|*MSYS*)
|
|
6
|
+
if command -v cygpath > /dev/null 2>&1; then
|
|
7
|
+
basedir=`cygpath -w "$basedir"`
|
|
8
|
+
fi
|
|
9
|
+
;;
|
|
10
|
+
esac
|
|
11
|
+
|
|
12
|
+
if [ -z "$NODE_PATH" ]; then
|
|
13
|
+
export NODE_PATH="/Users/brandoncryderman/AeonUI/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/bin/node_modules:/Users/brandoncryderman/AeonUI/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/node_modules:/Users/brandoncryderman/AeonUI/node_modules/.pnpm/typescript@5.9.3/node_modules:/Users/brandoncryderman/AeonUI/node_modules/.pnpm/node_modules"
|
|
14
|
+
else
|
|
15
|
+
export NODE_PATH="/Users/brandoncryderman/AeonUI/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/bin/node_modules:/Users/brandoncryderman/AeonUI/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/node_modules:/Users/brandoncryderman/AeonUI/node_modules/.pnpm/typescript@5.9.3/node_modules:/Users/brandoncryderman/AeonUI/node_modules/.pnpm/node_modules:$NODE_PATH"
|
|
16
|
+
fi
|
|
17
|
+
if [ -x "$basedir/node" ]; then
|
|
18
|
+
exec "$basedir/node" "$basedir/../typescript/bin/tsc" "$@"
|
|
19
|
+
else
|
|
20
|
+
exec node "$basedir/../typescript/bin/tsc" "$@"
|
|
21
|
+
fi
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
|
3
|
+
|
|
4
|
+
case `uname` in
|
|
5
|
+
*CYGWIN*|*MINGW*|*MSYS*)
|
|
6
|
+
if command -v cygpath > /dev/null 2>&1; then
|
|
7
|
+
basedir=`cygpath -w "$basedir"`
|
|
8
|
+
fi
|
|
9
|
+
;;
|
|
10
|
+
esac
|
|
11
|
+
|
|
12
|
+
if [ -z "$NODE_PATH" ]; then
|
|
13
|
+
export NODE_PATH="/Users/brandoncryderman/AeonUI/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/bin/node_modules:/Users/brandoncryderman/AeonUI/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/node_modules:/Users/brandoncryderman/AeonUI/node_modules/.pnpm/typescript@5.9.3/node_modules:/Users/brandoncryderman/AeonUI/node_modules/.pnpm/node_modules"
|
|
14
|
+
else
|
|
15
|
+
export NODE_PATH="/Users/brandoncryderman/AeonUI/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/bin/node_modules:/Users/brandoncryderman/AeonUI/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/node_modules:/Users/brandoncryderman/AeonUI/node_modules/.pnpm/typescript@5.9.3/node_modules:/Users/brandoncryderman/AeonUI/node_modules/.pnpm/node_modules:$NODE_PATH"
|
|
16
|
+
fi
|
|
17
|
+
if [ -x "$basedir/node" ]; then
|
|
18
|
+
exec "$basedir/node" "$basedir/../typescript/bin/tsserver" "$@"
|
|
19
|
+
else
|
|
20
|
+
exec node "$basedir/../typescript/bin/tsserver" "$@"
|
|
21
|
+
fi
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
|
3
|
+
|
|
4
|
+
case `uname` in
|
|
5
|
+
*CYGWIN*|*MINGW*|*MSYS*)
|
|
6
|
+
if command -v cygpath > /dev/null 2>&1; then
|
|
7
|
+
basedir=`cygpath -w "$basedir"`
|
|
8
|
+
fi
|
|
9
|
+
;;
|
|
10
|
+
esac
|
|
11
|
+
|
|
12
|
+
if [ -z "$NODE_PATH" ]; then
|
|
13
|
+
export NODE_PATH="/Users/brandoncryderman/AeonUI/node_modules/.pnpm/tsup@8.5.1_jiti@2.7.0_postcss@8.5.15_tsx@4.22.3_typescript@5.9.3/node_modules/tsup/dist/node_modules:/Users/brandoncryderman/AeonUI/node_modules/.pnpm/tsup@8.5.1_jiti@2.7.0_postcss@8.5.15_tsx@4.22.3_typescript@5.9.3/node_modules/tsup/node_modules:/Users/brandoncryderman/AeonUI/node_modules/.pnpm/tsup@8.5.1_jiti@2.7.0_postcss@8.5.15_tsx@4.22.3_typescript@5.9.3/node_modules:/Users/brandoncryderman/AeonUI/node_modules/.pnpm/node_modules"
|
|
14
|
+
else
|
|
15
|
+
export NODE_PATH="/Users/brandoncryderman/AeonUI/node_modules/.pnpm/tsup@8.5.1_jiti@2.7.0_postcss@8.5.15_tsx@4.22.3_typescript@5.9.3/node_modules/tsup/dist/node_modules:/Users/brandoncryderman/AeonUI/node_modules/.pnpm/tsup@8.5.1_jiti@2.7.0_postcss@8.5.15_tsx@4.22.3_typescript@5.9.3/node_modules/tsup/node_modules:/Users/brandoncryderman/AeonUI/node_modules/.pnpm/tsup@8.5.1_jiti@2.7.0_postcss@8.5.15_tsx@4.22.3_typescript@5.9.3/node_modules:/Users/brandoncryderman/AeonUI/node_modules/.pnpm/node_modules:$NODE_PATH"
|
|
16
|
+
fi
|
|
17
|
+
if [ -x "$basedir/node" ]; then
|
|
18
|
+
exec "$basedir/node" "$basedir/../tsup/dist/cli-default.js" "$@"
|
|
19
|
+
else
|
|
20
|
+
exec node "$basedir/../tsup/dist/cli-default.js" "$@"
|
|
21
|
+
fi
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
|
3
|
+
|
|
4
|
+
case `uname` in
|
|
5
|
+
*CYGWIN*|*MINGW*|*MSYS*)
|
|
6
|
+
if command -v cygpath > /dev/null 2>&1; then
|
|
7
|
+
basedir=`cygpath -w "$basedir"`
|
|
8
|
+
fi
|
|
9
|
+
;;
|
|
10
|
+
esac
|
|
11
|
+
|
|
12
|
+
if [ -z "$NODE_PATH" ]; then
|
|
13
|
+
export NODE_PATH="/Users/brandoncryderman/AeonUI/node_modules/.pnpm/tsup@8.5.1_jiti@2.7.0_postcss@8.5.15_tsx@4.22.3_typescript@5.9.3/node_modules/tsup/dist/node_modules:/Users/brandoncryderman/AeonUI/node_modules/.pnpm/tsup@8.5.1_jiti@2.7.0_postcss@8.5.15_tsx@4.22.3_typescript@5.9.3/node_modules/tsup/node_modules:/Users/brandoncryderman/AeonUI/node_modules/.pnpm/tsup@8.5.1_jiti@2.7.0_postcss@8.5.15_tsx@4.22.3_typescript@5.9.3/node_modules:/Users/brandoncryderman/AeonUI/node_modules/.pnpm/node_modules"
|
|
14
|
+
else
|
|
15
|
+
export NODE_PATH="/Users/brandoncryderman/AeonUI/node_modules/.pnpm/tsup@8.5.1_jiti@2.7.0_postcss@8.5.15_tsx@4.22.3_typescript@5.9.3/node_modules/tsup/dist/node_modules:/Users/brandoncryderman/AeonUI/node_modules/.pnpm/tsup@8.5.1_jiti@2.7.0_postcss@8.5.15_tsx@4.22.3_typescript@5.9.3/node_modules/tsup/node_modules:/Users/brandoncryderman/AeonUI/node_modules/.pnpm/tsup@8.5.1_jiti@2.7.0_postcss@8.5.15_tsx@4.22.3_typescript@5.9.3/node_modules:/Users/brandoncryderman/AeonUI/node_modules/.pnpm/node_modules:$NODE_PATH"
|
|
16
|
+
fi
|
|
17
|
+
if [ -x "$basedir/node" ]; then
|
|
18
|
+
exec "$basedir/node" "$basedir/../tsup/dist/cli-node.js" "$@"
|
|
19
|
+
else
|
|
20
|
+
exec node "$basedir/../tsup/dist/cli-node.js" "$@"
|
|
21
|
+
fi
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
|
3
|
+
|
|
4
|
+
case `uname` in
|
|
5
|
+
*CYGWIN*|*MINGW*|*MSYS*)
|
|
6
|
+
if command -v cygpath > /dev/null 2>&1; then
|
|
7
|
+
basedir=`cygpath -w "$basedir"`
|
|
8
|
+
fi
|
|
9
|
+
;;
|
|
10
|
+
esac
|
|
11
|
+
|
|
12
|
+
if [ -z "$NODE_PATH" ]; then
|
|
13
|
+
export NODE_PATH="/Users/brandoncryderman/AeonUI/node_modules/.pnpm/tsx@4.22.3/node_modules/tsx/dist/node_modules:/Users/brandoncryderman/AeonUI/node_modules/.pnpm/tsx@4.22.3/node_modules/tsx/node_modules:/Users/brandoncryderman/AeonUI/node_modules/.pnpm/tsx@4.22.3/node_modules:/Users/brandoncryderman/AeonUI/node_modules/.pnpm/node_modules"
|
|
14
|
+
else
|
|
15
|
+
export NODE_PATH="/Users/brandoncryderman/AeonUI/node_modules/.pnpm/tsx@4.22.3/node_modules/tsx/dist/node_modules:/Users/brandoncryderman/AeonUI/node_modules/.pnpm/tsx@4.22.3/node_modules/tsx/node_modules:/Users/brandoncryderman/AeonUI/node_modules/.pnpm/tsx@4.22.3/node_modules:/Users/brandoncryderman/AeonUI/node_modules/.pnpm/node_modules:$NODE_PATH"
|
|
16
|
+
fi
|
|
17
|
+
if [ -x "$basedir/node" ]; then
|
|
18
|
+
exec "$basedir/node" "$basedir/../../../../node_modules/.pnpm/tsx@4.22.3/node_modules/tsx/dist/cli.mjs" "$@"
|
|
19
|
+
else
|
|
20
|
+
exec node "$basedir/../../../../node_modules/.pnpm/tsx@4.22.3/node_modules/tsx/dist/cli.mjs" "$@"
|
|
21
|
+
fi
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
|
3
|
+
|
|
4
|
+
case `uname` in
|
|
5
|
+
*CYGWIN*|*MINGW*|*MSYS*)
|
|
6
|
+
if command -v cygpath > /dev/null 2>&1; then
|
|
7
|
+
basedir=`cygpath -w "$basedir"`
|
|
8
|
+
fi
|
|
9
|
+
;;
|
|
10
|
+
esac
|
|
11
|
+
|
|
12
|
+
if [ -z "$NODE_PATH" ]; then
|
|
13
|
+
export NODE_PATH="/Users/brandoncryderman/AeonUI/node_modules/.pnpm/vite@8.0.16_@types+node@22.19.19_esbuild@0.28.0_jiti@2.7.0_tsx@4.22.3/node_modules/vite/bin/node_modules:/Users/brandoncryderman/AeonUI/node_modules/.pnpm/vite@8.0.16_@types+node@22.19.19_esbuild@0.28.0_jiti@2.7.0_tsx@4.22.3/node_modules/vite/node_modules:/Users/brandoncryderman/AeonUI/node_modules/.pnpm/vite@8.0.16_@types+node@22.19.19_esbuild@0.28.0_jiti@2.7.0_tsx@4.22.3/node_modules:/Users/brandoncryderman/AeonUI/node_modules/.pnpm/node_modules"
|
|
14
|
+
else
|
|
15
|
+
export NODE_PATH="/Users/brandoncryderman/AeonUI/node_modules/.pnpm/vite@8.0.16_@types+node@22.19.19_esbuild@0.28.0_jiti@2.7.0_tsx@4.22.3/node_modules/vite/bin/node_modules:/Users/brandoncryderman/AeonUI/node_modules/.pnpm/vite@8.0.16_@types+node@22.19.19_esbuild@0.28.0_jiti@2.7.0_tsx@4.22.3/node_modules/vite/node_modules:/Users/brandoncryderman/AeonUI/node_modules/.pnpm/vite@8.0.16_@types+node@22.19.19_esbuild@0.28.0_jiti@2.7.0_tsx@4.22.3/node_modules:/Users/brandoncryderman/AeonUI/node_modules/.pnpm/node_modules:$NODE_PATH"
|
|
16
|
+
fi
|
|
17
|
+
if [ -x "$basedir/node" ]; then
|
|
18
|
+
exec "$basedir/node" "$basedir/../../../../node_modules/.pnpm/vite@8.0.16_@types+node@22.19.19_esbuild@0.28.0_jiti@2.7.0_tsx@4.22.3/node_modules/vite/bin/vite.js" "$@"
|
|
19
|
+
else
|
|
20
|
+
exec node "$basedir/../../../../node_modules/.pnpm/vite@8.0.16_@types+node@22.19.19_esbuild@0.28.0_jiti@2.7.0_tsx@4.22.3/node_modules/vite/bin/vite.js" "$@"
|
|
21
|
+
fi
|