aeon-ui-engine 1.1.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/CONSUMER.md +45 -0
  3. package/package.json +1 -1
  4. package/packages/core/dist/index.d.ts +88 -8
  5. package/packages/core/dist/index.js +123 -0
  6. package/packages/core/src/anatomy.ts +38 -0
  7. package/packages/core/src/brand-theme.ts +183 -0
  8. package/packages/core/src/index.ts +1 -0
  9. package/packages/panda/aeon.css +1 -0
  10. package/packages/panda/src/theme/consumer-shell.css +250 -0
  11. package/packages/panda/src/theme/electron.css +114 -0
  12. package/packages/primitives/dist/chunk-4NOKRUSG.js +81 -0
  13. package/packages/primitives/dist/index.d.ts +1 -0
  14. package/packages/primitives/dist/index.js +16 -6
  15. package/packages/primitives/dist/machines/app-nav.d.ts +96 -0
  16. package/packages/primitives/dist/machines/app-nav.js +12 -0
  17. package/packages/primitives/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
  18. package/packages/primitives/src/__tests__/app-nav.test.ts +30 -0
  19. package/packages/primitives/src/index.ts +1 -0
  20. package/packages/primitives/src/machines/app-nav.ts +107 -0
  21. package/packages/react/dist/index.d.ts +135 -3
  22. package/packages/react/dist/index.js +1126 -720
  23. package/packages/react/src/app-nav/index.tsx +249 -0
  24. package/packages/react/src/avatar/index.tsx +4 -1
  25. package/packages/react/src/hooks/use-fit-text.ts +59 -0
  26. package/packages/react/src/index.ts +4 -0
  27. package/packages/react/src/prompt/index.tsx +161 -0
  28. package/packages/react/src/status-banner/index.tsx +84 -0
  29. package/packages/ui/src/app-nav.tsx +2 -0
  30. package/packages/ui/src/index.ts +4 -0
  31. package/packages/ui/src/prompt.tsx +29 -0
  32. package/packages/ui/src/status-banner.tsx +1 -0
  33. package/scripts/vite-aliases.mjs +8 -0
