aeon-ui-engine 1.4.0 → 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 +139 -0
- package/CONSUMER.md +67 -27
- package/INTEGRATION.md +1 -1
- package/README.md +17 -8
- package/package.json +5 -2
- package/packages/cli/dist/bin.js +29 -3
- package/packages/cli/dist/{chunk-5L47SLNV.js → chunk-JMNRJCWF.js} +63 -9
- package/packages/cli/dist/index.d.ts +15 -1
- package/packages/cli/dist/index.js +3 -1
- package/packages/cli/node_modules/.bin/vitest +2 -2
- package/packages/cli/src/bin.ts +30 -2
- package/packages/cli/src/index.ts +1 -0
- package/packages/cli/src/init.ts +10 -4
- package/packages/cli/src/link.ts +61 -0
- 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/dist/{chunk-PB3LFHXU.js → chunk-VUPWYBGU.js} +14 -0
- package/packages/primitives/dist/index.js +1 -1
- package/packages/primitives/dist/machines/range-slider.d.ts +3 -3
- package/packages/primitives/dist/machines/slider.d.ts +22 -0
- package/packages/primitives/dist/machines/slider.js +1 -1
- package/packages/primitives/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
- package/packages/primitives/src/machines/slider.ts +17 -0
- package/packages/react/dist/index.d.ts +44 -5
- package/packages/react/dist/index.js +173 -48
- 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 +19 -1
- package/packages/react/src/hooks/use-anchor-position.ts +88 -17
- package/packages/react/src/menu/index.tsx +45 -6
- package/packages/react/src/popover/index.tsx +31 -4
- package/packages/react/src/slider/index.tsx +66 -19
- package/packages/react/src/tooltip/index.tsx +7 -1
- package/packages/schemas/dist/index.js +3 -1
- package/packages/schemas/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
- package/packages/schemas/prompts/cursorrules.md +3 -1
- package/packages/schemas/registry/machines/slider.json +18 -1
- package/packages/schemas/src/prompts.ts +3 -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/link-scoped-packages.mjs +206 -0
- package/scripts/vite-aliases.mjs +9 -8
|
@@ -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
|
|
@@ -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/vitest@4.1.8_@types+node@22.19.19_jsdom@29.1.1_vite@8.0.16_@types+node@22.19.19_esbuild@0.28.0_jiti@2.7.0_tsx@4.22.3_/node_modules/vitest/node_modules:/Users/brandoncryderman/AeonUI/node_modules/.pnpm/vitest@4.1.8_@types+node@22.19.19_jsdom@29.1.1_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/vitest@4.1.8_@types+node@22.19.19_jsdom@29.1.1_vite@8.0.16_@types+node@22.19.19_esbuild@0.28.0_jiti@2.7.0_tsx@4.22.3_/node_modules/vitest/node_modules:/Users/brandoncryderman/AeonUI/node_modules/.pnpm/vitest@4.1.8_@types+node@22.19.19_jsdom@29.1.1_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/../vitest/vitest.mjs" "$@"
|
|
19
|
+
else
|
|
20
|
+
exec node "$basedir/../vitest/vitest.mjs" "$@"
|
|
21
|
+
fi
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":"4.1.8","results":[[":src/exclusive-action.test.ts",{"duration":2.708500000000001,"failed":false}]]}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@aeon-ui/tree",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Widget trees: UI as data projected from a statechart snapshot. Flutter's Widget layer, Aeon's anatomy, no local state.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/GenericCPU/aeon-ui.git",
|
|
9
|
+
"directory": "packages/tree"
|
|
10
|
+
},
|
|
11
|
+
"publishConfig": { "access": "public" },
|
|
12
|
+
"files": ["dist", "README.md"],
|
|
13
|
+
"keywords": ["aeon-ui", "statechart", "widget", "xstate"],
|
|
14
|
+
"type": "module",
|
|
15
|
+
"sideEffects": false,
|
|
16
|
+
"main": "./dist/index.js",
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"import": "./dist/index.js"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "tsup src/index.ts --format esm --dts --clean --external react",
|
|
26
|
+
"typecheck": "tsc --noEmit",
|
|
27
|
+
"test": "vitest run"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@aeon-ui/core": "workspace:*"
|
|
31
|
+
},
|
|
32
|
+
"peerDependencies": {
|
|
33
|
+
"react": "^18 || ^19"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@types/react": "^19.1.2",
|
|
37
|
+
"react": "^19.1.0",
|
|
38
|
+
"tsup": "^8.4.0",
|
|
39
|
+
"typescript": "^5.8.3",
|
|
40
|
+
"vitest": "^4.1.8"
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { exclusiveActionWidget, EXCLUSIVE_ACTION_STATES } from './exclusive-action.js'
|
|
3
|
+
import { uncoveredStates, widgetStates } from './totality.js'
|
|
4
|
+
import { host } from './widget.js'
|
|
5
|
+
|
|
6
|
+
const project = (state: string) =>
|
|
7
|
+
exclusiveActionWidget({
|
|
8
|
+
scope: 'example',
|
|
9
|
+
part: 'form',
|
|
10
|
+
state,
|
|
11
|
+
busy: state === 'busy' || state === 'confirming',
|
|
12
|
+
error: state === 'failure' ? 'nope' : null,
|
|
13
|
+
body: host('body'),
|
|
14
|
+
idleLabel: 'Save',
|
|
15
|
+
pendingLabel: 'Saving…',
|
|
16
|
+
onSubmit: () => {},
|
|
17
|
+
confirm: state === 'confirming',
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
describe('exclusiveActionWidget', () => {
|
|
21
|
+
it('covers every state of the chart', () => {
|
|
22
|
+
expect(uncoveredStates(EXCLUSIVE_ACTION_STATES, project)).toEqual([])
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
it('shows the failure copy only on failure', () => {
|
|
26
|
+
expect(widgetStates(project('idle'))).not.toContain('failure')
|
|
27
|
+
const failure = project('failure')
|
|
28
|
+
const error = failure.type === 'el' && failure.children.find((c) => c.type === 'el' && c.part === 'error')
|
|
29
|
+
expect(error && error.type === 'el' && error.children[0]).toEqual({ type: 'text', text: 'nope' })
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it('mounts the confirm host only while confirming', () => {
|
|
33
|
+
const confirming = project('confirming')
|
|
34
|
+
const idle = project('idle')
|
|
35
|
+
const hasConfirm = (tree: ReturnType<typeof project>) =>
|
|
36
|
+
tree.type === 'el' && tree.children.some((c) => c.type === 'host' && c.id === 'confirm')
|
|
37
|
+
expect(hasConfirm(confirming)).toBe(true)
|
|
38
|
+
expect(hasConfirm(idle)).toBe(false)
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it('labels the primary from busy, and refuses a second click', () => {
|
|
42
|
+
const busy = project('busy')
|
|
43
|
+
const primary =
|
|
44
|
+
busy.type === 'el'
|
|
45
|
+
? busy.children
|
|
46
|
+
.flatMap((c) => (c.type === 'el' ? c.children : []))
|
|
47
|
+
.find((c) => c.type === 'el' && c.part === 'primary')
|
|
48
|
+
: undefined
|
|
49
|
+
expect(primary && primary.type === 'el' && primary.disabled).toBe(true)
|
|
50
|
+
expect(primary && primary.type === 'el' && primary.children).toEqual([
|
|
51
|
+
{ type: 'text', text: 'Saving…' },
|
|
52
|
+
])
|
|
53
|
+
})
|
|
54
|
+
})
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { host, text, type Widget } from './widget.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The chart every "click, maybe confirm, await, succeed or fail" region
|
|
5
|
+
* shares. One region, four faces — not a `busy` boolean beside the markup.
|
|
6
|
+
*/
|
|
7
|
+
export const EXCLUSIVE_ACTION_STATES = ['idle', 'confirming', 'busy', 'failure'] as const
|
|
8
|
+
|
|
9
|
+
export type ExclusiveActionState = (typeof EXCLUSIVE_ACTION_STATES)[number]
|
|
10
|
+
|
|
11
|
+
export type ExclusiveActionInput = {
|
|
12
|
+
scope: string
|
|
13
|
+
part: string
|
|
14
|
+
/** Active snapshot token, usually `stateToAttr(snapshot.value)`. */
|
|
15
|
+
state: string
|
|
16
|
+
busy: boolean
|
|
17
|
+
error: string | null
|
|
18
|
+
className?: string
|
|
19
|
+
tag?: 'form' | 'div'
|
|
20
|
+
/** Panel-specific fields. A host, so the tree stays generic. */
|
|
21
|
+
body: Widget
|
|
22
|
+
idleLabel: string
|
|
23
|
+
pendingLabel: string
|
|
24
|
+
primaryClassName?: string
|
|
25
|
+
primaryDisabled?: boolean
|
|
26
|
+
onSubmit?: (event: { preventDefault(): void }) => void
|
|
27
|
+
secondary?: {
|
|
28
|
+
label: string
|
|
29
|
+
className?: string
|
|
30
|
+
disabled?: boolean
|
|
31
|
+
onClick: () => void
|
|
32
|
+
}
|
|
33
|
+
actionsClassName?: string
|
|
34
|
+
/** When set, the `confirm` host is mounted. The app renders its Prompt there. */
|
|
35
|
+
confirm?: boolean
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* One widget for an exclusive mutation. The root carries the chart state;
|
|
40
|
+
* the primary label and the disabled set are functions of `busy`, and the
|
|
41
|
+
* failure copy is a child that exists only when `error` is set.
|
|
42
|
+
*/
|
|
43
|
+
export function exclusiveActionWidget(input: ExclusiveActionInput): Widget {
|
|
44
|
+
const children: Widget[] = [input.body]
|
|
45
|
+
|
|
46
|
+
if (input.error) {
|
|
47
|
+
children.push({
|
|
48
|
+
type: 'el',
|
|
49
|
+
tag: 'p',
|
|
50
|
+
part: 'error',
|
|
51
|
+
className: 'error',
|
|
52
|
+
role: 'alert',
|
|
53
|
+
children: [text(input.error)],
|
|
54
|
+
})
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const actions: Widget[] = [
|
|
58
|
+
{
|
|
59
|
+
type: 'el',
|
|
60
|
+
tag: 'button',
|
|
61
|
+
part: 'primary',
|
|
62
|
+
buttonType: input.tag === 'div' ? 'button' : 'submit',
|
|
63
|
+
disabled: input.busy || input.primaryDisabled,
|
|
64
|
+
className: input.primaryClassName,
|
|
65
|
+
onClick: input.tag === 'div' ? () => input.onSubmit?.({ preventDefault() {} }) : undefined,
|
|
66
|
+
children: [text(input.busy ? input.pendingLabel : input.idleLabel)],
|
|
67
|
+
},
|
|
68
|
+
]
|
|
69
|
+
|
|
70
|
+
if (input.secondary) {
|
|
71
|
+
actions.push({
|
|
72
|
+
type: 'el',
|
|
73
|
+
tag: 'button',
|
|
74
|
+
part: 'secondary',
|
|
75
|
+
buttonType: 'button',
|
|
76
|
+
disabled: input.busy || input.secondary.disabled,
|
|
77
|
+
className: input.secondary.className,
|
|
78
|
+
onClick: input.secondary.onClick,
|
|
79
|
+
children: [text(input.secondary.label)],
|
|
80
|
+
})
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
children.push({
|
|
84
|
+
type: 'el',
|
|
85
|
+
tag: 'div',
|
|
86
|
+
part: 'actions',
|
|
87
|
+
className: input.actionsClassName,
|
|
88
|
+
state: input.state,
|
|
89
|
+
children: actions,
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
if (input.confirm) children.push(host('confirm'))
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
type: 'el',
|
|
96
|
+
tag: input.tag ?? 'form',
|
|
97
|
+
scope: input.scope,
|
|
98
|
+
part: input.part,
|
|
99
|
+
state: input.state,
|
|
100
|
+
className: input.className,
|
|
101
|
+
onSubmit: input.tag === 'div' ? undefined : input.onSubmit,
|
|
102
|
+
children,
|
|
103
|
+
}
|
|
104
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { exclusiveActionWidget, EXCLUSIVE_ACTION_STATES } from './exclusive-action.js'
|
|
2
|
+
export type { ExclusiveActionInput, ExclusiveActionState } from './exclusive-action.js'
|
|
3
|
+
export { interpret } from './interpret.js'
|
|
4
|
+
export type { WidgetHosts } from './interpret.js'
|
|
5
|
+
export { uncoveredStates, widgetStates } from './totality.js'
|
|
6
|
+
export { host, text } from './widget.js'
|
|
7
|
+
export type { Widget } from './widget.js'
|