@zag-js/tooltip 2.0.0-next.0 → 2.0.0-next.2

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/dist/index.d.mts CHANGED
@@ -2,7 +2,7 @@ export { anatomy } from './tooltip.anatomy.mjs';
2
2
  export { connect } from './tooltip.connect.mjs';
3
3
  export { machine } from './tooltip.machine.mjs';
4
4
  export { props, splitProps } from './tooltip.props.mjs';
5
- export { TooltipApi as Api, ElementIds, TooltipMachine as Machine, OpenChangeDetails, TooltipProps as Props, TooltipService as Service, TriggerProps, TriggerValueChangeDetails } from './tooltip.types.mjs';
5
+ export { TooltipApi as Api, ContentState, ElementIds, TooltipMachine as Machine, OpenChangeDetails, TooltipProps as Props, TooltipService as Service, TriggerProps, TriggerState, TriggerValueChangeDetails } from './tooltip.types.mjs';
6
6
  export { Placement, PositioningOptions } from '@zag-js/popper';
7
7
  import '@zag-js/anatomy';
8
8
  import '@zag-js/core';
package/dist/index.d.ts CHANGED
@@ -2,7 +2,7 @@ export { anatomy } from './tooltip.anatomy.js';
2
2
  export { connect } from './tooltip.connect.js';
3
3
  export { machine } from './tooltip.machine.js';
4
4
  export { props, splitProps } from './tooltip.props.js';
5
- export { TooltipApi as Api, ElementIds, TooltipMachine as Machine, OpenChangeDetails, TooltipProps as Props, TooltipService as Service, TriggerProps, TriggerValueChangeDetails } from './tooltip.types.js';
5
+ export { TooltipApi as Api, ContentState, ElementIds, TooltipMachine as Machine, OpenChangeDetails, TooltipProps as Props, TooltipService as Service, TriggerProps, TriggerState, TriggerValueChangeDetails } from './tooltip.types.js';
6
6
  export { Placement, PositioningOptions } from '@zag-js/popper';
7
7
  import '@zag-js/anatomy';
8
8
  import '@zag-js/core';
