@tangible/ui 0.0.1 → 0.0.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.
Files changed (129) hide show
  1. package/components/Card/Card.d.ts +1 -0
  2. package/components/Card/Card.js +17 -20
  3. package/components/Checkbox/Checkbox.d.ts +9 -0
  4. package/components/Checkbox/Checkbox.js +92 -0
  5. package/components/Checkbox/index.d.ts +2 -0
  6. package/components/Checkbox/index.js +1 -0
  7. package/components/Checkbox/types.d.ts +10 -0
  8. package/components/Checkbox/types.js +1 -0
  9. package/components/Chip/Chip.d.ts +4 -1
  10. package/components/Chip/Chip.js +32 -7
  11. package/components/ChipGroup/ChipGroup.d.ts +5 -0
  12. package/components/ChipGroup/ChipGroup.js +68 -0
  13. package/components/ChipGroup/ChipGroupContext.d.ts +3 -0
  14. package/components/ChipGroup/ChipGroupContext.js +5 -0
  15. package/components/ChipGroup/index.d.ts +3 -0
  16. package/components/ChipGroup/index.js +2 -0
  17. package/components/ChipGroup/types.d.ts +36 -0
  18. package/components/ChipGroup/types.js +1 -0
  19. package/components/Chips/Chips.d.ts +2 -0
  20. package/components/Chips/Chips.js +1 -1
  21. package/components/Combobox/Combobox.d.ts +33 -0
  22. package/components/Combobox/Combobox.js +466 -0
  23. package/components/Combobox/ComboboxContext.d.ts +8 -0
  24. package/components/Combobox/ComboboxContext.js +36 -0
  25. package/components/Combobox/index.d.ts +2 -0
  26. package/components/Combobox/index.js +1 -0
  27. package/components/Combobox/types.d.ts +204 -0
  28. package/components/Combobox/types.js +1 -0
  29. package/components/Dropdown/Dropdown.js +2 -1
  30. package/components/Field/Field.d.ts +39 -0
  31. package/components/Field/Field.js +92 -0
  32. package/components/Field/FieldContext.d.ts +16 -0
  33. package/components/Field/FieldContext.js +10 -0
  34. package/components/Field/index.d.ts +2 -0
  35. package/components/Field/index.js +1 -0
  36. package/components/Modal/Modal.d.ts +4 -4
  37. package/components/Modal/Modal.js +14 -12
  38. package/components/MultiSelect/MultiSelect.d.ts +39 -0
  39. package/components/MultiSelect/MultiSelect.js +623 -0
  40. package/components/MultiSelect/MultiSelectContext.d.ts +20 -0
  41. package/components/MultiSelect/MultiSelectContext.js +56 -0
  42. package/components/MultiSelect/index.d.ts +2 -0
  43. package/components/MultiSelect/index.js +1 -0
  44. package/components/MultiSelect/types.d.ts +218 -0
  45. package/components/MultiSelect/types.js +3 -0
  46. package/components/Notice/Notice.d.ts +1 -1
  47. package/components/Notice/Notice.js +1 -1
  48. package/components/Progress/Progress.js +1 -1
  49. package/components/Progress/types.d.ts +7 -7
  50. package/components/Radio/Radio.d.ts +2 -0
  51. package/components/Radio/Radio.js +50 -0
  52. package/components/Radio/RadioGroup.d.ts +2 -0
  53. package/components/Radio/RadioGroup.js +54 -0
  54. package/components/Radio/RadioGroupContext.d.ts +3 -0
  55. package/components/Radio/RadioGroupContext.js +9 -0
  56. package/components/Radio/index.d.ts +8 -0
  57. package/components/Radio/index.js +6 -0
  58. package/components/Radio/types.d.ts +32 -0
  59. package/components/Radio/types.js +1 -0
  60. package/components/Rating/Rating.d.ts +5 -5
  61. package/components/Rating/Rating.js +2 -2
  62. package/components/SegmentedControl/SegmentedControl.js +20 -104
  63. package/components/SegmentedControl/types.d.ts +4 -8
  64. package/components/Select/Select.d.ts +39 -0
  65. package/components/Select/Select.js +497 -0
  66. package/components/Select/SelectContext.d.ts +20 -0
  67. package/components/Select/SelectContext.js +56 -0
  68. package/components/Select/index.d.ts +3 -0
  69. package/components/Select/index.js +1 -0
  70. package/components/Select/types.d.ts +216 -0
  71. package/components/Select/types.js +11 -0
  72. package/components/Sidebar/Sidebar.js +12 -12
  73. package/components/Sidebar/types.d.ts +5 -5
  74. package/components/StepIndicator/StepIndicator.js +1 -1
  75. package/components/StepList/StepList.js +2 -2
  76. package/components/StepList/types.d.ts +4 -4
  77. package/components/Switch/Switch.d.ts +9 -0
  78. package/components/Switch/Switch.js +91 -0
  79. package/components/Switch/index.d.ts +2 -0
  80. package/components/Switch/index.js +1 -0
  81. package/components/Switch/types.d.ts +11 -0
  82. package/components/Switch/types.js +1 -0
  83. package/components/TextInput/TextInput.d.ts +8 -0
  84. package/components/TextInput/TextInput.js +25 -0
  85. package/components/TextInput/index.d.ts +2 -0
  86. package/components/TextInput/index.js +1 -0
  87. package/components/TextInput/types.d.ts +32 -0
  88. package/components/TextInput/types.js +1 -0
  89. package/components/Textarea/Textarea.d.ts +6 -0
  90. package/components/Textarea/Textarea.js +49 -0
  91. package/components/Textarea/index.d.ts +2 -0
  92. package/components/Textarea/index.js +1 -0
  93. package/components/Textarea/types.d.ts +25 -0
  94. package/components/Textarea/types.js +1 -0
  95. package/components/index.d.ts +20 -0
  96. package/components/index.js +10 -0
  97. package/icons/icons.svg +1 -0
  98. package/icons/manifest.json +8 -0
  99. package/icons/registry.d.ts +2 -0
  100. package/icons/registry.js +1 -0
  101. package/icons/system/index.d.ts +2 -0
  102. package/icons/system/index.js +11 -0
  103. package/package.json +1 -1
  104. package/styles/all.css +1 -1
  105. package/styles/all.expanded.css +1187 -96
  106. package/styles/all.expanded.unlayered.css +1187 -96
  107. package/styles/all.unlayered.css +1 -1
  108. package/styles/components/_bundle.scss +20 -0
  109. package/styles/components/input/index.scss +5 -20
  110. package/styles/index.scss +16 -0
  111. package/styles/system/_control.scss +34 -0
  112. package/styles/system/_tokens.scss +8 -0
  113. package/styles/system/index.scss +2 -1
  114. package/styles/utilities/_index.scss +50 -0
  115. package/tui-manifest.json +632 -61
  116. package/utils/compose-events.d.ts +15 -0
  117. package/utils/compose-events.js +27 -0
  118. package/utils/hash.d.ts +15 -0
  119. package/utils/hash.js +32 -0
  120. package/utils/index.d.ts +3 -0
  121. package/utils/index.js +6 -0
  122. package/utils/is-dev.d.ts +5 -0
  123. package/utils/is-dev.js +7 -0
  124. package/utils/use-controllable-state.d.ts +19 -0
  125. package/utils/use-controllable-state.js +59 -0
  126. package/utils/use-roving-group.d.ts +33 -0
  127. package/utils/use-roving-group.js +123 -0
  128. package/utils/value-key.d.ts +16 -0
  129. package/utils/value-key.js +14 -0