@@ -0,0 +1,250 @@
1
+ /**
2
+ * Consumer shell recipes — baseline look for Prompt, StatusBanner, AppNav,
3
+ * and headless Dialog chrome.
4
+ * Opt into brand color via applyBrandPalette(); these rules only use Aeon CSS vars.
5
+ * Intentionally restrained — products still own marketing atmosphere.
6
+ */
7
+
8
+ /* ── Headless Dialog (Prompt composes Dialog without recipe class names) ── */
9
+
10
+ [data-aeon-scope='dialog'][data-aeon-part='backdrop'] {
11
+ position: fixed;
12
+ inset: 0;
13
+ z-index: 1000;
14
+ background: color-mix(in srgb, #000 72%, transparent);
15
+ animation: aeon-shell-rise 200ms ease both;
16
+ }
17
+
18
+ [data-aeon-scope='dialog'][data-aeon-part='positioner'] {
19
+ position: fixed;
20
+ inset: 0;
21
+ z-index: 1001;
22
+ display: flex;
23
+ align-items: center;
24
+ justify-content: center;
25
+ padding: 16px;
26
+ overflow-y: auto;
27
+ overscroll-behavior: contain;
28
+ }
29
+
30
+ [data-aeon-scope='dialog'][data-aeon-part='content'] {
31
+ position: relative;
32
+ width: min(440px, 100%);
33
+ margin: auto;
34
+ flex-shrink: 0;
35
+ max-height: calc(100vh - 32px);
36
+ overflow-y: auto;
37
+ border-radius: calc(var(--aeon-radius, 0.5rem) + 4px);
38
+ border: 1px solid var(--colors-aeon-border);
39
+ background: var(--colors-aeon-surface, var(--colors-aeon-bg));
40
+ color: var(--colors-aeon-text);
41
+ box-shadow: 0 24px 64px rgba(0, 0, 0, 0.55);
42
+ padding: 20px 22px;
43
+ }
44
+
45
+ @keyframes aeon-shell-rise {
46
+ from {
47
+ opacity: 0;
48
+ transform: translateY(6px);
49
+ }
50
+ to {
51
+ opacity: 1;
52
+ transform: none;
53
+ }
54
+ }
55
+
56
+ /* ── Prompt ── */
57
+
58
+ [data-aeon-scope='prompt'][data-aeon-part='root'] {
59
+ display: contents;
60
+ }
61
+
62
+ [data-aeon-scope='dialog'][data-aeon-part='title'],
63
+ [data-aeon-scope='prompt'][data-aeon-part='title'] {
64
+ margin: 0;
65
+ font-family: var(--aeon-font-display, var(--fonts-display, inherit));
66
+ font-size: 1.05rem;
67
+ font-weight: 700;
68
+ letter-spacing: 0.06em;
69
+ text-transform: uppercase;
70
+ color: var(--colors-aeon-text);
71
+ line-height: 1.25;
72
+ }
73
+
74
+ [data-aeon-scope='prompt'][data-aeon-part='eyebrow'] {
75
+ margin: 0 0 8px;
76
+ color: var(--colors-aeon-accent);
77
+ font-family: var(--aeon-font-display, var(--fonts-display, inherit));
78
+ font-size: 0.72rem;
79
+ letter-spacing: 0.14em;
80
+ text-transform: uppercase;
81
+ }
82
+
83
+ [data-aeon-scope='prompt'][data-aeon-part='description'],
84
+ [data-aeon-scope='dialog'][data-aeon-part='description'] {
85
+ margin: 8px 0 0;
86
+ color: var(--colors-aeon-muted);
87
+ font-size: 0.9rem;
88
+ line-height: 1.45;
89
+ }
90
+
91
+ [data-aeon-scope='prompt'][data-aeon-part='meta'] {
92
+ display: grid;
93
+ gap: 10px;
94
+ margin: 16px 0;
95
+ padding: 14px 16px;
96
+ border-radius: var(--aeon-radius, 12px);
97
+ background: var(--colors-aeon-accent-dim);
98
+ }
99
+
100
+ [data-aeon-scope='prompt'][data-aeon-part='amount'] {
101
+ font-size: 1.35rem;
102
+ font-weight: 700;
103
+ color: var(--colors-aeon-text);
104
+ }
105
+
106
+ [data-aeon-scope='prompt'][data-aeon-part='actions'] {
107
+ display: flex;
108
+ justify-content: flex-end;
109
+ gap: 10px;
110
+ margin-top: 18px;
111
+ }
112
+
113
+ [data-aeon-scope='prompt'][data-aeon-part='primary'],
114
+ [data-aeon-scope='prompt'][data-aeon-part='secondary'] {
115
+ font: inherit;
116
+ cursor: pointer;
117
+ border-radius: var(--aeon-radius, 10px);
118
+ padding: 10px 14px;
119
+ border: 1px solid transparent;
120
+ letter-spacing: 0.02em;
121
+ text-transform: none;
122
+ }
123
+
124
+ [data-aeon-scope='prompt'][data-aeon-part='primary'] {
125
+ background: var(--colors-aeon-accent);
126
+ color: var(--colors-aeon-bg);
127
+ font-weight: 700;
128
+ }
129
+
130
+ [data-aeon-scope='prompt'][data-aeon-part='secondary'] {
131
+ background: transparent;
132
+ color: var(--colors-aeon-muted);
133
+ border-color: var(--colors-aeon-border);
134
+ }
135
+
136
+ /* Product `.btn` inside Prompt / StatusBanner — sentence case, not all-caps CTAs */
137
+ [data-aeon-scope='statusBanner'] button.btn,
138
+ [data-aeon-scope='prompt'] button.btn {
139
+ text-transform: none;
140
+ letter-spacing: 0.02em;
141
+ }
142
+
143
+ /* ── StatusBanner (sticky top chrome) ── */
144
+
145
+ [data-aeon-scope='statusBanner'][data-aeon-part='root'] {
146
+ position: sticky;
147
+ top: 0;
148
+ z-index: 40;
149
+ display: flex;
150
+ align-items: center;
151
+ justify-content: space-between;
152
+ gap: 12px;
153
+ padding: 10px 14px;
154
+ background: color-mix(in srgb, var(--colors-aeon-accent) 14%, var(--colors-aeon-bg));
155
+ border-bottom: 1px solid color-mix(in srgb, var(--colors-aeon-accent) 35%, transparent);
156
+ }
157
+
158
+ [data-aeon-scope='statusBanner'][data-aeon-part='copy'] {
159
+ display: grid;
160
+ gap: 2px;
161
+ min-width: 0;
162
+ }
163
+
164
+ [data-aeon-scope='statusBanner'][data-aeon-part='title'] {
165
+ font-size: 0.9rem;
166
+ color: var(--colors-aeon-text);
167
+ }
168
+
169
+ [data-aeon-scope='statusBanner'][data-aeon-part='body'] {
170
+ font-size: 0.78rem;
171
+ line-height: 1.35;
172
+ color: var(--colors-aeon-muted);
173
+ }
174
+
175
+ [data-aeon-scope='statusBanner'][data-aeon-part='actions'] {
176
+ flex-shrink: 0;
177
+ }
178
+
179
+ /* ── AppNav breadcrumb / back (dense 28px rail) ── */
180
+
181
+ [data-aeon-scope='appNav'][data-aeon-part='breadcrumb'] {
182
+ display: flex;
183
+ flex-wrap: nowrap;
184
+ align-items: center;
185
+ gap: 0;
186
+ min-width: 0;
187
+ overflow: hidden;
188
+ margin: 0;
189
+ height: 28px;
190
+ font-family: var(--aeon-font-display, var(--fonts-display, inherit));
191
+ font-size: 0.78rem;
192
+ font-weight: 700;
193
+ letter-spacing: 0.1em;
194
+ text-transform: uppercase;
195
+ line-height: 28px;
196
+ color: var(--colors-aeon-text);
197
+ }
198
+
199
+ [data-aeon-scope='appNav'][data-aeon-part='crumb'] {
200
+ font: inherit;
201
+ background: transparent;
202
+ border: 0;
203
+ color: var(--colors-aeon-muted);
204
+ cursor: pointer;
205
+ padding: 0;
206
+ height: 28px;
207
+ line-height: 28px;
208
+ letter-spacing: inherit;
209
+ text-transform: inherit;
210
+ }
211
+
212
+ [data-aeon-scope='appNav'][data-aeon-part='crumb']:hover {
213
+ color: var(--colors-aeon-accent);
214
+ }
215
+
216
+ [data-aeon-scope='appNav'][data-aeon-part='crumb'][data-aeon-state~='current'] {
217
+ color: var(--colors-aeon-text);
218
+ font-weight: 700;
219
+ cursor: default;
220
+ }
221
+
222
+ [data-aeon-scope='appNav'][data-aeon-part='crumb']:not(:last-child)::after {
223
+ content: '/';
224
+ margin: 0 0.45em;
225
+ color: var(--colors-aeon-muted);
226
+ font-weight: 500;
227
+ pointer-events: none;
228
+ }
229
+
230
+ [data-aeon-scope='appNav'][data-aeon-part='back'] {
231
+ appearance: none;
232
+ flex-shrink: 0;
233
+ width: 28px;
234
+ height: 28px;
235
+ margin: 0;
236
+ padding: 0;
237
+ border: 0;
238
+ border-radius: var(--aeon-radius, 0.5rem);
239
+ background: transparent;
240
+ color: var(--colors-aeon-muted);
241
+ display: inline-grid;
242
+ place-items: center;
243
+ cursor: pointer;
244
+ transition: color 140ms ease, background 140ms ease;
245
+ }
246
+
247
+ [data-aeon-scope='appNav'][data-aeon-part='back']:hover {
248
+ color: var(--colors-aeon-accent);
249
+ background: color-mix(in srgb, var(--colors-aeon-accent) 10%, transparent);
250
+ }
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Optional Electron chrome helpers.
3
+ * Import only in desktop shells: import '@aeon-ui/panda/electron.css'
4
+ *
5
+ * Defaults aligned with dense wallet / Electron fill-height layouts.
6
+ */
7
+
8
+ /* Compact scrollbars on Electron platforms */
9
+ html[data-aeon-platform],
10
+ html.platform-darwin {
11
+ --aeon-scrollbar-size: 3px;
12
+ --aeon-scrollbar-track: transparent;
13
+ --aeon-scrollbar-thumb: color-mix(in srgb, var(--colors-aeon-muted, #a1a1aa) 35%, transparent);
14
+ --aeon-scrollbar-thumb-hover: color-mix(in srgb, var(--colors-aeon-muted, #a1a1aa) 55%, transparent);
15
+ --aeon-scrollbar-corner: transparent;
16
+ }
17
+
18
+ html[data-aeon-platform],
19
+ html[data-aeon-platform] body,
20
+ html[data-aeon-platform] #root,
21
+ html.platform-darwin,
22
+ html.platform-darwin body,
23
+ html.platform-darwin #root {
24
+ height: 100%;
25
+ min-height: 100%;
26
+ }
27
+
28
+ html[data-aeon-platform] body,
29
+ html.platform-darwin body {
30
+ margin: 0;
31
+ overflow: hidden;
32
+ font-family: var(--aeon-font, var(--fonts-ui, system-ui, sans-serif));
33
+ color: var(--colors-aeon-text);
34
+ background: var(--colors-aeon-bg);
35
+ -webkit-font-smoothing: antialiased;
36
+ letter-spacing: 0.01em;
37
+ }
38
+
39
+ .aeon-app-shell,
40
+ .app-shell {
41
+ height: 100%;
42
+ display: grid;
43
+ grid-template-rows: auto 1fr;
44
+ min-height: 0;
45
+ }
46
+
47
+ .aeon-stage,
48
+ .stage {
49
+ height: 100%;
50
+ min-height: 0;
51
+ overflow: auto;
52
+ padding: 20px clamp(14px, 3vw, 28px) 28px;
53
+ }
54
+
55
+ @media (max-width: 640px) {
56
+ .aeon-stage,
57
+ .stage {
58
+ padding: 16px 12px 24px;
59
+ }
60
+ }
61
+
62
+ .aeon-titlebar,
63
+ .titlebar {
64
+ display: flex;
65
+ align-items: center;
66
+ justify-content: space-between;
67
+ padding: 12px 20px;
68
+ border-bottom: 1px solid var(--colors-aeon-border);
69
+ background: color-mix(in srgb, var(--colors-aeon-bg) 88%, transparent);
70
+ backdrop-filter: blur(12px);
71
+ -webkit-app-region: drag;
72
+ app-region: drag;
73
+ }
74
+
75
+ .aeon-titlebar button,
76
+ .aeon-titlebar a,
77
+ .aeon-titlebar input,
78
+ .aeon-titlebar [data-aeon-no-drag],
79
+ .titlebar button,
80
+ .titlebar a,
81
+ .titlebar input,
82
+ .titlebar [data-aeon-no-drag] {
83
+ -webkit-app-region: no-drag;
84
+ app-region: no-drag;
85
+ }
86
+
87
+ html.platform-darwin .aeon-titlebar,
88
+ html.platform-darwin .titlebar,
89
+ html[data-aeon-platform='darwin'] .aeon-titlebar,
90
+ html[data-aeon-platform='darwin'] .titlebar {
91
+ /* hiddenInset traffic lights */
92
+ padding-top: 36px;
93
+ }
94
+
95
+ /* Hover-reveal scrollbars for dense nav stages */
96
+ .aeon-scroll-hover,
97
+ [data-aeon-scroll='hover'] {
98
+ scrollbar-color: transparent transparent;
99
+ }
100
+
101
+ .aeon-scroll-hover::-webkit-scrollbar-thumb,
102
+ [data-aeon-scroll='hover']::-webkit-scrollbar-thumb {
103
+ background: transparent;
104
+ }
105
+
106
+ .aeon-scroll-hover:hover,
107
+ [data-aeon-scroll='hover']:hover {
108
+ scrollbar-color: var(--aeon-scrollbar-thumb) transparent;
109
+ }
110
+
111
+ .aeon-scroll-hover:hover::-webkit-scrollbar-thumb,
112
+ [data-aeon-scroll='hover']:hover::-webkit-scrollbar-thumb {
113
+ background: var(--aeon-scrollbar-thumb);
114
+ }
@@ -0,0 +1,81 @@
1
+ // src/machines/app-nav.ts
2
+ import { assign, setup } from "xstate";
3
+ function appNavDepth(ctx) {
4
+ return ctx.stack.length;
5
+ }
6
+ function appNavChild(ctx) {
7
+ return ctx.stack.length ? ctx.stack[ctx.stack.length - 1] : null;
8
+ }
9
+ function appNavStateAttr(ctx) {
10
+ const child = appNavChild(ctx);
11
+ return child ? `${ctx.section}/${child.type}` : ctx.section;
12
+ }
13
+ var appNavMachine = setup({
14
+ types: {
15
+ context: {},
16
+ events: {},
17
+ input: {}
18
+ },
19
+ actions: {
20
+ setSection: assign({
21
+ section: ({ event }) => event.type === "SET_SECTION" ? event.section : "",
22
+ stack: []
23
+ }),
24
+ push: assign({
25
+ stack: ({ context, event }) => event.type === "PUSH" ? [...context.stack, event.child] : context.stack
26
+ }),
27
+ replace: assign({
28
+ stack: ({ context, event }) => {
29
+ if (event.type !== "REPLACE") return context.stack;
30
+ if (!context.stack.length) return [event.child];
31
+ return [...context.stack.slice(0, -1), event.child];
32
+ }
33
+ }),
34
+ pop: assign({
35
+ stack: ({ context }) => context.stack.slice(0, -1)
36
+ }),
37
+ popTo: assign({
38
+ stack: ({ context, event }) => {
39
+ if (event.type !== "POP_TO") return context.stack;
40
+ const idx = context.stack.findIndex((c) => c.id === event.id);
41
+ if (idx < 0) return context.stack;
42
+ return context.stack.slice(0, idx + 1);
43
+ }
44
+ }),
45
+ clear: assign({ stack: [] }),
46
+ reset: assign({
47
+ section: ({ event }) => event.type === "RESET" ? event.section : "",
48
+ stack: []
49
+ })
50
+ },
51
+ guards: {
52
+ canPop: ({ context }) => context.stack.length > 0
53
+ }
54
+ }).createMachine({
55
+ id: "appNav",
56
+ context: ({ input }) => ({
57
+ section: input?.section ?? "home",
58
+ stack: input?.stack ?? []
59
+ }),
60
+ initial: "ready",
61
+ states: {
62
+ ready: {
63
+ on: {
64
+ SET_SECTION: { actions: "setSection" },
65
+ PUSH: { actions: "push" },
66
+ REPLACE: { actions: "replace" },
67
+ POP: { guard: "canPop", actions: "pop" },
68
+ POP_TO: { actions: "popTo" },
69
+ CLEAR: { actions: "clear" },
70
+ RESET: { actions: "reset" }
71
+ }
72
+ }
73
+ }
74
+ });
75
+
76
+ export {
77
+ appNavDepth,
78
+ appNavChild,
79
+ appNavStateAttr,
80
+ appNavMachine
81
+ };
@@ -10,6 +10,7 @@ export { PopoverContext, PopoverEvent, popoverMachine } from './machines/popover
10
10
  export { ToastContext, ToastEvent, toastMachine } from './machines/toast.js';
11
11
  export { SliderContext, SliderEvent, clampSliderValue, sliderMachine, snapSliderValue } from './machines/slider.js';
12
12
  export { APP_SHELL_STATES, AppShellContext, AppShellEvent, AppShellState, appShellMachine } from './machines/app-shell.js';
13
+ export { AppNavChild, AppNavContext, AppNavEvent, appNavChild, appNavDepth, appNavMachine, appNavStateAttr } from './machines/app-nav.js';
13
14
  export { STICKY_BAR_STATES, StickyBarContext, StickyBarEvent, StickyBarState, stickyBarMachine } from './machines/sticky-bar.js';
14
15
  export { CONTENT_REGION_STATES, ContentRegionContext, ContentRegionEvent, ContentRegionState, contentRegionMachine } from './machines/content-region.js';
15
16
  import 'xstate';
@@ -1,3 +1,9 @@
1
+ import {
2
+ FIELD_INTERACTION_STATES,
3
+ FIELD_SUBMISSION_STATES,
4
+ FIELD_VALIDATION_STATES,
5
+ fieldMachine
6
+ } from "./chunk-ZMGRX64G.js";
1
7
  import {
2
8
  panelMachine
3
9
  } from "./chunk-V5D7LS4J.js";
@@ -25,6 +31,12 @@ import {
25
31
  import {
26
32
  accordionMachine
27
33
  } from "./chunk-JBAG5E6W.js";
34
+ import {
35
+ appNavChild,
36
+ appNavDepth,
37
+ appNavMachine,
38
+ appNavStateAttr
39
+ } from "./chunk-4NOKRUSG.js";
28
40
  import {
29
41
  APP_SHELL_STATES,
30
42
  appShellMachine
@@ -46,12 +58,6 @@ import {
46
58
  import {
47
59
  dialogMachine
48
60
  } from "./chunk-SZV6KGXN.js";
49
- import {
50
- FIELD_INTERACTION_STATES,
51
- FIELD_SUBMISSION_STATES,
52
- FIELD_VALIDATION_STATES,
53
- fieldMachine
54
- } from "./chunk-ZMGRX64G.js";
55
61
  export {
56
62
  APP_SHELL_STATES,
57
63
  ASYNC_LIFECYCLE_STATES,
@@ -62,6 +68,10 @@ export {
62
68
  FIELD_VALIDATION_STATES,
63
69
  STICKY_BAR_STATES,
64
70
  accordionMachine,
71
+ appNavChild,
72
+ appNavDepth,
73
+ appNavMachine,
74
+ appNavStateAttr,
65
75
  appShellMachine,
66
76
  asyncMachine,
67
77
  buttonLifecycleMachine,
@@ -0,0 +1,96 @@
1
+ import * as xstate from 'xstate';
2
+
3
+ /**
4
+ * Section + detail stack navigation — the pattern every multi-panel product
5
+ * reinvented (wallet sections, settings drill-in, list→detail).
6
+ *
7
+ * Not product-specific: `section` is a string id; `stack` is opaque child records.
8
+ */
9
+ type AppNavChild = {
10
+ id: string;
11
+ /** Free-form type tag (e.g. "detail", "compose", "settings"). */
12
+ type: string;
13
+ /** Optional payload for the view layer. */
14
+ data?: unknown;
15
+ };
16
+ interface AppNavContext {
17
+ section: string;
18
+ stack: AppNavChild[];
19
+ }
20
+ type AppNavEvent = {
21
+ type: 'SET_SECTION';
22
+ section: string;
23
+ } | {
24
+ type: 'PUSH';
25
+ child: AppNavChild;
26
+ } | {
27
+ type: 'REPLACE';
28
+ child: AppNavChild;
29
+ } | {
30
+ type: 'POP';
31
+ } | {
32
+ type: 'POP_TO';
33
+ id: string;
34
+ } | {
35
+ type: 'CLEAR';
36
+ } | {
37
+ type: 'RESET';
38
+ section: string;
39
+ };
40
+ declare function appNavDepth(ctx: AppNavContext): number;
41
+ declare function appNavChild(ctx: AppNavContext): AppNavChild | null;
42
+ /** Projected state for data-aeon-state: `section` or `section/childType`. */
43
+ declare function appNavStateAttr(ctx: AppNavContext): string;
44
+ declare const appNavMachine: xstate.StateMachine<AppNavContext, {
45
+ type: "SET_SECTION";
46
+ section: string;
47
+ } | {
48
+ type: "PUSH";
49
+ child: AppNavChild;
50
+ } | {
51
+ type: "REPLACE";
52
+ child: AppNavChild;
53
+ } | {
54
+ type: "POP";
55
+ } | {
56
+ type: "POP_TO";
57
+ id: string;
58
+ } | {
59
+ type: "CLEAR";
60
+ } | {
61
+ type: "RESET";
62
+ section: string;
63
+ }, {}, never, {
64
+ type: "replace";
65
+ params: xstate.NonReducibleUnknown;
66
+ } | {
67
+ type: "pop";
68
+ params: xstate.NonReducibleUnknown;
69
+ } | {
70
+ type: "push";
71
+ params: xstate.NonReducibleUnknown;
72
+ } | {
73
+ type: "clear";
74
+ params: xstate.NonReducibleUnknown;
75
+ } | {
76
+ type: "setSection";
77
+ params: xstate.NonReducibleUnknown;
78
+ } | {
79
+ type: "popTo";
80
+ params: xstate.NonReducibleUnknown;
81
+ } | {
82
+ type: "reset";
83
+ params: xstate.NonReducibleUnknown;
84
+ }, {
85
+ type: "canPop";
86
+ params: unknown;
87
+ }, never, "ready", string, Partial<AppNavContext> & {
88
+ section?: string;
89
+ }, xstate.NonReducibleUnknown, xstate.EventObject, xstate.MetaObject, {
90
+ id: "appNav";
91
+ states: {
92
+ readonly ready: {};
93
+ };
94
+ }>;
95
+
96
+ export { type AppNavChild, type AppNavContext, type AppNavEvent, appNavChild, appNavDepth, appNavMachine, appNavStateAttr };
@@ -0,0 +1,12 @@
1
+ import {
2
+ appNavChild,
3
+ appNavDepth,
4
+ appNavMachine,
5
+ appNavStateAttr
6
+ } from "../chunk-4NOKRUSG.js";
7
+ export {
8
+ appNavChild,
9
+ appNavDepth,
10
+ appNavMachine,
11
+ appNavStateAttr
12
+ };
@@ -1 +1 @@
1
- {"version":"3.2.4","results":[[":src/machines/dialog.test.ts",{"duration":5.771292000000017,"failed":false}],[":src/machines/select.test.ts",{"duration":6.394416000000035,"failed":false}],[":src/__tests__/dialog.test.ts",{"duration":3.686000000000007,"failed":false}],[":src/__tests__/select.test.ts",{"duration":2.8540000000000134,"failed":false}],[":src/__tests__/floating-surface.test.ts",{"duration":4.650791999999996,"failed":false}]]}
1
+ {"version":"3.2.4","results":[[":src/machines/dialog.test.ts",{"duration":5.771292000000017,"failed":false}],[":src/machines/select.test.ts",{"duration":6.394416000000035,"failed":false}],[":src/__tests__/dialog.test.ts",{"duration":3.686000000000007,"failed":false}],[":src/__tests__/select.test.ts",{"duration":2.8540000000000134,"failed":false}],[":src/__tests__/floating-surface.test.ts",{"duration":6.529208000000011,"failed":false}],[":src/__tests__/app-nav.test.ts",{"duration":7.017959000000019,"failed":false}]]}
@@ -0,0 +1,30 @@
1
+ import { createActor } from 'xstate'
2
+ import { describe, expect, it } from 'vitest'
3
+ import {
4
+ appNavChild,
5
+ appNavMachine,
6
+ appNavStateAttr,
7
+ } from '../machines/app-nav.js'
8
+
9
+ describe('appNavMachine', () => {
10
+ it('switches section and clears stack', () => {
11
+ const actor = createActor(appNavMachine, { input: { section: 'home' } }).start()
12
+ actor.send({ type: 'PUSH', child: { id: '1', type: 'detail' } })
13
+ expect(appNavChild(actor.getSnapshot().context)?.id).toBe('1')
14
+ actor.send({ type: 'SET_SECTION', section: 'settings' })
15
+ const ctx = actor.getSnapshot().context
16
+ expect(ctx.section).toBe('settings')
17
+ expect(ctx.stack).toEqual([])
18
+ })
19
+
20
+ it('push / pop / popTo maintain stack', () => {
21
+ const actor = createActor(appNavMachine, { input: { section: 'apps' } }).start()
22
+ actor.send({ type: 'PUSH', child: { id: 'a', type: 'app' } })
23
+ actor.send({ type: 'PUSH', child: { id: 'p', type: 'permission' } })
24
+ expect(appNavStateAttr(actor.getSnapshot().context)).toBe('apps/permission')
25
+ actor.send({ type: 'POP_TO', id: 'a' })
26
+ expect(appNavChild(actor.getSnapshot().context)?.id).toBe('a')
27
+ actor.send({ type: 'POP' })
28
+ expect(appNavChild(actor.getSnapshot().context)).toBeNull()
29
+ })
30
+ })
@@ -10,5 +10,6 @@ export * from './machines/popover.js'
10
10
  export * from './machines/toast.js'
11
11
  export * from './machines/slider.js'
12
12
  export * from './machines/app-shell.js'
13
+ export * from './machines/app-nav.js'
13
14
  export * from './machines/sticky-bar.js'
14
15
  export * from './machines/content-region.js'