@zag-js/tabs 1.44.0 → 1.45.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.
@@ -44,6 +44,9 @@ function connect(service, normalize) {
44
44
  const isVertical = prop("orientation") === "vertical";
45
45
  const isHorizontal = prop("orientation") === "horizontal";
46
46
  const composite = prop("composite");
47
+ function sendKeyboardEvent(type, key) {
48
+ send({ type, key, src: "keyboard" });
49
+ }
47
50
  function getTriggerState(props) {
48
51
  return {
49
52
  selected: context.get("value") === props.value,
@@ -106,25 +109,25 @@ function connect(service, normalize) {
106
109
  const keyMap = {
107
110
  ArrowDown() {
108
111
  if (isHorizontal) return;
109
- send({ type: "ARROW_NEXT", key: "ArrowDown" });
112
+ sendKeyboardEvent("ARROW_NEXT", "ArrowDown");
110
113
  },
111
114
  ArrowUp() {
112
115
  if (isHorizontal) return;
113
- send({ type: "ARROW_PREV", key: "ArrowUp" });
116
+ sendKeyboardEvent("ARROW_PREV", "ArrowUp");
114
117
  },
115
118
  ArrowLeft() {
116
119
  if (isVertical) return;
117
- send({ type: "ARROW_PREV", key: "ArrowLeft" });
120
+ sendKeyboardEvent("ARROW_PREV", "ArrowLeft");
118
121
  },
119
122
  ArrowRight() {
120
123
  if (isVertical) return;
121
- send({ type: "ARROW_NEXT", key: "ArrowRight" });
124
+ sendKeyboardEvent("ARROW_NEXT", "ArrowRight");
122
125
  },
123
126
  Home() {
124
- send({ type: "HOME" });
127
+ sendKeyboardEvent("HOME");
125
128
  },
126
129
  End() {
127
- send({ type: "END" });
130
+ sendKeyboardEvent("END");
128
131
  }
129
132
  };
130
133
  let key = (0, import_dom_query.getEventKey)(event, {
@@ -178,6 +181,9 @@ function connect(service, normalize) {
178
181
  if ((0, import_dom_query.isSafari)()) {
179
182
  event.currentTarget.focus();
180
183
  }
184
+ const node = event.currentTarget;
185
+ const shouldNavigate = prop("navigate") != null && (0, import_dom_query.isAnchorElement)(node);
186
+ if (shouldNavigate) event.preventDefault();
181
187
  send({ type: "TAB_CLICK", value });
182
188
  }
183
189
  });
@@ -4,6 +4,7 @@ import {
4
4
  dataAttr,
5
5
  getEventKey,
6
6
  getEventTarget,
7
+ isAnchorElement,
7
8
  isComposingEvent,
8
9
  isOpeningInNewTab,
9
10
  isSafari
@@ -18,6 +19,9 @@ function connect(service, normalize) {
18
19
  const isVertical = prop("orientation") === "vertical";
19
20
  const isHorizontal = prop("orientation") === "horizontal";
20
21
  const composite = prop("composite");
22
+ function sendKeyboardEvent(type, key) {
23
+ send({ type, key, src: "keyboard" });
24
+ }
21
25
  function getTriggerState(props) {
22
26
  return {
23
27
  selected: context.get("value") === props.value,
@@ -80,25 +84,25 @@ function connect(service, normalize) {
80
84
  const keyMap = {
81
85
  ArrowDown() {
82
86
  if (isHorizontal) return;
83
- send({ type: "ARROW_NEXT", key: "ArrowDown" });
87
+ sendKeyboardEvent("ARROW_NEXT", "ArrowDown");
84
88
  },
85
89
  ArrowUp() {
86
90
  if (isHorizontal) return;
87
- send({ type: "ARROW_PREV", key: "ArrowUp" });
91
+ sendKeyboardEvent("ARROW_PREV", "ArrowUp");
88
92
  },
89
93
  ArrowLeft() {
90
94
  if (isVertical) return;
91
- send({ type: "ARROW_PREV", key: "ArrowLeft" });
95
+ sendKeyboardEvent("ARROW_PREV", "ArrowLeft");
92
96
  },
93
97
  ArrowRight() {
94
98
  if (isVertical) return;
95
- send({ type: "ARROW_NEXT", key: "ArrowRight" });
99
+ sendKeyboardEvent("ARROW_NEXT", "ArrowRight");
96
100
  },
97
101
  Home() {
98
- send({ type: "HOME" });
102
+ sendKeyboardEvent("HOME");
99
103
  },
100
104
  End() {
101
- send({ type: "END" });
105
+ sendKeyboardEvent("END");
102
106
  }
103
107
  };
104
108
  let key = getEventKey(event, {
@@ -152,6 +156,9 @@ function connect(service, normalize) {
152
156
  if (isSafari()) {
153
157
  event.currentTarget.focus();
154
158
  }
159
+ const node = event.currentTarget;
160
+ const shouldNavigate = prop("navigate") != null && isAnchorElement(node);
161
+ if (shouldNavigate) event.preventDefault();
155
162
  send({ type: "TAB_CLICK", value });
156
163
  }
157
164
  });
@@ -46,9 +46,6 @@ var machine = createMachine({
46
46
  activationMode: "automatic",
47
47
  loopFocus: true,
48
48
  composite: true,
49
- navigate(details) {
50
- (0, import_dom_query.clickIfLink)(details.node);
51
- },
52
49
  defaultValue: null,
53
50
  ...props
54
51
  };
@@ -89,7 +86,7 @@ var machine = createMachine({
89
86
  },
90
87
  watch({ context, prop, track, action }) {
91
88
  track([() => context.get("value")], () => {
92
- action(["syncIndicatorAnimation", "syncIndicatorRect", "syncTabIndex", "navigateIfNeeded"]);
89
+ action(["syncIndicatorAnimation", "syncIndicatorRect", "syncTabIndex"]);
93
90
  });
94
91
  track([() => prop("dir"), () => prop("orientation")], () => {
95
92
  action(["syncIndicatorRect"]);
@@ -123,14 +120,14 @@ var machine = createMachine({
123
120
  },
124
121
  TAB_CLICK: {
125
122
  target: "focused",
126
- actions: ["setFocusedValue", "setValue"]
123
+ actions: ["setFocusedValue", "setValue", "requestNavigation"]
127
124
  }
128
125
  }
129
126
  },
130
127
  focused: {
131
128
  on: {
132
129
  TAB_CLICK: {
133
- actions: ["setFocusedValue", "setValue"]
130
+ actions: ["setFocusedValue", "setValue", "requestNavigation"]
134
131
  },
135
132
  ARROW_PREV: [
136
133
  {
@@ -183,12 +180,20 @@ var machine = createMachine({
183
180
  selectOnFocus: ({ prop }) => prop("activationMode") === "automatic"
184
181
  },
185
182
  actions: {
186
- selectFocusedTab({ context, prop }) {
183
+ selectFocusedTab({ context, prop, event, scope }) {
187
184
  (0, import_dom_query.raf)(() => {
188
185
  const focusedValue = context.get("focusedValue");
189
186
  if (!focusedValue) return;
190
187
  const nullable = prop("deselectable") && context.get("value") === focusedValue;
191
188
  const value = nullable ? null : focusedValue;
189
+ if (context.get("value") === value) return;
190
+ if (event.src === "keyboard") {
191
+ const triggerEl = dom.getTriggerEl(scope, focusedValue);
192
+ if ((0, import_dom_query.isAnchorElement)(triggerEl) && prop("navigate") !== null) {
193
+ triggerEl.click();
194
+ return;
195
+ }
196
+ }
192
197
  context.set("value", value);
193
198
  });
194
199
  },
@@ -311,13 +316,12 @@ var machine = createMachine({
311
316
  );
312
317
  refs.set("indicatorCleanup", indicatorCleanup);
313
318
  },
314
- navigateIfNeeded({ context, prop, scope }) {
315
- const value = context.get("value");
316
- if (!value) return;
317
- const triggerEl = dom.getTriggerEl(scope, value);
318
- if ((0, import_dom_query.isAnchorElement)(triggerEl)) {
319
- prop("navigate")?.({ value, node: triggerEl, href: triggerEl.href });
320
- }
319
+ requestNavigation({ event, prop, scope }) {
320
+ const navigate = prop("navigate");
321
+ if (!navigate || event.value == null) return;
322
+ const node = dom.getTriggerEl(scope, event.value);
323
+ if (!(0, import_dom_query.isAnchorElement)(node)) return;
324
+ navigate({ value: event.value, node, href: node.href });
321
325
  }
322
326
  }
323
327
  }
@@ -1,6 +1,6 @@
1
1
  // src/tabs.machine.ts
2
2
  import { setup } from "@zag-js/core";
3
- import { clickIfLink, getFocusables, isAnchorElement, raf, resizeObserverBorderBox } from "@zag-js/dom-query";
3
+ import { getFocusables, isAnchorElement, raf, resizeObserverBorderBox } from "@zag-js/dom-query";
4
4
  import { callAll, isEqual } from "@zag-js/utils";
5
5
  import * as dom from "./tabs.dom.mjs";
6
6
  var { createMachine } = setup();
@@ -12,9 +12,6 @@ var machine = createMachine({
12
12
  activationMode: "automatic",
13
13
  loopFocus: true,
14
14
  composite: true,
15
- navigate(details) {
16
- clickIfLink(details.node);
17
- },
18
15
  defaultValue: null,
19
16
  ...props
20
17
  };
@@ -55,7 +52,7 @@ var machine = createMachine({
55
52
  },
56
53
  watch({ context, prop, track, action }) {
57
54
  track([() => context.get("value")], () => {
58
- action(["syncIndicatorAnimation", "syncIndicatorRect", "syncTabIndex", "navigateIfNeeded"]);
55
+ action(["syncIndicatorAnimation", "syncIndicatorRect", "syncTabIndex"]);
59
56
  });
60
57
  track([() => prop("dir"), () => prop("orientation")], () => {
61
58
  action(["syncIndicatorRect"]);
@@ -89,14 +86,14 @@ var machine = createMachine({
89
86
  },
90
87
  TAB_CLICK: {
91
88
  target: "focused",
92
- actions: ["setFocusedValue", "setValue"]
89
+ actions: ["setFocusedValue", "setValue", "requestNavigation"]
93
90
  }
94
91
  }
95
92
  },
96
93
  focused: {
97
94
  on: {
98
95
  TAB_CLICK: {
99
- actions: ["setFocusedValue", "setValue"]
96
+ actions: ["setFocusedValue", "setValue", "requestNavigation"]
100
97
  },
101
98
  ARROW_PREV: [
102
99
  {
@@ -149,12 +146,20 @@ var machine = createMachine({
149
146
  selectOnFocus: ({ prop }) => prop("activationMode") === "automatic"
150
147
  },
151
148
  actions: {
152
- selectFocusedTab({ context, prop }) {
149
+ selectFocusedTab({ context, prop, event, scope }) {
153
150
  raf(() => {
154
151
  const focusedValue = context.get("focusedValue");
155
152
  if (!focusedValue) return;
156
153
  const nullable = prop("deselectable") && context.get("value") === focusedValue;
157
154
  const value = nullable ? null : focusedValue;
155
+ if (context.get("value") === value) return;
156
+ if (event.src === "keyboard") {
157
+ const triggerEl = dom.getTriggerEl(scope, focusedValue);
158
+ if (isAnchorElement(triggerEl) && prop("navigate") !== null) {
159
+ triggerEl.click();
160
+ return;
161
+ }
162
+ }
158
163
  context.set("value", value);
159
164
  });
160
165
  },
@@ -277,13 +282,12 @@ var machine = createMachine({
277
282
  );
278
283
  refs.set("indicatorCleanup", indicatorCleanup);
279
284
  },
280
- navigateIfNeeded({ context, prop, scope }) {
281
- const value = context.get("value");
282
- if (!value) return;
283
- const triggerEl = dom.getTriggerEl(scope, value);
284
- if (isAnchorElement(triggerEl)) {
285
- prop("navigate")?.({ value, node: triggerEl, href: triggerEl.href });
286
- }
285
+ requestNavigation({ event, prop, scope }) {
286
+ const navigate = prop("navigate");
287
+ if (!navigate || event.value == null) return;
288
+ const node = dom.getTriggerEl(scope, event.value);
289
+ if (!isAnchorElement(node)) return;
290
+ navigate({ value: event.value, node, href: node.href });
287
291
  }
288
292
  }
289
293
  }
@@ -104,7 +104,9 @@ type TabsSchema = {
104
104
  action: string;
105
105
  guard: string;
106
106
  effect: string;
107
- event: EventObject;
107
+ event: EventObject & {
108
+ src?: "keyboard" | "selectNext" | "selectPrev" | undefined;
109
+ };
108
110
  };
109
111
  type TabsService = Service<TabsSchema>;
110
112
  type TabsMachine = Machine<TabsSchema>;
@@ -104,7 +104,9 @@ type TabsSchema = {
104
104
  action: string;
105
105
  guard: string;
106
106
  effect: string;
107
- event: EventObject;
107
+ event: EventObject & {
108
+ src?: "keyboard" | "selectNext" | "selectPrev" | undefined;
109
+ };
108
110
  };
109
111
  type TabsService = Service<TabsSchema>;
110
112
  type TabsMachine = Machine<TabsSchema>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zag-js/tabs",
3
- "version": "1.44.0",
3
+ "version": "1.45.0",
4
4
  "description": "Core logic for the tabs widget implemented as a state machine",
5
5
  "keywords": [
6
6
  "js",
@@ -29,11 +29,11 @@
29
29
  "url": "https://github.com/chakra-ui/zag/issues"
30
30
  },
31
31
  "dependencies": {
32
- "@zag-js/anatomy": "1.44.0",
33
- "@zag-js/dom-query": "1.44.0",
34
- "@zag-js/utils": "1.44.0",
35
- "@zag-js/core": "1.44.0",
36
- "@zag-js/types": "1.44.0"
32
+ "@zag-js/anatomy": "1.45.0",
33
+ "@zag-js/dom-query": "1.45.0",
34
+ "@zag-js/utils": "1.45.0",
35
+ "@zag-js/core": "1.45.0",
36
+ "@zag-js/types": "1.45.0"
37
37
  },
38
38
  "devDependencies": {
39
39
  "clean-package": "2.2.0"