@@ -0,0 +1,204 @@
1
+ import type { FloatingContext, ReferenceType } from '@floating-ui/react';
2
+ import type { RefObject, CSSProperties, MutableRefObject } from 'react';
3
+ import type { SizeStandard } from '../../types/sizes';
4
+ import type { OptionValue } from '../../utils/value-key';
5
+ /**
6
+ * Controls when filtering should be active.
7
+ * - 'always': Consumer filters whenever inputValue changes (default)
8
+ * - 'when-searching': Query becomes empty when not actively searching,
9
+ * allowing consumers to show all options when browsing a selection
10
+ */
11
+ export type FilterMode = 'always' | 'when-searching';
12
+ export type ComboboxProps = {
13
+ /**
14
+ * Control size.
15
+ * @default 'md'
16
+ */
17
+ size?: SizeStandard;
18
+ /**
19
+ * Controlled selected value.
20
+ */
21
+ value?: OptionValue;
22
+ /**
23
+ * Default value for uncontrolled usage.
24
+ */
25
+ defaultValue?: OptionValue;
26
+ /**
27
+ * Callback when selection changes.
28
+ * Called with the option's value when user selects, or undefined when cleared.
29
+ * Does NOT mutate inputValue — consumer should update inputValue in handler if desired.
30
+ */
31
+ onValueChange?: (value: OptionValue | undefined) => void;
32
+ /**
33
+ * Controlled input value for search/filter.
34
+ */
35
+ inputValue?: string;
36
+ /**
37
+ * Callback when input value changes (typing, clear).
38
+ */
39
+ onInputChange?: (value: string) => void;
40
+ /**
41
+ * Controls filtering behavior.
42
+ * - 'always': Filter on inputValue at all times (default)
43
+ * - 'when-searching': Query is empty when not actively typing,
44
+ * so consumers can show all options when user is browsing a selection
45
+ * @default 'always'
46
+ */
47
+ filterMode?: FilterMode;
48
+ /**
49
+ * Callback when the effective query changes.
50
+ * Query differs from inputValue when filterMode='when-searching':
51
+ * it becomes empty after selection to show all options.
52
+ */
53
+ onQueryChange?: (query: string) => void;
54
+ /**
55
+ * Controlled open state.
56
+ */
57
+ open?: boolean;
58
+ /**
59
+ * Default open state for uncontrolled usage.
60
+ * @default false
61
+ */
62
+ defaultOpen?: boolean;
63
+ /**
64
+ * Callback when open state changes.
65
+ */
66
+ onOpenChange?: (open: boolean) => void;
67
+ /**
68
+ * Whether the combobox is disabled.
69
+ * @default false
70
+ */
71
+ disabled?: boolean;
72
+ /**
73
+ * Placeholder text for the input.
74
+ */
75
+ placeholder?: string;
76
+ /**
77
+ * Whether to open the listbox when the input receives focus.
78
+ * @default true
79
+ */
80
+ openOnFocus?: boolean;
81
+ /**
82
+ * Accessible name for the combobox.
83
+ */
84
+ 'aria-label'?: string;
85
+ /**
86
+ * ID of element that labels this combobox.
87
+ */
88
+ 'aria-labelledby'?: string;
89
+ /**
90
+ * Class name applied directly to the `<input>` element.
91
+ * Use for utilities like `tui-input-reset` that must target the input itself.
92
+ */
93
+ inputClassName?: string;
94
+ children: React.ReactNode;
95
+ };
96
+ export type ComboboxContentProps = {
97
+ /**
98
+ * Additional CSS class names.
99
+ */
100
+ className?: string;
101
+ children: React.ReactNode;
102
+ };
103
+ export type ComboboxOptionProps = {
104
+ /**
105
+ * The value for this option. Required and must be unique.
106
+ * Can be string or number.
107
+ */
108
+ value: OptionValue;
109
+ /**
110
+ * Whether this option is disabled.
111
+ * @default false
112
+ */
113
+ disabled?: boolean;
114
+ /**
115
+ * Text value used for display when selected.
116
+ * Required when children is not a string.
117
+ * If children is a string, defaults to that.
118
+ */
119
+ textValue?: string;
120
+ /**
121
+ * Additional CSS class names.
122
+ */
123
+ className?: string;
124
+ children: React.ReactNode;
125
+ };
126
+ export type ComboboxGroupProps = {
127
+ /**
128
+ * Additional CSS class names.
129
+ */
130
+ className?: string;
131
+ children: React.ReactNode;
132
+ };
133
+ export type ComboboxLabelProps = {
134
+ /**
135
+ * Additional CSS class names.
136
+ */
137
+ className?: string;
138
+ children: React.ReactNode;
139
+ };
140
+ /**
141
+ * Registration record for an option.
142
+ */
143
+ export type RegisteredOption = {
144
+ value: OptionValue;
145
+ ref: RefObject<HTMLElement | null>;
146
+ disabled: boolean;
147
+ textValue: string;
148
+ };
149
+ /**
150
+ * Stable context: config, IDs, refs, and stable callbacks.
151
+ */
152
+ export type ComboboxActionsContextValue = {
153
+ disabled: boolean;
154
+ placeholder: string;
155
+ openOnFocus: boolean;
156
+ inputId: string;
157
+ listboxId: string;
158
+ ariaLabel?: string;
159
+ ariaLabelledBy?: string;
160
+ setOpen: (open: boolean) => void;
161
+ setInputValue: (value: string) => void;
162
+ selectOption: (value: OptionValue) => void;
163
+ registerOption: (option: RegisteredOption) => void;
164
+ unregisterOption: (value: OptionValue) => void;
165
+ refs: {
166
+ reference: React.RefObject<ReferenceType | null>;
167
+ floating: React.RefObject<HTMLElement | null>;
168
+ setReference: (node: ReferenceType | null) => void;
169
+ setFloating: (node: HTMLElement | null) => void;
170
+ };
171
+ inputRef: RefObject<HTMLInputElement | null>;
172
+ listRef: MutableRefObject<(HTMLElement | null)[]>;
173
+ getReferenceProps: (userProps?: React.HTMLProps<Element>) => Record<string, unknown>;
174
+ getFloatingProps: (userProps?: React.HTMLProps<HTMLElement>) => Record<string, unknown>;
175
+ getItemProps: (userProps?: React.HTMLProps<HTMLElement>) => Record<string, unknown>;
176
+ };
177
+ /**
178
+ * State context: values that change during interaction.
179
+ */
180
+ export type ComboboxStateContextValue = {
181
+ open: boolean;
182
+ value: OptionValue | undefined;
183
+ inputValue: string;
184
+ /**
185
+ * The effective query to filter on.
186
+ * When filterMode='when-searching', this is empty after selection
187
+ * (allowing all options to show). Otherwise equals inputValue.
188
+ */
189
+ query: string;
190
+ activeIndex: number;
191
+ activeOptionId: string | undefined;
192
+ orderedOptions: RegisteredOption[];
193
+ floatingStyles: CSSProperties;
194
+ floatingContext: FloatingContext;
195
+ };
196
+ /**
197
+ * Combined context value.
198
+ */
199
+ export type ComboboxContextValue = ComboboxActionsContextValue & ComboboxStateContextValue;
200
+ export type ComboboxContentContextValue = {
201
+ listRef: MutableRefObject<(HTMLElement | null)[]>;
202
+ activeIndex: number;
203
+ orderedOptions: RegisteredOption[];
204
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -204,7 +204,7 @@ function DropdownItemComponent({ onSelect, href, target = '_self', disabled = fa
204
204
  });
205
205
  }
