@tangible/ui 0.0.8 → 0.0.10
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/components/Avatar/Avatar.d.ts +1 -1
- package/components/Avatar/Avatar.js +2 -2
- package/components/Combobox/Combobox.js +26 -2
- package/components/Dropdown/Dropdown.d.ts +1 -1
- package/components/Dropdown/Dropdown.js +16 -8
- package/components/Icon/Icon.d.ts +1 -1
- package/components/Icon/Icon.js +2 -2
- package/components/Modal/Modal.d.ts +5 -1
- package/components/Modal/Modal.js +2 -2
- package/components/MoveHandle/MoveHandle.d.ts +1 -1
- package/components/MoveHandle/MoveHandle.js +3 -3
- package/components/MultiSelect/MultiSelect.js +27 -4
- package/components/Progress/Progress.d.ts +2 -1
- package/components/Progress/Progress.js +3 -3
- package/components/Select/Select.js +33 -2
- package/components/StepIndicator/StepIndicator.d.ts +1 -1
- package/components/StepIndicator/StepIndicator.js +3 -2
- package/components/Tooltip/Tooltip.d.ts +1 -1
- package/components/Tooltip/Tooltip.js +16 -10
- package/package.json +1 -1
- package/tui-manifest.json +7 -10
|
@@ -8,4 +8,4 @@ import type { AvatarProps } from './types';
|
|
|
8
8
|
* - Shows placeholder icon if neither `src` nor `name` provided
|
|
9
9
|
* - Colors for initials are derived from the name hash for consistency
|
|
10
10
|
*/
|
|
11
|
-
export declare const Avatar: React.
|
|
11
|
+
export declare const Avatar: React.NamedExoticComponent<AvatarProps & React.RefAttributes<HTMLSpanElement>>;
|
|
@@ -46,7 +46,7 @@ function getColorFromName(name, colors) {
|
|
|
46
46
|
* - Shows placeholder icon if neither `src` nor `name` provided
|
|
47
47
|
* - Colors for initials are derived from the name hash for consistency
|
|
48
48
|
*/
|
|
49
|
-
export const Avatar = React.forwardRef(({ src, name, size = 'md', shape = 'circle', color, indicator, indicatorLabel, indicatorPosition = 'bottom-right', tooltip, labels: labelsProp, className, }, ref) => {
|
|
49
|
+
export const Avatar = React.memo(React.forwardRef(({ src, name, size = 'md', shape = 'circle', color, indicator, indicatorLabel, indicatorPosition = 'bottom-right', tooltip, labels: labelsProp, className, }, ref) => {
|
|
50
50
|
const [imgError, setImgError] = useState(false);
|
|
51
51
|
// Reset error state when src changes
|
|
52
52
|
React.useEffect(() => {
|
|
@@ -73,5 +73,5 @@ export const Avatar = React.forwardRef(({ src, name, size = 'md', shape = 'circl
|
|
|
73
73
|
return (_jsxs(Tooltip, { children: [_jsx(Tooltip.Trigger, { asChild: true, children: avatarElement }), _jsx(Tooltip.Content, { "aria-hidden": "true", children: name })] }));
|
|
74
74
|
}
|
|
75
75
|
return avatarElement;
|
|
76
|
-
});
|
|
76
|
+
}));
|
|
77
77
|
Avatar.displayName = 'Avatar';
|
|
@@ -43,6 +43,9 @@ function ComboboxRoot({ value: controlledValue, defaultValue, onValueChange, inp
|
|
|
43
43
|
// Option registration
|
|
44
44
|
const optionsRef = useRef(new Map());
|
|
45
45
|
const [registryVersion, setRegistryVersion] = useState(0);
|
|
46
|
+
// Track open state via ref so unregisterOption can check synchronously
|
|
47
|
+
const openRef = useRef(false);
|
|
48
|
+
openRef.current = open;
|
|
46
49
|
// IDs
|
|
47
50
|
const baseId = useId();
|
|
48
51
|
const inputId = `${baseId}-input`;
|
|
@@ -149,12 +152,27 @@ function ComboboxRoot({ value: controlledValue, defaultValue, onValueChange, inp
|
|
|
149
152
|
role,
|
|
150
153
|
listNavigation,
|
|
151
154
|
]);
|
|
155
|
+
// Flag: first registerOption call after open clears stale entries.
|
|
156
|
+
const prevOpenForFlush = useRef(open);
|
|
157
|
+
const needsFlushRef = useRef(false);
|
|
158
|
+
if (open && !prevOpenForFlush.current)
|
|
159
|
+
needsFlushRef.current = true;
|
|
160
|
+
prevOpenForFlush.current = open;
|
|
152
161
|
// Register option
|
|
153
162
|
const registerOption = useCallback((option) => {
|
|
163
|
+
if (needsFlushRef.current) {
|
|
164
|
+
optionsRef.current.clear();
|
|
165
|
+
needsFlushRef.current = false;
|
|
166
|
+
}
|
|
154
167
|
optionsRef.current.set(toKey(option.value), option);
|
|
155
168
|
setRegistryVersion((v) => v + 1);
|
|
156
169
|
}, []);
|
|
157
170
|
const unregisterOption = useCallback((optionValue) => {
|
|
171
|
+
// When the dropdown closes, options unmount and call unregister in cleanup.
|
|
172
|
+
// Skip the delete to preserve the registry — filtering and input display
|
|
173
|
+
// depend on it while closed. Options re-register on next open (same keys).
|
|
174
|
+
if (!openRef.current)
|
|
175
|
+
return;
|
|
158
176
|
optionsRef.current.delete(toKey(optionValue));
|
|
159
177
|
setRegistryVersion((v) => v + 1);
|
|
160
178
|
}, []);
|
|
@@ -368,7 +386,7 @@ function ComboboxRoot({ value: controlledValue, defaultValue, onValueChange, inp
|
|
|
368
386
|
return (_jsx(ComboboxActionsContext.Provider, { value: actionsValue, children: _jsx(ComboboxStateContext.Provider, { value: stateValue, children: _jsxs("div", { className: "tui-combobox", children: [_jsxs("div", { className: "tui-combobox__input-wrapper", children: [_jsx("input", { ref: (node) => {
|
|
369
387
|
inputRef.current = node;
|
|
370
388
|
refs.setReference(node);
|
|
371
|
-
}, type: "text", id: inputId, className: cx('tui-combobox__input', size !== 'md' && `is-size-${size}`, inputClassName), role: "combobox", "aria-expanded": open, "aria-controls": listboxId, "aria-autocomplete": "list", "aria-activedescendant": activeOptionId, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, disabled: disabled, placeholder: placeholder, value: inputValue, autoComplete: "off", ...referenceProps, onChange: handleInputChange }), clearable && inputValue && !disabled && (_jsx("button", { type: "button", className: "tui-combobox__clear", onClick: handleClear, onMouseDown: (e) => e.preventDefault(), "aria-label": labels.clear, tabIndex: -1, children: _jsx(Icon, { name: "system/close", size: "sm" }) })), _jsx("span", { className: "tui-combobox__icon", "aria-hidden": "true", children: _jsx(Icon, { name: "system/chevron-down", size: "sm" }) })] }), children] }) }) }));
|
|
389
|
+
}, type: "text", id: inputId, className: cx('tui-combobox__input', size !== 'md' && `is-size-${size}`, inputClassName), role: "combobox", "aria-expanded": open, "aria-controls": open ? listboxId : undefined, "aria-autocomplete": "list", "aria-activedescendant": activeOptionId, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, disabled: disabled, placeholder: placeholder, value: inputValue, autoComplete: "off", ...referenceProps, onChange: handleInputChange }), clearable && inputValue && !disabled && (_jsx("button", { type: "button", className: "tui-combobox__clear", onClick: handleClear, onMouseDown: (e) => e.preventDefault(), "aria-label": labels.clear, tabIndex: -1, children: _jsx(Icon, { name: "system/close", size: "sm" }) })), _jsx("span", { className: "tui-combobox__icon", "aria-hidden": "true", children: _jsx(Icon, { name: "system/chevron-down", size: "sm" }) })] }), children] }) }) }));
|
|
372
390
|
}
|
|
373
391
|
ComboboxRoot.displayName = 'Combobox';
|
|
374
392
|
// =============================================================================
|
|
@@ -376,6 +394,12 @@ ComboboxRoot.displayName = 'Combobox';
|
|
|
376
394
|
// =============================================================================
|
|
377
395
|
function ComboboxContentComponent({ className, children }) {
|
|
378
396
|
const { open, listboxId, inputId, refs, floatingStyles, getFloatingProps, listRef, activeIndex, orderedOptions, } = useComboboxContext();
|
|
397
|
+
// Track whether dropdown has ever been opened. Before first open, mount
|
|
398
|
+
// children in a hidden div for option registration (defaultValue resolution).
|
|
399
|
+
// After first open, only mount children when open (in portal).
|
|
400
|
+
const hasEverOpened = useRef(false);
|
|
401
|
+
if (open)
|
|
402
|
+
hasEverOpened.current = true;
|
|
379
403
|
const portalRoot = getPortalRootFor(refs.reference.current);
|
|
380
404
|
const contentContext = useMemo(() => ({
|
|
381
405
|
listRef,
|
|
@@ -383,7 +407,7 @@ function ComboboxContentComponent({ className, children }) {
|
|
|
383
407
|
orderedOptions,
|
|
384
408
|
}), [listRef, activeIndex, orderedOptions]);
|
|
385
409
|
// Always render for option registration
|
|
386
|
-
return (_jsxs(_Fragment, { children: [!open && (_jsx("div", { id: listboxId, role: "listbox", style: { display: 'none' }, "aria-hidden": "true", children: _jsx(ComboboxContentContext.Provider, { value: contentContext, children: children }) })), open && (_jsx(FloatingPortal, { root: portalRoot, children: _jsx("div", { ref: refs.setFloating, id: listboxId, role: "listbox", "aria-labelledby": inputId, className: cx('tui-combobox__content', className), style: {
|
|
410
|
+
return (_jsxs(_Fragment, { children: [!open && !hasEverOpened.current && (_jsx("div", { id: listboxId, role: "listbox", style: { display: 'none' }, "aria-hidden": "true", children: _jsx(ComboboxContentContext.Provider, { value: contentContext, children: children }) })), open && (_jsx(FloatingPortal, { root: portalRoot, children: _jsx("div", { ref: refs.setFloating, id: listboxId, role: "listbox", "aria-labelledby": inputId, className: cx('tui-combobox__content', className), style: {
|
|
387
411
|
...floatingStyles,
|
|
388
412
|
minWidth: refs.reference.current?.offsetWidth,
|
|
389
413
|
pointerEvents: 'auto',
|
|
@@ -7,7 +7,7 @@ declare function DropdownTriggerComponent({ asChild, children }: DropdownTrigger
|
|
|
7
7
|
declare namespace DropdownTriggerComponent {
|
|
8
8
|
var displayName: string;
|
|
9
9
|
}
|
|
10
|
-
declare function DropdownContentComponent(
|
|
10
|
+
declare function DropdownContentComponent(props: DropdownContentProps): import("react/jsx-runtime").JSX.Element | null;
|
|
11
11
|
declare namespace DropdownContentComponent {
|
|
12
12
|
var displayName: string;
|
|
13
13
|
}
|
|
@@ -112,12 +112,23 @@ DropdownTriggerComponent.displayName = 'Dropdown.Trigger';
|
|
|
112
112
|
// =============================================================================
|
|
113
113
|
// DropdownContent
|
|
114
114
|
// =============================================================================
|
|
115
|
-
|
|
116
|
-
|
|
115
|
+
// Gate component: reads context to decide whether to mount the real content.
|
|
116
|
+
// This ensures useFloating and all Floating UI hooks in DropdownContentInner
|
|
117
|
+
// only run when the dropdown is actually open — not on every render cycle.
|
|
118
|
+
function DropdownContentComponent(props) {
|
|
119
|
+
const { open } = useDropdownContext();
|
|
120
|
+
if (!open)
|
|
121
|
+
return null;
|
|
122
|
+
return _jsx(DropdownContentInner, { ...props });
|
|
123
|
+
}
|
|
124
|
+
DropdownContentComponent.displayName = 'Dropdown.Content';
|
|
125
|
+
// Inner component: only mounted when open. All Floating UI hooks live here.
|
|
126
|
+
function DropdownContentInner({ side = 'bottom', align = 'start', sideOffset = 4, className, style, children, }) {
|
|
127
|
+
const { setOpen, triggerRef, contentId, activeIndex, setActiveIndex, openedVia } = useDropdownContext();
|
|
117
128
|
const listRef = useRef([]);
|
|
118
129
|
const { refs, floatingStyles, context } = useFloating({
|
|
119
130
|
placement: toPlacement(side, align),
|
|
120
|
-
open,
|
|
131
|
+
open: true, // Always true when mounted (gate handles the conditional)
|
|
121
132
|
onOpenChange: setOpen,
|
|
122
133
|
middleware: [offset(sideOffset), flip(), shift({ padding: 8 })],
|
|
123
134
|
whileElementsMounted: autoUpdate,
|
|
@@ -156,7 +167,7 @@ function DropdownContentComponent({ side = 'bottom', align = 'start', sideOffset
|
|
|
156
167
|
}, [children]);
|
|
157
168
|
// ArrowUp focus-last: set activeIndex to last valid item before paint
|
|
158
169
|
useLayoutEffect(() => {
|
|
159
|
-
if (
|
|
170
|
+
if (openedVia.current === 'ArrowUp') {
|
|
160
171
|
let lastValid = totalItemCount - 1;
|
|
161
172
|
while (lastValid >= 0 && disabledIndices.includes(lastValid)) {
|
|
162
173
|
lastValid--;
|
|
@@ -166,7 +177,7 @@ function DropdownContentComponent({ side = 'bottom', align = 'start', sideOffset
|
|
|
166
177
|
}
|
|
167
178
|
openedVia.current = null;
|
|
168
179
|
}
|
|
169
|
-
}, [
|
|
180
|
+
}, [openedVia, totalItemCount, disabledIndices, setActiveIndex]);
|
|
170
181
|
const dismiss = useDismiss(context);
|
|
171
182
|
const role = useRole(context, { role: 'menu' });
|
|
172
183
|
const listNavigation = useListNavigation(context, {
|
|
@@ -184,8 +195,6 @@ function DropdownContentComponent({ side = 'bottom', align = 'start', sideOffset
|
|
|
184
195
|
]);
|
|
185
196
|
// Get portal root inside .tui-interface
|
|
186
197
|
const portalRoot = getPortalRootFor(triggerRef.current);
|
|
187
|
-
if (!open)
|
|
188
|
-
return null;
|
|
189
198
|
// Clone children to inject item props and role.
|
|
190
199
|
// Non-navigable children (Separator, Header) are rendered as-is.
|
|
191
200
|
let itemIndex = 0;
|
|
@@ -229,7 +238,6 @@ function DropdownContentComponent({ side = 'bottom', align = 'start', sideOffset
|
|
|
229
238
|
...style,
|
|
230
239
|
}, ...getFloatingProps(), children: items }) }) }));
|
|
231
240
|
}
|
|
232
|
-
DropdownContentComponent.displayName = 'Dropdown.Content';
|
|
233
241
|
// =============================================================================
|
|
234
242
|
// DropdownItem
|
|
235
243
|
// =============================================================================
|
|
@@ -19,4 +19,4 @@ export interface IconProps {
|
|
|
19
19
|
* - Decorative icons (no label): automatically hidden from screen readers
|
|
20
20
|
* - Informative icons: provide a `label` prop for screen reader announcement
|
|
21
21
|
*/
|
|
22
|
-
export declare const Icon: React.
|
|
22
|
+
export declare const Icon: React.NamedExoticComponent<IconProps & React.RefAttributes<HTMLSpanElement>>;
|
package/components/Icon/Icon.js
CHANGED
|
@@ -10,7 +10,7 @@ import { iconRegistry } from '../../icons/registry.js';
|
|
|
10
10
|
* - Decorative icons (no label): automatically hidden from screen readers
|
|
11
11
|
* - Informative icons: provide a `label` prop for screen reader announcement
|
|
12
12
|
*/
|
|
13
|
-
export const Icon = React.forwardRef(({ name, emoji, label, size, className }, ref) => {
|
|
13
|
+
export const Icon = React.memo(React.forwardRef(({ name, emoji, label, size, className }, ref) => {
|
|
14
14
|
const SvgIcon = name ? iconRegistry[name] : null;
|
|
15
15
|
// Dev warning for invalid icon name
|
|
16
16
|
if (isDev() && name && !SvgIcon) {
|
|
@@ -21,5 +21,5 @@ export const Icon = React.forwardRef(({ name, emoji, label, size, className }, r
|
|
|
21
21
|
return (_jsxs("span", { ref: ref, className: cx('tui-icon', size && `is-size-${size}`, className), ...(isDecorative
|
|
22
22
|
? { 'aria-hidden': true }
|
|
23
23
|
: { role: 'img', 'aria-label': label }), children: [SvgIcon && _jsx(SvgIcon, { "aria-hidden": "true", focusable: "false" }), !SvgIcon && emoji] }));
|
|
24
|
-
});
|
|
24
|
+
}));
|
|
25
25
|
Icon.displayName = 'Icon';
|
|
@@ -17,9 +17,13 @@ export type ModalProps = {
|
|
|
17
17
|
closeLabel?: string;
|
|
18
18
|
closeOnBackdropClick?: boolean;
|
|
19
19
|
closeOnEscape?: boolean;
|
|
20
|
+
/** When true, prevents the browser from scrolling to the trigger element
|
|
21
|
+
* when focus is restored on close. Useful when the trigger may be off-screen
|
|
22
|
+
* inside a scrollable container. Default: false. */
|
|
23
|
+
preventScrollOnRestore?: boolean;
|
|
20
24
|
children?: React.ReactNode;
|
|
21
25
|
};
|
|
22
|
-
declare function ModalRoot({ open, onClose, size, stickyHead, stickyFoot, 'aria-labelledby': labelledBy, 'aria-describedby': describedBy, initialFocusSelector, container, showCloseButton, closeLabel, closeOnBackdropClick, closeOnEscape, children, }: ModalProps): React.ReactPortal | null;
|
|
26
|
+
declare function ModalRoot({ open, onClose, size, stickyHead, stickyFoot, 'aria-labelledby': labelledBy, 'aria-describedby': describedBy, initialFocusSelector, container, showCloseButton, closeLabel, closeOnBackdropClick, closeOnEscape, preventScrollOnRestore, children, }: ModalProps): React.ReactPortal | null;
|
|
23
27
|
type ModalCloseProps = {
|
|
24
28
|
label?: string;
|
|
25
29
|
className?: string;
|
|
@@ -8,7 +8,7 @@ import { useFocusTrap, getInitialFocus } from '../../utils/focus-trap.js';
|
|
|
8
8
|
import { ModalContext, useModalContext } from './context.js';
|
|
9
9
|
import { IconButton } from '../IconButton/index.js';
|
|
10
10
|
const isBrowser = typeof document !== 'undefined';
|
|
11
|
-
function ModalRoot({ open, onClose, size = 'md', stickyHead, stickyFoot, 'aria-labelledby': labelledBy, 'aria-describedby': describedBy, initialFocusSelector, container, showCloseButton, closeLabel = 'Close', closeOnBackdropClick = true, closeOnEscape = true, children, }) {
|
|
11
|
+
function ModalRoot({ open, onClose, size = 'md', stickyHead, stickyFoot, 'aria-labelledby': labelledBy, 'aria-describedby': describedBy, initialFocusSelector, container, showCloseButton, closeLabel = 'Close', closeOnBackdropClick = true, closeOnEscape = true, preventScrollOnRestore = false, children, }) {
|
|
12
12
|
const dialogRef = useRef(null);
|
|
13
13
|
const restoreRef = useRef(null);
|
|
14
14
|
const warnedRef = useRef(false);
|
|
@@ -45,7 +45,7 @@ function ModalRoot({ open, onClose, size = 'md', stickyHead, stickyFoot, 'aria-l
|
|
|
45
45
|
return;
|
|
46
46
|
const el = restoreRef.current;
|
|
47
47
|
if (el && typeof el.focus === 'function') {
|
|
48
|
-
el.focus();
|
|
48
|
+
el.focus({ preventScroll: preventScrollOnRestore });
|
|
49
49
|
}
|
|
50
50
|
restoreRef.current = null;
|
|
51
51
|
setMount(null);
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import type { MoveHandleProps } from './types';
|
|
2
|
-
export declare const MoveHandle: import("react").
|
|
2
|
+
export declare const MoveHandle: import("react").NamedExoticComponent<MoveHandleProps & import("react").RefAttributes<HTMLElement>>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { forwardRef, useCallback, useEffect, useId, useRef, useState } from 'react';
|
|
2
|
+
import { forwardRef, memo, useCallback, useEffect, useId, useRef, useState } from 'react';
|
|
3
3
|
import { cx } from '../../utils/cx.js';
|
|
4
4
|
import { isDev } from '../../utils/is-dev.js';
|
|
5
5
|
import { Icon } from '../Icon/index.js';
|
|
@@ -24,7 +24,7 @@ import { Icon } from '../Icon/index.js';
|
|
|
24
24
|
// --tui-move-handle-icon-size Override icon size
|
|
25
25
|
//
|
|
26
26
|
// =============================================================================
|
|
27
|
-
export const MoveHandle = forwardRef(function MoveHandle({ mode = 'full', size = 'md', index, locked = false, onMoveUp, onMoveDown, canMoveUp = true, canMoveDown = true, labels, dragHandleProps, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, className, }, ref) {
|
|
27
|
+
export const MoveHandle = memo(forwardRef(function MoveHandle({ mode = 'full', size = 'md', index, locked = false, onMoveUp, onMoveDown, canMoveUp = true, canMoveDown = true, labels, dragHandleProps, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, className, }, ref) {
|
|
28
28
|
// All hooks must be called unconditionally (rules of hooks)
|
|
29
29
|
const innerRef = useRef(null);
|
|
30
30
|
const mergedRef = useCallback((node) => {
|
|
@@ -92,4 +92,4 @@ export const MoveHandle = forwardRef(function MoveHandle({ mode = 'full', size =
|
|
|
92
92
|
? (labels?.locked ?? 'This item is locked and cannot be reordered')
|
|
93
93
|
: undefined;
|
|
94
94
|
return (_jsxs("div", { ref: mergedRef, role: "group", "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, "aria-describedby": locked ? lockedDescId : undefined, "aria-disabled": locked || undefined, className: cx('tui-move-handle', `is-size-${size}`, locked && 'is-locked', hasIndex && 'has-index', className), children: [locked && (_jsx("span", { id: lockedDescId, className: "tui-visually-hidden", children: resolvedLockedDesc })), onMoveUp && (_jsx("button", { type: "button", className: "tui-move-handle__up", "data-direction": "up", "aria-label": labels?.moveUp ?? 'Move up', disabled: locked || !canMoveUp, onClick: onMoveUp, children: _jsx(Icon, { name: "system/chevron-up" }) })), _jsx("div", { className: "tui-move-handle__center", children: showLockIcon ? (_jsx("span", { className: "tui-move-handle__lock", "aria-hidden": "true", children: _jsx(Icon, { name: "system/lock" }) })) : (_jsxs(_Fragment, { children: [hasIndex && (_jsx("span", { className: "tui-move-handle__index", "aria-hidden": "true", children: index })), _jsx("button", { type: "button", className: "tui-move-handle__handle", "data-role": "drag-handle", "aria-label": resolvedDragLabel, tabIndex: hasArrows ? -1 : 0, ...restDragProps, children: _jsx(Icon, { name: "system/handle-alt" }) })] })) }), onMoveDown && (_jsx("button", { type: "button", className: "tui-move-handle__down", "data-direction": "down", "aria-label": labels?.moveDown ?? 'Move down', disabled: locked || !canMoveDown, onClick: onMoveDown, children: _jsx(Icon, { name: "system/chevron-down" }) }))] }));
|
|
95
|
-
});
|
|
95
|
+
}));
|
|
@@ -22,6 +22,8 @@ function MultiSelectRoot({ id: triggerIdProp, value: controlledValue, defaultVal
|
|
|
22
22
|
// Option registration
|
|
23
23
|
const optionsRef = useRef(new Map());
|
|
24
24
|
const [registryVersion, setRegistryVersion] = useState(0);
|
|
25
|
+
// Track open state via ref so unregisterOption can check synchronously
|
|
26
|
+
const openRef = useRef(false);
|
|
25
27
|
// Is selected helper
|
|
26
28
|
const isSelected = useCallback((optionValue) => {
|
|
27
29
|
const key = toKey(optionValue);
|
|
@@ -106,6 +108,7 @@ function MultiSelectRoot({ id: triggerIdProp, value: controlledValue, defaultVal
|
|
|
106
108
|
const [uncontrolledOpen, setUncontrolledOpen] = useState(defaultOpen ?? false);
|
|
107
109
|
const isOpenControlled = controlledOpen !== undefined;
|
|
108
110
|
const open = isOpenControlled ? controlledOpen : uncontrolledOpen;
|
|
111
|
+
openRef.current = open;
|
|
109
112
|
const setOpen = useCallback((nextOpen) => {
|
|
110
113
|
if (disabled)
|
|
111
114
|
return;
|
|
@@ -210,11 +213,25 @@ function MultiSelectRoot({ id: triggerIdProp, value: controlledValue, defaultVal
|
|
|
210
213
|
typeahead,
|
|
211
214
|
]);
|
|
212
215
|
// Register option
|
|
216
|
+
// Flag: first registerOption call after open clears stale entries.
|
|
217
|
+
const prevOpenForFlush = useRef(open);
|
|
218
|
+
const needsFlushRef = useRef(false);
|
|
219
|
+
if (open && !prevOpenForFlush.current)
|
|
220
|
+
needsFlushRef.current = true;
|
|
221
|
+
prevOpenForFlush.current = open;
|
|
213
222
|
const registerOption = useCallback((option) => {
|
|
223
|
+
if (needsFlushRef.current) {
|
|
224
|
+
optionsRef.current.clear();
|
|
225
|
+
needsFlushRef.current = false;
|
|
226
|
+
}
|
|
214
227
|
optionsRef.current.set(toKey(option.value), option);
|
|
215
228
|
setRegistryVersion((v) => v + 1);
|
|
216
229
|
}, []);
|
|
217
230
|
const unregisterOption = useCallback((optionValue) => {
|
|
231
|
+
// Skip when closing — preserve registry for chip display text.
|
|
232
|
+
// Options re-register on next open (same keys).
|
|
233
|
+
if (!openRef.current)
|
|
234
|
+
return;
|
|
218
235
|
optionsRef.current.delete(toKey(optionValue));
|
|
219
236
|
setRegistryVersion((v) => v + 1);
|
|
220
237
|
}, []);
|
|
@@ -447,7 +464,7 @@ function MultiSelectTriggerComponent({ asChild = false, className, children, })
|
|
|
447
464
|
'aria-describedby': ariaDescribedBy,
|
|
448
465
|
'aria-haspopup': 'listbox',
|
|
449
466
|
'aria-expanded': open,
|
|
450
|
-
'aria-controls': listboxId,
|
|
467
|
+
'aria-controls': open ? listboxId : undefined,
|
|
451
468
|
'aria-keyshortcuts': hasSelection && !open ? 'Delete' : undefined,
|
|
452
469
|
'data-state': open ? 'open' : 'closed',
|
|
453
470
|
...floatingProps,
|
|
@@ -479,7 +496,7 @@ function MultiSelectTriggerComponent({ asChild = false, className, children, })
|
|
|
479
496
|
ref: refs.setReference,
|
|
480
497
|
'aria-haspopup': 'listbox',
|
|
481
498
|
'aria-expanded': open,
|
|
482
|
-
'aria-controls': listboxId,
|
|
499
|
+
'aria-controls': open ? listboxId : undefined,
|
|
483
500
|
'aria-activedescendant': floatingProps['aria-activedescendant'],
|
|
484
501
|
'aria-describedby': ariaDescribedBy,
|
|
485
502
|
// asChild: use aria-disabled + data-disabled since element may not support native disabled
|
|
@@ -507,7 +524,7 @@ function MultiSelectTriggerComponent({ asChild = false, className, children, })
|
|
|
507
524
|
}
|
|
508
525
|
// Default: render button with optional custom content
|
|
509
526
|
const triggerContent = children ?? defaultTriggerContent;
|
|
510
|
-
return (_jsxs(_Fragment, { children: [_jsx("button", { ref: refs.setReference, type: "button", id: triggerId, className: cx('tui-multiselect__trigger', sizeClass, className), disabled: disabled, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, "aria-describedby": ariaDescribedBy, "aria-haspopup": "listbox", "aria-expanded": open, "aria-controls": listboxId, "data-state": open ? 'open' : 'closed', ...floatingProps,
|
|
527
|
+
return (_jsxs(_Fragment, { children: [_jsx("button", { ref: refs.setReference, type: "button", id: triggerId, className: cx('tui-multiselect__trigger', sizeClass, className), disabled: disabled, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, "aria-describedby": ariaDescribedBy, "aria-haspopup": "listbox", "aria-expanded": open, "aria-controls": open ? listboxId : undefined, "data-state": open ? 'open' : 'closed', ...floatingProps,
|
|
511
528
|
// Handle Backspace/Delete AFTER floatingProps to ensure we catch it
|
|
512
529
|
// (typeahead may intercept these for its buffer)
|
|
513
530
|
onKeyDown: (e) => {
|
|
@@ -528,13 +545,19 @@ MultiSelectTriggerComponent.displayName = 'MultiSelect.Trigger';
|
|
|
528
545
|
function MultiSelectContentComponent({ className, children }) {
|
|
529
546
|
const { open, listboxId, triggerId, refs, floatingStyles, getFloatingProps, listRef, activeIndex, orderedOptions, } = useMultiSelectContext();
|
|
530
547
|
const portalRoot = getPortalRootFor(refs.reference.current);
|
|
548
|
+
// Track whether dropdown has ever been opened. Before first open, mount
|
|
549
|
+
// children in a hidden div for option registration. After first open,
|
|
550
|
+
// only mount children when open (in portal).
|
|
551
|
+
const hasEverOpened = useRef(false);
|
|
552
|
+
if (open)
|
|
553
|
+
hasEverOpened.current = true;
|
|
531
554
|
// Memoized context for options
|
|
532
555
|
const contentContext = useMemo(() => ({
|
|
533
556
|
listRef,
|
|
534
557
|
activeIndex,
|
|
535
558
|
orderedOptions,
|
|
536
559
|
}), [listRef, activeIndex, orderedOptions]);
|
|
537
|
-
return (_jsxs(_Fragment, { children: [!open && (_jsx("div", { id: listboxId, role: "listbox", style: { display: 'none' }, "aria-hidden": "true", children: _jsx(MultiSelectContentContext.Provider, { value: contentContext, children: children }) })), open && (_jsx(FloatingPortal, { root: portalRoot, children: _jsx("div", { ref: refs.setFloating, id: listboxId, role: "listbox", "aria-labelledby": triggerId, "aria-multiselectable": "true", className: cx('tui-multiselect__content', className), style: {
|
|
560
|
+
return (_jsxs(_Fragment, { children: [!open && !hasEverOpened.current && (_jsx("div", { id: listboxId, role: "listbox", style: { display: 'none' }, "aria-hidden": "true", children: _jsx(MultiSelectContentContext.Provider, { value: contentContext, children: children }) })), open && (_jsx(FloatingPortal, { root: portalRoot, children: _jsx("div", { ref: refs.setFloating, id: listboxId, role: "listbox", "aria-labelledby": triggerId, "aria-multiselectable": "true", className: cx('tui-multiselect__content', className), style: {
|
|
538
561
|
...floatingStyles,
|
|
539
562
|
minWidth: refs.reference.current?.offsetWidth,
|
|
540
563
|
pointerEvents: 'auto',
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import React from 'react';
|
|
2
|
+
import React, { memo } from 'react';
|
|
3
3
|
import { useProgressSegments } from './useProgressSegments.js';
|
|
4
4
|
import { cx } from '../../utils/cx.js';
|
|
5
5
|
import { isDev } from '../../utils/is-dev.js';
|
|
6
6
|
// =============================================================================
|
|
7
7
|
// COMPONENT
|
|
8
8
|
// =============================================================================
|
|
9
|
-
export function Progress(props) {
|
|
9
|
+
export const Progress = memo(function Progress(props) {
|
|
10
10
|
const { children, mode = 'line', size = 'md', max = 100, showLabels = true, 'aria-labelledby': labelledBy, 'aria-label': ariaLabel, defaultLabel, className, } = props;
|
|
11
11
|
// Determine mode
|
|
12
12
|
const isSegmented = 'segments' in props && Array.isArray(props.segments);
|
|
@@ -98,4 +98,4 @@ export function Progress(props) {
|
|
|
98
98
|
// Above/below/inline positions: render labels outside the track
|
|
99
99
|
const labelRow = (_jsxs("div", { className: "tui-progress__labels", children: [labelStart && _jsx("span", { className: "tui-progress__label is-start", children: labelStart }), labelEnd && _jsx("span", { className: "tui-progress__label is-end", children: labelEnd })] }));
|
|
100
100
|
return (_jsxs("div", { ...rootProps, children: [labelPosition === 'above' && labelRow, labelPosition === 'inline' ? (_jsxs("div", { className: "tui-progress__inline", children: [labelStart && _jsx("span", { className: "tui-progress__label is-start", children: labelStart }), trackContent, labelEnd && _jsx("span", { className: "tui-progress__label is-end", children: labelEnd })] })) : (trackContent), labelPosition === 'below' && labelRow, !labelledBy && !ariaLabel && defaultLabel && (_jsx("span", { className: "visually-hidden", children: defaultLabel }))] }));
|
|
101
|
-
}
|
|
101
|
+
});
|
|
@@ -22,6 +22,8 @@ function SelectRoot({ id: triggerIdProp, value: controlledValue, defaultValue, o
|
|
|
22
22
|
// Option registration
|
|
23
23
|
const optionsRef = useRef(new Map());
|
|
24
24
|
const [registryVersion, setRegistryVersion] = useState(0);
|
|
25
|
+
// Track open state via ref so unregisterOption can check synchronously
|
|
26
|
+
const openRef = useRef(false);
|
|
25
27
|
const setValue = useCallback((newValue, textValue) => {
|
|
26
28
|
if (!isValueControlled) {
|
|
27
29
|
setUncontrolledValue(newValue);
|
|
@@ -55,6 +57,7 @@ function SelectRoot({ id: triggerIdProp, value: controlledValue, defaultValue, o
|
|
|
55
57
|
const [uncontrolledOpen, setUncontrolledOpen] = useState(defaultOpen ?? false);
|
|
56
58
|
const isOpenControlled = controlledOpen !== undefined;
|
|
57
59
|
const open = isOpenControlled ? controlledOpen : uncontrolledOpen;
|
|
60
|
+
openRef.current = open;
|
|
58
61
|
// Guard flag: when Backspace/Delete clears a closed Select, Floating UI's
|
|
59
62
|
// composed handlers (useTypeahead/useListNavigation) also fire and call
|
|
60
63
|
// setOpen(true). This ref rejects that open request within the same microtask.
|
|
@@ -182,12 +185,30 @@ function SelectRoot({ id: triggerIdProp, value: controlledValue, defaultValue, o
|
|
|
182
185
|
listNavigation,
|
|
183
186
|
typeahead,
|
|
184
187
|
]);
|
|
188
|
+
// Flag: when the dropdown opens, the first registerOption call clears the
|
|
189
|
+
// registry before adding. This ensures stale entries from options that changed
|
|
190
|
+
// while closed don't persist — and avoids a useLayoutEffect ordering issue
|
|
191
|
+
// where a parent clear would run after children register.
|
|
192
|
+
const prevOpenForFlush = useRef(open);
|
|
193
|
+
const needsFlushRef = useRef(false);
|
|
194
|
+
if (open && !prevOpenForFlush.current)
|
|
195
|
+
needsFlushRef.current = true;
|
|
196
|
+
prevOpenForFlush.current = open;
|
|
185
197
|
// Register option (stable callback - no value/displayText deps)
|
|
186
198
|
const registerOption = useCallback((option) => {
|
|
199
|
+
if (needsFlushRef.current) {
|
|
200
|
+
optionsRef.current.clear();
|
|
201
|
+
needsFlushRef.current = false;
|
|
202
|
+
}
|
|
187
203
|
optionsRef.current.set(toKey(option.value), option);
|
|
188
204
|
setRegistryVersion((v) => v + 1);
|
|
189
205
|
}, []);
|
|
190
206
|
const unregisterOption = useCallback((optionValue) => {
|
|
207
|
+
// When the dropdown closes, options unmount and call unregister in cleanup.
|
|
208
|
+
// Skip the delete to preserve the registry — displayText and typeahead
|
|
209
|
+
// depend on it while closed. Options re-register on next open (same keys).
|
|
210
|
+
if (!openRef.current)
|
|
211
|
+
return;
|
|
191
212
|
optionsRef.current.delete(toKey(optionValue));
|
|
192
213
|
setRegistryVersion((v) => v + 1);
|
|
193
214
|
}, []);
|
|
@@ -355,6 +376,10 @@ function SelectTriggerComponent({ asChild = false, className, children, }) {
|
|
|
355
376
|
'aria-keyshortcuts': clearable && hasValue ? 'Delete' : undefined,
|
|
356
377
|
'data-state': open ? 'open' : 'closed',
|
|
357
378
|
...floatingProps,
|
|
379
|
+
// Override Floating UI's aria-controls — only reference the listbox when
|
|
380
|
+
// it's actually in the DOM. After first open, the listbox unmounts when
|
|
381
|
+
// closed to save hooks. A phantom aria-controls violates ARIA 1.2.
|
|
382
|
+
'aria-controls': open ? floatingProps['aria-controls'] : undefined,
|
|
358
383
|
};
|
|
359
384
|
if (asChild && isValidElement(children)) {
|
|
360
385
|
const childProps = children.props;
|
|
@@ -382,7 +407,7 @@ function SelectTriggerComponent({ asChild = false, className, children, }) {
|
|
|
382
407
|
ref: refs.setReference,
|
|
383
408
|
'aria-haspopup': floatingProps['aria-haspopup'],
|
|
384
409
|
'aria-expanded': floatingProps['aria-expanded'],
|
|
385
|
-
'aria-controls': floatingProps['aria-controls'],
|
|
410
|
+
'aria-controls': open ? floatingProps['aria-controls'] : undefined,
|
|
386
411
|
'aria-activedescendant': floatingProps['aria-activedescendant'],
|
|
387
412
|
'aria-describedby': ariaDescribedBy,
|
|
388
413
|
// asChild: use aria-disabled + data-disabled since element may not support native disabled
|
|
@@ -411,6 +436,12 @@ SelectTriggerComponent.displayName = 'Select.Trigger';
|
|
|
411
436
|
function SelectContentComponent({ className, children, }) {
|
|
412
437
|
const { open, listboxId, triggerId, refs, floatingStyles, getFloatingProps, listRef, activeIndex, handleSelect, orderedOptions, optionIndexMap, } = useSelectContext();
|
|
413
438
|
const portalRoot = getPortalRootFor(refs.reference.current);
|
|
439
|
+
// Track whether dropdown has ever been opened. Before first open, mount
|
|
440
|
+
// children in a hidden div for option registration (defaultValue resolution).
|
|
441
|
+
// After first open, only mount children when open (in portal).
|
|
442
|
+
const hasEverOpened = useRef(false);
|
|
443
|
+
if (open)
|
|
444
|
+
hasEverOpened.current = true;
|
|
414
445
|
// Memoized context for options
|
|
415
446
|
const contentContext = useMemo(() => ({
|
|
416
447
|
listRef,
|
|
@@ -419,7 +450,7 @@ function SelectContentComponent({ className, children, }) {
|
|
|
419
450
|
orderedOptions,
|
|
420
451
|
optionIndexMap,
|
|
421
452
|
}), [listRef, activeIndex, handleSelect, orderedOptions, optionIndexMap]);
|
|
422
|
-
return (_jsxs(_Fragment, { children: [!open && (_jsx("div", { id: listboxId, role: "listbox", style: { display: 'none' }, "aria-hidden": "true", children: _jsx(SelectContentContext.Provider, { value: contentContext, children: children }) })), open && (_jsx(FloatingPortal, { root: portalRoot, children: _jsx("div", { ref: refs.setFloating, id: listboxId, role: "listbox", "aria-labelledby": triggerId, className: cx('tui-select__content', className), style: {
|
|
453
|
+
return (_jsxs(_Fragment, { children: [!open && !hasEverOpened.current && (_jsx("div", { id: listboxId, role: "listbox", style: { display: 'none' }, "aria-hidden": "true", children: _jsx(SelectContentContext.Provider, { value: contentContext, children: children }) })), open && (_jsx(FloatingPortal, { root: portalRoot, children: _jsx("div", { ref: refs.setFloating, id: listboxId, role: "listbox", "aria-labelledby": triggerId, className: cx('tui-select__content', className), style: {
|
|
423
454
|
...floatingStyles,
|
|
424
455
|
minWidth: refs.reference.current?.offsetWidth,
|
|
425
456
|
pointerEvents: 'auto',
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import type { StepIndicatorProps } from './types';
|
|
2
|
-
export declare
|
|
2
|
+
export declare const StepIndicator: import("react").NamedExoticComponent<StepIndicatorProps>;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { memo } from 'react';
|
|
2
3
|
import { defaultStepIndicatorLabels } from './types.js';
|
|
3
4
|
import { Progress } from '../Progress/index.js';
|
|
4
5
|
import { Icon } from '../Icon/index.js';
|
|
@@ -22,7 +23,7 @@ function inferStatus(value) {
|
|
|
22
23
|
// =============================================================================
|
|
23
24
|
// Component
|
|
24
25
|
// =============================================================================
|
|
25
|
-
export function StepIndicator(props) {
|
|
26
|
+
export const StepIndicator = memo(function StepIndicator(props) {
|
|
26
27
|
const { value = 0, status: statusOverride, icon: customIcon, size = 'sm', showValue = false, label, className, labels: labelsProp, } = props;
|
|
27
28
|
const labels = { ...defaultStepIndicatorLabels, ...labelsProp };
|
|
28
29
|
// Infer status from value, allow override
|
|
@@ -64,4 +65,4 @@ export function StepIndicator(props) {
|
|
|
64
65
|
.filter(Boolean)
|
|
65
66
|
.join(' ');
|
|
66
67
|
return (_jsx("div", { className: rootClassName, children: _jsxs(Progress, { mode: "circle", variant: variant, size: size, value: displayValue, showLabels: showValue || hasIcon, labelPosition: "inside", "aria-label": ariaLabel, children: [hasIcon && iconName && (_jsx(Icon, { name: iconName })), showValue && status === 'in-progress' && (_jsxs("span", { className: "tui-step-indicator__value", children: [Math.round(displayValue), "%"] }))] }) }));
|
|
67
|
-
}
|
|
68
|
+
});
|
|
@@ -2,7 +2,7 @@ import type { TooltipProviderProps, TooltipProps, TooltipTriggerProps, TooltipCo
|
|
|
2
2
|
declare function TooltipProviderComponent({ delayDuration, closeDelayDuration, children, }: TooltipProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
3
3
|
declare function TooltipRoot({ open: controlledOpen, onOpenChange, defaultOpen, delayDuration: localDelay, children, }: TooltipProps): import("react/jsx-runtime").JSX.Element;
|
|
4
4
|
declare function TooltipTriggerComponent({ asChild, 'aria-label': ariaLabel, children, }: TooltipTriggerProps): import("react/jsx-runtime").JSX.Element;
|
|
5
|
-
declare function TooltipContentComponent(
|
|
5
|
+
declare function TooltipContentComponent(props: TooltipContentProps): import("react/jsx-runtime").JSX.Element | null;
|
|
6
6
|
type TooltipCompound = typeof TooltipRoot & {
|
|
7
7
|
Provider: typeof TooltipProviderComponent;
|
|
8
8
|
Trigger: typeof TooltipTriggerComponent;
|
|
@@ -120,12 +120,22 @@ function TooltipTriggerComponent({ asChild = false, 'aria-label': ariaLabel, chi
|
|
|
120
120
|
// =============================================================================
|
|
121
121
|
// TooltipContent
|
|
122
122
|
// =============================================================================
|
|
123
|
-
|
|
124
|
-
|
|
123
|
+
// Gate component: reads context to decide whether to mount the real content.
|
|
124
|
+
// This ensures useFloating and all other hooks in TooltipContentInner only
|
|
125
|
+
// run when the tooltip is actually open — not on every render cycle.
|
|
126
|
+
function TooltipContentComponent(props) {
|
|
127
|
+
const { open } = useTooltipContext();
|
|
128
|
+
if (!open)
|
|
129
|
+
return null;
|
|
130
|
+
return _jsx(TooltipContentInner, { ...props });
|
|
131
|
+
}
|
|
132
|
+
// Inner component: only mounted when open. All Floating UI hooks live here.
|
|
133
|
+
function TooltipContentInner({ side = 'top', align = 'center', sideOffset = 8, theme = 'dark', className, children, }) {
|
|
134
|
+
const { setOpen, triggerRef, contentId, cancelClose, handleClose } = useTooltipContext();
|
|
125
135
|
const arrowRef = useRef(null);
|
|
126
136
|
const { refs, floatingStyles, context } = useFloating({
|
|
127
137
|
placement: toPlacement(side, align),
|
|
128
|
-
open,
|
|
138
|
+
open: true, // Always true when mounted (gate handles the conditional)
|
|
129
139
|
middleware: [
|
|
130
140
|
offset(sideOffset),
|
|
131
141
|
flip(),
|
|
@@ -145,8 +155,6 @@ function TooltipContentComponent({ side = 'top', align = 'center', sideOffset =
|
|
|
145
155
|
// - Focus on trigger: close + stopPropagation (avoid closing parent modal)
|
|
146
156
|
// - Hover-only (focus elsewhere): close without stopPropagation
|
|
147
157
|
useEffect(() => {
|
|
148
|
-
if (!open)
|
|
149
|
-
return;
|
|
150
158
|
const handleKeyDown = (e) => {
|
|
151
159
|
if (e.key === 'Escape') {
|
|
152
160
|
const activeEl = document.activeElement;
|
|
@@ -160,22 +168,20 @@ function TooltipContentComponent({ side = 'top', align = 'center', sideOffset =
|
|
|
160
168
|
};
|
|
161
169
|
document.addEventListener('keydown', handleKeyDown);
|
|
162
170
|
return () => document.removeEventListener('keydown', handleKeyDown);
|
|
163
|
-
}, [
|
|
171
|
+
}, [setOpen, triggerRef]);
|
|
164
172
|
// Get portal root inside .tui-interface
|
|
165
173
|
const portalRoot = getPortalRootFor(triggerRef.current);
|
|
166
174
|
// Dev warning: tooltips should not contain interactive content (WCAG 1.4.13)
|
|
167
175
|
// Use Popover for interactive overlays instead
|
|
168
176
|
useEffect(() => {
|
|
169
|
-
if (isDev() &&
|
|
177
|
+
if (isDev() && refs.floating.current) {
|
|
170
178
|
const interactive = refs.floating.current.querySelectorAll('a, button, input, select, textarea, [tabindex]:not([tabindex="-1"])');
|
|
171
179
|
if (interactive.length > 0) {
|
|
172
180
|
console.warn('[Tooltip] Contains interactive elements which violates WCAG 1.4.13. ' +
|
|
173
181
|
'Tooltips should only contain plain text. Use Popover for interactive content.');
|
|
174
182
|
}
|
|
175
183
|
}
|
|
176
|
-
}, [
|
|
177
|
-
if (!open)
|
|
178
|
-
return null;
|
|
184
|
+
}, [refs.floating]);
|
|
179
185
|
return (_jsx(FloatingPortal, { root: portalRoot, children: _jsxs("div", { ref: refs.setFloating, id: contentId, role: "tooltip", className: cx('tui-tooltip', theme === 'light' && 'is-theme-light', className), style: floatingStyles, onMouseEnter: cancelClose, onMouseLeave: handleClose, children: [children, _jsx(FloatingArrow, { ref: arrowRef, context: context, className: "tui-tooltip__arrow" })] }) }));
|
|
180
186
|
}
|
|
181
187
|
export const Tooltip = TooltipRoot;
|
package/package.json
CHANGED
package/tui-manifest.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.0.
|
|
3
|
-
"generated": "2026-03-
|
|
2
|
+
"version": "0.0.10",
|
|
3
|
+
"generated": "2026-03-19T23:08:17.377Z",
|
|
4
4
|
"components": {
|
|
5
5
|
"Accordion": {
|
|
6
6
|
"props": {
|
|
@@ -110,13 +110,11 @@
|
|
|
110
110
|
"size": {
|
|
111
111
|
"type": "\"xs\" | \"sm\" | \"md\" | \"lg\" | \"xl\" | \"xxl\"",
|
|
112
112
|
"required": false,
|
|
113
|
-
"defaultValue": "md",
|
|
114
113
|
"description": "Size of the avatar"
|
|
115
114
|
},
|
|
116
115
|
"shape": {
|
|
117
116
|
"type": "\"circle\" | \"square\"",
|
|
118
117
|
"required": false,
|
|
119
|
-
"defaultValue": "circle",
|
|
120
118
|
"description": "Shape of the avatar"
|
|
121
119
|
},
|
|
122
120
|
"indicator": {
|
|
@@ -132,7 +130,6 @@
|
|
|
132
130
|
"indicatorPosition": {
|
|
133
131
|
"type": "\"top-left\" | \"top-right\" | \"bottom-left\" | \"bottom-right\"",
|
|
134
132
|
"required": false,
|
|
135
|
-
"defaultValue": "bottom-right",
|
|
136
133
|
"description": "Position of the indicator"
|
|
137
134
|
},
|
|
138
135
|
"tooltip": {
|
|
@@ -1208,6 +1205,11 @@
|
|
|
1208
1205
|
"size": {
|
|
1209
1206
|
"type": "\"sm\" | \"md\" | \"lg\"",
|
|
1210
1207
|
"required": false
|
|
1208
|
+
},
|
|
1209
|
+
"preventScrollOnRestore": {
|
|
1210
|
+
"type": "boolean",
|
|
1211
|
+
"required": false,
|
|
1212
|
+
"description": "When true, prevents the browser from scrolling to the trigger element\nwhen focus is restored on close. Useful when the trigger may be off-screen\ninside a scrollable container. Default: false."
|
|
1211
1213
|
}
|
|
1212
1214
|
},
|
|
1213
1215
|
"cssTokens": [
|
|
@@ -1277,13 +1279,11 @@
|
|
|
1277
1279
|
"mode": {
|
|
1278
1280
|
"type": "\"full\" | \"handle\"",
|
|
1279
1281
|
"required": false,
|
|
1280
|
-
"defaultValue": "full",
|
|
1281
1282
|
"description": "Structural mode. 'full' (default) shows background panel with arrows/index. 'handle' shows only the bare drag icon button."
|
|
1282
1283
|
},
|
|
1283
1284
|
"size": {
|
|
1284
1285
|
"type": "\"sm\" | \"md\"",
|
|
1285
1286
|
"required": false,
|
|
1286
|
-
"defaultValue": "md",
|
|
1287
1287
|
"description": "Component scale. Full mode: sm = 32px, md = 40px. Handle mode: sm = 24px, md = 32px."
|
|
1288
1288
|
},
|
|
1289
1289
|
"index": {
|
|
@@ -1294,19 +1294,16 @@
|
|
|
1294
1294
|
"locked": {
|
|
1295
1295
|
"type": "boolean",
|
|
1296
1296
|
"required": false,
|
|
1297
|
-
"defaultValue": "false",
|
|
1298
1297
|
"description": "When true, shows lock icon and disables all interaction."
|
|
1299
1298
|
},
|
|
1300
1299
|
"canMoveUp": {
|
|
1301
1300
|
"type": "boolean",
|
|
1302
1301
|
"required": false,
|
|
1303
|
-
"defaultValue": "true",
|
|
1304
1302
|
"description": "When false, disables the move-up button without hiding it. Default: true."
|
|
1305
1303
|
},
|
|
1306
1304
|
"canMoveDown": {
|
|
1307
1305
|
"type": "boolean",
|
|
1308
1306
|
"required": false,
|
|
1309
|
-
"defaultValue": "true",
|
|
1310
1307
|
"description": "When false, disables the move-down button without hiding it. Default: true."
|
|
1311
1308
|
},
|
|
1312
1309
|
"labels": {
|