@seed-design/lynx-react 0.5.0 → 0.5.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.
@@ -1 +1 @@
1
- {"version":3,"file":"RadioGroup.d.ts","sourceRoot":"","sources":["../../../src/components/RadioGroup/RadioGroup.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,gBAAgB,CAAC;AACxC,OAAO,EAAkB,KAAK,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAInE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qCAAqC,CAAC;AAE7E,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,yCAAyC,CAAC;AAKrF,OAAO,KAAK,EACV,oBAAoB,EACpB,sBAAsB,EAGvB,MAAM,aAAa,CAAC;AAsErB,MAAM,WAAW,mBACf,SAAQ,iBAAiB,EACvB,IAAI,CAAC,qBAAqB,EAAE,MAAM,GAAG,SAAS,GAAG,UAAU,CAAC,EAC5D,sBAAsB;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACzC;AAED,eAAO,MAAM,cAAc,qFAyDzB,CAAC;AAKH,MAAM,WAAW,mBAAoB,SAAQ,sBAAsB;IACjE,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,eAAO,MAAM,cAAc,qFA+CzB,CAAC;AAKH,MAAM,WAAW,0BACf,SAAQ,IAAI,CAAC,qBAAqB,EAAE,MAAM,CAAC,EACzC,sBAAsB;CAAG;AAE7B,eAAO,MAAM,qBAAqB,4FA6BjC,CAAC;AAKF,MAAM,WAAW,4BACf,SAAQ,IAAI,CAAC,sBAAsB,EAAE,WAAW,GAAG,OAAO,CAAC;IAC3D,kGAAkG;IAClG,SAAS,CAAC,EAAE,YAAY,CAAC,oBAAoB,CAAC,CAAC;IAC/C,0GAA0G;IAC1G,OAAO,CAAC,EAAE,YAAY,CAAC,oBAAoB,CAAC,CAAC;CAC9C;AAED,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,4BAA4B,qBA6B1E;yBA7Be,uBAAuB;;;AAkCvC,MAAM,WAAW,wBAAyB,SAAQ,sBAAsB;CAAG;AAE3E,eAAO,MAAM,mBAAmB,0FAoB/B,CAAC"}
1
+ {"version":3,"file":"RadioGroup.d.ts","sourceRoot":"","sources":["../../../src/components/RadioGroup/RadioGroup.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,gBAAgB,CAAC;AACxC,OAAO,EAAkB,KAAK,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAInE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qCAAqC,CAAC;AAE7E,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,yCAAyC,CAAC;AAKrF,OAAO,KAAK,EACV,oBAAoB,EACpB,sBAAsB,EAGvB,MAAM,aAAa,CAAC;AAsErB,MAAM,WAAW,mBACf,SAAQ,iBAAiB,EACvB,IAAI,CAAC,qBAAqB,EAAE,MAAM,GAAG,SAAS,GAAG,UAAU,CAAC,EAC5D,sBAAsB;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACzC;AAED,eAAO,MAAM,cAAc,qFAyDzB,CAAC;AAKH,MAAM,WAAW,mBAAoB,SAAQ,sBAAsB;IACjE,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,eAAO,MAAM,cAAc,qFA+CzB,CAAC;AAKH,MAAM,WAAW,0BACf,SAAQ,IAAI,CAAC,qBAAqB,EAAE,MAAM,CAAC,EACzC,sBAAsB;CAAG;AAE7B,eAAO,MAAM,qBAAqB,4FA8BjC,CAAC;AAKF,MAAM,WAAW,4BACf,SAAQ,IAAI,CAAC,sBAAsB,EAAE,WAAW,GAAG,OAAO,CAAC;IAC3D,kGAAkG;IAClG,SAAS,CAAC,EAAE,YAAY,CAAC,oBAAoB,CAAC,CAAC;IAC/C,0GAA0G;IAC1G,OAAO,CAAC,EAAE,YAAY,CAAC,oBAAoB,CAAC,CAAC;CAC9C;AAED,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,4BAA4B,qBA6B1E;yBA7Be,uBAAuB;;;AAkCvC,MAAM,WAAW,wBAAyB,SAAQ,sBAAsB;CAAG;AAE3E,eAAO,MAAM,mBAAmB,0FAoB/B,CAAC"}
@@ -119,11 +119,12 @@ var RadioGroupItemControl = React.forwardRef((props, ref) => {
119
119
  pressed: itemContext.pressed
120
120
  };
121
121
  const classes = radiomark(radiomarkVariantProps);
122
+ const controlClassName = radio(groupContext.radioVariantProps).control;
122
123
  return <RadiomarkControlContext.Provider value={{
123
124
  iconClassName: classes.icon,
124
125
  radiomarkVariantProps
125
126
  }}>
126
- <view {...ref ? { ref } : {}} className={clsx(classes.root, className)} {...nativeProps}>
127
+ <view {...ref ? { ref } : {}} className={clsx(classes.root, controlClassName, className)} {...nativeProps}>
127
128
  {children}
128
129
  </view>
129
130
  </RadiomarkControlContext.Provider>;
@@ -3,7 +3,7 @@ import { IntrinsicElements } from '@lynx-js/types';
3
3
  import { LynxAccessibilityProps, LynxStyledElementProps } from '../../types';
4
4
  import * as React from "@lynx-js/react";
5
5
  type NativeViewPagerProps = IntrinsicElements["viewpager"];
6
- type TabsPublicVariantProps = Omit<TabsVariantProps, "selected" | "disabled" | "inCarousel">;
6
+ type TabsPublicVariantProps = Omit<TabsVariantProps, "selected" | "disabled" | "inCarousel" | "transitionEnabled">;
7
7
  /**
8
8
  * @platform Lynx
9
9
  *
@@ -1 +1 @@
1
- {"version":3,"file":"Tabs.d.ts","sourceRoot":"","sources":["../../../src/components/Tabs/Tabs.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,gBAAgB,EAAE,MAAM,oCAAoC,CAAC;AACjF,OAAO,KAAK,KAAK,MAAM,gBAAgB,CAAC;AACxC,OAAO,KAAK,EACV,iBAAiB,EAMlB,MAAM,gBAAgB,CAAC;AAKxB,OAAO,KAAK,EACV,sBAAsB,EACtB,sBAAsB,EAGvB,MAAM,aAAa,CAAC;AAIrB,KAAK,oBAAoB,GAAG,iBAAiB,CAAC,WAAW,CAAC,CAAC;AAI3D,KAAK,sBAAsB,GAAG,IAAI,CAAC,gBAAgB,EAAE,UAAU,GAAG,UAAU,GAAG,YAAY,CAAC,CAAC;AAyF7F;;;;;;;GAOG;AACH,MAAM,WAAW,aAAc,SAAQ,sBAAsB,EAAE,sBAAsB;IACnF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACzC;AAED,eAAO,MAAM,QAAQ,+EAmMnB,CAAC;AAKH,MAAM,WAAW,aAAc,SAAQ,sBAAsB;CAAG;AAEhE,eAAO,MAAM,QAAQ,+EAsCnB,CAAC;AAKH,MAAM,WAAW,gBAAiB,SAAQ,sBAAsB;IAC9D,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,qBAAqB,CAAC,EAAE,sBAAsB,CAAC,qBAAqB,CAAC,CAAC;CACvE;AAED,eAAO,MAAM,WAAW,kFAiEtB,CAAC;AAKH,MAAM,WAAW,kBAAmB,SAAQ,sBAAsB;CAAG;AAErE,eAAO,MAAM,aAAa,oFAkCxB,CAAC;AAKH,MAAM,WAAW,gBAAiB,SAAQ,sBAAsB;IAC9D,KAAK,EAAE,MAAM,CAAC;CACf;AAED,eAAO,MAAM,WAAW,kFAgCtB,CAAC;AAKH;;;;;GAKG;AACH,MAAM,WAAW,iBAAkB,SAAQ,sBAAsB;IAC/D,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,2CAA2C;IAC3C,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;CACzB;AAED,eAAO,MAAM,YAAY,mFA8BvB,CAAC;AAKH,MAAM,WAAW,uBAAwB,SAAQ,sBAAsB;IACrE,UAAU,CAAC,EAAE,oBAAoB,CAAC,YAAY,CAAC,CAAC;IAChD,cAAc,CAAC,EAAE,oBAAoB,CAAC,gBAAgB,CAAC,CAAC;IACxD,gBAAgB,CAAC,EAAE,oBAAoB,CAAC,kBAAkB,CAAC,CAAC;CAC7D;AAED,eAAO,MAAM,kBAAkB,yFAmH9B,CAAC"}
1
+ {"version":3,"file":"Tabs.d.ts","sourceRoot":"","sources":["../../../src/components/Tabs/Tabs.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,gBAAgB,EAAE,MAAM,oCAAoC,CAAC;AACjF,OAAO,KAAK,KAAK,MAAM,gBAAgB,CAAC;AACxC,OAAO,KAAK,EACV,iBAAiB,EAMlB,MAAM,gBAAgB,CAAC;AAKxB,OAAO,KAAK,EACV,sBAAsB,EACtB,sBAAsB,EAGvB,MAAM,aAAa,CAAC;AAWrB,KAAK,oBAAoB,GAAG,iBAAiB,CAAC,WAAW,CAAC,CAAC;AAI3D,KAAK,sBAAsB,GAAG,IAAI,CAChC,gBAAgB,EAChB,UAAU,GAAG,UAAU,GAAG,YAAY,GAAG,mBAAmB,CAC7D,CAAC;AAkFF;;;;;;;GAOG;AACH,MAAM,WAAW,aAAc,SAAQ,sBAAsB,EAAE,sBAAsB;IACnF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACzC;AAED,eAAO,MAAM,QAAQ,+EAoNnB,CAAC;AAyBH,MAAM,WAAW,aAAc,SAAQ,sBAAsB;CAAG;AAEhE,eAAO,MAAM,QAAQ,+EAkCnB,CAAC;AAKH,MAAM,WAAW,gBAAiB,SAAQ,sBAAsB;IAC9D,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,qBAAqB,CAAC,EAAE,sBAAsB,CAAC,qBAAqB,CAAC,CAAC;CACvE;AAED,eAAO,MAAM,WAAW,kFAkEtB,CAAC;AAKH,MAAM,WAAW,kBAAmB,SAAQ,sBAAsB;CAAG;AAErE,eAAO,MAAM,aAAa,oFAiCxB,CAAC;AAKH,MAAM,WAAW,gBAAiB,SAAQ,sBAAsB;IAC9D,KAAK,EAAE,MAAM,CAAC;CACf;AAED,eAAO,MAAM,WAAW,kFAgCtB,CAAC;AAKH;;;;;GAKG;AACH,MAAM,WAAW,iBAAkB,SAAQ,sBAAsB;IAC/D,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,2CAA2C;IAC3C,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;CACzB;AAED,eAAO,MAAM,YAAY,mFA8BvB,CAAC;AAKH,MAAM,WAAW,uBAAwB,SAAQ,sBAAsB;IACrE,UAAU,CAAC,EAAE,oBAAoB,CAAC,YAAY,CAAC,CAAC;IAChD,cAAc,CAAC,EAAE,oBAAoB,CAAC,gBAAgB,CAAC,CAAC;IACxD,gBAAgB,CAAC,EAAE,oBAAoB,CAAC,kBAAkB,CAAC,CAAC;CAC7D;AAED,eAAO,MAAM,kBAAkB,yFAgH9B,CAAC"}
@@ -1,6 +1,7 @@
1
1
  import { useControllableState } from "../../hooks/useControllableState.js";
2
2
  import { usePressTap } from "../../hooks/usePressTap.js";
3
3
  import { createSlotRecipeContext } from "../../utils/create-slot-recipe-context.jsx";
4
+ import { areTabsTransitionsEnabled, getTabsLayoutWidth, getTabsOrderedItems, getTabsTriggerRects } from "./Tabs.utils.js";
4
5
  import * as React from "@lynx-js/react";
5
6
  import clsx from "clsx";
6
7
  import { tabs } from "@seed-design/lynx-css/recipes/tabs";
@@ -32,15 +33,6 @@ function invokeScrollToOffset(list, offset) {
32
33
  }).exec();
33
34
  } catch {}
34
35
  }
35
- function getLayoutRect(event) {
36
- const width = event.detail?.width ?? event.params?.width;
37
- const left = event.detail?.left ?? event.params?.left;
38
- if (!Number.isFinite(width) || !Number.isFinite(left)) return null;
39
- return {
40
- width: Math.max(0, width),
41
- left
42
- };
43
- }
44
36
  var TabsContext = React.createContext(null);
45
37
  function useTabsContext(consumer) {
46
38
  const context = React.useContext(TabsContext);
@@ -60,7 +52,6 @@ function useTabsCarouselContext(consumer) {
60
52
  var TabsRoot = React.forwardRef((props, ref) => {
61
53
  const [variantProps, otherProps] = tabs.splitVariantProps(props);
62
54
  const { children, className, style, value: valueProp, defaultValue, onValueChange, ...nativeProps } = otherProps;
63
- const classNames = tabs(variantProps);
64
55
  const [value, setValueInternal] = useControllableState({
65
56
  value: valueProp,
66
57
  defaultValue,
@@ -72,17 +63,21 @@ var TabsRoot = React.forwardRef((props, ref) => {
72
63
  const [items, setItems] = React.useState([]);
73
64
  const [contentValues, setContentValues] = React.useState([]);
74
65
  const [indicatorValue, setIndicatorValue] = React.useState();
75
- const [triggerRects, setTriggerRects] = React.useState({});
76
- const [listLeft, setListLeft] = React.useState(0);
66
+ const [triggerWidths, setTriggerWidths] = React.useState({});
77
67
  const listRef = React.useRef(null);
78
68
  const pagerRef = React.useRef(null);
79
69
  const indicatorRef = React.useMainThreadRef(null);
80
70
  const pagerValues = React.useMemo(() => contentValues.filter((contentValue) => !items.find((item) => item.value === contentValue)?.disabled), [contentValues, items]);
71
+ const triggerRects = React.useMemo(() => getTabsTriggerRects(items.map((item) => item.value), triggerWidths), [items, triggerWidths]);
72
+ const transitionsEnabled = areTabsTransitionsEnabled(items.map((item) => item.value), triggerRects);
73
+ const classNames = tabs({
74
+ ...variantProps,
75
+ transitionEnabled: transitionsEnabled
76
+ });
81
77
  const visualValue = indicatorValue ?? value;
82
78
  const indicatorIndex = items.findIndex((item) => item.value === visualValue);
83
79
  const selectedPagerIndex = value === void 0 ? -1 : pagerValues.indexOf(value);
84
- const selectedRect = value === void 0 ? void 0 : triggerRects[value];
85
- const selectedOffset = selectedRect ? Math.max(0, selectedRect.left - listLeft) : null;
80
+ const selectedOffset = (value === void 0 ? void 0 : triggerRects[value])?.left ?? null;
86
81
  const setListNode = React.useCallback((node) => {
87
82
  listRef.current = node;
88
83
  if (node && selectedOffset !== null) invokeScrollToOffset(node, selectedOffset);
@@ -102,7 +97,7 @@ var TabsRoot = React.forwardRef((props, ref) => {
102
97
  return () => {
103
98
  "background only";
104
99
  setItems((current) => current.filter((item) => item.value !== triggerValue));
105
- setTriggerRects((current) => {
100
+ setTriggerWidths((current) => {
106
101
  const next = { ...current };
107
102
  delete next[triggerValue];
108
103
  return next;
@@ -122,14 +117,16 @@ var TabsRoot = React.forwardRef((props, ref) => {
122
117
  disabled
123
118
  } : item));
124
119
  }, []);
125
- const updateTriggerRect = React.useCallback((triggerValue, rect) => {
126
- setTriggerRects((current) => {
127
- const previous = current[triggerValue];
128
- if (previous?.left === rect.left && previous.width === rect.width) return current;
129
- return {
130
- ...current,
131
- [triggerValue]: rect
132
- };
120
+ const syncTriggerOrder = React.useCallback((triggerValues) => {
121
+ setItems((current) => {
122
+ const ordered = getTabsOrderedItems(current, triggerValues);
123
+ return ordered.every((item, index) => item === current[index]) ? current : ordered;
124
+ });
125
+ }, []);
126
+ const updateTriggerWidth = React.useCallback((triggerValue, width) => {
127
+ setTriggerWidths((current) => current[triggerValue] === width ? current : {
128
+ ...current,
129
+ [triggerValue]: width
133
130
  });
134
131
  }, []);
135
132
  const selectValue = React.useCallback((nextValue) => {
@@ -159,13 +156,13 @@ var TabsRoot = React.forwardRef((props, ref) => {
159
156
  indicatorIndex,
160
157
  selectedPagerIndex,
161
158
  indicatorRef,
162
- listLeft,
159
+ transitionsEnabled,
163
160
  triggerRects,
164
161
  registerTrigger,
165
162
  registerContent,
166
163
  updateTriggerDisabled,
167
- updateTriggerRect,
168
- setListLeft,
164
+ syncTriggerOrder,
165
+ updateTriggerWidth,
169
166
  setListRef: setListNode,
170
167
  setPagerRef: setPagerNode,
171
168
  selectValue,
@@ -180,12 +177,13 @@ var TabsRoot = React.forwardRef((props, ref) => {
180
177
  indicatorIndex,
181
178
  selectedPagerIndex,
182
179
  indicatorRef,
183
- listLeft,
184
180
  triggerRects,
181
+ transitionsEnabled,
185
182
  registerTrigger,
186
183
  registerContent,
187
184
  updateTriggerDisabled,
188
- updateTriggerRect,
185
+ syncTriggerOrder,
186
+ updateTriggerWidth,
189
187
  setListNode,
190
188
  setPagerNode,
191
189
  selectValue,
@@ -201,21 +199,42 @@ var TabsRoot = React.forwardRef((props, ref) => {
201
199
  </TabsContext.Provider>;
202
200
  });
203
201
  TabsRoot.displayName = "TabsRoot";
202
+ function getTabsTriggerValues(children) {
203
+ const values = [];
204
+ function visit(node) {
205
+ if (Array.isArray(node)) {
206
+ node.forEach(visit);
207
+ return;
208
+ }
209
+ if (!React.isValidElement(node)) return;
210
+ if (node.type === TabsTrigger) {
211
+ values.push(node.props.value);
212
+ return;
213
+ }
214
+ if (node.type === React.Fragment) visit(node.props.children);
215
+ }
216
+ visit(children);
217
+ return values;
218
+ }
204
219
  var TabsList = React.forwardRef((props, ref) => {
205
220
  const { children, className, style, ...nativeProps } = props;
206
221
  const classNames = useClassNames();
207
- const { setListLeft, setListRef } = useTabsContext("TabsList");
222
+ const { items, setListRef, syncTriggerOrder } = useTabsContext("TabsList");
223
+ const triggerOrder = React.useMemo(() => getTabsTriggerValues(children), [children]);
224
+ React.useEffect(() => {
225
+ "background only";
226
+ syncTriggerOrder(triggerOrder);
227
+ }, [
228
+ items,
229
+ syncTriggerOrder,
230
+ triggerOrder
231
+ ]);
208
232
  const mergedRef = React.useCallback((node) => {
209
233
  setListRef(node);
210
234
  if (typeof ref === "function") ref(node);
211
235
  else if (ref) ref.current = node;
212
236
  }, [ref, setListRef]);
213
- const handleLayoutChange = React.useCallback((...args) => {
214
- "background only";
215
- const rect = getLayoutRect(args[0]);
216
- if (rect) setListLeft(rect.left);
217
- }, [setListLeft]);
218
- return <scroll-view ref={mergedRef} {...nativeProps} scroll-orientation="horizontal" scroll-bar-enable={false} bindlayoutchange={handleLayoutChange} accessibility-element={false} accessibility-traits="tabbar" className={clsx(classNames.list, className)} style={style}>
237
+ return <scroll-view ref={mergedRef} {...nativeProps} scroll-orientation="horizontal" scroll-bar-enable={false} accessibility-element={false} accessibility-traits="tabbar" className={clsx(classNames.list, className)} style={style}>
219
238
  <view className={classNames.listContent}>{children}</view>
220
239
  </scroll-view>;
221
240
  });
@@ -246,13 +265,14 @@ var TabsTrigger = React.forwardRef((props, ref) => {
246
265
  });
247
266
  const handleLayoutChange = React.useCallback((...args) => {
248
267
  "background only";
249
- const rect = getLayoutRect(args[0]);
250
- if (rect) context.updateTriggerRect(triggerValue, rect);
251
- }, [context.updateTriggerRect, triggerValue]);
268
+ const width = getTabsLayoutWidth(args[0]);
269
+ if (width !== null) context.updateTriggerWidth(triggerValue, width);
270
+ }, [context.updateTriggerWidth, triggerValue]);
252
271
  const triggerClasses = tabs({
253
272
  ...context.variantProps,
254
273
  selected: visuallySelected,
255
- disabled
274
+ disabled,
275
+ transitionEnabled: context.transitionsEnabled
256
276
  });
257
277
  const label = typeof children === "string" || typeof children === "number" ? String(children) : void 0;
258
278
  return <view {...ref ? { ref } : {}} {...nativeProps} {...pressHandlers} flatten={false} bindlayoutchange={handleLayoutChange} accessibility-element={true} accessibility-role-description="tab" accessibility-label={accessibilityLabel ?? label} accessibility-value={selected ? "selected" : "not selected"} accessibility-traits={disabled ? "disabled" : selected ? "selected" : "button"} className={clsx(triggerClasses.trigger, className)} style={style}>
@@ -263,14 +283,14 @@ TabsTrigger.displayName = "TabsTrigger";
263
283
  var TabsIndicator = React.forwardRef((props, ref) => {
264
284
  const { className, style, ...nativeProps } = props;
265
285
  const classNames = useClassNames();
266
- const { indicatorRef, items, listLeft, indicatorIndex, triggerRects } = useTabsContext("TabsIndicator");
286
+ const { indicatorRef, items, indicatorIndex, triggerRects } = useTabsContext("TabsIndicator");
267
287
  const position = indicatorIndex;
268
288
  const lowerIndex = Math.max(0, Math.floor(position));
269
289
  const upperIndex = Math.min(items.length - 1, Math.ceil(position));
270
290
  const progress = Math.max(0, Math.min(1, position - lowerIndex));
271
291
  const lowerRect = triggerRects[items[lowerIndex]?.value ?? ""];
272
292
  const upperRect = triggerRects[items[upperIndex]?.value ?? ""] ?? lowerRect;
273
- const x = lowerRect ? lowerRect.left - listLeft + ((upperRect?.left ?? lowerRect.left) - lowerRect.left) * progress : 0;
293
+ const x = lowerRect ? lowerRect.left + ((upperRect?.left ?? lowerRect.left) - lowerRect.left) * progress : 0;
274
294
  const width = lowerRect ? lowerRect.width + ((upperRect?.width ?? lowerRect.width) - lowerRect.width) * progress : 0;
275
295
  return <view {...ref ? { ref } : {}} main-thread:ref={indicatorRef} {...nativeProps} accessibility-elements-hidden={true} className={clsx(classNames.indicator, className)} style={{
276
296
  "--tabs-indicator-x": `${x}px`,
@@ -337,7 +357,7 @@ var TabsCarouselCamera = React.forwardRef((props, ref) => {
337
357
  const tabsContext = useTabsContext("TabsCarouselCamera");
338
358
  const carouselContext = useTabsCarouselContext("TabsCarouselCamera");
339
359
  const swipingRef = React.useRef(false);
340
- const { indicatorRef, pagerValues, listLeft, triggerRects } = tabsContext;
360
+ const { indicatorRef, pagerValues, triggerRects } = tabsContext;
341
361
  const indicatorRects = pagerValues.map((value) => triggerRects[value] ?? null);
342
362
  const mergedRef = React.useCallback((node) => {
343
363
  tabsContext.setPagerRef(node);
@@ -388,7 +408,7 @@ var TabsCarouselCamera = React.forwardRef((props, ref) => {
388
408
  const lowerRect = indicatorRects[lowerIndex];
389
409
  const upperRect = indicatorRects[upperIndex] ?? lowerRect;
390
410
  if (!lowerRect) return;
391
- const x = lowerRect.left - listLeft + ((upperRect?.left ?? lowerRect.left) - lowerRect.left) * progress;
411
+ const x = lowerRect.left + ((upperRect?.left ?? lowerRect.left) - lowerRect.left) * progress;
392
412
  const width = lowerRect.width + ((upperRect?.width ?? lowerRect.width) - lowerRect.width) * progress;
393
413
  indicatorRef.current?.setStyleProperties({
394
414
  "--tabs-indicator-x": `${x}px`,
@@ -0,0 +1,19 @@
1
+ export interface TabsLayoutChangeEvent {
2
+ detail?: {
3
+ width?: number;
4
+ };
5
+ params?: {
6
+ width?: number;
7
+ };
8
+ }
9
+ export interface TabsLayoutRect {
10
+ left: number;
11
+ width: number;
12
+ }
13
+ export declare function getTabsLayoutWidth(event: TabsLayoutChangeEvent): number | null;
14
+ export declare function getTabsOrderedItems<T extends {
15
+ value: string;
16
+ }>(items: T[], values: string[]): T[];
17
+ export declare function getTabsTriggerRects(values: string[], widths: Record<string, number>): Record<string, TabsLayoutRect>;
18
+ export declare function areTabsTransitionsEnabled(values: string[], rects: Record<string, TabsLayoutRect>): boolean;
19
+ //# sourceMappingURL=Tabs.utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Tabs.utils.d.ts","sourceRoot":"","sources":["../../../src/components/Tabs/Tabs.utils.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,qBAAqB;IACpC,MAAM,CAAC,EAAE;QACP,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC;IACF,MAAM,CAAC,EAAE;QACP,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC;CACH;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,qBAAqB,GAAG,MAAM,GAAG,IAAI,CAI9E;AAED,wBAAgB,mBAAmB,CAAC,CAAC,SAAS;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,EAC7D,KAAK,EAAE,CAAC,EAAE,EACV,MAAM,EAAE,MAAM,EAAE,GACf,CAAC,EAAE,CAUL;AAED,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,MAAM,EAAE,EAChB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC7B,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAiBhC;AAED,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,MAAM,EAAE,EAChB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,GACpC,OAAO,CAET"}
@@ -0,0 +1,40 @@
1
+ //#region src/components/Tabs/Tabs.utils.ts
2
+ function getTabsLayoutWidth(event) {
3
+ const width = event.detail?.width ?? event.params?.width;
4
+ if (typeof width !== "number" || !Number.isFinite(width)) return null;
5
+ return Math.max(0, width);
6
+ }
7
+ function getTabsOrderedItems(items, values) {
8
+ const itemsByValue = new Map(items.map((item) => [item.value, item]));
9
+ const ordered = values.flatMap((value) => {
10
+ const item = itemsByValue.get(value);
11
+ if (!item) return [];
12
+ itemsByValue.delete(value);
13
+ return [item];
14
+ });
15
+ ordered.push(...itemsByValue.values());
16
+ return ordered;
17
+ }
18
+ function getTabsTriggerRects(values, widths) {
19
+ const rects = Object.create(null);
20
+ let left = 0;
21
+ let hasCompletePrefix = true;
22
+ for (const value of values) {
23
+ const width = Object.prototype.hasOwnProperty.call(widths, value) ? widths[value] : void 0;
24
+ if (width === void 0) {
25
+ hasCompletePrefix = false;
26
+ continue;
27
+ }
28
+ if (hasCompletePrefix) rects[value] = {
29
+ left,
30
+ width
31
+ };
32
+ left += width;
33
+ }
34
+ return rects;
35
+ }
36
+ function areTabsTransitionsEnabled(values, rects) {
37
+ return values.length > 0 && values.every((value) => rects[value] !== void 0);
38
+ }
39
+ //#endregion
40
+ export { areTabsTransitionsEnabled, getTabsLayoutWidth, getTabsOrderedItems, getTabsTriggerRects };
@@ -1 +1 @@
1
- {"version":3,"file":"TextField.d.ts","sourceRoot":"","sources":["../../../src/components/TextField/TextField.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"TextField.d.ts","sourceRoot":"","sources":["../../../src/components/TextField/TextField.tsx"],"names":[],"mappings":"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"}
@@ -257,12 +257,16 @@ function useNativeTextControl({ forwardedRef, disabled: disabledProp, readOnly:
257
257
  ]);
258
258
  const handleBlur = React.useCallback((event) => {
259
259
  "background only";
260
+ const nativeValue = event.detail?.value;
261
+ if (typeof nativeValue === "string") lastNativeValueRef.current = nativeValue;
260
262
  textFieldContext.setFocused(false);
261
263
  keyboardAvoidance?.blur(ownerRef.current);
262
264
  bindblur?.(event);
265
+ if (typeof nativeValue === "string") reconcileControlledValue(nativeValue);
263
266
  }, [
264
267
  bindblur,
265
268
  keyboardAvoidance,
269
+ reconcileControlledValue,
266
270
  textFieldContext.setFocused
267
271
  ]);
268
272
  const notifyLayoutChanged = React.useCallback(() => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seed-design/lynx-react",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/daangn/seed-design.git",
@@ -39,7 +39,7 @@
39
39
  "devDependencies": {
40
40
  "@lynx-js/react": "^0.117.0",
41
41
  "@lynx-js/types": "^3.7.0",
42
- "@seed-design/lynx-css": "0.9.0",
42
+ "@seed-design/lynx-css": "0.9.3",
43
43
  "@testing-library/jest-dom": "^6.9.1",
44
44
  "jsdom": "^29.0.2",
45
45
  "preact": "npm:@lynx-js/internal-preact@10.28.4-ee7bb26",
@@ -232,6 +232,7 @@ export const RadioGroupItemControl = React.forwardRef<unknown, RadioGroupItemCon
232
232
  pressed: itemContext.pressed,
233
233
  };
234
234
  const classes = radiomark(radiomarkVariantProps);
235
+ const controlClassName = radio(groupContext.radioVariantProps).control;
235
236
 
236
237
  return (
237
238
  <RadiomarkControlContext.Provider
@@ -239,7 +240,7 @@ export const RadioGroupItemControl = React.forwardRef<unknown, RadioGroupItemCon
239
240
  >
240
241
  <view
241
242
  {...(ref ? { ref: ref as LynxViewRef } : {})}
242
- className={clsx(classes.root, className)}
243
+ className={clsx(classes.root, controlClassName, className)}
243
244
  {...nativeProps}
244
245
  >
245
246
  {children}
@@ -3,6 +3,12 @@ import { fireEvent, render } from "@lynx-js/react/testing-library";
3
3
  import { describe, expect, it, vi } from "vitest";
4
4
 
5
5
  import * as Tabs from "./Tabs.namespace";
6
+ import {
7
+ areTabsTransitionsEnabled,
8
+ getTabsLayoutWidth,
9
+ getTabsOrderedItems,
10
+ getTabsTriggerRects,
11
+ } from "./Tabs.utils";
6
12
 
7
13
  function BasicTabs(props: {
8
14
  value?: string;
@@ -40,6 +46,26 @@ describe("Tabs", () => {
40
46
  expect(listContent?.querySelectorAll(".seed-tabs__trigger")).toHaveLength(2);
41
47
  });
42
48
 
49
+ it("disables label and indicator transitions during the initial render", () => {
50
+ const { container } = render(<BasicTabs defaultValue="two" />);
51
+ const labels = container.querySelectorAll<HTMLElement>(".seed-tabs__triggerLabel");
52
+ const indicator = container.querySelector<HTMLElement>(".seed-tabs__indicator");
53
+
54
+ expect(indicator).toHaveClass("seed-tabs__indicator--transitionEnabled_false");
55
+ expect(indicator?.style.transitionDuration).toBe("");
56
+ for (const label of labels) {
57
+ expect(label).toHaveClass("seed-tabs__triggerLabel--transitionEnabled_false");
58
+ expect(label.style.transitionDuration).toBe("");
59
+ }
60
+ });
61
+
62
+ it("disables transitions when a dynamically added trigger is unmeasured", () => {
63
+ const rects = getTabsTriggerRects(["one", "two"], { one: 80, two: 80 });
64
+
65
+ expect(areTabsTransitionsEnabled(["one", "two"], rects)).toBe(true);
66
+ expect(areTabsTransitionsEnabled(["one", "two", "three"], rects)).toBe(false);
67
+ });
68
+
43
69
  it("changes an uncontrolled value when a trigger is tapped", () => {
44
70
  const onValueChange = vi.fn();
45
71
  const { container } = render(<BasicTabs defaultValue="one" onValueChange={onValueChange} />);
@@ -56,6 +82,45 @@ describe("Tabs", () => {
56
82
  expect(second).toHaveAttribute("accessibility-value", "selected");
57
83
  });
58
84
 
85
+ it("derives trigger positions from ordered widths instead of platform coordinates", () => {
86
+ expect(getTabsLayoutWidth({ detail: { width: 128 }, params: { width: 128 } })).toBe(128);
87
+ expect(
88
+ getTabsTriggerRects(["one", "two", "three"], {
89
+ one: 128,
90
+ two: 128,
91
+ three: 128,
92
+ }),
93
+ ).toEqual({
94
+ one: { left: 0, width: 128 },
95
+ two: { left: 128, width: 128 },
96
+ three: { left: 256, width: 128 },
97
+ });
98
+ });
99
+
100
+ it("waits for every preceding trigger width before positioning later triggers", () => {
101
+ expect(getTabsTriggerRects(["one", "two", "three"], { one: 80, three: 120 })).toEqual({
102
+ one: { left: 0, width: 80 },
103
+ });
104
+ });
105
+
106
+ it("ignores inherited object properties used as trigger values", () => {
107
+ expect(getTabsTriggerRects(["toString"], {})).toEqual({});
108
+ expect(getTabsTriggerRects(["constructor"], {})).toEqual({});
109
+ expect(getTabsTriggerRects(["__proto__"], {})).toEqual({});
110
+ });
111
+
112
+ it("updates trigger positions when keyed triggers are reordered", () => {
113
+ const items = [{ value: "one" }, { value: "two" }, { value: "three" }];
114
+ const reordered = getTabsOrderedItems(items, ["three", "two", "one"]);
115
+ const rects = getTabsTriggerRects(
116
+ reordered.map((item) => item.value),
117
+ { one: 80, two: 80, three: 80 },
118
+ );
119
+
120
+ expect(reordered.map((item) => item.value)).toEqual(["three", "two", "one"]);
121
+ expect(rects["three"]?.left).toBe(0);
122
+ });
123
+
59
124
  it("does not select a disabled trigger", () => {
60
125
  const onValueChange = vi.fn();
61
126
  const { container } = render(
@@ -19,13 +19,23 @@ import type {
19
19
  LynxViewRef,
20
20
  } from "../../types";
21
21
  import { createSlotRecipeContext } from "../../utils/create-slot-recipe-context";
22
+ import {
23
+ areTabsTransitionsEnabled,
24
+ getTabsLayoutWidth,
25
+ getTabsOrderedItems,
26
+ getTabsTriggerRects,
27
+ type TabsLayoutRect,
28
+ } from "./Tabs.utils";
22
29
 
23
30
  type NativeViewProps = IntrinsicElements["view"];
24
31
  type NativeViewPagerProps = IntrinsicElements["viewpager"];
25
32
  type LayoutChangeHandler = NonNullable<NativeViewProps["bindlayoutchange"]>;
26
- type TriggerRect = { left: number; width: number };
33
+ type TriggerRect = TabsLayoutRect;
27
34
  type TriggerItem = { value: string; disabled: boolean };
28
- type TabsPublicVariantProps = Omit<TabsVariantProps, "selected" | "disabled" | "inCarousel">;
35
+ type TabsPublicVariantProps = Omit<
36
+ TabsVariantProps,
37
+ "selected" | "disabled" | "inCarousel" | "transitionEnabled"
38
+ >;
29
39
 
30
40
  const { ClassNamesProvider, useClassNames } = createSlotRecipeContext(tabs);
31
41
 
@@ -49,13 +59,6 @@ function invokeScrollToOffset(list: NodesRef | null, offset: number) {
49
59
  }
50
60
  }
51
61
 
52
- function getLayoutRect(event: Parameters<LayoutChangeHandler>[0]): TriggerRect | null {
53
- const width = event.detail?.width ?? event.params?.width;
54
- const left = event.detail?.left ?? event.params?.left;
55
- if (!Number.isFinite(width) || !Number.isFinite(left)) return null;
56
- return { width: Math.max(0, width), left };
57
- }
58
-
59
62
  interface TabsContextValue {
60
63
  value: string | undefined;
61
64
  visualValue: string | undefined;
@@ -65,13 +68,13 @@ interface TabsContextValue {
65
68
  indicatorIndex: number;
66
69
  selectedPagerIndex: number;
67
70
  indicatorRef: React.RefObject<MainThread.Element>;
68
- listLeft: number;
69
71
  triggerRects: Record<string, TriggerRect>;
72
+ transitionsEnabled: boolean;
70
73
  registerTrigger: (value: string, disabled: boolean) => () => void;
71
74
  registerContent: (value: string) => () => void;
72
75
  updateTriggerDisabled: (value: string, disabled: boolean) => void;
73
- updateTriggerRect: (value: string, rect: TriggerRect) => void;
74
- setListLeft: (left: number) => void;
76
+ syncTriggerOrder: (values: string[]) => void;
77
+ updateTriggerWidth: (value: string, width: number) => void;
75
78
  setListRef: (ref: NodesRef | null) => void;
76
79
  setPagerRef: (ref: NodesRef | null) => void;
77
80
  selectValue: (value: string) => void;
@@ -139,7 +142,6 @@ export const TabsRoot = React.forwardRef<unknown, TabsRootProps>((props, ref) =>
139
142
  onValueChange,
140
143
  ...nativeProps
141
144
  } = otherProps;
142
- const classNames = tabs(variantProps);
143
145
  const [value, setValueInternal] = useControllableState<string | undefined>({
144
146
  value: valueProp,
145
147
  defaultValue,
@@ -151,8 +153,7 @@ export const TabsRoot = React.forwardRef<unknown, TabsRootProps>((props, ref) =>
151
153
  const [items, setItems] = React.useState<TriggerItem[]>([]);
152
154
  const [contentValues, setContentValues] = React.useState<string[]>([]);
153
155
  const [indicatorValue, setIndicatorValue] = React.useState<string | undefined>();
154
- const [triggerRects, setTriggerRects] = React.useState<Record<string, TriggerRect>>({});
155
- const [listLeft, setListLeft] = React.useState(0);
156
+ const [triggerWidths, setTriggerWidths] = React.useState<Record<string, number>>({});
156
157
  const listRef = React.useRef<NodesRef | null>(null);
157
158
  const pagerRef = React.useRef<NodesRef | null>(null);
158
159
  const indicatorRef = React.useMainThreadRef<MainThread.Element>(null);
@@ -164,11 +165,24 @@ export const TabsRoot = React.forwardRef<unknown, TabsRootProps>((props, ref) =>
164
165
  ),
165
166
  [contentValues, items],
166
167
  );
168
+ const triggerRects = React.useMemo(
169
+ () =>
170
+ getTabsTriggerRects(
171
+ items.map((item) => item.value),
172
+ triggerWidths,
173
+ ),
174
+ [items, triggerWidths],
175
+ );
176
+ const transitionsEnabled = areTabsTransitionsEnabled(
177
+ items.map((item) => item.value),
178
+ triggerRects,
179
+ );
180
+ const classNames = tabs({ ...variantProps, transitionEnabled: transitionsEnabled });
167
181
  const visualValue = indicatorValue ?? value;
168
182
  const indicatorIndex = items.findIndex((item) => item.value === visualValue);
169
183
  const selectedPagerIndex = value === undefined ? -1 : pagerValues.indexOf(value);
170
184
  const selectedRect = value === undefined ? undefined : triggerRects[value];
171
- const selectedOffset = selectedRect ? Math.max(0, selectedRect.left - listLeft) : null;
185
+ const selectedOffset = selectedRect?.left ?? null;
172
186
 
173
187
  const setListNode = React.useCallback(
174
188
  (node: NodesRef | null) => {
@@ -195,7 +209,7 @@ export const TabsRoot = React.forwardRef<unknown, TabsRootProps>((props, ref) =>
195
209
  return () => {
196
210
  "background only";
197
211
  setItems((current) => current.filter((item) => item.value !== triggerValue));
198
- setTriggerRects((current) => {
212
+ setTriggerWidths((current) => {
199
213
  const next = { ...current };
200
214
  delete next[triggerValue];
201
215
  return next;
@@ -222,14 +236,19 @@ export const TabsRoot = React.forwardRef<unknown, TabsRootProps>((props, ref) =>
222
236
  );
223
237
  }, []);
224
238
 
225
- const updateTriggerRect = React.useCallback((triggerValue: string, rect: TriggerRect) => {
226
- setTriggerRects((current) => {
227
- const previous = current[triggerValue];
228
- if (previous?.left === rect.left && previous.width === rect.width) return current;
229
- return { ...current, [triggerValue]: rect };
239
+ const syncTriggerOrder = React.useCallback((triggerValues: string[]) => {
240
+ setItems((current) => {
241
+ const ordered = getTabsOrderedItems(current, triggerValues);
242
+ return ordered.every((item, index) => item === current[index]) ? current : ordered;
230
243
  });
231
244
  }, []);
232
245
 
246
+ const updateTriggerWidth = React.useCallback((triggerValue: string, width: number) => {
247
+ setTriggerWidths((current) =>
248
+ current[triggerValue] === width ? current : { ...current, [triggerValue]: width },
249
+ );
250
+ }, []);
251
+
233
252
  const selectValue = React.useCallback(
234
253
  (nextValue: string) => {
235
254
  setIndicatorValue(undefined);
@@ -273,13 +292,13 @@ export const TabsRoot = React.forwardRef<unknown, TabsRootProps>((props, ref) =>
273
292
  indicatorIndex,
274
293
  selectedPagerIndex,
275
294
  indicatorRef,
276
- listLeft,
295
+ transitionsEnabled,
277
296
  triggerRects,
278
297
  registerTrigger,
279
298
  registerContent,
280
299
  updateTriggerDisabled,
281
- updateTriggerRect,
282
- setListLeft,
300
+ syncTriggerOrder,
301
+ updateTriggerWidth,
283
302
  setListRef: setListNode,
284
303
  setPagerRef: setPagerNode,
285
304
  selectValue,
@@ -295,12 +314,13 @@ export const TabsRoot = React.forwardRef<unknown, TabsRootProps>((props, ref) =>
295
314
  indicatorIndex,
296
315
  selectedPagerIndex,
297
316
  indicatorRef,
298
- listLeft,
299
317
  triggerRects,
318
+ transitionsEnabled,
300
319
  registerTrigger,
301
320
  registerContent,
302
321
  updateTriggerDisabled,
303
- updateTriggerRect,
322
+ syncTriggerOrder,
323
+ updateTriggerWidth,
304
324
  setListNode,
305
325
  setPagerNode,
306
326
  selectValue,
@@ -328,12 +348,38 @@ TabsRoot.displayName = "TabsRoot";
328
348
 
329
349
  ////////////////////////////////////////////////////////////////////////////////////
330
350
 
351
+ function getTabsTriggerValues(children: React.ReactNode): string[] {
352
+ const values: string[] = [];
353
+
354
+ function visit(node: React.ReactNode) {
355
+ if (Array.isArray(node)) {
356
+ node.forEach(visit);
357
+ return;
358
+ }
359
+ if (!React.isValidElement<TabsTriggerProps>(node)) return;
360
+ if (node.type === TabsTrigger) {
361
+ values.push(node.props.value);
362
+ return;
363
+ }
364
+ if (node.type === React.Fragment) visit(node.props.children);
365
+ }
366
+
367
+ visit(children);
368
+ return values;
369
+ }
370
+
331
371
  export interface TabsListProps extends LynxStyledElementProps {}
332
372
 
333
373
  export const TabsList = React.forwardRef<unknown, TabsListProps>((props, ref) => {
334
374
  const { children, className, style, ...nativeProps } = props;
335
375
  const classNames = useClassNames();
336
- const { setListLeft, setListRef } = useTabsContext("TabsList");
376
+ const { items, setListRef, syncTriggerOrder } = useTabsContext("TabsList");
377
+ const triggerOrder = React.useMemo(() => getTabsTriggerValues(children), [children]);
378
+
379
+ React.useEffect(() => {
380
+ "background only";
381
+ syncTriggerOrder(triggerOrder);
382
+ }, [items, syncTriggerOrder, triggerOrder]);
337
383
 
338
384
  const mergedRef = React.useCallback(
339
385
  (node: NodesRef | null) => {
@@ -344,22 +390,12 @@ export const TabsList = React.forwardRef<unknown, TabsListProps>((props, ref) =>
344
390
  [ref, setListRef],
345
391
  );
346
392
 
347
- const handleLayoutChange = React.useCallback<LayoutChangeHandler>(
348
- (...args) => {
349
- "background only";
350
- const rect = getLayoutRect(args[0]);
351
- if (rect) setListLeft(rect.left);
352
- },
353
- [setListLeft],
354
- );
355
-
356
393
  return (
357
394
  <scroll-view
358
395
  ref={mergedRef}
359
396
  {...nativeProps}
360
397
  scroll-orientation="horizontal"
361
398
  scroll-bar-enable={false}
362
- bindlayoutchange={handleLayoutChange}
363
399
  accessibility-element={false}
364
400
  accessibility-traits="tabbar"
365
401
  className={clsx(classNames.list, className)}
@@ -412,16 +448,17 @@ export const TabsTrigger = React.forwardRef<unknown, TabsTriggerProps>((props, r
412
448
  const handleLayoutChange = React.useCallback<LayoutChangeHandler>(
413
449
  (...args) => {
414
450
  "background only";
415
- const rect = getLayoutRect(args[0]);
416
- if (rect) context.updateTriggerRect(triggerValue, rect);
451
+ const width = getTabsLayoutWidth(args[0]);
452
+ if (width !== null) context.updateTriggerWidth(triggerValue, width);
417
453
  },
418
- [context.updateTriggerRect, triggerValue],
454
+ [context.updateTriggerWidth, triggerValue],
419
455
  );
420
456
 
421
457
  const triggerClasses = tabs({
422
458
  ...context.variantProps,
423
459
  selected: visuallySelected,
424
460
  disabled,
461
+ transitionEnabled: context.transitionsEnabled,
425
462
  });
426
463
  const label =
427
464
  typeof children === "string" || typeof children === "number" ? String(children) : undefined;
@@ -454,8 +491,7 @@ export interface TabsIndicatorProps extends LynxStyledElementProps {}
454
491
  export const TabsIndicator = React.forwardRef<unknown, TabsIndicatorProps>((props, ref) => {
455
492
  const { className, style, ...nativeProps } = props;
456
493
  const classNames = useClassNames();
457
- const { indicatorRef, items, listLeft, indicatorIndex, triggerRects } =
458
- useTabsContext("TabsIndicator");
494
+ const { indicatorRef, items, indicatorIndex, triggerRects } = useTabsContext("TabsIndicator");
459
495
  const position = indicatorIndex;
460
496
  const lowerIndex = Math.max(0, Math.floor(position));
461
497
  const upperIndex = Math.min(items.length - 1, Math.ceil(position));
@@ -463,7 +499,7 @@ export const TabsIndicator = React.forwardRef<unknown, TabsIndicatorProps>((prop
463
499
  const lowerRect = triggerRects[items[lowerIndex]?.value ?? ""];
464
500
  const upperRect = triggerRects[items[upperIndex]?.value ?? ""] ?? lowerRect;
465
501
  const x = lowerRect
466
- ? lowerRect.left - listLeft + ((upperRect?.left ?? lowerRect.left) - lowerRect.left) * progress
502
+ ? lowerRect.left + ((upperRect?.left ?? lowerRect.left) - lowerRect.left) * progress
467
503
  : 0;
468
504
  const width = lowerRect
469
505
  ? lowerRect.width + ((upperRect?.width ?? lowerRect.width) - lowerRect.width) * progress
@@ -602,7 +638,7 @@ export const TabsCarouselCamera = React.forwardRef<unknown, TabsCarouselCameraPr
602
638
  const tabsContext = useTabsContext("TabsCarouselCamera");
603
639
  const carouselContext = useTabsCarouselContext("TabsCarouselCamera");
604
640
  const swipingRef = React.useRef(false);
605
- const { indicatorRef, pagerValues, listLeft, triggerRects } = tabsContext;
641
+ const { indicatorRef, pagerValues, triggerRects } = tabsContext;
606
642
  const indicatorRects = pagerValues.map((value) => triggerRects[value] ?? null);
607
643
 
608
644
  const mergedRef = React.useCallback(
@@ -669,10 +705,7 @@ export const TabsCarouselCamera = React.forwardRef<unknown, TabsCarouselCameraPr
669
705
  const upperRect = indicatorRects[upperIndex] ?? lowerRect;
670
706
  if (!lowerRect) return;
671
707
 
672
- const x =
673
- lowerRect.left -
674
- listLeft +
675
- ((upperRect?.left ?? lowerRect.left) - lowerRect.left) * progress;
708
+ const x = lowerRect.left + ((upperRect?.left ?? lowerRect.left) - lowerRect.left) * progress;
676
709
  const width =
677
710
  lowerRect.width + ((upperRect?.width ?? lowerRect.width) - lowerRect.width) * progress;
678
711
 
@@ -0,0 +1,63 @@
1
+ export interface TabsLayoutChangeEvent {
2
+ detail?: {
3
+ width?: number;
4
+ };
5
+ params?: {
6
+ width?: number;
7
+ };
8
+ }
9
+
10
+ export interface TabsLayoutRect {
11
+ left: number;
12
+ width: number;
13
+ }
14
+
15
+ export function getTabsLayoutWidth(event: TabsLayoutChangeEvent): number | null {
16
+ const width = event.detail?.width ?? event.params?.width;
17
+ if (typeof width !== "number" || !Number.isFinite(width)) return null;
18
+ return Math.max(0, width);
19
+ }
20
+
21
+ export function getTabsOrderedItems<T extends { value: string }>(
22
+ items: T[],
23
+ values: string[],
24
+ ): T[] {
25
+ const itemsByValue = new Map(items.map((item) => [item.value, item]));
26
+ const ordered = values.flatMap((value) => {
27
+ const item = itemsByValue.get(value);
28
+ if (!item) return [];
29
+ itemsByValue.delete(value);
30
+ return [item];
31
+ });
32
+ ordered.push(...itemsByValue.values());
33
+ return ordered;
34
+ }
35
+
36
+ export function getTabsTriggerRects(
37
+ values: string[],
38
+ widths: Record<string, number>,
39
+ ): Record<string, TabsLayoutRect> {
40
+ const rects = Object.create(null) as Record<string, TabsLayoutRect>;
41
+ let left = 0;
42
+ let hasCompletePrefix = true;
43
+
44
+ for (const value of values) {
45
+ // biome-ignore lint/suspicious/noPrototypeBuiltins: Object.hasOwn is unavailable in supported Lynx runtimes.
46
+ const width = Object.prototype.hasOwnProperty.call(widths, value) ? widths[value] : undefined;
47
+ if (width === undefined) {
48
+ hasCompletePrefix = false;
49
+ continue;
50
+ }
51
+ if (hasCompletePrefix) rects[value] = { left, width };
52
+ left += width;
53
+ }
54
+
55
+ return rects;
56
+ }
57
+
58
+ export function areTabsTransitionsEnabled(
59
+ values: string[],
60
+ rects: Record<string, TabsLayoutRect>,
61
+ ): boolean {
62
+ return values.length > 0 && values.every((value) => rects[value] !== undefined);
63
+ }
@@ -301,6 +301,54 @@ describe("TextField", () => {
301
301
  expect(invoke).not.toHaveBeenCalled();
302
302
  });
303
303
 
304
+ it("restores the committed controlled value when blur reports a stale native value", async () => {
305
+ const inputRef = createRef<NodesRef>();
306
+
307
+ function ControlledInput() {
308
+ const [value, setValue] = useState("초기값");
309
+
310
+ return (
311
+ <TextField.Root value={value} onValueChange={setValue}>
312
+ <TextField.Input ref={inputRef} />
313
+ </TextField.Root>
314
+ );
315
+ }
316
+
317
+ render(<ControlledInput />);
318
+
319
+ const input = getRenderedRoot().querySelector("input");
320
+ if (!input || !inputRef.current) throw new Error("Expected native input to exist.");
321
+
322
+ const exec = vi.fn();
323
+ const invoke = vi.fn(() => ({ exec }));
324
+ inputRef.current.invoke = invoke as unknown as NodesRef["invoke"];
325
+
326
+ fireNativeEvent(inputRef.current, input, "input", {
327
+ value: "최신값",
328
+ selectionStart: 3,
329
+ selectionEnd: 3,
330
+ isComposing: false,
331
+ });
332
+ await flushControlledReconciliation();
333
+
334
+ fireNativeEvent(inputRef.current, input, "blur", { value: "이전값" });
335
+ await flushControlledReconciliation();
336
+
337
+ expect(invoke).toHaveBeenCalledTimes(1);
338
+ expect(invoke).toHaveBeenCalledWith(
339
+ expect.objectContaining({
340
+ method: "setValue",
341
+ params: { value: "최신값" },
342
+ }),
343
+ );
344
+ expect(exec).toHaveBeenCalledTimes(1);
345
+
346
+ fireNativeEvent(inputRef.current, input, "blur", { value: "최신값" });
347
+ await flushControlledReconciliation();
348
+
349
+ expect(invoke).toHaveBeenCalledTimes(1);
350
+ });
351
+
304
352
  it("waits for a parent value update queued in a microtask", async () => {
305
353
  const inputRef = createRef<NodesRef>();
306
354
 
@@ -456,11 +456,18 @@ function useNativeTextControl({
456
456
  (event: Parameters<NonNullable<NativeInputProps["bindblur"]>>[0]) => {
457
457
  "background only";
458
458
 
459
+ const nativeValue = event.detail?.value;
460
+ if (typeof nativeValue === "string") {
461
+ lastNativeValueRef.current = nativeValue;
462
+ }
459
463
  textFieldContext.setFocused(false);
460
464
  keyboardAvoidance?.blur(ownerRef.current);
461
465
  bindblur?.(event);
466
+ if (typeof nativeValue === "string") {
467
+ reconcileControlledValue(nativeValue);
468
+ }
462
469
  },
463
- [bindblur, keyboardAvoidance, textFieldContext.setFocused],
470
+ [bindblur, keyboardAvoidance, reconcileControlledValue, textFieldContext.setFocused],
464
471
  );
465
472
 
466
473
  const notifyLayoutChanged = React.useCallback(() => {