206
206
  }, [disabled, onSelect, keepOpen, setOpen, triggerRef]);
207
- return (_jsx(Button, { variant: "ghost", href: disabled ? undefined : href, target: href ? target : undefined, disabled: disabled, onClick: handleClick, className: cx('tui-dropdown__item', className), ...props, children: children }));
207
+ return (_jsx(Button, { ...props, variant: "ghost", href: disabled ? undefined : href, target: href ? target : undefined, disabled: disabled, onClick: handleClick, className: cx('tui-dropdown__item', className), children: children }));
208
208
  }
209
209
  DropdownItemComponent.displayName = 'Dropdown.Item';
210
210
  export const Dropdown = DropdownRoot;
@@ -216,4 +216,5 @@ export const DropdownTrigger = DropdownTriggerComponent;
216
216
  export const DropdownContent = DropdownContentComponent;
217
217
  export const DropdownItem = DropdownItemComponent;
218
218
  // Hook for advanced use cases (custom items that need to close the dropdown)
219
+ // eslint-disable-next-line react-refresh/only-export-components
219
220
  export { useDropdownContext as useDropdown } from './DropdownContext.js';
@@ -0,0 +1,39 @@
1
+ import React from 'react';
2
+ export type FieldProps = {
3
+ /** Whether the field has an error state */
4
+ error?: boolean;
5
+ /** Whether the field is required */
6
+ required?: boolean;
7
+ /** Whether the field is disabled */
8
+ disabled?: boolean;
9
+ /** Inline layout: label and control on same row */
10
+ inline?: boolean;
11
+ /** Additional class name for the field wrapper */
12
+ className?: string;
13
+ children?: React.ReactNode;
14
+ };
15
+ type LabelProps = React.LabelHTMLAttributes<HTMLLabelElement> & {
16
+ /** Visually hide the label while keeping it accessible */
17
+ hidden?: boolean;
18
+ className?: string;
19
+ children?: React.ReactNode;
20
+ };
21
+ type ControlProps = {
22
+ children: React.ReactElement;
23
+ };
24
+ type HelperTextProps = React.HTMLAttributes<HTMLDivElement> & {
25
+ className?: string;
26
+ children?: React.ReactNode;
27
+ };
28
+ type ErrorProps = React.HTMLAttributes<HTMLDivElement> & {
29
+ className?: string;
30
+ children?: React.ReactNode;
31
+ };
32
+ type FieldCompound = React.ForwardRefExoticComponent<FieldProps & React.RefAttributes<HTMLDivElement>> & {
33
+ Label: React.FC<LabelProps>;
34
+ Control: React.FC<ControlProps>;
35
+ HelperText: React.FC<HelperTextProps>;
36
+ Error: React.FC<ErrorProps>;
37
+ };
38
+ export declare const Field: FieldCompound;
39
+ export {};
@@ -0,0 +1,92 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import React, { forwardRef, useId, useMemo, cloneElement, isValidElement, Children, } from 'react';
3
+ import { cx } from '../../utils/cx.js';
4
+ import { FieldContext, useFieldContext } from './FieldContext.js';
5
+ export const Field = forwardRef(function Field({ error = false, required = false, disabled = false, inline = false, className, children, }, ref) {
6
+ const baseId = useId();
7
+ const controlId = `${baseId}-control`;
8
+ const helperTextId = `${baseId}-helper`;
9
+ const errorId = `${baseId}-error`;
10
+ const contextValue = useMemo(() => ({
11
+ controlId,
12
+ helperTextId,
13
+ errorId,
14
+ hasError: error,
15
+ required,
16
+ disabled,
17
+ }), [controlId, helperTextId, errorId, error, required, disabled]);
18
+ const classes = cx('tui-field', error && 'is-error', disabled && 'is-disabled', inline && 'is-layout-inline', className);
19
+ return (_jsx(FieldContext.Provider, { value: contextValue, children: _jsx("div", { ref: ref, className: classes, children: children }) }));
20
+ });
21
+ // =============================================================================
22
+ // Field.Label
23
+ // =============================================================================
24
+ function FieldLabel({ hidden = false, className, children, ...rest }) {
25
+ const { controlId, required } = useFieldContext();
26
+ const classes = cx('tui-field__label', hidden && 'tui-visually-hidden', className);
27
+ return (_jsxs("label", { htmlFor: controlId, className: classes, ...rest, children: [children, required && (_jsxs(_Fragment, { children: [_jsx("span", { className: "tui-field__required", "aria-hidden": "true", children: "*" }), _jsx("span", { className: "tui-visually-hidden", children: "required" })] }))] }));
28
+ }
29
+ FieldLabel.displayName = 'Field.Label';
30
+ // =============================================================================
31
+ // Field.Control
32
+ // =============================================================================
33
+ function FieldControl({ children }) {
34
+ const { controlId, helperTextId, errorId, hasError, required, disabled, } = useFieldContext();
35
+ const child = Children.only(children);
36
+ if (!isValidElement(child)) {
37
+ throw new Error('Field.Control expects a single React element as its child');
38
+ }
39
+ const childProps = child.props;
40
+ // Build aria-describedby, merging with any existing value from child
41
+ // Per ARIA spec, referencing non-existent IDs is valid (AT ignores them)
42
+ const describedByParts = [];
43
+ if (typeof childProps['aria-describedby'] === 'string') {
44
+ describedByParts.push(childProps['aria-describedby']);
45
+ }
46
+ describedByParts.push(helperTextId);
47
+ if (hasError) {
48
+ describedByParts.push(errorId);
49
+ }
50
+ const describedBy = describedByParts.join(' ');
51
+ // Clone child with a11y props
52
+ // Note: aria-invalid and aria-required must be string "true", not boolean
53
+ return cloneElement(child, {
54
+ id: controlId,
55
+ 'aria-describedby': describedBy,
56
+ 'aria-invalid': hasError ? 'true' : undefined,
57
+ 'aria-required': required ? 'true' : undefined,
58
+ disabled: disabled || childProps.disabled,
59
+ });
60
+ }
61
+ FieldControl.displayName = 'Field.Control';
62
+ // =============================================================================
63
+ // Field.HelperText
64
+ // =============================================================================
65
+ function FieldHelperText({ className, children, ...rest }) {
66
+ const { helperTextId } = useFieldContext();
67
+ if (!children)
68
+ return null;
69
+ const classes = cx('tui-field__helper', className);
70
+ return (_jsx("div", { id: helperTextId, className: classes, ...rest, children: children }));
71
+ }
72
+ FieldHelperText.displayName = 'Field.HelperText';
73
+ // =============================================================================
74
+ // Field.Error
75
+ // =============================================================================
76
+ function FieldError({ className, children, ...rest }) {
77
+ const { errorId, hasError } = useFieldContext();
78
+ // Only render if there's an error and content
79
+ if (!hasError || !children)
80
+ return null;
81
+ const classes = cx('tui-field__error', className);
82
+ // role="alert" implies aria-live="assertive" - no need to add aria-live
83
+ return (_jsx("div", { id: errorId, className: classes, role: "alert", ...rest, children: children }));
84
+ }
85
+ FieldError.displayName = 'Field.Error';
86
+ // =============================================================================
87
+ // Compound Export
88
+ // =============================================================================
89
+ Field.Label = FieldLabel;
90
+ Field.Control = FieldControl;
91
+ Field.HelperText = FieldHelperText;
92
+ Field.Error = FieldError;
@@ -0,0 +1,16 @@
1
+ export type FieldContextValue = {
2
+ /** ID for the form control element */
3
+ controlId: string;
4
+ /** ID for helper text (for aria-describedby) */
5
+ helperTextId: string;
6
+ /** ID for error message (for aria-describedby) */
7
+ errorId: string;
8
+ /** Whether the field has an error */
9
+ hasError: boolean;
10
+ /** Whether the field is required */
11
+ required: boolean;
12
+ /** Whether the field is disabled */
13
+ disabled: boolean;
14
+ };
15
+ export declare const FieldContext: import("react").Context<FieldContextValue | null>;
16
+ export declare function useFieldContext(): FieldContextValue;
@@ -0,0 +1,10 @@
1
+ import { createContext, useContext } from 'react';
2
+ export const FieldContext = createContext(null);
3
+ export function useFieldContext() {
4
+ const context = useContext(FieldContext);
5
+ if (!context) {
6
+ throw new Error('Field compound components must be used within a Field. ' +
7
+ 'Wrap your Field.Label, Field.Control, Field.HelperText, and Field.Error in a <Field> component.');
8
+ }
9
+ return context;
10
+ }
@@ -0,0 +1,2 @@
1
+ export { Field } from './Field';
2
+ export type { FieldProps } from './Field';
@@ -0,0 +1 @@
1
+ export { Field } from './Field.js';
@@ -2,13 +2,13 @@ import React from 'react';
2
2
  import type { SizeStandard } from '../../types';