@@ -51,26 +51,37 @@ function connect(service, normalize) {
51
51
  const disabled = prop("disabled");
52
52
  const popperStyles = (0, import_popper.getPlacementStyles)({
53
53
  ...prop("positioning"),
54
- placement: currentPlacement,
55
- positioned: context.get("positioned")
54
+ placement: currentPlacement
56
55
  });
56
+ function getTriggerState(props = {}) {
57
+ const { value } = props;
58
+ const current = value == null ? false : triggerValue === value;
59
+ return { value, current, open: value == null ? open : open && current };
60
+ }
61
+ function getContentState() {
62
+ const isCurrentTooltip = import_tooltip2.store.get("id") === id;
63
+ const isPrevTooltip = import_tooltip2.store.get("prevId") === id;
64
+ const instant = !!import_tooltip2.store.get("instant") && (open && isCurrentTooltip || isPrevTooltip);
65
+ return { open, instant, placement: currentPlacement, side: currentPlacementSide };
66
+ }
57
67
  return {
58
68
  open,
59
69
  setOpen(nextOpen) {
60
70
  const open2 = state.matches("open", "closing");
61
71
  if (open2 === nextOpen) return;
62
- send({ type: nextOpen ? "open" : "close" });
72
+ send({ type: nextOpen ? "OPEN" : "CLOSE" });
63
73
  },
64
74
  triggerValue,
65
75
  setTriggerValue(value) {
66
- send({ type: "triggerValue.set", value: value ?? void 0 });
76
+ send({ type: "TRIGGER_VALUE.SET", value: value ?? void 0 });
67
77
  },
68
78
  reposition(options = {}) {
69
- send({ type: "positioning.set", options });
79
+ send({ type: "POSITIONING.SET", options });
70
80
  },
81
+ getTriggerState,
71
82
  getTriggerProps(props = {}) {
72
83
  const { value } = props;
73
- const current = value == null ? false : triggerValue === value;
84
+ const { current } = getTriggerState(props);
74
85
  const triggerId = dom.getTriggerId(scope, value);
75
86
  return normalize.button({
76
87
  ...import_tooltip.parts.trigger.attrs(scope.id),
@@ -86,14 +97,14 @@ function connect(service, normalize) {
86
97
  if (disabled) return;
87
98
  if (!prop("closeOnClick")) return;
88
99
  const shouldSwitch = open && value != null && !current;
89
- send({ type: shouldSwitch ? "triggerValue.set" : "close", src: "trigger.click", value, triggerId });
100
+ send({ type: shouldSwitch ? "TRIGGER_VALUE.SET" : "CLOSE", src: "trigger.click", value, triggerId });
90
101
  },
91
102
  onFocus(event) {
92
103
  if (event.defaultPrevented) return;
93
104
  if (disabled) return;
94
105
  if (!(0, import_focus_visible.isFocusVisible)()) return;
95
106
  const shouldSwitch = open && value != null && !current;
96
- send({ type: shouldSwitch ? "triggerValue.set" : "open", src: "trigger.focus", value, triggerId });
107
+ send({ type: shouldSwitch ? "TRIGGER_VALUE.SET" : "OPEN", src: "trigger.focus", value, triggerId });
97
108
  },
98
109
  onBlur(event) {
99
110
  if (event.defaultPrevented) return;
@@ -102,7 +113,7 @@ function connect(service, normalize) {
102
113
  const activeEl = event.relatedTarget ?? scope.getDoc().activeElement;
103
114
  const focusedAnotherTrigger = activeEl?.closest(`[${import_tooltip.parts.trigger.attr}="${scope.id}"]`) != null;
104
115
  if (!focusedAnotherTrigger) {
105
- send({ type: "close", src: "trigger.blur", value, triggerId });
116
+ send({ type: "CLOSE", src: "trigger.blur", value, triggerId });
106
117
  }
107
118
  },
108
119
  onPointerDown(event) {
@@ -111,7 +122,7 @@ function connect(service, normalize) {
111
122
  if (!(0, import_dom_query.isLeftClick)(event)) return;
112
123
  if (!prop("closeOnPointerDown")) return;
113
124
  if (id === import_tooltip2.store.get("id")) {
114
- send({ type: "close", src: "trigger.pointerdown", value, triggerId });
125
+ send({ type: "CLOSE", src: "trigger.pointerdown", value, triggerId });
115
126
  }
116
127
  },
117
128
  onPointerMove(event) {
@@ -119,21 +130,21 @@ function connect(service, normalize) {
119
130
  if (disabled) return;
120
131
  if (event.pointerType === "touch") return;
121
132
  const shouldSwitch = open && value != null && !current;
122
- send({ type: shouldSwitch ? "triggerValue.set" : "pointer.move", value, triggerId });
133
+ send({ type: shouldSwitch ? "TRIGGER_VALUE.SET" : "POINTER_MOVE", value, triggerId });
123
134
  },
124
135
  onPointerOver(event) {
125
136
  if (event.defaultPrevented) return;
126
137
  if (disabled) return;
127
138
  if (event.pointerType === "touch") return;
128
- send({ type: "pointer.move", value, triggerId });
139
+ send({ type: "POINTER_MOVE", value, triggerId });
129
140
  },
130
141
  onPointerLeave() {
131
142
  if (disabled) return;
132
- send({ type: "pointer.leave" });
143
+ send({ type: "POINTER_LEAVE" });
133
144
  },
134
145
  onPointerCancel() {
135
146
  if (disabled) return;
136
- send({ type: "pointer.leave" });
147
+ send({ type: "POINTER_LEAVE" });
137
148
  }
138
149
  });
139
150
  },
@@ -158,25 +169,21 @@ function connect(service, normalize) {
158
169
  style: popperStyles.floating
159
170
  });
160
171
  },
172
+ getContentState,
161
173
  getContentProps() {
162
- const isCurrentTooltip = import_tooltip2.store.get("id") === id;
163
- const isPrevTooltip = import_tooltip2.store.get("prevId") === id;
164
- const instant = import_tooltip2.store.get("instant") && (open && isCurrentTooltip || isPrevTooltip);
174
+ const contentState = getContentState();
165
175
  return normalize.element({
166
176
  ...import_tooltip.parts.content.attrs(scope.id),
167
177
  dir: prop("dir"),
168
- hidden: !open,
169
- "data-state": open ? "open" : "closed",
170
- "data-instant": (0, import_dom_query.dataAttr)(instant),
178
+ hidden: !contentState.open,
179
+ "data-state": contentState.open ? "open" : "closed",
180
+ "data-instant": (0, import_dom_query.dataAttr)(contentState.instant),
171
181
  role: hasAriaLabel ? void 0 : "tooltip",
172
182
  id: hasAriaLabel ? void 0 : contentId,
173
- "data-placement": currentPlacement,
174
- "data-side": currentPlacementSide,
183
+ "data-placement": contentState.placement,
184
+ "data-side": contentState.side,
175
185
  onPointerEnter() {
176
- send({ type: "content.pointer.move" });
177
- },
178
- onPointerLeave() {
179
- send({ type: "content.pointer.leave" });
186
+ send({ type: "CONTENT_POINTER_MOVE" });
180
187
  },
181
188
  style: {
182
189
  pointerEvents: prop("interactive") ? "auto" : "none"
@@ -17,26 +17,37 @@ function connect(service, normalize) {
17
17
  const disabled = prop("disabled");
18
18
  const popperStyles = getPlacementStyles({
19
19
  ...prop("positioning"),
20
- placement: currentPlacement,
21
- positioned: context.get("positioned")
20
+ placement: currentPlacement
22
21
  });
22
+ function getTriggerState(props = {}) {
23
+ const { value } = props;
24
+ const current = value == null ? false : triggerValue === value;
25
+ return { value, current, open: value == null ? open : open && current };
26
+ }
27
+ function getContentState() {
28
+ const isCurrentTooltip = store.get("id") === id;
29
+ const isPrevTooltip = store.get("prevId") === id;
30
+ const instant = !!store.get("instant") && (open && isCurrentTooltip || isPrevTooltip);
31
+ return { open, instant, placement: currentPlacement, side: currentPlacementSide };
32
+ }
23
33
  return {
24
34
  open,
25
35
  setOpen(nextOpen) {
26
36
  const open2 = state.matches("open", "closing");
27
37
  if (open2 === nextOpen) return;
28
- send({ type: nextOpen ? "open" : "close" });
38
+ send({ type: nextOpen ? "OPEN" : "CLOSE" });
29
39
  },
30
40
  triggerValue,
31
41
  setTriggerValue(value) {
32
- send({ type: "triggerValue.set", value: value ?? void 0 });
42
+ send({ type: "TRIGGER_VALUE.SET", value: value ?? void 0 });
33
43
  },
34
44
  reposition(options = {}) {
35
- send({ type: "positioning.set", options });
45
+ send({ type: "POSITIONING.SET", options });
36
46
  },
47
+ getTriggerState,
37
48
  getTriggerProps(props = {}) {
38
49
  const { value } = props;
39
- const current = value == null ? false : triggerValue === value;
50
+ const { current } = getTriggerState(props);
40
51
  const triggerId = dom.getTriggerId(scope, value);
41
52
  return normalize.button({
42
53
  ...parts.trigger.attrs(scope.id),
@@ -52,14 +63,14 @@ function connect(service, normalize) {
52
63
  if (disabled) return;
53
64
  if (!prop("closeOnClick")) return;
54
65
  const shouldSwitch = open && value != null && !current;
55
- send({ type: shouldSwitch ? "triggerValue.set" : "close", src: "trigger.click", value, triggerId });
66
+ send({ type: shouldSwitch ? "TRIGGER_VALUE.SET" : "CLOSE", src: "trigger.click", value, triggerId });
56
67
  },
57
68
  onFocus(event) {
58
69
  if (event.defaultPrevented) return;
59
70
  if (disabled) return;
60
71
  if (!isFocusVisible()) return;
61
72
  const shouldSwitch = open && value != null && !current;
62
- send({ type: shouldSwitch ? "triggerValue.set" : "open", src: "trigger.focus", value, triggerId });
73
+ send({ type: shouldSwitch ? "TRIGGER_VALUE.SET" : "OPEN", src: "trigger.focus", value, triggerId });
63
74
  },
64
75
  onBlur(event) {
65
76
  if (event.defaultPrevented) return;
@@ -68,7 +79,7 @@ function connect(service, normalize) {
68
79
  const activeEl = event.relatedTarget ?? scope.getDoc().activeElement;
69
80
  const focusedAnotherTrigger = activeEl?.closest(`[${parts.trigger.attr}="${scope.id}"]`) != null;
70
81
  if (!focusedAnotherTrigger) {
71
- send({ type: "close", src: "trigger.blur", value, triggerId });
82
+ send({ type: "CLOSE", src: "trigger.blur", value, triggerId });
72
83
  }
73
84
  },
74
85
  onPointerDown(event) {
@@ -77,7 +88,7 @@ function connect(service, normalize) {
77
88
  if (!isLeftClick(event)) return;
78
89
  if (!prop("closeOnPointerDown")) return;
79
90
  if (id === store.get("id")) {
80
- send({ type: "close", src: "trigger.pointerdown", value, triggerId });
91
+ send({ type: "CLOSE", src: "trigger.pointerdown", value, triggerId });
81
92
  }
82
93
  },
83
94
  onPointerMove(event) {
@@ -85,21 +96,21 @@ function connect(service, normalize) {
85
96
  if (disabled) return;
86
97
  if (event.pointerType === "touch") return;
87
98
  const shouldSwitch = open && value != null && !current;
88
- send({ type: shouldSwitch ? "triggerValue.set" : "pointer.move", value, triggerId });
99
+ send({ type: shouldSwitch ? "TRIGGER_VALUE.SET" : "POINTER_MOVE", value, triggerId });
89
100
  },
90
101
  onPointerOver(event) {
91
102
  if (event.defaultPrevented) return;
92
103
  if (disabled) return;
93
104
  if (event.pointerType === "touch") return;
94
- send({ type: "pointer.move", value, triggerId });
105
+ send({ type: "POINTER_MOVE", value, triggerId });
95
106
  },
96
107
  onPointerLeave() {
97
108
  if (disabled) return;
98
- send({ type: "pointer.leave" });
109
+ send({ type: "POINTER_LEAVE" });
99
110
  },
100
111
  onPointerCancel() {
101
112
  if (disabled) return;
102
- send({ type: "pointer.leave" });
113
+ send({ type: "POINTER_LEAVE" });
103
114
  }
104
115
  });
105
116
  },
@@ -124,25 +135,21 @@ function connect(service, normalize) {
124
135
  style: popperStyles.floating
125
136
  });
126
137
  },
138
+ getContentState,
127
139
  getContentProps() {
128
- const isCurrentTooltip = store.get("id") === id;
129
- const isPrevTooltip = store.get("prevId") === id;
130
- const instant = store.get("instant") && (open && isCurrentTooltip || isPrevTooltip);
140
+ const contentState = getContentState();
131
141
  return normalize.element({
132
142
  ...parts.content.attrs(scope.id),
133
143
  dir: prop("dir"),
134
- hidden: !open,
135
- "data-state": open ? "open" : "closed",
136
- "data-instant": dataAttr(instant),
144
+ hidden: !contentState.open,
145
+ "data-state": contentState.open ? "open" : "closed",
146
+ "data-instant": dataAttr(contentState.instant),
137
147
  role: hasAriaLabel ? void 0 : "tooltip",
138
148
  id: hasAriaLabel ? void 0 : contentId,
139
- "data-placement": currentPlacement,
140
- "data-side": currentPlacementSide,
149
+ "data-placement": contentState.placement,
150
+ "data-side": contentState.side,
141
151
  onPointerEnter() {
142
- send({ type: "content.pointer.move" });
143
- },
144
- onPointerLeave() {
145
- send({ type: "content.pointer.leave" });
152
+ send({ type: "CONTENT_POINTER_MOVE" });
146
153
  },
147
154
  style: {
148
155
  pointerEvents: prop("interactive") ? "auto" : "none"
@@ -37,6 +37,7 @@ var import_core = require("@zag-js/core");
37
37
  var import_dom_query = require("@zag-js/dom-query");
38
38
  var import_focus_visible = require("@zag-js/focus-visible");
39
39
  var import_popper = require("@zag-js/popper");
40
+ var import_safe_area = require("@zag-js/safe-area");
40
41
  var import_utils = require("@zag-js/utils");
41
42
  var dom = __toESM(require("./tooltip.dom.js"));
42
43
  var import_tooltip = require("./tooltip.store.js");
@@ -70,6 +71,7 @@ var machine = (0, import_core.createMachine)({
70
71
  context: ({ bindable, prop, scope }) => ({
71
72
  currentPlacement: bindable(() => ({ defaultValue: void 0 })),
72
73
  hasPointerMoveOpened: bindable(() => ({ defaultValue: null })),
74
+ isPointer: bindable(() => ({ defaultValue: false })),
73
75
  triggerValue: bindable(() => ({
74
76
  defaultValue: prop("defaultTriggerValue") ?? null,
75
77
  value: prop("triggerValue"),
@@ -79,8 +81,7 @@ var machine = (0, import_core.createMachine)({
79
81
  const triggerElement = dom.getActiveTriggerEl(scope, value);
80
82
  onTriggerValueChange({ value, triggerElement });
81
83
  }
82
- })),
83
- positioned: bindable(() => ({ defaultValue: false }))
84
+ }))
84
85
  }),
85
86
  watch({ track, action, prop }) {
86
87
  track([() => prop("disabled")], () => {
@@ -94,18 +95,18 @@ var machine = (0, import_core.createMachine)({
94
95
  });
95
96
  },
96
97
  on: {
97
- "triggerValue.set": {
98
+ "TRIGGER_VALUE.SET": {
98
99
  actions: ["setTriggerValue", "repositionImmediate"]
99
100
  }
100
101
  },
101
102
  states: {
102
103
  closed: {
103
- entry: ["clearGlobalId"],
104
+ entry: ["clearGlobalId", "clearIsPointer"],
104
105
  on: {
105
- "controlled.open": {
106
+ "CONTROLLED.OPEN": {
106
107
  target: "open"
107
108
  },
108
- open: [
109
+ OPEN: [
109
110
  {
110
111
  guard: "isOpenControlled",
111
112
  actions: ["setTriggerValue", "invokeOnOpen"]
@@ -115,10 +116,10 @@ var machine = (0, import_core.createMachine)({
115
116
  actions: ["setTriggerValue", "invokeOnOpen"]
116
117
  }
117
118
  ],
118
- "pointer.leave": {
119
+ POINTER_LEAVE: {
119
120
  actions: ["clearPointerMoveOpened"]
120
121
  },
121
- "pointer.move": [
122
+ POINTER_MOVE: [
122
123
  {
123
124
  guard: and("noVisibleTooltip", not("hasPointerMoveOpened")),
124
125
  target: "opening",
@@ -127,7 +128,7 @@ var machine = (0, import_core.createMachine)({
127
128
  {
128
129
  guard: not("hasPointerMoveOpened"),
129
130
  target: "open",
130
- actions: ["setPointerMoveOpened", "invokeOnOpen", "setTriggerValue"]
131
+ actions: ["setPointerMoveOpened", "setIsPointer", "invokeOnOpen", "setTriggerValue"]
131
132
  }
132
133
  ]
133
134
  }
@@ -135,23 +136,23 @@ var machine = (0, import_core.createMachine)({
135
136
  opening: {
136
137
  effects: ["trackScroll", "trackPointerlockChange", "waitForOpenDelay"],
137
138
  on: {
138
- "after.openDelay": [
139
+ OPEN_DELAY: [
139
140
  {
140
141
  guard: "isOpenControlled",
141
- actions: ["setPointerMoveOpened", "invokeOnOpen"]
142
+ actions: ["setPointerMoveOpened", "setIsPointer", "invokeOnOpen"]
142
143
  },
143
144
  {
144
145
  target: "open",
145
- actions: ["setPointerMoveOpened", "invokeOnOpen"]
146
+ actions: ["setPointerMoveOpened", "setIsPointer", "invokeOnOpen"]
146
147
  }
147
148
  ],
148
- "controlled.open": {
149
+ "CONTROLLED.OPEN": {
149
150
  target: "open"
150
151
  },
151
- "controlled.close": {
152
+ "CONTROLLED.CLOSE": {
152
153
  target: "closed"
153
154
  },
154
- open: [
155
+ OPEN: [
155
156
  {
156
157
  guard: "isOpenControlled",
157
158
  actions: ["setTriggerValue", "invokeOnOpen"]
@@ -161,7 +162,7 @@ var machine = (0, import_core.createMachine)({
161
162
  actions: ["setTriggerValue", "invokeOnOpen"]
162
163
  }
163
164
  ],
164
- "pointer.leave": [
165
+ POINTER_LEAVE: [
165
166
  {
166
167
  guard: "isOpenControlled",
167
168
  // We trigger toggleVisibility manually since the `ctx.open` has not changed yet (at this point)
@@ -172,7 +173,7 @@ var machine = (0, import_core.createMachine)({
172
173
  actions: ["clearPointerMoveOpened", "invokeOnClose"]
173
174
  }
174
175
  ],
175
- close: [
176
+ CLOSE: [
176
177
  {
177
178
  guard: "isOpenControlled",
178
179
  // We trigger toggleVisibility manually since the `ctx.open` has not changed yet (at this point)
@@ -186,13 +187,13 @@ var machine = (0, import_core.createMachine)({
186
187
  }
187
188
  },
188
189
  open: {
189
- effects: ["trackEscapeKey", "trackScroll", "trackPointerlockChange", "trackPositioning"],
190
+ effects: ["trackEscapeKey", "trackScroll", "trackPointerlockChange", "trackPositioning", "trackSafeArea"],
190
191
  entry: ["setGlobalId"],
191
192
  on: {
192
- "controlled.close": {
193
+ "CONTROLLED.CLOSE": {
193
194
  target: "closed"
194
195
  },
195
- close: [
196
+ CLOSE: [
196
197
  {
197
198
  guard: "isOpenControlled",
198
199
  actions: ["invokeOnClose"]
@@ -202,30 +203,34 @@ var machine = (0, import_core.createMachine)({
202
203
  actions: ["invokeOnClose"]
203
204
  }
204
205
  ],
205
- "pointer.leave": [
206
+ // Hoverable content means the pointer may be travelling to it (WCAG 1.4.13), so the
207
+ // safe area decides when it has really left.
208
+ POINTER_LEAVE: [
206
209
  {
207
- guard: "isVisible",
210
+ guard: and(not("isInteractive"), "isVisible"),
208
211
  target: "closing",
209
212
  actions: ["clearPointerMoveOpened"]
210
213
  },
211
214
  // == group ==
212
215
  {
213
- guard: "isOpenControlled",
216
+ guard: and(not("isInteractive"), "isOpenControlled"),
214
217
  actions: ["clearPointerMoveOpened", "invokeOnClose"]
215
218
  },
216
219
  {
220
+ guard: not("isInteractive"),
217
221
  target: "closed",
218
222
  actions: ["clearPointerMoveOpened", "invokeOnClose"]
219
223
  }
220
224
  ],
221
- "content.pointer.leave": {
222
- guard: "isInteractive",
223
- target: "closing"
225
+ "SAFE_AREA.EXIT": {
226
+ target: "closing",
227
+ // Same bookkeeping the `POINTER_LEAVE` path does, or `closed` refuses the next hover.
228
+ actions: ["clearPointerMoveOpened"]
224
229
  },
225
- "positioning.set": {
230
+ "POSITIONING.SET": {
226
231
  actions: ["reposition"]
227
232
  },
228
- "triggerValue.set": {
233
+ "TRIGGER_VALUE.SET": {
229
234
  // Transition to closing (which cleans up trackPositioning) then immediately back to open
230
235
  // This re-creates the positioning effect with the new trigger
231
236
  target: "closing",
@@ -236,7 +241,7 @@ var machine = (0, import_core.createMachine)({
236
241
  closing: {
237
242
  effects: ["trackPositioning", "waitForCloseDelay"],
238
243
  on: {
239
- "after.closeDelay": [
244
+ CLOSE_DELAY: [
240
245
  {
241
246
  guard: "isOpenControlled",
242
247
  actions: ["invokeOnClose"]
@@ -246,13 +251,13 @@ var machine = (0, import_core.createMachine)({
246
251
  actions: ["invokeOnClose"]
247
252
  }
248
253
  ],
249
- "controlled.close": {
254
+ "CONTROLLED.CLOSE": {
250
255
  target: "closed"
251
256
  },
252
- "controlled.open": {
257
+ "CONTROLLED.OPEN": {
253
258
  target: "open"
254
259
  },
255
- close: [
260
+ CLOSE: [
256
261
  {
257
262
  guard: "isOpenControlled",
258
263
  actions: ["invokeOnClose"]
@@ -262,29 +267,29 @@ var machine = (0, import_core.createMachine)({
262
267
  actions: ["invokeOnClose"]
263
268
  }
264
269
  ],
265
- "pointer.move": [
270
+ POINTER_MOVE: [
266
271
  {
267
272
  guard: "isOpenControlled",
268
273
  // We trigger toggleVisibility manually since the `ctx.open` has not changed yet (at this point)
269
- actions: ["setPointerMoveOpened", "setTriggerValue", "invokeOnOpen", "toggleVisibility"]
274
+ actions: ["setPointerMoveOpened", "setIsPointer", "setTriggerValue", "invokeOnOpen", "toggleVisibility"]
270
275
  },
271
276
  {
272
277
  target: "open",
273
- actions: ["setPointerMoveOpened", "setTriggerValue", "invokeOnOpen"]
278
+ actions: ["setPointerMoveOpened", "setIsPointer", "setTriggerValue", "invokeOnOpen"]
274
279
  }
275
280
  ],
276
- "triggerValue.set": {
281
+ "TRIGGER_VALUE.SET": {
277
282
  target: "open",
278
283
  actions: ["setTriggerValue", "repositionImmediate"]
279
284
  },
280
- reopen: {
285
+ REOPEN: {
281
286
  target: "open"
282
287
  },
283
- "content.pointer.move": {
288
+ CONTENT_POINTER_MOVE: {
284
289
  guard: "isInteractive",
285
290
  target: "open"
286
291
  },
287
- "positioning.set": {
292
+ "POSITIONING.SET": {
288
293
  actions: ["reposition"]
289
294
  }
290
295
  }
@@ -299,10 +304,10 @@ var machine = (0, import_core.createMachine)({
299
304
  isOpenControlled: ({ prop }) => prop("open") !== void 0
300
305
  },
301
306
  actions: {
302
- setGlobalId: ({ prop }) => {
307
+ setGlobalId: ({ prop, event }) => {
303
308
  const prevId = import_tooltip.store.get("id");
304
- const isInstant = prevId !== null && prevId !== prop("id");
305
- import_tooltip.store.update({ id: prop("id"), prevId: isInstant ? prevId : null, instant: isInstant });
309
+ const isInstant = event.src === "trigger.focus" || prevId !== null && prevId !== prop("id");
310
+ import_tooltip.store.update({ id: prop("id"), prevId: isInstant && prevId !== null ? prevId : null, instant: isInstant });
306
311
  },
307
312
  clearGlobalId: ({ prop }) => {
308
313
  if (prop("id") === import_tooltip.store.get("id")) {
@@ -317,10 +322,10 @@ var machine = (0, import_core.createMachine)({
317
322
  },
318
323
  closeIfDisabled: ({ prop, send }) => {
319
324
  if (!prop("disabled")) return;
320
- send({ type: "close", src: "disabled.change" });
325
+ send({ type: "CLOSE", src: "disabled.change" });
321
326
  },
322
327
  reposition: ({ context, event, prop, scope }) => {
323
- if (event.type !== "positioning.set") return;
328
+ if (event.type !== "POSITIONING.SET") return;
324
329
  const getPositionerEl2 = () => dom.getPositionerEl(scope);
325
330
  const getTriggerEl = () => dom.getActiveTriggerEl(scope, context.get("triggerValue"));
326
331
  (0, import_popper.getPlacement)(getTriggerEl, getPositionerEl2, {
@@ -346,7 +351,7 @@ var machine = (0, import_core.createMachine)({
346
351
  toggleVisibility: ({ prop, event, send }) => {
347
352
  queueMicrotask(() => {
348
353
  send({
349
- type: prop("open") ? "controlled.open" : "controlled.close",
354
+ type: prop("open") ? "CONTROLLED.OPEN" : "CONTROLLED.CLOSE",
350
355
  previousEvent: event
351
356
  });
352
357
  });
@@ -358,13 +363,19 @@ var machine = (0, import_core.createMachine)({
358
363
  clearPointerMoveOpened: ({ context }) => {
359
364
  context.set("hasPointerMoveOpened", null);
360
365
  },
366
+ setIsPointer: ({ context }) => {
367
+ context.set("isPointer", true);
368
+ },
369
+ clearIsPointer: ({ context }) => {
370
+ context.set("isPointer", false);
371
+ },
361
372
  setTriggerValue: ({ context, event }) => {
362
373
  if (event.value === void 0) return;
363
374
  context.set("triggerValue", event.value);
364
375
  },
365
376
  immediateReopen: ({ send }) => {
366
377
  queueMicrotask(() => {
367
- send({ type: "reopen" });
378
+ send({ type: "REOPEN" });
368
379
  });
369
380
  }
370
381
  },
@@ -372,6 +383,20 @@ var machine = (0, import_core.createMachine)({
372
383
  trackFocusVisible: ({ scope }) => {
373
384
  return (0, import_focus_visible.trackFocusVisible)({ root: scope.getRootNode?.() });
374
385
  },
386
+ // Only when hoverable: with `interactive: false` the content has `pointer-events: none`,
387
+ // so there is no journey to protect and its rect is unreachable.
388
+ trackSafeArea: ({ send, prop, scope, context }) => {
389
+ if (!prop("interactive")) return;
390
+ return (0, import_safe_area.trackSafeArea)({
391
+ getTriggerEl: () => dom.getActiveTriggerEl(scope, context.get("triggerValue")),
392
+ getContentEl: () => dom.getContentEl(scope),
393
+ openedByPointer: () => context.get("isPointer"),
394
+ defer: true,
395
+ onLeave() {
396
+ send({ type: "SAFE_AREA.EXIT" });
397
+ }
398
+ });
399
+ },
375
400
  trackPositioning: ({ context, prop, scope }) => {
376
401
  if (!context.get("currentPlacement")) {
377
402
  context.set("currentPlacement", prop("positioning").placement);
@@ -383,13 +408,12 @@ var machine = (0, import_core.createMachine)({
383
408
  defer: true,
384
409
  onComplete(data) {
385
410
  context.set("currentPlacement", data.placement);
386
- context.set("positioned", true);
387
411
  }
388
412
  });
389
413
  },
390
414
  trackPointerlockChange: ({ send, scope }) => {
391
415
  const doc = scope.getDoc();
392
- const onChange = () => send({ type: "close", src: "pointerlock:change" });
416
+ const onChange = () => send({ type: "CLOSE", src: "pointerlock:change" });
393
417
  return (0, import_dom_query.addDomEvent)(doc, "pointerlockchange", onChange, false);
394
418
  },
395
419
  trackScroll: ({ send, prop, scope, context }) => {
@@ -400,7 +424,7 @@ var machine = (0, import_core.createMachine)({
400
424
  const overflowParents = (0, import_dom_query.getOverflowAncestors)(triggerEl);
401
425
  const cleanups = overflowParents.map((overflowParent) => {
402
426
  const onScroll = () => {
403
- send({ type: "close", src: "scroll" });
427
+ send({ type: "CLOSE", src: "scroll" });
404
428
  };
405
429
  return (0, import_dom_query.addDomEvent)(overflowParent, "scroll", onScroll, {
406
430
  passive: true,
@@ -415,8 +439,9 @@ var machine = (0, import_core.createMachine)({
415
439
  let cleanup;
416
440
  queueMicrotask(() => {
417
441
  cleanup = import_tooltip.store.subscribe(() => {
418
- if (import_tooltip.store.get("id") !== prop("id")) {
419
- send({ type: "close", src: "id.change" });
442
+ const id = import_tooltip.store.get("id");
443
+ if (id !== null && id !== prop("id")) {
444
+ send({ type: "CLOSE", src: "id.change" });
420
445
  }
421
446
  });
422
447
  });
@@ -428,19 +453,19 @@ var machine = (0, import_core.createMachine)({
428
453
  if ((0, import_dom_query.isComposingEvent)(event)) return;
429
454
  if (event.key !== "Escape") return;
430
455
  event.stopPropagation();
431
- send({ type: "close", src: "keydown.escape" });
456
+ send({ type: "CLOSE", src: "keydown.escape" });
432
457
  };
433
458
  return (0, import_dom_query.addDomEvent)(document, "keydown", onKeyDown, true);
434
459
  },
435
460
  waitForOpenDelay: ({ send, prop, event }) => {
436
461
  const id = setTimeout(() => {
437
- send({ type: "after.openDelay", previousEvent: event });
462
+ send({ type: "OPEN_DELAY", previousEvent: event });
438
463
  }, prop("openDelay"));
439
464
  return () => clearTimeout(id);
440
465
  },
441
466
  waitForCloseDelay: ({ send, prop, event }) => {
442
467
  const id = setTimeout(() => {
443
- send({ type: "after.closeDelay", previousEvent: event });
468
+ send({ type: "CLOSE_DELAY", previousEvent: event });
444
469
  }, prop("closeDelay"));
445
470
  return () => clearTimeout(id);
446
471
  }
@@ -3,6 +3,7 @@ import { createGuards, createMachine } from "@zag-js/core";
3
3
  import { addDomEvent, getOverflowAncestors, isComposingEvent } from "@zag-js/dom-query";
4
4
  import { trackFocusVisible } from "@zag-js/focus-visible";
5
5
  import { getPlacement } from "@zag-js/popper";
6
+ import { trackSafeArea } from "@zag-js/safe-area";
6
7
  import { ensureProps } from "@zag-js/utils";
7
8
  import * as dom from "./tooltip.dom.mjs";
8
9
  import { store } from "./tooltip.store.mjs";
@@ -36,6 +37,7 @@ var machine = createMachine({
36
37
  context: ({ bindable, prop, scope }) => ({
37
38
  currentPlacement: bindable(() => ({ defaultValue: void 0 })),
38
39
  hasPointerMoveOpened: bindable(() => ({ defaultValue: null })),
40
+ isPointer: bindable(() => ({ defaultValue: false })),
39
41
  triggerValue: bindable(() => ({
40
42
  defaultValue: prop("defaultTriggerValue") ?? null,
41
43
  value: prop("triggerValue"),
@@ -45,8 +47,7 @@ var machine = createMachine({
45
47
  const triggerElement = dom.getActiveTriggerEl(scope, value);
46
48
  onTriggerValueChange({ value, triggerElement });
47
49
  }
48
- })),
49
- positioned: bindable(() => ({ defaultValue: false }))
50
+ }))
50
51
  }),
51
52
  watch({ track, action, prop }) {
52
53
  track([() => prop("disabled")], () => {
@@ -60,18 +61,18 @@ var machine = createMachine({
60
61
  });
61
62
  },
62
63
  on: {
63
- "triggerValue.set": {
64
+ "TRIGGER_VALUE.SET": {
64
65
  actions: ["setTriggerValue", "repositionImmediate"]
65
66
  }
66
67
  },
67
68
  states: {
68
69
  closed: {
69
- entry: ["clearGlobalId"],
70
+ entry: ["clearGlobalId", "clearIsPointer"],
70
71
  on: {
71
- "controlled.open": {
72
+ "CONTROLLED.OPEN": {
72
73
  target: "open"
73
74
  },
74
- open: [
75
+ OPEN: [
75
76
  {
76
77
  guard: "isOpenControlled",
77
78
  actions: ["setTriggerValue", "invokeOnOpen"]
@@ -81,10 +82,10 @@ var machine = createMachine({
81
82
  actions: ["setTriggerValue", "invokeOnOpen"]
82
83
  }
83
84
  ],
84
- "pointer.leave": {
85
+ POINTER_LEAVE: {
85
86
  actions: ["clearPointerMoveOpened"]
86
87
  },
87
- "pointer.move": [
88
+ POINTER_MOVE: [
88
89
  {
89
90
  guard: and("noVisibleTooltip", not("hasPointerMoveOpened")),
90
91
  target: "opening",
@@ -93,7 +94,7 @@ var machine = createMachine({
93
94
  {
94
95
  guard: not("hasPointerMoveOpened"),
95
96
  target: "open",
96
- actions: ["setPointerMoveOpened", "invokeOnOpen", "setTriggerValue"]
97
+ actions: ["setPointerMoveOpened", "setIsPointer", "invokeOnOpen", "setTriggerValue"]
97
98
  }
98
99
  ]
99
100
  }
@@ -101,23 +102,23 @@ var machine = createMachine({
101
102
  opening: {
102
103
  effects: ["trackScroll", "trackPointerlockChange", "waitForOpenDelay"],
103
104
  on: {
104
- "after.openDelay": [
105
+ OPEN_DELAY: [
105
106
  {
106
107
  guard: "isOpenControlled",
107
- actions: ["setPointerMoveOpened", "invokeOnOpen"]
108
+ actions: ["setPointerMoveOpened", "setIsPointer", "invokeOnOpen"]
108
109
  },
109
110
  {
110
111
  target: "open",
111
- actions: ["setPointerMoveOpened", "invokeOnOpen"]
112
+ actions: ["setPointerMoveOpened", "setIsPointer", "invokeOnOpen"]
112
113
  }
113
114
  ],
114
- "controlled.open": {
115
+ "CONTROLLED.OPEN": {
115
116
  target: "open"
116
117
  },
117
- "controlled.close": {
118
+ "CONTROLLED.CLOSE": {
118
119
  target: "closed"
119
120
  },
120
- open: [
121
+ OPEN: [
121
122
  {
122
123
  guard: "isOpenControlled",
123
124
  actions: ["setTriggerValue", "invokeOnOpen"]
@@ -127,7 +128,7 @@ var machine = createMachine({
127
128
  actions: ["setTriggerValue", "invokeOnOpen"]
128
129
  }
129
130
  ],
130
- "pointer.leave": [
131
+ POINTER_LEAVE: [
131
132
  {
132
133
  guard: "isOpenControlled",
133
134
  // We trigger toggleVisibility manually since the `ctx.open` has not changed yet (at this point)
@@ -138,7 +139,7 @@ var machine = createMachine({
138
139
  actions: ["clearPointerMoveOpened", "invokeOnClose"]
139
140
  }
140
141
  ],
141
- close: [
142
+ CLOSE: [
142
143
  {
143
144
  guard: "isOpenControlled",
144
145
  // We trigger toggleVisibility manually since the `ctx.open` has not changed yet (at this point)
@@ -152,13 +153,13 @@ var machine = createMachine({
152
153
  }
153
154
  },
154
155
  open: {
155
- effects: ["trackEscapeKey", "trackScroll", "trackPointerlockChange", "trackPositioning"],
156
+ effects: ["trackEscapeKey", "trackScroll", "trackPointerlockChange", "trackPositioning", "trackSafeArea"],
156
157
  entry: ["setGlobalId"],
157
158
  on: {
158
- "controlled.close": {
159
+ "CONTROLLED.CLOSE": {
159
160
  target: "closed"
160
161
  },
161
- close: [
162
+ CLOSE: [
162
163
  {
163
164
  guard: "isOpenControlled",
164
165
  actions: ["invokeOnClose"]
@@ -168,30 +169,34 @@ var machine = createMachine({
168
169
  actions: ["invokeOnClose"]
169
170
  }
170
171
  ],
171
- "pointer.leave": [
172
+ // Hoverable content means the pointer may be travelling to it (WCAG 1.4.13), so the
173
+ // safe area decides when it has really left.
174
+ POINTER_LEAVE: [
172
175
  {
173
- guard: "isVisible",
176
+ guard: and(not("isInteractive"), "isVisible"),
174
177
  target: "closing",
175
178
  actions: ["clearPointerMoveOpened"]
176
179
  },
177
180
  // == group ==
178
181
  {
179
- guard: "isOpenControlled",
182
+ guard: and(not("isInteractive"), "isOpenControlled"),
180
183
  actions: ["clearPointerMoveOpened", "invokeOnClose"]
181
184
  },
182
185
  {
186
+ guard: not("isInteractive"),
183
187
  target: "closed",
184
188
  actions: ["clearPointerMoveOpened", "invokeOnClose"]
185
189
  }
186
190
  ],
187
- "content.pointer.leave": {
188
- guard: "isInteractive",
189
- target: "closing"
191
+ "SAFE_AREA.EXIT": {
192
+ target: "closing",
193
+ // Same bookkeeping the `POINTER_LEAVE` path does, or `closed` refuses the next hover.
194
+ actions: ["clearPointerMoveOpened"]
190
195
  },
191
- "positioning.set": {
196
+ "POSITIONING.SET": {
192
197
  actions: ["reposition"]
193
198
  },
194
- "triggerValue.set": {
199
+ "TRIGGER_VALUE.SET": {
195
200
  // Transition to closing (which cleans up trackPositioning) then immediately back to open
196
201
  // This re-creates the positioning effect with the new trigger
197
202
  target: "closing",
@@ -202,7 +207,7 @@ var machine = createMachine({
202
207
  closing: {
203
208
  effects: ["trackPositioning", "waitForCloseDelay"],
204
209
  on: {
205
- "after.closeDelay": [
210
+ CLOSE_DELAY: [
206
211
  {
207
212
  guard: "isOpenControlled",
208
213
  actions: ["invokeOnClose"]
@@ -212,13 +217,13 @@ var machine = createMachine({
212
217
  actions: ["invokeOnClose"]
213
218
  }
214
219
  ],
215
- "controlled.close": {
220
+ "CONTROLLED.CLOSE": {
216
221
  target: "closed"
217
222
  },
218
- "controlled.open": {
223
+ "CONTROLLED.OPEN": {
219
224
  target: "open"
220
225
  },
221
- close: [
226
+ CLOSE: [
222
227
  {
223
228
  guard: "isOpenControlled",
224
229
  actions: ["invokeOnClose"]
@@ -228,29 +233,29 @@ var machine = createMachine({
228
233
  actions: ["invokeOnClose"]
229
234
  }
230
235
  ],
231
- "pointer.move": [
236
+ POINTER_MOVE: [
232
237
  {
233
238
  guard: "isOpenControlled",
234
239
  // We trigger toggleVisibility manually since the `ctx.open` has not changed yet (at this point)
235
- actions: ["setPointerMoveOpened", "setTriggerValue", "invokeOnOpen", "toggleVisibility"]
240
+ actions: ["setPointerMoveOpened", "setIsPointer", "setTriggerValue", "invokeOnOpen", "toggleVisibility"]
236
241
  },
237
242
  {
238
243
  target: "open",
239
- actions: ["setPointerMoveOpened", "setTriggerValue", "invokeOnOpen"]
244
+ actions: ["setPointerMoveOpened", "setIsPointer", "setTriggerValue", "invokeOnOpen"]
240
245
  }
241
246
  ],
242
- "triggerValue.set": {
247
+ "TRIGGER_VALUE.SET": {
243
248
  target: "open",
244
249
  actions: ["setTriggerValue", "repositionImmediate"]
245
250
  },
246
- reopen: {
251
+ REOPEN: {
247
252
  target: "open"
248
253
  },
249
- "content.pointer.move": {
254
+ CONTENT_POINTER_MOVE: {
250
255
  guard: "isInteractive",
251
256
  target: "open"
252
257
  },
253
- "positioning.set": {
258
+ "POSITIONING.SET": {
254
259
  actions: ["reposition"]
255
260
  }
256
261
  }
@@ -265,10 +270,10 @@ var machine = createMachine({
265
270
  isOpenControlled: ({ prop }) => prop("open") !== void 0
266
271
  },
267
272
  actions: {
268
- setGlobalId: ({ prop }) => {
273
+ setGlobalId: ({ prop, event }) => {
269
274
  const prevId = store.get("id");
270
- const isInstant = prevId !== null && prevId !== prop("id");
271
- store.update({ id: prop("id"), prevId: isInstant ? prevId : null, instant: isInstant });
275
+ const isInstant = event.src === "trigger.focus" || prevId !== null && prevId !== prop("id");
276
+ store.update({ id: prop("id"), prevId: isInstant && prevId !== null ? prevId : null, instant: isInstant });
272
277
  },
273
278
  clearGlobalId: ({ prop }) => {
274
279
  if (prop("id") === store.get("id")) {
@@ -283,10 +288,10 @@ var machine = createMachine({
283
288
  },
284
289
  closeIfDisabled: ({ prop, send }) => {
285
290
  if (!prop("disabled")) return;
286
- send({ type: "close", src: "disabled.change" });
291
+ send({ type: "CLOSE", src: "disabled.change" });
287
292
  },
288
293
  reposition: ({ context, event, prop, scope }) => {
289
- if (event.type !== "positioning.set") return;
294
+ if (event.type !== "POSITIONING.SET") return;
290
295
  const getPositionerEl2 = () => dom.getPositionerEl(scope);
291
296
  const getTriggerEl = () => dom.getActiveTriggerEl(scope, context.get("triggerValue"));
292
297
  getPlacement(getTriggerEl, getPositionerEl2, {
@@ -312,7 +317,7 @@ var machine = createMachine({
312
317
  toggleVisibility: ({ prop, event, send }) => {
313
318
  queueMicrotask(() => {
314
319
  send({
315
- type: prop("open") ? "controlled.open" : "controlled.close",
320
+ type: prop("open") ? "CONTROLLED.OPEN" : "CONTROLLED.CLOSE",
316
321
  previousEvent: event
317
322
  });
318
323
  });
@@ -324,13 +329,19 @@ var machine = createMachine({
324
329
  clearPointerMoveOpened: ({ context }) => {
325
330
  context.set("hasPointerMoveOpened", null);
326
331
  },
332
+ setIsPointer: ({ context }) => {
333
+ context.set("isPointer", true);
334
+ },
335
+ clearIsPointer: ({ context }) => {
336
+ context.set("isPointer", false);
337
+ },
327
338
  setTriggerValue: ({ context, event }) => {
328
339
  if (event.value === void 0) return;
329
340
  context.set("triggerValue", event.value);
330
341
  },
331
342
  immediateReopen: ({ send }) => {
332
343
  queueMicrotask(() => {
333
- send({ type: "reopen" });
344
+ send({ type: "REOPEN" });
334
345
  });
335
346
  }
336
347
  },
@@ -338,6 +349,20 @@ var machine = createMachine({
338
349
  trackFocusVisible: ({ scope }) => {
339
350
  return trackFocusVisible({ root: scope.getRootNode?.() });
340
351
  },
352
+ // Only when hoverable: with `interactive: false` the content has `pointer-events: none`,
353
+ // so there is no journey to protect and its rect is unreachable.
354
+ trackSafeArea: ({ send, prop, scope, context }) => {
355
+ if (!prop("interactive")) return;
356
+ return trackSafeArea({
357
+ getTriggerEl: () => dom.getActiveTriggerEl(scope, context.get("triggerValue")),
358
+ getContentEl: () => dom.getContentEl(scope),
359
+ openedByPointer: () => context.get("isPointer"),
360
+ defer: true,
361
+ onLeave() {
362
+ send({ type: "SAFE_AREA.EXIT" });
363
+ }
364
+ });
365
+ },
341
366
  trackPositioning: ({ context, prop, scope }) => {
342
367
  if (!context.get("currentPlacement")) {
343
368
  context.set("currentPlacement", prop("positioning").placement);
@@ -349,13 +374,12 @@ var machine = createMachine({
349
374
  defer: true,
350
375
  onComplete(data) {
351
376
  context.set("currentPlacement", data.placement);
352
- context.set("positioned", true);
353
377
  }
354
378
  });
355
379
  },
356
380
  trackPointerlockChange: ({ send, scope }) => {
357
381
  const doc = scope.getDoc();
358
- const onChange = () => send({ type: "close", src: "pointerlock:change" });
382
+ const onChange = () => send({ type: "CLOSE", src: "pointerlock:change" });
359
383
  return addDomEvent(doc, "pointerlockchange", onChange, false);
360
384
  },
361
385
  trackScroll: ({ send, prop, scope, context }) => {
@@ -366,7 +390,7 @@ var machine = createMachine({
366
390
  const overflowParents = getOverflowAncestors(triggerEl);
367
391
  const cleanups = overflowParents.map((overflowParent) => {
368
392
  const onScroll = () => {
369
- send({ type: "close", src: "scroll" });
393
+ send({ type: "CLOSE", src: "scroll" });
370
394
  };
371
395
  return addDomEvent(overflowParent, "scroll", onScroll, {
372
396
  passive: true,
@@ -381,8 +405,9 @@ var machine = createMachine({
381
405
  let cleanup;
382
406
  queueMicrotask(() => {
383
407
  cleanup = store.subscribe(() => {
384
- if (store.get("id") !== prop("id")) {
385
- send({ type: "close", src: "id.change" });
408
+ const id = store.get("id");
409
+ if (id !== null && id !== prop("id")) {
410
+ send({ type: "CLOSE", src: "id.change" });
386
411
  }
387
412
  });
388
413
  });
@@ -394,19 +419,19 @@ var machine = createMachine({
394
419
  if (isComposingEvent(event)) return;
395
420
  if (event.key !== "Escape") return;
396
421
  event.stopPropagation();
397
- send({ type: "close", src: "keydown.escape" });
422
+ send({ type: "CLOSE", src: "keydown.escape" });
398
423
  };
399
424
  return addDomEvent(document, "keydown", onKeyDown, true);
400
425
  },
401
426
  waitForOpenDelay: ({ send, prop, event }) => {
402
427
  const id = setTimeout(() => {
403
- send({ type: "after.openDelay", previousEvent: event });
428
+ send({ type: "OPEN_DELAY", previousEvent: event });
404
429
  }, prop("openDelay"));
405
430
  return () => clearTimeout(id);
406
431
  },
407
432
  waitForCloseDelay: ({ send, prop, event }) => {
408
433
  const id = setTimeout(() => {
409
- send({ type: "after.closeDelay", previousEvent: event });
434
+ send({ type: "CLOSE_DELAY", previousEvent: event });
410
435
  }, prop("closeDelay"));
411
436
  return () => clearTimeout(id);
412
437
  }
@@ -1,7 +1,7 @@
1
1
  import { Machine, EventObject, Service } from '@zag-js/core';
2
- import { PositioningOptions, Placement } from '@zag-js/popper';
2
+ import { PositioningOptions, Placement, PlacementSide } from '@zag-js/popper';
3
3
  export { Placement, PositioningOptions } from '@zag-js/popper';
4
- import { PropTypes, RequiredBy, DirectionProperty, CommonProperties } from '@zag-js/types';
4
+ import { PropTypes, DirectionProperty, CommonProperties } from '@zag-js/types';
5
5
 
6
6
  interface OpenChangeDetails {
7
7
  open: boolean;
@@ -16,12 +16,12 @@ interface TriggerValueChangeDetails {
16
16
  */
17
17
  triggerElement: HTMLElement | null;
18
18
  }
19
- type ElementIds = Partial<{
20
- trigger: string | ((value?: string) => string);
21
- content: string;
22
- arrow: string;
23
- positioner: string;
24
- }>;
19
+ interface ElementIds {
20
+ trigger?: (string | ((value?: string) => string)) | undefined;
21
+ content?: string | undefined;
22
+ arrow?: string | undefined;
23
+ positioner?: string | undefined;
24
+ }
25
25
  interface TooltipProps extends DirectionProperty, CommonProperties {
26
26
  /**
27
27
  * The ids of the elements in the tooltip. Useful for composition.
@@ -107,12 +107,13 @@ interface TooltipProps extends DirectionProperty, CommonProperties {
107
107
  type PropsWithDefault = "openDelay" | "closeDelay" | "closeOnPointerDown" | "closeOnEscape" | "closeOnScroll" | "closeOnClick" | "interactive" | "id" | "positioning";
108
108
  interface TooltipSchema {
109
109
  state: "open" | "closed" | "opening" | "closing";
110
- props: RequiredBy<TooltipProps, PropsWithDefault>;
110
+ props: TooltipProps;
111
+ defaultPropKey: PropsWithDefault;
111
112
  context: {
112
113
  currentPlacement: Placement | undefined;
113
114
  hasPointerMoveOpened: string | null;
115
+ isPointer: boolean;
114
116
  triggerValue: string | null;
115
- positioned: boolean;
116
117
  };
117
118
  event: EventObject;
118
119
  action: string;
@@ -127,6 +128,38 @@ interface TriggerProps {
127
128
  */
128
129
  value?: string;
129
130
  }
131
+ interface TriggerState {
132
+ /**
133
+ * The value that identifies this specific trigger
134
+ */
135
+ value: string | undefined;
136
+ /**
137
+ * Whether this trigger is the one that opened the tooltip
138
+ */
139
+ current: boolean;
140
+ /**
141
+ * Whether the tooltip is open
142
+ */
143
+ open: boolean;
144
+ }
145
+ interface ContentState {
146
+ /**
147
+ * Whether the tooltip is open
148
+ */
149
+ open: boolean;
150
+ /**
151
+ * Whether the tooltip should skip its enter animation, following a rapid switch between triggers
152
+ */
153
+ instant: boolean;
154
+ /**
155
+ * The current placement of the content relative to the trigger
156
+ */
157
+ placement: Placement | undefined;
158
+ /**
159
+ * The side of the trigger the content is placed on
160
+ */
161
+ side: PlacementSide | undefined;
162
+ }
130
163
  interface TooltipApi<T extends PropTypes = PropTypes> {
131
164
  /**
132
165
  * Whether the tooltip is open.
@@ -148,11 +181,19 @@ interface TooltipApi<T extends PropTypes = PropTypes> {
148
181
  * Function to reposition the popover
149
182
  */
150
183
  reposition: (options?: Partial<PositioningOptions>) => void;
184
+ /**
185
+ * Returns the state of a specific trigger, including whether it's the currently active one
186
+ */
187
+ getTriggerState: (props?: TriggerProps) => TriggerState;
151
188
  getTriggerProps: (props?: TriggerProps) => T["button"];
152
189
  getArrowProps: () => T["element"];
153
190
  getArrowTipProps: () => T["element"];
154
191
  getPositionerProps: () => T["element"];
192
+ /**
193
+ * Returns the state of the content
194
+ */
195
+ getContentState: () => ContentState;
155
196
  getContentProps: () => T["element"];
156
197
  }
157
198
 
158
- export type { ElementIds, OpenChangeDetails, TooltipApi, TooltipMachine, TooltipProps, TooltipSchema, TooltipService, TriggerProps, TriggerValueChangeDetails };
199
+ export type { ContentState, ElementIds, OpenChangeDetails, TooltipApi, TooltipMachine, TooltipProps, TooltipSchema, TooltipService, TriggerProps, TriggerState, TriggerValueChangeDetails };
@@ -1,7 +1,7 @@
1
1
  import { Machine, EventObject, Service } from '@zag-js/core';
2
- import { PositioningOptions, Placement } from '@zag-js/popper';
2
+ import { PositioningOptions, Placement, PlacementSide } from '@zag-js/popper';
3
3
  export { Placement, PositioningOptions } from '@zag-js/popper';
4
- import { PropTypes, RequiredBy, DirectionProperty, CommonProperties } from '@zag-js/types';
4
+ import { PropTypes, DirectionProperty, CommonProperties } from '@zag-js/types';
5
5
 
6
6
  interface OpenChangeDetails {
7
7
  open: boolean;
@@ -16,12 +16,12 @@ interface TriggerValueChangeDetails {
16
16
  */
17
17
  triggerElement: HTMLElement | null;
18
18
  }
19
- type ElementIds = Partial<{
20
- trigger: string | ((value?: string) => string);
21
- content: string;
22
- arrow: string;
23
- positioner: string;
24
- }>;
19
+ interface ElementIds {
20
+ trigger?: (string | ((value?: string) => string)) | undefined;
21
+ content?: string | undefined;
22
+ arrow?: string | undefined;
23
+ positioner?: string | undefined;
24
+ }
25
25
  interface TooltipProps extends DirectionProperty, CommonProperties {
26
26
  /**
27
27
  * The ids of the elements in the tooltip. Useful for composition.
@@ -107,12 +107,13 @@ interface TooltipProps extends DirectionProperty, CommonProperties {
107
107
  type PropsWithDefault = "openDelay" | "closeDelay" | "closeOnPointerDown" | "closeOnEscape" | "closeOnScroll" | "closeOnClick" | "interactive" | "id" | "positioning";
108
108
  interface TooltipSchema {
109
109
  state: "open" | "closed" | "opening" | "closing";
110
- props: RequiredBy<TooltipProps, PropsWithDefault>;
110
+ props: TooltipProps;
111
+ defaultPropKey: PropsWithDefault;
111
112
  context: {
112
113
  currentPlacement: Placement | undefined;
113
114
  hasPointerMoveOpened: string | null;
115
+ isPointer: boolean;
114
116
  triggerValue: string | null;
115
- positioned: boolean;
116
117
  };
117
118
  event: EventObject;
118
119
  action: string;
@@ -127,6 +128,38 @@ interface TriggerProps {
127
128
  */
128
129
  value?: string;
129
130
  }
131
+ interface TriggerState {
132
+ /**
133
+ * The value that identifies this specific trigger
134
+ */
135
+ value: string | undefined;
136
+ /**
137
+ * Whether this trigger is the one that opened the tooltip
138
+ */
139
+ current: boolean;
140
+ /**
141
+ * Whether the tooltip is open
142
+ */
143
+ open: boolean;
144
+ }
145
+ interface ContentState {
146
+ /**
147
+ * Whether the tooltip is open
148
+ */
149
+ open: boolean;
150
+ /**
151
+ * Whether the tooltip should skip its enter animation, following a rapid switch between triggers
152
+ */
153
+ instant: boolean;
154
+ /**
155
+ * The current placement of the content relative to the trigger
156
+ */
157
+ placement: Placement | undefined;
158
+ /**
159
+ * The side of the trigger the content is placed on
160
+ */
161
+ side: PlacementSide | undefined;
162
+ }
130
163
  interface TooltipApi<T extends PropTypes = PropTypes> {
131
164
  /**
132
165
  * Whether the tooltip is open.
@@ -148,11 +181,19 @@ interface TooltipApi<T extends PropTypes = PropTypes> {
148
181
  * Function to reposition the popover
149
182
  */
150
183
  reposition: (options?: Partial<PositioningOptions>) => void;
184
+ /**
185
+ * Returns the state of a specific trigger, including whether it's the currently active one
186
+ */
187
+ getTriggerState: (props?: TriggerProps) => TriggerState;
151
188
  getTriggerProps: (props?: TriggerProps) => T["button"];
152
189
  getArrowProps: () => T["element"];
153
190
  getArrowTipProps: () => T["element"];
154
191
  getPositionerProps: () => T["element"];
192
+ /**
193
+ * Returns the state of the content
194
+ */
195
+ getContentState: () => ContentState;
155
196
  getContentProps: () => T["element"];
156
197
  }
157
198
 
158
- export type { ElementIds, OpenChangeDetails, TooltipApi, TooltipMachine, TooltipProps, TooltipSchema, TooltipService, TriggerProps, TriggerValueChangeDetails };
199
+ export type { ContentState, ElementIds, OpenChangeDetails, TooltipApi, TooltipMachine, TooltipProps, TooltipSchema, TooltipService, TriggerProps, TriggerState, TriggerValueChangeDetails };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zag-js/tooltip",
3
- "version": "2.0.0-next.0",
3
+ "version": "2.0.0-next.2",
4
4
  "description": "Core logic for the tooltip widget implemented as a state machine",
5
5
  "keywords": [
6
6
  "js",
@@ -14,7 +14,10 @@
14
14
  "author": "Segun Adebayo <sage@adebayosegun.com>",
15
15
  "homepage": "https://github.com/chakra-ui/zag#readme",
16
16
  "license": "MIT",
17
- "repository": "https://github.com/chakra-ui/zag/tree/main/packages/tooltip",
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "https://github.com/chakra-ui/zag/tree/main/packages/tooltip"
20
+ },
18
21
  "sideEffects": false,
19
22
  "files": [
20
23
  "dist"
@@ -26,13 +29,14 @@
26
29
  "url": "https://github.com/chakra-ui/zag/issues"
27
30
  },
28
31
  "dependencies": {
29
- "@zag-js/anatomy": "2.0.0-next.0",
30
- "@zag-js/core": "2.0.0-next.0",
31
- "@zag-js/popper": "2.0.0-next.0",
32
- "@zag-js/focus-visible": "2.0.0-next.0",
33
- "@zag-js/dom-query": "2.0.0-next.0",
34
- "@zag-js/utils": "2.0.0-next.0",
35
- "@zag-js/types": "2.0.0-next.0"
32
+ "@zag-js/anatomy": "2.0.0-next.2",
33
+ "@zag-js/core": "2.0.0-next.2",
34
+ "@zag-js/dom-query": "2.0.0-next.2",
35
+ "@zag-js/focus-visible": "2.0.0-next.2",
36
+ "@zag-js/popper": "2.0.0-next.2",
37
+ "@zag-js/safe-area": "2.0.0-next.2",
38
+ "@zag-js/types": "2.0.0-next.2",
39
+ "@zag-js/utils": "2.0.0-next.2"
36
40
  },
37
41
  "devDependencies": {
38
42
  "clean-package": "2.2.0"
@@ -66,7 +70,7 @@
66
70
  },
67
71
  "scripts": {
68
72
  "build": "tsup",
69
- "lint": "eslint src",
73
+ "lint": "oxlint src",
70
74
  "typecheck": "tsc --noEmit"
71
75
  }
72
76
  }