@zag-js/core 1.35.0 → 1.35.1

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/README.md CHANGED
@@ -102,6 +102,45 @@ const machine = createMachine({
102
102
  // service.state.matches("dialog.open") === true when nested state is active
103
103
  ```
104
104
 
105
+ ### State IDs and `#id` targets
106
+
107
+ Use `id` on a state node when you want to target it explicitly from anywhere in the machine.
108
+
109
+ ```ts
110
+ import { createMachine } from "@zag-js/core"
111
+
112
+ const machine = createMachine({
113
+ initialState() {
114
+ return "dialog"
115
+ },
116
+ states: {
117
+ dialog: {
118
+ initial: "open",
119
+ states: {
120
+ focused: {
121
+ id: "dialogFocused",
122
+ },
123
+ open: {
124
+ initial: "idle",
125
+ states: {
126
+ idle: {
127
+ on: {
128
+ CLOSE: { target: "#dialogFocused" },
129
+ },
130
+ },
131
+ },
132
+ },
133
+ },
134
+ },
135
+ },
136
+ })
137
+ ```
138
+
139
+ Notes:
140
+
141
+ - `#id` targets resolve by state node `id` (XState-style).
142
+ - State IDs must be unique within a machine.
143
+
105
144
  ## API
106
145
 
107
146
  ### `createMachine(config, options)`
package/dist/state.js CHANGED
@@ -31,20 +31,36 @@ __export(state_exports, {
31
31
  });
32
32
  module.exports = __toCommonJS(state_exports);
33
33
  var STATE_DELIMITER = ".";
34
+ var ABSOLUTE_PREFIX = "#";
34
35
  var stateIndexCache = /* @__PURE__ */ new WeakMap();
36
+ var stateIdIndexCache = /* @__PURE__ */ new WeakMap();
35
37
  function joinStatePath(parts) {
36
38
  return parts.join(STATE_DELIMITER);
37
39
  }
38
40
  function isAbsoluteStatePath(value) {
39
41
  return value.includes(STATE_DELIMITER);
40
42
  }
43
+ function isExplicitAbsoluteStatePath(value) {
44
+ return value.startsWith(ABSOLUTE_PREFIX);
45
+ }
46
+ function stripAbsolutePrefix(value) {
47
+ return isExplicitAbsoluteStatePath(value) ? value.slice(ABSOLUTE_PREFIX.length) : value;
48
+ }
41
49
  function appendStatePath(base, segment) {
42
50
  return base ? `${base}${STATE_DELIMITER}${segment}` : segment;
43
51
  }
44
52
  function buildStateIndex(machine) {
45
53
  const index = /* @__PURE__ */ new Map();
54
+ const idIndex = /* @__PURE__ */ new Map();
46
55
  const visit = (basePath, state) => {
47
56
  index.set(basePath, state);
57
+ const stateId = state.id;
58
+ if (stateId) {
59
+ if (idIndex.has(stateId)) {
60
+ throw new Error(`Duplicate state id: ${stateId}`);
61
+ }
62
+ idIndex.set(stateId, basePath);
63
+ }
48
64
  const childStates = state.states;
49
65
  if (!childStates) return;
50
66
  for (const [childKey, childState] of Object.entries(childStates)) {
@@ -57,15 +73,20 @@ function buildStateIndex(machine) {
57
73
  if (!topState) continue;
58
74
  visit(topKey, topState);
59
75
  }
60
- return index;
76
+ return { index, idIndex };
61
77
  }
62
78
  function ensureStateIndex(machine) {
63
79
  const cached = stateIndexCache.get(machine);
64
80
  if (cached) return cached;
65
- const index = buildStateIndex(machine);
81
+ const { index, idIndex } = buildStateIndex(machine);
66
82
  stateIndexCache.set(machine, index);
83
+ stateIdIndexCache.set(machine, idIndex);
67
84
  return index;
68
85
  }
86
+ function getStatePathById(machine, stateId) {
87
+ ensureStateIndex(machine);
88
+ return stateIdIndexCache.get(machine)?.get(stateId);
89
+ }
69
90
  function toSegments(value) {
70
91
  if (!value) return [];
71
92
  return String(value).split(STATE_DELIMITER).filter(Boolean);
@@ -112,6 +133,14 @@ function hasStatePath(machine, value) {
112
133
  }
113
134
  function resolveStateValue(machine, value, source) {
114
135
  const stateValue = String(value);
136
+ if (isExplicitAbsoluteStatePath(stateValue)) {
137
+ const stateId = stripAbsolutePrefix(stateValue);
138
+ const statePath = getStatePathById(machine, stateId);
139
+ if (!statePath) {
140
+ throw new Error(`Unknown state id: ${stateId}`);
141
+ }
142
+ return resolveAbsoluteStateValue(machine, statePath);
143
+ }
115
144
  if (!isAbsoluteStatePath(stateValue) && source) {
116
145
  const sourceSegments = toSegments(source);
117
146
  for (let index = sourceSegments.length; index >= 1; index--) {
package/dist/state.mjs CHANGED
@@ -1,19 +1,35 @@
1
1
  // src/state.ts
2
2
  var STATE_DELIMITER = ".";
3
+ var ABSOLUTE_PREFIX = "#";
3
4
  var stateIndexCache = /* @__PURE__ */ new WeakMap();
5
+ var stateIdIndexCache = /* @__PURE__ */ new WeakMap();
4
6
  function joinStatePath(parts) {
5
7
  return parts.join(STATE_DELIMITER);
6
8
  }
7
9
  function isAbsoluteStatePath(value) {
8
10
  return value.includes(STATE_DELIMITER);
9
11
  }
12
+ function isExplicitAbsoluteStatePath(value) {
13
+ return value.startsWith(ABSOLUTE_PREFIX);
14
+ }
15
+ function stripAbsolutePrefix(value) {
16
+ return isExplicitAbsoluteStatePath(value) ? value.slice(ABSOLUTE_PREFIX.length) : value;
17
+ }
10
18
  function appendStatePath(base, segment) {
11
19
  return base ? `${base}${STATE_DELIMITER}${segment}` : segment;
12
20
  }
13
21
  function buildStateIndex(machine) {
14
22
  const index = /* @__PURE__ */ new Map();
23
+ const idIndex = /* @__PURE__ */ new Map();
15
24
  const visit = (basePath, state) => {
16
25
  index.set(basePath, state);
26
+ const stateId = state.id;
27
+ if (stateId) {
28
+ if (idIndex.has(stateId)) {
29
+ throw new Error(`Duplicate state id: ${stateId}`);
30
+ }
31
+ idIndex.set(stateId, basePath);
32
+ }
17
33
  const childStates = state.states;
18
34
  if (!childStates) return;
19
35
  for (const [childKey, childState] of Object.entries(childStates)) {
@@ -26,15 +42,20 @@ function buildStateIndex(machine) {
26
42
  if (!topState) continue;
27
43
  visit(topKey, topState);
28
44
  }
29
- return index;
45
+ return { index, idIndex };
30
46
  }
31
47
  function ensureStateIndex(machine) {
32
48
  const cached = stateIndexCache.get(machine);
33
49
  if (cached) return cached;
34
- const index = buildStateIndex(machine);
50
+ const { index, idIndex } = buildStateIndex(machine);
35
51
  stateIndexCache.set(machine, index);
52
+ stateIdIndexCache.set(machine, idIndex);
36
53
  return index;
37
54
  }
55
+ function getStatePathById(machine, stateId) {
56
+ ensureStateIndex(machine);
57
+ return stateIdIndexCache.get(machine)?.get(stateId);
58
+ }
38
59
  function toSegments(value) {
39
60
  if (!value) return [];
40
61
  return String(value).split(STATE_DELIMITER).filter(Boolean);
@@ -81,6 +102,14 @@ function hasStatePath(machine, value) {
81
102
  }
82
103
  function resolveStateValue(machine, value, source) {
83
104
  const stateValue = String(value);
105
+ if (isExplicitAbsoluteStatePath(stateValue)) {
106
+ const stateId = stripAbsolutePrefix(stateValue);
107
+ const statePath = getStatePathById(machine, stateId);
108
+ if (!statePath) {
109
+ throw new Error(`Unknown state id: ${stateId}`);
110
+ }
111
+ return resolveAbsoluteStateValue(machine, statePath);
112
+ }
84
113
  if (!isAbsoluteStatePath(stateValue) && source) {
85
114
  const sourceSegments = toSegments(source);
86
115
  for (let index = sourceSegments.length; index >= 1; index--) {
package/dist/types.d.mts CHANGED
@@ -109,8 +109,9 @@ type ChildStateKey<S extends string, Parent extends string> = S extends `${Paren
109
109
  type ParentPath<S extends string> = S extends `${infer Parent}.${string}` ? Parent : never;
110
110
  type AncestorPaths<S extends string> = S | (ParentPath<S> extends never ? never : AncestorPaths<ParentPath<S>>);
111
111
  type RelativeStateTarget<S extends string, Source extends string> = ChildStateKey<S, AncestorPaths<Source>>;
112
+ type StateIdTarget = `#${string}`;
112
113
  interface Transition<T extends Dict, Source extends string | undefined = string | undefined> {
113
- target?: T["state"] | (Source extends string ? RelativeStateTarget<T["state"], Source> : never) | undefined;
114
+ target?: T["state"] | StateIdTarget | (Source extends string ? RelativeStateTarget<T["state"], Source> : never) | undefined;
114
115
  actions?: T["action"][] | undefined;
115
116
  guard?: T["guard"] | GuardFn<T> | undefined;
116
117
  reenter?: boolean | undefined;
@@ -134,6 +135,7 @@ interface RefsParams<T extends Dict> {
134
135
  type ActionsOrFn<T extends Dict> = T["action"][] | ((params: Params<T>) => T["action"][] | undefined);
135
136
  type EffectsOrFn<T extends Dict> = T["effect"][] | ((params: Params<T>) => T["effect"][] | undefined);
136
137
  interface MachineState<T extends Dict, Parent extends string = string> {
138
+ id?: string | undefined;
137
139
  tags?: T["tag"][] | undefined;
138
140
  entry?: ActionsOrFn<T> | undefined;
139
141
  exit?: ActionsOrFn<T> | undefined;
package/dist/types.d.ts CHANGED
@@ -109,8 +109,9 @@ type ChildStateKey<S extends string, Parent extends string> = S extends `${Paren
109
109
  type ParentPath<S extends string> = S extends `${infer Parent}.${string}` ? Parent : never;
110
110
  type AncestorPaths<S extends string> = S | (ParentPath<S> extends never ? never : AncestorPaths<ParentPath<S>>);
111
111
  type RelativeStateTarget<S extends string, Source extends string> = ChildStateKey<S, AncestorPaths<Source>>;
112
+ type StateIdTarget = `#${string}`;
112
113
  interface Transition<T extends Dict, Source extends string | undefined = string | undefined> {
113
- target?: T["state"] | (Source extends string ? RelativeStateTarget<T["state"], Source> : never) | undefined;
114
+ target?: T["state"] | StateIdTarget | (Source extends string ? RelativeStateTarget<T["state"], Source> : never) | undefined;
114
115
  actions?: T["action"][] | undefined;
115
116
  guard?: T["guard"] | GuardFn<T> | undefined;
116
117
  reenter?: boolean | undefined;
@@ -134,6 +135,7 @@ interface RefsParams<T extends Dict> {
134
135
  type ActionsOrFn<T extends Dict> = T["action"][] | ((params: Params<T>) => T["action"][] | undefined);
135
136
  type EffectsOrFn<T extends Dict> = T["effect"][] | ((params: Params<T>) => T["effect"][] | undefined);
136
137
  interface MachineState<T extends Dict, Parent extends string = string> {
138
+ id?: string | undefined;
137
139
  tags?: T["tag"][] | undefined;
138
140
  entry?: ActionsOrFn<T> | undefined;
139
141
  exit?: ActionsOrFn<T> | undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zag-js/core",
3
- "version": "1.35.0",
3
+ "version": "1.35.1",
4
4
  "description": "A minimal implementation of xstate fsm for UI machines",
5
5
  "keywords": [
6
6
  "ui-machines",
@@ -25,8 +25,8 @@
25
25
  "url": "https://github.com/chakra-ui/zag/issues"
26
26
  },
27
27
  "dependencies": {
28
- "@zag-js/utils": "1.35.0",
29
- "@zag-js/dom-query": "1.35.0"
28
+ "@zag-js/utils": "1.35.1",
29
+ "@zag-js/dom-query": "1.35.1"
30
30
  },
31
31
  "devDependencies": {
32
32
  "clean-package": "2.2.0"