3
3
  type Size = SizeStandard;
4
4
  export type ModalProps = {
5
- isOpen: boolean;
5
+ open: boolean;
6
6
  onClose: () => void;
7
7
  size?: Size;
8
8
  stickyHead?: boolean;
9
9
  stickyFoot?: boolean;
10
- labelledBy?: string;
11
- describedBy?: string;
10
+ 'aria-labelledby'?: string;
11
+ 'aria-describedby'?: string;
12
12
  initialFocusSelector?: string;
13
13
  container?: Element | null;
14
14
  showCloseButton?: boolean;
@@ -17,7 +17,7 @@ export type ModalProps = {
17
17
  closeOnEscape?: boolean;
18
18
  children?: React.ReactNode;
19
19
  };
20
- declare function ModalRoot({ isOpen, onClose, size, stickyHead, stickyFoot, labelledBy, describedBy, initialFocusSelector, container, showCloseButton, closeLabel, closeOnBackdropClick, closeOnEscape, children, }: ModalProps): React.ReactPortal | null;
20
+ 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;
21
21
  type ModalCloseProps = {
22
22
  label?: string;
23
23
  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({ isOpen, onClose, size = 'md', stickyHead, stickyFoot, labelledBy, 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, children, }) {
12
12
  const dialogRef = useRef(null);
13
13
  const restoreRef = useRef(null);
14
14
  const [mount, setMount] = useState(null);
@@ -21,7 +21,7 @@ function ModalRoot({ isOpen, onClose, size = 'md', stickyHead, stickyFoot, label
21
21
  // the captured element may not be meaningful — focus restore still works,
22
22
  // portal scoping may fall back to global interface.
23
23
  useLayoutEffect(() => {
24
- if (!isOpen || !isBrowser) {
24
+ if (!open || !isBrowser) {
25
25
  return;
26
26
  }
27
27
  if (mount)
@@ -29,10 +29,10 @@ function ModalRoot({ isOpen, onClose, size = 'md', stickyHead, stickyFoot, label
29
29
  const trigger = document.activeElement;
30
30
  restoreRef.current = trigger;
31
31
  setMount(container ?? getPortalRootFor(trigger));
32
- }, [isOpen, container, mount]);
32
+ }, [open, container, mount]);
33
33
  // Reset state when modal closes, restore focus to trigger.
34
34
  useEffect(() => {
35
- if (isOpen)
35
+ if (open)
36
36
  return;
37
37
  const el = restoreRef.current;
38
38
  if (el && typeof el.focus === 'function') {
@@ -40,25 +40,27 @@ function ModalRoot({ isOpen, onClose, size = 'md', stickyHead, stickyFoot, label
40
40
  }
41
41
  restoreRef.current = null;
42
42
  setMount(null);
43
- }, [isOpen]);
43
+ }, [open]);
44
44
  // Body scroll lock.
45
45
  useEffect(() => {
46
- if (!isOpen)
46
+ if (!open)
47
47
  return;
48
48
  document.body.classList.add('tui-modal-open');
49
49
  return () => {
50
50
  document.body.classList.remove('tui-modal-open');
51
51
  };
52
- }, [isOpen]);
52
+ }, [open]);
53
53
  // Focus trap (handles Tab cycling and ESC to close).
54
54
  useFocusTrap(dialogRef, {
55
- isActive: isOpen,
55
+ // Modal mount is two-phase (capture portal root, then render portal).
56
+ // Activate trap only once dialog is actually mounted.
57
+ isActive: open && !!mount,
56
58
  onEscape: onClose,
57
59
  escapeDeactivates: closeOnEscape,
58
60
  });
59
61
  // Make scrollable region focusable; set initial focus.
60
62
  useEffect(() => {
61
- if (!isOpen)
63
+ if (!open)
62
64
  return;
63
65
  const dialog = dialogRef.current;
64
66
  if (!dialog)
@@ -96,12 +98,12 @@ function ModalRoot({ isOpen, onClose, size = 'md', stickyHead, stickyFoot, label
96
98
  `Ensure an element with id="${labelledBy}" exists inside the modal.`);
97
99
  }
98
100
  }
99
- }, [isOpen, initialFocusSelector, labelledBy]);
101
+ }, [open, mount, initialFocusSelector, labelledBy]);
100
102
  // Memoize context value to prevent unnecessary re-renders
101
103
  const contextValue = useMemo(() => ({ onClose }), [onClose]);
102
- if (!isOpen || !mount)
104
+ if (!open || !mount)
103
105
  return null;
104
- return createPortal(_jsx(ModalContext.Provider, { value: contextValue, children: _jsxs("div", { className: "tui-modal", "data-state": "open", children: [_jsx("div", { className: "tui-modal__backdrop", onClick: closeOnBackdropClick ? onClose : undefined }), _jsxs("div", { ref: dialogRef, role: "dialog", "aria-modal": "true", "aria-labelledby": labelledBy, "aria-describedby": describedBy, className: cx('tui-modal__dialog', `is-size-${size}`, stickyHead && 'is-sticky-head', stickyFoot && 'is-sticky-foot'), tabIndex: -1, children: [showCloseButton && (_jsx(IconButton, { icon: "system/close", label: closeLabel, variant: "ghost", size: "sm", onClick: onClose, className: "tui-modal__close", showTooltip: true })), children] })] }) }), mount);
106
+ return createPortal(_jsx(ModalContext.Provider, { value: contextValue, children: _jsxs("div", { className: "tui-modal", "data-state": "open", style: { pointerEvents: 'auto' }, children: [_jsx("div", { className: "tui-modal__backdrop", onClick: closeOnBackdropClick ? onClose : undefined }), _jsxs("div", { ref: dialogRef, role: "dialog", "aria-modal": "true", "aria-labelledby": labelledBy, "aria-describedby": describedBy, className: cx('tui-modal__dialog', `is-size-${size}`, stickyHead && 'is-sticky-head', stickyFoot && 'is-sticky-foot'), tabIndex: -1, children: [showCloseButton && (_jsx(IconButton, { icon: "system/close", label: closeLabel, variant: "ghost", size: "sm", onClick: onClose, className: "tui-modal__close", showTooltip: true })), children] })] }) }), mount);
105
107
  }
