@zag-js/tabs 0.9.1 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zag-js/tabs",
3
- "version": "0.9.1",
3
+ "version": "0.10.0",
4
4
  "description": "Core logic for the tabs widget implemented as a state machine",
5
5
  "keywords": [
6
6
  "js",
@@ -17,7 +17,8 @@
17
17
  "repository": "https://github.com/chakra-ui/zag/tree/main/packages/tabs",
18
18
  "sideEffects": false,
19
19
  "files": [
20
- "dist/**/*"
20
+ "dist",
21
+ "src"
21
22
  ],
22
23
  "publishConfig": {
23
24
  "access": "public"
@@ -26,14 +27,14 @@
26
27
  "url": "https://github.com/chakra-ui/zag/issues"
27
28
  },
28
29
  "dependencies": {
29
- "@zag-js/anatomy": "0.9.1",
30
- "@zag-js/dom-query": "0.9.1",
31
- "@zag-js/dom-event": "0.9.1",
32
- "@zag-js/element-rect": "0.9.1",
33
- "@zag-js/tabbable": "0.9.1",
34
- "@zag-js/utils": "0.9.1",
35
- "@zag-js/core": "0.9.1",
36
- "@zag-js/types": "0.9.1"
30
+ "@zag-js/anatomy": "0.10.0",
31
+ "@zag-js/dom-query": "0.10.0",
32
+ "@zag-js/dom-event": "0.10.0",
33
+ "@zag-js/element-rect": "0.10.0",
34
+ "@zag-js/tabbable": "0.10.0",
35
+ "@zag-js/utils": "0.10.0",
36
+ "@zag-js/core": "0.10.0",
37
+ "@zag-js/types": "0.10.0"
37
38
  },
38
39
  "devDependencies": {
39
40
  "clean-package": "2.2.0"
package/src/index.ts ADDED
@@ -0,0 +1,4 @@
1
+ export { anatomy } from "./tabs.anatomy"
2
+ export { connect } from "./tabs.connect"
3
+ export { machine } from "./tabs.machine"
4
+ export type { UserDefinedContext as Context, TriggerProps, ContentProps } from "./tabs.types"
@@ -0,0 +1,4 @@
1
+ import { createAnatomy } from "@zag-js/anatomy"
2
+
3
+ export const anatomy = createAnatomy("tabs").parts("root", "tablist", "trigger", "contentGroup", "content", "indicator")
4
+ export const parts = anatomy.build()
@@ -0,0 +1,168 @@
1
+ import { EventKeyMap, getEventKey } from "@zag-js/dom-event"
2
+ import { dataAttr, isSafari } from "@zag-js/dom-query"
3
+ import type { NormalizeProps, PropTypes } from "@zag-js/types"
4
+ import { parts } from "./tabs.anatomy"
5
+ import { dom } from "./tabs.dom"
6
+ import type { ContentProps, Send, State, TriggerProps } from "./tabs.types"
7
+
8
+ export function connect<T extends PropTypes>(state: State, send: Send, normalize: NormalizeProps<T>) {
9
+ const translations = state.context.translations
10
+ const isFocused = state.matches("focused")
11
+
12
+ return {
13
+ /**
14
+ * The current value of the tabs.
15
+ */
16
+ value: state.context.value,
17
+ /**
18
+ * The value of the tab that is currently focused.
19
+ */
20
+ focusedValue: state.context.focusedValue,
21
+ /**
22
+ * The previous values of the tabs in sequence of selection.
23
+ */
24
+ previousValues: Array.from(state.context.previousValues),
25
+ /**
26
+ * Sets the value of the tabs.
27
+ */
28
+ setValue(value: string) {
29
+ send({ type: "SET_VALUE", value })
30
+ },
31
+ /**
32
+ * Clears the value of the tabs.
33
+ */
34
+ clearValue() {
35
+ send({ type: "CLEAR_VALUE" })
36
+ },
37
+ /**
38
+ * Sets the indicator rect to the tab with the given id.
39
+ */
40
+ setIndicatorRect(id: string | null | undefined) {
41
+ send({ type: "SET_INDICATOR_RECT", id })
42
+ },
43
+
44
+ rootProps: normalize.element({
45
+ ...parts.root.attrs,
46
+ id: dom.getRootId(state.context),
47
+ "data-orientation": state.context.orientation,
48
+ "data-focus": dataAttr(isFocused),
49
+ dir: state.context.dir,
50
+ }),
51
+
52
+ tablistProps: normalize.element({
53
+ ...parts.tablist.attrs,
54
+ id: dom.getTablistId(state.context),
55
+ role: "tablist",
56
+ "data-focus": dataAttr(isFocused),
57
+ "aria-orientation": state.context.orientation,
58
+ "data-orientation": state.context.orientation,
59
+ "aria-label": translations.tablistLabel,
60
+ onKeyDown(event) {
61
+ const keyMap: EventKeyMap = {
62
+ ArrowDown() {
63
+ send("ARROW_DOWN")
64
+ },
65
+ ArrowUp() {
66
+ send("ARROW_UP")
67
+ },
68
+ ArrowLeft() {
69
+ send("ARROW_LEFT")
70
+ },
71
+ ArrowRight() {
72
+ send("ARROW_RIGHT")
73
+ },
74
+ Home() {
75
+ send("HOME")
76
+ },
77
+ End() {
78
+ send("END")
79
+ },
80
+ Enter() {
81
+ send({ type: "ENTER", value: state.context.focusedValue })
82
+ },
83
+ }
84
+
85
+ let key = getEventKey(event, state.context)
86
+ const exec = keyMap[key]
87
+
88
+ if (exec) {
89
+ event.preventDefault()
90
+ exec(event)
91
+ }
92
+ },
93
+ }),
94
+
95
+ getTriggerProps(props: TriggerProps) {
96
+ const { value, disabled } = props
97
+ const selected = state.context.value === value
98
+
99
+ return normalize.button({
100
+ ...parts.trigger.attrs,
101
+ role: "tab",
102
+ type: "button",
103
+ disabled,
104
+ "data-orientation": state.context.orientation,
105
+ "data-disabled": dataAttr(disabled),
106
+ "aria-disabled": disabled,
107
+ "data-value": value,
108
+ "aria-selected": selected,
109
+ "data-selected": dataAttr(selected),
110
+ "aria-controls": dom.getContentId(state.context, value),
111
+ "data-ownedby": dom.getTablistId(state.context),
112
+ id: dom.getTriggerId(state.context, value),
113
+ tabIndex: selected ? 0 : -1,
114
+ onFocus() {
115
+ send({ type: "TAB_FOCUS", value })
116
+ },
117
+ onBlur(event) {
118
+ const target = event.relatedTarget as HTMLElement | null
119
+ if (target?.getAttribute("role") !== "tab") {
120
+ send({ type: "TAB_BLUR" })
121
+ }
122
+ },
123
+ onClick(event) {
124
+ if (disabled) return
125
+ if (isSafari()) {
126
+ event.currentTarget.focus()
127
+ }
128
+ send({ type: "TAB_CLICK", value })
129
+ },
130
+ })
131
+ },
132
+
133
+ contentGroupProps: normalize.element({
134
+ ...parts.contentGroup.attrs,
135
+ id: dom.getContentGroupId(state.context),
136
+ "data-orientation": state.context.orientation,
137
+ }),
138
+
139
+ getContentProps({ value }: ContentProps) {
140
+ const selected = state.context.value === value
141
+ return normalize.element({
142
+ ...parts.content.attrs,
143
+ id: dom.getContentId(state.context, value),
144
+ tabIndex: 0,
145
+ "aria-labelledby": dom.getTriggerId(state.context, value),
146
+ role: "tabpanel",
147
+ "data-ownedby": dom.getTablistId(state.context),
148
+ hidden: !selected,
149
+ })
150
+ },
151
+
152
+ indicatorProps: normalize.element({
153
+ id: dom.getIndicatorId(state.context),
154
+ ...parts.indicator.attrs,
155
+ "data-orientation": state.context.orientation,
156
+ style: {
157
+ "--transition-duration": "150ms",
158
+ "--transition-property": "left, right, top, bottom, width, height",
159
+ position: "absolute",
160
+ willChange: "var(--transition-property)",
161
+ transitionProperty: "var(--transition-property)",
162
+ transitionDuration: state.context.canIndicatorTransition ? "var(--transition-duration)" : "0ms",
163
+ transitionTimingFunction: "var(--transition-timing-function)",
164
+ ...state.context.indicatorRect,
165
+ },
166
+ }),
167
+ }
168
+ }
@@ -0,0 +1,59 @@
1
+ import { createScope, itemById, nextById, prevById, queryAll } from "@zag-js/dom-query"
2
+ import { first, last } from "@zag-js/utils"
3
+ import type { MachineContext as Ctx } from "./tabs.types"
4
+
5
+ export const dom = createScope({
6
+ getRootId: (ctx: Ctx) => ctx.ids?.root ?? `tabs:${ctx.id}`,
7
+ getTablistId: (ctx: Ctx) => ctx.ids?.tablist ?? `tabs:${ctx.id}:list`,
8
+ getContentId: (ctx: Ctx, id: string) => ctx.ids?.content ?? `tabs:${ctx.id}:content-${id}`,
9
+ getContentGroupId: (ctx: Ctx) => ctx.ids?.contentGroup ?? `tabs:${ctx.id}:content-group`,
10
+ getTriggerId: (ctx: Ctx, id: string) => ctx.ids?.trigger ?? `tabs:${ctx.id}:trigger-${id}`,
11
+ getIndicatorId: (ctx: Ctx) => ctx.ids?.indicator ?? `tabs:${ctx.id}:indicator`,
12
+
13
+ getTablistEl: (ctx: Ctx) => dom.getById(ctx, dom.getTablistId(ctx)),
14
+ getContentEl: (ctx: Ctx, id: string) => dom.getById(ctx, dom.getContentId(ctx, id)),
15
+ getTriggerEl: (ctx: Ctx, id: string) => dom.getById(ctx, dom.getTriggerId(ctx, id)),
16
+ getIndicatorEl: (ctx: Ctx) => dom.getById(ctx, dom.getIndicatorId(ctx)),
17
+
18
+ getElements: (ctx: Ctx) => {
19
+ const ownerId = CSS.escape(dom.getTablistId(ctx))
20
+ const selector = `[role=tab][data-ownedby='${ownerId}']:not([disabled])`
21
+ return queryAll(dom.getTablistEl(ctx), selector)
22
+ },
23
+
24
+ getFirstEl: (ctx: Ctx) => first(dom.getElements(ctx)),
25
+ getLastEl: (ctx: Ctx) => last(dom.getElements(ctx)),
26
+ getNextEl: (ctx: Ctx, id: string) => nextById(dom.getElements(ctx), dom.getTriggerId(ctx, id), ctx.loop),
27
+ getPrevEl: (ctx: Ctx, id: string) => prevById(dom.getElements(ctx), dom.getTriggerId(ctx, id), ctx.loop),
28
+ getActiveContentEl: (ctx: Ctx) => {
29
+ if (!ctx.value) return
30
+ return dom.getContentEl(ctx, ctx.value)
31
+ },
32
+ getActiveTabEl: (ctx: Ctx) => {
33
+ if (!ctx.value) return
34
+ return dom.getTriggerEl(ctx, ctx.value)
35
+ },
36
+
37
+ getOffsetRect: (el: HTMLElement | undefined) => {
38
+ return {
39
+ left: el?.offsetLeft ?? 0,
40
+ top: el?.offsetTop ?? 0,
41
+ width: el?.offsetWidth ?? 0,
42
+ height: el?.offsetHeight ?? 0,
43
+ }
44
+ },
45
+
46
+ getRectById: (ctx: Ctx, id: string) => {
47
+ const tab = itemById(dom.getElements(ctx), dom.getTriggerId(ctx, id))
48
+ return dom.resolveRect(dom.getOffsetRect(tab), ctx.orientation)
49
+ },
50
+
51
+ resolveRect(rect: Record<"width" | "height" | "left" | "top", number>, orientation?: "horizontal" | "vertical") {
52
+ const sizeProp = orientation === "vertical" ? "height" : "width"
53
+ const placementProp = orientation === "vertical" ? "top" : "left"
54
+ return {
55
+ [placementProp]: `${rect[placementProp]}px`,
56
+ [sizeProp]: `${rect[sizeProp]}px`,
57
+ }
58
+ },
59
+ })
@@ -0,0 +1,256 @@
1
+ import { createMachine, guards } from "@zag-js/core"
2
+ import { nextTick, raf } from "@zag-js/dom-query"
3
+ import { trackElementRect } from "@zag-js/element-rect"
4
+ import { getFocusables } from "@zag-js/tabbable"
5
+ import { compact } from "@zag-js/utils"
6
+ import { dom } from "./tabs.dom"
7
+ import type { MachineContext, MachineState, UserDefinedContext } from "./tabs.types"
8
+
9
+ const { not } = guards
10
+
11
+ export function machine(userContext: UserDefinedContext) {
12
+ const ctx = compact(userContext)
13
+ return createMachine<MachineContext, MachineState>(
14
+ {
15
+ initial: "idle",
16
+
17
+ context: {
18
+ dir: "ltr",
19
+ orientation: "horizontal",
20
+ activationMode: "automatic",
21
+ value: null,
22
+ focusedValue: null,
23
+ previousValues: [],
24
+ indicatorRect: {
25
+ left: "0px",
26
+ top: "0px",
27
+ width: "0px",
28
+ height: "0px",
29
+ },
30
+ canIndicatorTransition: false,
31
+ isIndicatorRendered: false,
32
+ loop: true,
33
+ translations: {},
34
+ ...ctx,
35
+ },
36
+
37
+ computed: {
38
+ isHorizontal: (ctx) => ctx.orientation === "horizontal",
39
+ isVertical: (ctx) => ctx.orientation === "vertical",
40
+ },
41
+
42
+ created: ["setPrevSelectedTabs"],
43
+
44
+ entry: ["checkRenderedElements", "syncIndicatorRect", "setContentTabIndex"],
45
+
46
+ exit: ["cleanupObserver"],
47
+
48
+ watch: {
49
+ focusedValue: "invokeOnFocus",
50
+ value: [
51
+ "enableIndicatorTransition",
52
+ "invokeOnChange",
53
+ "setPrevSelectedTabs",
54
+ "syncIndicatorRect",
55
+ "setContentTabIndex",
56
+ ],
57
+ dir: ["syncIndicatorRect"],
58
+ orientation: ["syncIndicatorRect"],
59
+ },
60
+
61
+ on: {
62
+ SET_VALUE: {
63
+ actions: "setValue",
64
+ },
65
+ CLEAR_VALUE: {
66
+ actions: "clearValue",
67
+ },
68
+ SET_INDICATOR_RECT: {
69
+ actions: "setIndicatorRect",
70
+ },
71
+ },
72
+
73
+ states: {
74
+ idle: {
75
+ on: {
76
+ TAB_FOCUS: [
77
+ {
78
+ guard: "selectOnFocus",
79
+ target: "focused",
80
+ actions: ["setFocusedValue", "setValue"],
81
+ },
82
+ {
83
+ target: "focused",
84
+ actions: "setFocusedValue",
85
+ },
86
+ ],
87
+ TAB_CLICK: {
88
+ target: "focused",
89
+ actions: ["setFocusedValue", "setValue"],
90
+ },
91
+ },
92
+ },
93
+ focused: {
94
+ on: {
95
+ TAB_CLICK: {
96
+ target: "focused",
97
+ actions: ["setFocusedValue", "setValue"],
98
+ },
99
+ ARROW_LEFT: {
100
+ guard: "isHorizontal",
101
+ actions: "focusPrevTab",
102
+ },
103
+ ARROW_RIGHT: {
104
+ guard: "isHorizontal",
105
+ actions: "focusNextTab",
106
+ },
107
+ ARROW_UP: {
108
+ guard: "isVertical",
109
+ actions: "focusPrevTab",
110
+ },
111
+ ARROW_DOWN: {
112
+ guard: "isVertical",
113
+ actions: "focusNextTab",
114
+ },
115
+ HOME: {
116
+ actions: "focusFirstTab",
117
+ },
118
+ END: {
119
+ actions: "focusLastTab",
120
+ },
121
+ ENTER: {
122
+ guard: not("selectOnFocus"),
123
+ actions: "setValue",
124
+ },
125
+ TAB_FOCUS: [
126
+ {
127
+ guard: "selectOnFocus",
128
+ actions: ["setFocusedValue", "setValue"],
129
+ },
130
+ { actions: "setFocusedValue" },
131
+ ],
132
+ TAB_BLUR: {
133
+ target: "idle",
134
+ actions: "clearFocusedValue",
135
+ },
136
+ },
137
+ },
138
+ },
139
+ },
140
+ {
141
+ guards: {
142
+ isVertical: (ctx) => ctx.isVertical,
143
+ isHorizontal: (ctx) => ctx.isHorizontal,
144
+ selectOnFocus: (ctx) => ctx.activationMode === "automatic",
145
+ },
146
+
147
+ actions: {
148
+ setFocusedValue(ctx, evt) {
149
+ ctx.focusedValue = evt.value
150
+ },
151
+ clearFocusedValue(ctx) {
152
+ ctx.focusedValue = null
153
+ },
154
+ setValue(ctx, evt) {
155
+ ctx.value = evt.value
156
+ },
157
+ clearValue(ctx) {
158
+ ctx.value = null
159
+ },
160
+ focusFirstTab(ctx) {
161
+ raf(() => dom.getFirstEl(ctx)?.focus())
162
+ },
163
+ focusLastTab(ctx) {
164
+ raf(() => dom.getLastEl(ctx)?.focus())
165
+ },
166
+ focusNextTab(ctx) {
167
+ if (!ctx.focusedValue) return
168
+ const next = dom.getNextEl(ctx, ctx.focusedValue)
169
+ raf(() => next?.focus())
170
+ },
171
+ focusPrevTab(ctx) {
172
+ if (!ctx.focusedValue) return
173
+ const prev = dom.getPrevEl(ctx, ctx.focusedValue)
174
+ raf(() => prev?.focus())
175
+ },
176
+ checkRenderedElements(ctx) {
177
+ ctx.isIndicatorRendered = !!dom.getIndicatorEl(ctx)
178
+ },
179
+ invokeOnChange(ctx) {
180
+ ctx.onChange?.({ value: ctx.value })
181
+ },
182
+ invokeOnFocus(ctx) {
183
+ ctx.onFocus?.({ value: ctx.focusedValue })
184
+ },
185
+ setPrevSelectedTabs(ctx) {
186
+ if (ctx.value != null) {
187
+ ctx.previousValues = pushUnique(ctx.previousValues, ctx.value)
188
+ }
189
+ },
190
+ // if tab panel contains focusable elements, remove the tabindex attribute
191
+ setContentTabIndex(ctx) {
192
+ raf(() => {
193
+ const panel = dom.getActiveContentEl(ctx)
194
+ if (!panel) return
195
+ const focusables = getFocusables(panel)
196
+ if (focusables.length > 0) {
197
+ panel.removeAttribute("tabindex")
198
+ } else {
199
+ panel.setAttribute("tabindex", "0")
200
+ }
201
+ })
202
+ },
203
+ cleanupObserver(ctx) {
204
+ ctx.indicatorCleanup?.()
205
+ },
206
+ enableIndicatorTransition(ctx) {
207
+ ctx.canIndicatorTransition = true
208
+ },
209
+ setIndicatorRect(ctx, evt) {
210
+ const value = evt.id ?? ctx.value
211
+ if (!ctx.isIndicatorRendered || !value) return
212
+
213
+ const tabEl = dom.getTriggerEl(ctx, value)
214
+ if (!tabEl) return
215
+
216
+ ctx.indicatorRect = dom.getRectById(ctx, value)
217
+ nextTick(() => {
218
+ ctx.canIndicatorTransition = false
219
+ })
220
+ },
221
+ syncIndicatorRect(ctx) {
222
+ ctx.indicatorCleanup?.()
223
+
224
+ const value = ctx.value
225
+ if (!ctx.isIndicatorRendered || !value) return
226
+
227
+ const tabEl = dom.getActiveTabEl(ctx)
228
+ if (!tabEl) return
229
+
230
+ ctx.indicatorCleanup = trackElementRect(tabEl, {
231
+ getRect(el) {
232
+ return dom.getOffsetRect(el)
233
+ },
234
+ onChange(rect) {
235
+ ctx.indicatorRect = dom.resolveRect(rect, ctx.orientation)
236
+ nextTick(() => {
237
+ ctx.canIndicatorTransition = false
238
+ })
239
+ },
240
+ })
241
+ },
242
+ },
243
+ },
244
+ )
245
+ }
246
+
247
+ // function to push value array and remove previous instances of value
248
+ function pushUnique(arr: string[], value: any) {
249
+ const newArr = Array.from(arr).slice()
250
+ const index = newArr.indexOf(value)
251
+ if (index > -1) {
252
+ newArr.splice(index, 1)
253
+ }
254
+ newArr.push(value)
255
+ return newArr
256
+ }
@@ -0,0 +1,130 @@
1
+ import type { StateMachine as S } from "@zag-js/core"
2
+ import type { CommonProperties, Context, DirectionProperty, RequiredBy } from "@zag-js/types"
3
+
4
+ type IntlTranslations = {
5
+ tablistLabel?: string
6
+ }
7
+
8
+ type ElementIds = Partial<{
9
+ root: string
10
+ trigger: string
11
+ tablist: string
12
+ contentGroup: string
13
+ content: string
14
+ indicator: string
15
+ }>
16
+
17
+ export type TriggerProps = {
18
+ value: string
19
+ disabled?: boolean
20
+ }
21
+
22
+ export type ContentProps = {
23
+ value: string
24
+ }
25
+
26
+ type PublicContext = DirectionProperty &
27
+ CommonProperties & {
28
+ /**
29
+ * The ids of the elements in the tabs. Useful for composition.
30
+ */
31
+ ids?: ElementIds
32
+ /**
33
+ * Specifies the localized strings that identifies the accessibility elements and their states
34
+ */
35
+ translations: IntlTranslations
36
+ /**
37
+ * Whether the keyboard navigation will loop from last tab to first, and vice versa.
38
+ * @default true
39
+ */
40
+ loop: boolean
41
+ /**
42
+ * The selected tab id
43
+ */
44
+ value: string | null
45
+ /**
46
+ * The orientation of the tabs. Can be `horizontal` or `vertical`
47
+ * - `horizontal`: only left and right arrow key navigation will work.
48
+ * - `vertical`: only up and down arrow key navigation will work.
49
+ *
50
+ * @default "horizontal"
51
+ */
52
+ orientation?: "horizontal" | "vertical"
53
+ /**
54
+ * The activation mode of the tabs. Can be `manual` or `automatic`
55
+ * - `manual`: Tabs are activated when clicked or press `enter` key.
56
+ * - `automatic`: Tabs are activated when receiving focus
57
+ * @default "automatic"
58
+ */
59
+ activationMode?: "manual" | "automatic"
60
+ /**
61
+ * Callback to be called when the selected/active tab changes
62
+ */
63
+ onChange?: (details: { value: string | null }) => void
64
+ /**
65
+ * Callback to be called when the focused tab changes
66
+ */
67
+ onFocus?: (details: { value: string | null }) => void
68
+ /**
69
+ * Callback to be called when a tab's close button is clicked
70
+ */
71
+ onDelete?: (details: { value: string }) => void
72
+ }
73
+
74
+ export type UserDefinedContext = RequiredBy<PublicContext, "id">
75
+
76
+ type ComputedContext = Readonly<{
77
+ /**
78
+ * @computed
79
+ * Whether the tab is in the horizontal orientation
80
+ */
81
+ isHorizontal: boolean
82
+ /**
83
+ * @computed
84
+ * Whether the tab is in the vertical orientation
85
+ */
86
+ isVertical: boolean
87
+ }>
88
+
89
+ type PrivateContext = Context<{
90
+ /**
91
+ * @internal
92
+ * The focused tab id
93
+ */
94
+ focusedValue: string | null
95
+ /**
96
+ * @internal
97
+ * Whether the indicator is rendered.
98
+ */
99
+ isIndicatorRendered: boolean
100
+ /**
101
+ * @internal
102
+ * The active tab indicator's dom rect
103
+ */
104
+ indicatorRect?: Partial<{ left: string; top: string; width: string; height: string }>
105
+ /**
106
+ * @internal
107
+ * Whether the active tab indicator's rect can transition
108
+ */
109
+ canIndicatorTransition?: boolean
110
+ /**
111
+ * @internal
112
+ * The previously selected tab ids. This is useful for performance optimization
113
+ */
114
+ previousValues: string[]
115
+ /**
116
+ * @internal
117
+ * Function to clean up the observer for the active tab's rect
118
+ */
119
+ indicatorCleanup?: VoidFunction | null
120
+ }>
121
+
122
+ export type MachineContext = PublicContext & ComputedContext & PrivateContext
123
+
124
+ export type MachineState = {
125
+ value: "idle" | "focused"
126
+ }
127
+
128
+ export type State = S.State<MachineContext, MachineState>
129
+
130
+ export type Send = S.Send<S.AnyEventObject>