106
108
  function ModalClose({ label = 'Close', className }) {
107
109
  const { onClose } = useModalContext();
@@ -0,0 +1,39 @@
1
+ import { type MultiSelectProps, type MultiSelectTriggerProps, type MultiSelectContentProps, type MultiSelectOptionProps, type MultiSelectGroupProps, type MultiSelectLabelProps } from './types';
2
+ declare function MultiSelectRoot({ id: triggerIdProp, value: controlledValue, defaultValue, onValueChange, open: controlledOpen, defaultOpen, onOpenChange, disabled, placeholder, size, display, maxChips, max, onMaxReached, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, 'aria-describedby': ariaDescribedBy, children, }: MultiSelectProps): import("react/jsx-runtime").JSX.Element;
3
+ declare namespace MultiSelectRoot {
4
+ var displayName: string;
5
+ }
6
+ declare function MultiSelectTriggerComponent({ asChild, className, children, }: MultiSelectTriggerProps): import("react/jsx-runtime").JSX.Element;
7
+ declare namespace MultiSelectTriggerComponent {
8
+ var displayName: string;
9
+ }
10
+ declare function MultiSelectContentComponent({ className, children }: MultiSelectContentProps): import("react/jsx-runtime").JSX.Element;
11
+ declare namespace MultiSelectContentComponent {
12
+ var displayName: string;
13
+ }
14
+ declare function MultiSelectOptionComponent({ value: optionValue, disabled, textValue: explicitTextValue, className, children, }: MultiSelectOptionProps): import("react/jsx-runtime").JSX.Element;
15
+ declare namespace MultiSelectOptionComponent {
16
+ var displayName: string;
17
+ }
18
+ declare function MultiSelectGroupComponent({ className, children }: MultiSelectGroupProps): import("react/jsx-runtime").JSX.Element;
19
+ declare namespace MultiSelectGroupComponent {
20
+ var displayName: string;
21
+ }
22
+ declare function MultiSelectLabelComponent({ className, children }: MultiSelectLabelProps): import("react/jsx-runtime").JSX.Element;
23
+ declare namespace MultiSelectLabelComponent {
24
+ var displayName: string;
25
+ }
26
+ type MultiSelectCompound = typeof MultiSelectRoot & {
27
+ Trigger: typeof MultiSelectTriggerComponent;
28
+ Content: typeof MultiSelectContentComponent;
29
+ Option: typeof MultiSelectOptionComponent;
30
+ Group: typeof MultiSelectGroupComponent;
31
+ Label: typeof MultiSelectLabelComponent;
32
+ };
33
+ export declare const MultiSelect: MultiSelectCompound;
34
+ export declare const MultiSelectTrigger: typeof MultiSelectTriggerComponent;
35
+ export declare const MultiSelectContent: typeof MultiSelectContentComponent;
36
+ export declare const MultiSelectOption: typeof MultiSelectOptionComponent;
37
+ export declare const MultiSelectGroup: typeof MultiSelectGroupComponent;
38
+ export declare const MultiSelectLabel: typeof MultiSelectLabelComponent;
39
+ export { useMultiSelectContext as useMultiSelect } from './MultiSelectContext';