@mlola-ui/registry 1.0.2 → 1.0.3

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 (103) hide show
  1. package/components/accordion.json +2 -2
  2. package/components/alert.json +2 -2
  3. package/components/app-shell.json +1 -1
  4. package/components/badge.json +2 -2
  5. package/components/breadcrumb.json +2 -2
  6. package/components/button.json +1 -1
  7. package/components/calendar.json +1 -1
  8. package/components/carousel.json +3 -3
  9. package/components/checkbox.json +2 -2
  10. package/components/color-picker.json +5 -5
  11. package/components/combobox.json +2 -2
  12. package/components/command-menu.json +1 -1
  13. package/components/context-menu.json +1 -1
  14. package/components/date-picker.json +2 -2
  15. package/components/dropdown-menu.json +2 -2
  16. package/components/dropzone.json +2 -2
  17. package/components/empty-state.json +1 -1
  18. package/components/hover-card.json +1 -1
  19. package/components/modal.json +2 -2
  20. package/components/number-input.json +2 -2
  21. package/components/otp-input.json +1 -1
  22. package/components/pagination.json +1 -1
  23. package/components/progress.json +1 -1
  24. package/components/segmented-control.json +2 -2
  25. package/components/select.json +3 -3
  26. package/components/sheet.json +2 -2
  27. package/components/slider.json +1 -1
  28. package/components/status-icon.json +1 -1
  29. package/components/stepper.json +2 -2
  30. package/components/tabs.json +1 -1
  31. package/components/tag-input.json +2 -2
  32. package/components/textarea.json +1 -1
  33. package/components/time-picker.json +2 -2
  34. package/components/timeline.json +2 -2
  35. package/components/toast.json +2 -2
  36. package/components/toggle.json +1 -1
  37. package/components/tour.json +2 -2
  38. package/generated/catalog.ts +40 -40
  39. package/index.json +1 -1
  40. package/package.json +1 -1
  41. package/snapshot/index.json +65 -65
  42. package/snapshot/items/components/accordion.json +2 -2
  43. package/snapshot/items/components/alert.json +2 -2
  44. package/snapshot/items/components/app-shell.json +1 -1
  45. package/snapshot/items/components/badge.json +2 -2
  46. package/snapshot/items/components/breadcrumb.json +2 -2
  47. package/snapshot/items/components/button.json +1 -1
  48. package/snapshot/items/components/calendar.json +1 -1
  49. package/snapshot/items/components/carousel.json +3 -3
  50. package/snapshot/items/components/checkbox.json +2 -2
  51. package/snapshot/items/components/color-picker.json +5 -5
  52. package/snapshot/items/components/combobox.json +2 -2
  53. package/snapshot/items/components/command-menu.json +1 -1
  54. package/snapshot/items/components/context-menu.json +1 -1
  55. package/snapshot/items/components/date-picker.json +2 -2
  56. package/snapshot/items/components/dropdown-menu.json +2 -2
  57. package/snapshot/items/components/dropzone.json +2 -2
  58. package/snapshot/items/components/empty-state.json +1 -1
  59. package/snapshot/items/components/hover-card.json +1 -1
  60. package/snapshot/items/components/modal.json +2 -2
  61. package/snapshot/items/components/number-input.json +2 -2
  62. package/snapshot/items/components/otp-input.json +1 -1
  63. package/snapshot/items/components/pagination.json +1 -1
  64. package/snapshot/items/components/progress.json +1 -1
  65. package/snapshot/items/components/segmented-control.json +2 -2
  66. package/snapshot/items/components/select.json +3 -3
  67. package/snapshot/items/components/sheet.json +2 -2
  68. package/snapshot/items/components/slider.json +1 -1
  69. package/snapshot/items/components/status-icon.json +1 -1
  70. package/snapshot/items/components/stepper.json +2 -2
  71. package/snapshot/items/components/tabs.json +1 -1
  72. package/snapshot/items/components/tag-input.json +2 -2
  73. package/snapshot/items/components/textarea.json +1 -1
  74. package/snapshot/items/components/time-picker.json +2 -2
  75. package/snapshot/items/components/timeline.json +2 -2
  76. package/snapshot/items/components/toast.json +2 -2
  77. package/snapshot/items/components/toggle.json +1 -1
  78. package/snapshot/items/components/tour.json +2 -2
  79. package/snapshot/source/packages/components/alert/alert.tsx +1 -1
  80. package/snapshot/source/packages/components/badge/badge.tsx +2 -2
  81. package/snapshot/source/packages/components/breadcrumb/breadcrumb.tsx +1 -1
  82. package/snapshot/source/packages/components/carousel/carousel.tsx +1 -0
  83. package/snapshot/source/packages/components/checkbox/checkbox.tsx +3 -2
  84. package/snapshot/source/packages/components/color-picker/color-picker.tsx +15 -13
  85. package/snapshot/source/packages/components/color-picker/color.ts +1 -1
  86. package/snapshot/source/packages/components/combobox/combobox.tsx +33 -6
  87. package/snapshot/source/packages/components/date-picker/date-picker.tsx +5 -3
  88. package/snapshot/source/packages/components/dropzone/dropzone.tsx +1 -1
  89. package/snapshot/source/packages/components/empty-state/empty-state.tsx +1 -1
  90. package/snapshot/source/packages/components/hover-card/hover-card.tsx +1 -1
  91. package/snapshot/source/packages/components/number-input/number-input.tsx +6 -5
  92. package/snapshot/source/packages/components/otp-input/otp-input.tsx +6 -4
  93. package/snapshot/source/packages/components/progress/progress.tsx +1 -1
  94. package/snapshot/source/packages/components/segmented-control/segmented-control.tsx +5 -3
  95. package/snapshot/source/packages/components/select/select.tsx +6 -5
  96. package/snapshot/source/packages/components/tag-input/tag-input.tsx +7 -6
  97. package/snapshot/source/packages/components/textarea/textarea.tsx +1 -1
  98. package/snapshot/source/packages/components/time-picker/time-picker.tsx +8 -5
  99. package/snapshot/source/packages/components/timeline/timeline.tsx +1 -1
  100. package/snapshot/source/packages/components/toast/toast.tsx +1 -1
  101. package/snapshot/source/packages/components/toggle/toggle.tsx +1 -0
  102. package/snapshot/source/packages/components/tour/tour.tsx +3 -3
  103. package/snapshot/source-manifest.json +24 -24
@@ -116,11 +116,36 @@ export function ComboboxList({ id, options, active, onActiveChange, onPick, isSe
116
116
  );
117
117
  }
118
118
 
119
- /** Open the list above the field when there is not enough room below it. */
119
+ /** The visible band a list can open into: the viewport, cut down by any ancestor that clips its content. */
120
+ function visibleBand(element: HTMLElement) {
121
+ let top = 0;
122
+ let bottom = window.innerHeight;
123
+ for (let node = element.parentElement; node && node !== document.body; node = node.parentElement) {
124
+ const style = getComputedStyle(node);
125
+ if (/(hidden|clip|auto|scroll)/.test(style.overflowY) || /(hidden|clip|auto|scroll)/.test(style.overflow)) {
126
+ const rect = node.getBoundingClientRect();
127
+ top = Math.max(top, rect.top);
128
+ bottom = Math.min(bottom, rect.bottom);
129
+ }
130
+ }
131
+ return { top, bottom };
132
+ }
133
+
134
+ /**
135
+ * Open the list below the field unless there is too little room there and
136
+ * more above, counting only space that is actually visible (a card that clips
137
+ * its content counts as an edge). The room found is left on the field as
138
+ * --ml-combobox-room, so the list never grows past what can be seen.
139
+ */
120
140
  export function sideFor(element: HTMLElement | null, room = 280): "top" | "bottom" {
121
141
  if (!element) return "bottom";
122
142
  const rect = element.getBoundingClientRect();
123
- return window.innerHeight - rect.bottom < room && rect.top > window.innerHeight - rect.bottom ? "top" : "bottom";
143
+ const band = visibleBand(element);
144
+ const below = band.bottom - rect.bottom - 12;
145
+ const above = rect.top - band.top - 12;
146
+ const side = below < room && above > below ? "top" : "bottom";
147
+ element.style.setProperty("--ml-combobox-room", `${Math.max(120, Math.floor(side === "top" ? above : below))}px`);
148
+ return side;
124
149
  }
125
150
 
126
151
  export interface ComboboxProps {
@@ -148,7 +173,7 @@ export interface ComboboxProps {
148
173
  * long lists such as countries, people or repositories. Arrows move, Enter
149
174
  * chooses, Escape restores; with `onCreate`, a missing option can be added.
150
175
  */
151
- export function Combobox({
176
+ export const Combobox = React.forwardRef<HTMLInputElement, ComboboxProps>(function Combobox({
152
177
  options,
153
178
  value,
154
179
  defaultValue = null,
@@ -164,7 +189,7 @@ export function Combobox({
164
189
  required,
165
190
  id,
166
191
  className,
167
- }: ComboboxProps) {
192
+ }: ComboboxProps, ref) {
168
193
  const autoId = React.useId();
169
194
  const fieldId = id ?? autoId;
170
195
  const listId = `${fieldId}-list`;
@@ -253,6 +278,7 @@ export function Combobox({
253
278
  </span>
254
279
  ) : null}
255
280
  <input
281
+ ref={ref}
256
282
  id={fieldId}
257
283
  className="ml-input ml-combobox-input"
258
284
  data-leading={chosen?.leading && !typed.current ? "" : undefined}
@@ -286,7 +312,7 @@ export function Combobox({
286
312
  {clearable && current && !disabled ? (
287
313
  <button
288
314
  type="button"
289
- className="ml-combobox-clear"
315
+ className="ml-combobox-clear" data-hit="expand"
290
316
  aria-label="Clear"
291
317
  tabIndex={-1}
292
318
  onMouseDown={(event) => event.preventDefault()}
@@ -317,4 +343,5 @@ export function Combobox({
317
343
  </div>
318
344
  </Field>
319
345
  );
320
- }
346
+ });
347
+ Combobox.displayName = "Combobox";
@@ -78,7 +78,7 @@ export function rangePresets(today = todayOf()): DatePreset[] {
78
78
  * beside two months. The field shows the choice in the reader's own format;
79
79
  * the value stays an ISO date, so it round-trips through forms and APIs.
80
80
  */
81
- export function DatePicker(props: DatePickerProps) {
81
+ export const DatePicker = React.forwardRef<HTMLButtonElement, DatePickerProps>(function DatePicker(props: DatePickerProps, ref) {
82
82
  const { label, hint, error, placeholder, min, max, disabledDates, enabledDates, isDisabled, presets, weekStart, locale = "en", clearable = true, disabled, required, id, today, className } = props;
83
83
  const mode = props.mode ?? "single";
84
84
  const autoId = React.useId();
@@ -111,6 +111,7 @@ export function DatePicker(props: DatePickerProps) {
111
111
 
112
112
  const trigger = (
113
113
  <button
114
+ ref={ref}
114
115
  id={fieldId}
115
116
  type="button"
116
117
  className="ml-input ml-date-picker-trigger"
@@ -175,11 +176,12 @@ export function DatePicker(props: DatePickerProps) {
175
176
  </div>
176
177
  </Popover>
177
178
  {clearable && text && !disabled ? (
178
- <button type="button" className="ml-date-picker-clear" aria-label="Clear date" onClick={() => commit(null)}>
179
+ <button type="button" className="ml-date-picker-clear" data-hit="expand" aria-label="Clear date" onClick={() => commit(null)}>
179
180
  <IconX aria-hidden="true" size="0.9em" />
180
181
  </button>
181
182
  ) : null}
182
183
  </div>
183
184
  </Field>
184
185
  );
185
- }
186
+ });
187
+ DatePicker.displayName = "DatePicker";
@@ -161,7 +161,7 @@ export function Dropzone({ onFiles, files = [], onRemove, accept, maxSize, maxFi
161
161
  {file.status === "error" ? file.error ?? "Upload failed" : file.status === "uploading" ? `${Math.round(file.progress ?? 0)}%` : formatBytes(file.size)}
162
162
  </span>
163
163
  {onRemove ? (
164
- <button type="button" className="ml-dropzone-remove" aria-label={`Remove ${file.name}`} onClick={() => onRemove(file)}>
164
+ <button type="button" className="ml-dropzone-remove" data-hit="expand" aria-label={`Remove ${file.name}`} onClick={() => onRemove(file)}>
165
165
  <IconX aria-hidden="true" size="0.875em" />
166
166
  </button>
167
167
  ) : null}
@@ -8,7 +8,7 @@ export interface EmptyStateProps extends Omit<React.HTMLAttributes<HTMLElement>,
8
8
  description?: React.ReactNode;
9
9
  /** The way forward: usually one primary action and one secondary. */
10
10
  actions?: React.ReactNode;
11
- /** "page" centres in a large area; "inline" sits inside a card or table. */
11
+ /** "page" centers in a large area; "inline" sits inside a card or table. */
12
12
  size?: "inline" | "page";
13
13
  }
14
14
 
@@ -6,7 +6,7 @@ import { cx } from "../_internal/react";
6
6
  import { useFloating, usePortalNode, type Align, type Side } from "../_internal/floating";
7
7
 
8
8
  export interface HoverCardProps {
9
- /** The link or name that shows the card; it keeps its own behaviour. */
9
+ /** The link or name that shows the card; it keeps its own behavior. */
10
10
  children: React.ReactElement<Record<string, unknown>>;
11
11
  /** The preview: a profile, a page summary, a repository. */
12
12
  content: React.ReactNode;
@@ -3,7 +3,7 @@
3
3
  import * as React from "react";
4
4
  import { IconMinus, IconPlus } from "@mlola-ui/icons";
5
5
  import { Field, fieldDescription } from "../input/input";
6
- import { cx } from "../_internal/react";
6
+ import { composeRefs, cx } from "../_internal/react";
7
7
  import { parseNumber, settle, stepValue } from "./number";
8
8
 
9
9
  export { parseNumber, settle, stepValue } from "./number";
@@ -40,7 +40,7 @@ export interface NumberInputProps {
40
40
  * (Shift for ten), Page keys take large steps, Home and End go to the
41
41
  * bounds, and holding − or + keeps counting.
42
42
  */
43
- export function NumberInput({
43
+ export const NumberInput = React.forwardRef<HTMLInputElement, NumberInputProps>(function NumberInput({
44
44
  value,
45
45
  defaultValue = null,
46
46
  onValueChange,
@@ -60,7 +60,7 @@ export function NumberInput({
60
60
  required,
61
61
  id,
62
62
  className,
63
- }: NumberInputProps) {
63
+ }: NumberInputProps, ref) {
64
64
  const autoId = React.useId();
65
65
  const fieldId = id ?? autoId;
66
66
  const [inner, setInner] = React.useState<number | null>(defaultValue);
@@ -144,7 +144,7 @@ export function NumberInput({
144
144
  <Field id={fieldId} label={label} hint={hint} error={error} required={required} disabled={disabled} className={cx("ml-number-input", className)}>
145
145
  <div className="ml-number-input-control" data-disabled={disabled || undefined} data-invalid={error ? "" : undefined}>
146
146
  <input
147
- ref={input}
147
+ ref={composeRefs(input, ref)}
148
148
  id={fieldId}
149
149
  className="ml-number-input-field"
150
150
  role="spinbutton"
@@ -181,4 +181,5 @@ export function NumberInput({
181
181
  </div>
182
182
  </Field>
183
183
  );
184
- }
184
+ });
185
+ NumberInput.displayName = "NumberInput";
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  import * as React from "react";
4
- import { useControllableState } from "../_internal/react";
4
+ import { composeRefs, useControllableState } from "../_internal/react";
5
5
  import { Field, fieldDescription } from "../input/input";
6
6
 
7
7
  export interface OtpInputProps {
@@ -28,7 +28,7 @@ export interface OtpInputProps {
28
28
  * forward, Backspace moves back, arrows move freely, and pasting or the
29
29
  * phone's SMS autofill fills every box at once.
30
30
  */
31
- export function OtpInput({
31
+ export const OtpInput = React.forwardRef<HTMLInputElement, OtpInputProps>(function OtpInput({
32
32
  length = 6,
33
33
  value,
34
34
  defaultValue = "",
@@ -42,7 +42,7 @@ export function OtpInput({
42
42
  groupSize,
43
43
  id,
44
44
  className,
45
- }: OtpInputProps) {
45
+ }: OtpInputProps, ref) {
46
46
  const [code, setCode] = useControllableState({ value, defaultValue, onChange: onValueChange });
47
47
  const boxes = React.useRef<Array<HTMLInputElement | null>>([]);
48
48
  const generated = React.useId();
@@ -75,6 +75,7 @@ export function OtpInput({
75
75
  <input
76
76
  ref={(node) => {
77
77
  boxes.current[index] = node;
78
+ if (index === 0) composeRefs(ref)(node);
78
79
  }}
79
80
  id={index === 0 ? fieldId : undefined}
80
81
  className="ml-otp-box"
@@ -113,4 +114,5 @@ export function OtpInput({
113
114
  </div>
114
115
  </Field>
115
116
  );
116
- }
117
+ });
118
+ OtpInput.displayName = "OtpInput";
@@ -3,7 +3,7 @@
3
3
  import * as React from "react";
4
4
  import { cx } from "../_internal/react";
5
5
 
6
- /** The meaning of the fill, from the theme's colour roles. */
6
+ /** The meaning of the fill, from the theme's color roles. */
7
7
  export type ProgressTone = "primary" | "info" | "success" | "warning" | "danger";
8
8
  type ProgressSize = "sm" | "md" | "lg";
9
9
  export interface ProgressProps {
@@ -17,7 +17,7 @@ export interface SegmentedControlProps extends React.HTMLAttributes<HTMLDivEleme
17
17
  onValueChange?: (value: string) => void;
18
18
  }
19
19
 
20
- export function SegmentedControl({
20
+ export const SegmentedControl = React.forwardRef<HTMLDivElement, SegmentedControlProps>(function SegmentedControl({
21
21
  options,
22
22
  label,
23
23
  value,
@@ -25,7 +25,7 @@ export function SegmentedControl({
25
25
  onValueChange,
26
26
  className,
27
27
  ...props
28
- }: SegmentedControlProps) {
28
+ }: SegmentedControlProps, ref) {
29
29
  const [current, setCurrent] = useControllableState({
30
30
  value,
31
31
  defaultValue: defaultValue ?? options[0]?.value ?? "",
@@ -48,6 +48,7 @@ export function SegmentedControl({
48
48
  return (
49
49
  <div
50
50
  {...props}
51
+ ref={ref}
51
52
  className={cx("ml-segmented-control", className)}
52
53
  role="radiogroup"
53
54
  aria-label={label}
@@ -75,4 +76,5 @@ export function SegmentedControl({
75
76
  })}
76
77
  </div>
77
78
  );
78
- }
79
+ });
80
+ SegmentedControl.displayName = "SegmentedControl";
@@ -4,7 +4,7 @@ import * as React from "react";
4
4
  import { createPortal } from "react-dom";
5
5
  import { rovingIndex } from "@mlola-ui/behavior/logic";
6
6
  import { useFloating, usePortalNode } from "../_internal/floating";
7
- import { cx, useControllableState } from "../_internal/react";
7
+ import { composeRefs, cx, useControllableState } from "../_internal/react";
8
8
  import { IconCheck, IconChevronDown, IconX } from "@mlola-ui/icons";
9
9
 
10
10
  export interface SelectOption {
@@ -56,7 +56,7 @@ interface SelectMultipleProps {
56
56
 
57
57
  export type SelectProps = SelectBaseProps & (SelectSingleProps | SelectMultipleProps);
58
58
 
59
- export function Select(props: SelectProps) {
59
+ export const Select = React.forwardRef<HTMLButtonElement, SelectProps>(function Select(props: SelectProps, ref) {
60
60
  const {
61
61
  options,
62
62
  maxVisibleChips = 3,
@@ -209,7 +209,7 @@ export function Select(props: SelectProps) {
209
209
  {label ? <span id={labelId} className={hideLabel ? "ml-visually-hidden" : "ml-select-label"}>{label}</span> : null}
210
210
  <div ref={controlRef} className="ml-select-control">
211
211
  <button
212
- ref={triggerRef}
212
+ ref={composeRefs(triggerRef, ref)}
213
213
  type="button"
214
214
  role="combobox"
215
215
  aria-haspopup="listbox"
@@ -256,7 +256,7 @@ export function Select(props: SelectProps) {
256
256
  <button
257
257
  type="button"
258
258
  aria-label={`Clear ${label ?? "selection"}`}
259
- className="ml-select-clear"
259
+ className="ml-select-clear" data-hit="expand"
260
260
  onClick={() => {
261
261
  if (multiple) setSelectedValues([]);
262
262
  else setSelectedValue("");
@@ -329,4 +329,5 @@ export function Select(props: SelectProps) {
329
329
  {error ? <p id={errorId} role="alert" className="ml-select-error">{error}</p> : null}
330
330
  </div>
331
331
  );
332
- }
332
+ });
333
+ Select.displayName = "Select";
@@ -4,7 +4,7 @@ import * as React from "react";
4
4
  import { IconX } from "@mlola-ui/icons";
5
5
  import { ComboboxList, filterOptions, sideFor, type ComboboxOption } from "../combobox/combobox";
6
6
  import { Field, fieldDescription } from "../input/input";
7
- import { cx } from "../_internal/react";
7
+ import { composeRefs, cx } from "../_internal/react";
8
8
 
9
9
  export interface TagInputProps {
10
10
  value?: string[];
@@ -43,7 +43,7 @@ export function splitTags(text: string) {
43
43
  * marks the last tag and then removes it, and arrows move between tags.
44
44
  * Suggestions, validation and a limit are optional.
45
45
  */
46
- export function TagInput({
46
+ export const TagInput = React.forwardRef<HTMLInputElement, TagInputProps>(function TagInput({
47
47
  value,
48
48
  defaultValue = [],
49
49
  onValueChange,
@@ -60,7 +60,7 @@ export function TagInput({
60
60
  required,
61
61
  id,
62
62
  className,
63
- }: TagInputProps) {
63
+ }: TagInputProps, ref) {
64
64
  const autoId = React.useId();
65
65
  const fieldId = id ?? autoId;
66
66
  const listId = `${fieldId}-list`;
@@ -195,7 +195,7 @@ export function TagInput({
195
195
  <li key={tag} className="ml-tag-input-tag" data-armed={armed === index || undefined}>
196
196
  <span className="ml-tag-input-text">{tag}</span>
197
197
  {disabled ? null : (
198
- <button type="button" className="ml-tag-input-remove" tabIndex={-1} aria-label={`Remove ${tag}`} onMouseDown={(event) => event.preventDefault()} onClick={() => remove(index)}>
198
+ <button type="button" className="ml-tag-input-remove" data-hit="expand" tabIndex={-1} aria-label={`Remove ${tag}`} onMouseDown={(event) => event.preventDefault()} onClick={() => remove(index)}>
199
199
  <IconX aria-hidden="true" size="0.8em" />
200
200
  </button>
201
201
  )}
@@ -203,7 +203,7 @@ export function TagInput({
203
203
  ))}
204
204
  </ul>
205
205
  <input
206
- ref={input}
206
+ ref={composeRefs(input, ref)}
207
207
  id={fieldId}
208
208
  className="ml-tag-input-field"
209
209
  role={suggestions ? "combobox" : undefined}
@@ -270,4 +270,5 @@ export function TagInput({
270
270
  </p>
271
271
  </Field>
272
272
  );
273
- }
273
+ });
274
+ TagInput.displayName = "TagInput";
@@ -17,7 +17,7 @@ export interface TextareaProps extends React.TextareaHTMLAttributes<HTMLTextArea
17
17
 
18
18
  /**
19
19
  * Multi-line text that grows as it is written. With a `maxLength`, a counter
20
- * appears near the limit and turns to the danger colour past it.
20
+ * appears near the limit and turns to the danger color past it.
21
21
  */
22
22
  export const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
23
23
  ({ label, hint, error, maxRows = 12, showCount = false, containerClassName, className, id, disabled, required, rows = 3, maxLength, onChange, value, defaultValue, style, "aria-describedby": describedBy, ...props }, ref) => {
@@ -4,7 +4,7 @@ import * as React from "react";
4
4
  import { IconClock } from "@mlola-ui/icons";
5
5
  import { Field, fieldDescription } from "../input/input";
6
6
  import { Popover } from "../popover/popover";
7
- import { cx } from "../_internal/react";
7
+ import { composeRefs, cx } from "../_internal/react";
8
8
  import { fromMinutes, isTimeAllowed, nearestAllowed, nextAllowed, toMinutes, type TimeMatchers, type TimeRules } from "./times";
9
9
 
10
10
  export type { TimeMatcher, TimeMatchers } from "./times";
@@ -51,7 +51,7 @@ type Segment = "hour" | "minute" | "period";
51
51
  * the field moves on by itself. A quick list offers common times. The value
52
52
  * is always "HH:MM" in 24-hour time, whatever the reader sees.
53
53
  */
54
- export function TimePicker({
54
+ export const TimePicker = React.forwardRef<HTMLSpanElement, TimePickerProps>(function TimePicker({
55
55
  label,
56
56
  hint,
57
57
  error,
@@ -70,7 +70,7 @@ export function TimePicker({
70
70
  required,
71
71
  id,
72
72
  className,
73
- }: TimePickerProps) {
73
+ }: TimePickerProps, ref) {
74
74
  const autoId = React.useId();
75
75
  const fieldId = id ?? autoId;
76
76
  const [inner, setInner] = React.useState<string | null>(defaultValue);
@@ -185,7 +185,7 @@ export function TimePicker({
185
185
  const options = enabledTimes === undefined ? grid : [...new Set([...grid, ...exact])].sort((a, b) => a - b).filter((at) => isTimeAllowed(at, rules));
186
186
  const shownAt = total !== null ? options.reduce((best, at) => (Math.abs(at - total) < Math.abs(best - total) ? at : best), options[0] ?? low) : (options.find((at) => isTimeAllowed(at, rules)) ?? low);
187
187
 
188
- // Open the list centred on the current time, once the popover has laid out.
188
+ // Open the list centered on the current time, once the popover has laid out.
189
189
  // Only the list scrolls, never the page.
190
190
  React.useEffect(() => {
191
191
  if (!open) return;
@@ -204,6 +204,8 @@ export function TimePicker({
204
204
  ref: (element: HTMLSpanElement | null) => {
205
205
  if (element) segments.current.set(segment, element);
206
206
  else segments.current.delete(segment);
207
+ // The forwarded ref is the first segment: the one a form focuses.
208
+ if (segment === "hour") composeRefs(ref)(element);
207
209
  },
208
210
  role: "spinbutton",
209
211
  tabIndex: disabled ? -1 : 0,
@@ -283,4 +285,5 @@ export function TimePicker({
283
285
  </div>
284
286
  </Field>
285
287
  );
286
- }
288
+ });
289
+ TimePicker.displayName = "TimePicker";
@@ -19,7 +19,7 @@ Timeline.displayName = "Timeline";
19
19
  interface TimelineItemProps extends Omit<React.LiHTMLAttributes<HTMLLIElement>, "title"> {
20
20
  /** Progress through a sequence: done, happening now, or still to come. */
21
21
  status?: TimelineStatus;
22
- /** Colours an event marker, for activity feeds: a failed deploy, a merged change. */
22
+ /** Colors an event marker, for activity feeds: a failed deploy, a merged change. */
23
23
  tone?: TimelineTone;
24
24
  time?: React.ReactNode;
25
25
  /** A machine-readable time for `<time dateTime>`. */
@@ -4,7 +4,7 @@ import * as React from "react";
4
4
  import { IconCircleCheck, IconCircleX, IconInfo, IconTriangleAlert, IconX } from "@mlola-ui/icons";
5
5
  import { cx } from "../_internal/react";
6
6
 
7
- /** The meaning of the message, from the theme's colour roles. */
7
+ /** The meaning of the message, from the theme's color roles. */
8
8
  export type ToastTone = "neutral" | "info" | "success" | "warning" | "danger";
9
9
  export type ToastPosition = "top-right" | "top-center" | "bottom-right" | "bottom-center";
10
10
 
@@ -30,6 +30,7 @@ const Switch = React.forwardRef<HTMLButtonElement, SwitchProps>(
30
30
  data-state={on ? "checked" : "unchecked"}
31
31
  data-size={size}
32
32
  className={cx("ml-toggle", className)}
33
+ data-hit="expand"
33
34
  onClick={(event) => {
34
35
  onClick?.(event);
35
36
  if (!event.defaultPrevented) setOn(!on);
@@ -8,7 +8,7 @@ import { usePortalNode, type Side } from "../_internal/floating";
8
8
  import { placeFloating } from "../_internal/anchor";
9
9
 
10
10
  export interface TourStep {
11
- /** A CSS selector or a ref for the element to point at; none shows the card centred. */
11
+ /** A CSS selector or a ref for the element to point at; none shows the card centered. */
12
12
  target?: string | React.RefObject<HTMLElement | null>;
13
13
  title: string;
14
14
  body?: React.ReactNode;
@@ -55,7 +55,7 @@ export function Tour({ steps, open, onOpenChange, step: stepProp, defaultStep =
55
55
  const titleId = React.useId();
56
56
  const current = steps[index];
57
57
 
58
- // Place the card beside the spotlight whenever the target moves; centre it when there is none.
58
+ // Place the card beside the spotlight whenever the target moves; center it when there is none.
59
59
  React.useLayoutEffect(() => {
60
60
  const element = card.current;
61
61
  if (!element) return;
@@ -128,7 +128,7 @@ export function Tour({ steps, open, onOpenChange, step: stepProp, defaultStep =
128
128
  <div
129
129
  ref={card}
130
130
  className="ml-tour-card"
131
- data-centred={rect ? undefined : ""}
131
+ data-centered={rect ? undefined : ""}
132
132
  role="dialog"
133
133
  aria-modal="true"
134
134
  aria-label={`${label}: step ${index + 1} of ${steps.length}`}
@@ -8,43 +8,43 @@
8
8
  "packages/components/accordion/accordion.tsx": "sha256-0ALaLJd1fvj8Vbx86HLBKm3qUItrtwbrGJTykVfNSMg=",
9
9
  "packages/components/activity-heatmap/activity-heatmap.tsx": "sha256-fMGDlXKpOfIOy0hmbpR8hKrbbN3fC386tXK27A21ZwM=",
10
10
  "packages/components/activity-heatmap/calendar.ts": "sha256-NB+AjXY5Xc+KzR8MMTRBgZxdzOeoHhkW0KQOW7WJPuQ=",
11
- "packages/components/alert/alert.tsx": "sha256-kg//PTcUW7j7AEnhfO/pZFddm++RIo8yKur46OeoeNo=",
11
+ "packages/components/alert/alert.tsx": "sha256-j680akXvCIh4MTVnUo79acvVrv51XyQ3UBJ5FBAy8Dw=",
12
12
  "packages/components/app-shell/app-shell.tsx": "sha256-XAq82XOTH3ZR+hxTkYFu8P7Ih2aI6ERy/TpXc2VMUwM=",
13
13
  "packages/components/avatar/avatar.tsx": "sha256-hvZUGhatN95ZyX6p6NBhTRf6h1/rrJ+bJrqBMtIspq0=",
14
- "packages/components/badge/badge.tsx": "sha256-+mgudoxlPn+mjpQycUfQmv8sC/e/K5FXfXm2+wnH7bI=",
15
- "packages/components/breadcrumb/breadcrumb.tsx": "sha256-SiIpg0oAieyoZbMlBwwQDZhR/KVIFLFCLCQJ6vwv7Fs=",
14
+ "packages/components/badge/badge.tsx": "sha256-flEH8S2jZ/64n3Y/ROhC9+9rvF7wXaN0xLcMMwqNOVQ=",
15
+ "packages/components/breadcrumb/breadcrumb.tsx": "sha256-Mt9mvmwwDgdp5/+mj5byIrh8+hicsp7R6UcvUlK0yog=",
16
16
  "packages/components/button/button.tsx": "sha256-khqYxcV3hlZ+Q+cYNTRXEpAfIijDFSvkuDJXMPtlk7g=",
17
17
  "packages/components/calendar/calendar.tsx": "sha256-uE22LI4jpU8ErR/oBmAkeKiu+Rp0bz3IXZVCurycpwc=",
18
18
  "packages/components/calendar/dates.ts": "sha256-mygYkoC70av2ZQ0vkxf885trwW97Wd10I4dptYQe+4A=",
19
19
  "packages/components/card/card.tsx": "sha256-aMKbv5DWYYWq7GZAFR9O1fuYE0H61WCjkvEbRMOd894=",
20
- "packages/components/carousel/carousel.tsx": "sha256-LAPKz9DG9vEBv/8jKjqgH7XiSEERkWWLr4n/Kve1omQ=",
21
- "packages/components/checkbox/checkbox.tsx": "sha256-cqTFwYwNPRxKiBnXXWkcFeDlJ6tpJ9GX4IdqrYRp9Fs=",
22
- "packages/components/color-picker/color-picker.tsx": "sha256-UziuULSI7dIGLfu4jjWIz6Cbh2/HiaY2KYXlfQ+6iQg=",
23
- "packages/components/color-picker/color.ts": "sha256-3IO6DaelZ6TVn2aw+6o6pZCQGAQdeNRkN7sPypUzddE=",
24
- "packages/components/combobox/combobox.tsx": "sha256-CnuJP01m0ZPMB4MGjoKbJCBQ7inI6bPf5aMDrq4m1qI=",
20
+ "packages/components/carousel/carousel.tsx": "sha256-pamugpB6Ujt9Ylxd37jNF/ZwSb8iRkW/MoYe12nY3i4=",
21
+ "packages/components/checkbox/checkbox.tsx": "sha256-hz4YbGwOxv/aJpJHrLXR4MKSWJGyjzoJEIGvdhyML9M=",
22
+ "packages/components/color-picker/color-picker.tsx": "sha256-E+QB6MbSxg+3AZX3A7bOkst4arxgpmFL2ZmochoWCl8=",
23
+ "packages/components/color-picker/color.ts": "sha256-YqSgYjzn7XfPWAHuyzbwOhbg/ScaSfB7V73e9w+OjJs=",
24
+ "packages/components/combobox/combobox.tsx": "sha256-CvuYc/hJoS7yw6QM9vv1SIkwflx0Kwd95UjchtC+AOs=",
25
25
  "packages/components/command-menu/command-menu.tsx": "sha256-mF1z92yrPY609pjx5Dt1l5EhGHl9EWV8F8Sq0hyJRw4=",
26
26
  "packages/components/context-menu/context-menu.tsx": "sha256-YgYsIzvLGFcJG1XX5zFJwrid5Z1Kn0lyWVzmLKmbMVU=",
27
- "packages/components/date-picker/date-picker.tsx": "sha256-pX7snZCO3SxVftkCvI/f5b82bNHPconUa50BJnjYNWc=",
27
+ "packages/components/date-picker/date-picker.tsx": "sha256-rsmDweD3RVfKTXR2i+/pnTb/MsrDAZXmHAYBvcUGxig=",
28
28
  "packages/components/dropdown-menu/dropdown-menu.tsx": "sha256-xgybCu9HBxIieafu5CjAtY4aCOQoaAj/VUCH6D6vLP8=",
29
- "packages/components/dropzone/dropzone.tsx": "sha256-jMVEnTki4Suhx7pOWfLX9MO1Q3nLz0xqDJ/2EPHdpYg=",
30
- "packages/components/empty-state/empty-state.tsx": "sha256-AWwOuZgm71hossjCGYygtvzGcDy6JOPryF+U1UrsLhM=",
31
- "packages/components/hover-card/hover-card.tsx": "sha256-HZyB9SUDcRPG8MvRX1sHqHA2rNcO63yVevH9hiGqVAA=",
29
+ "packages/components/dropzone/dropzone.tsx": "sha256-tHCCpEWE6839UeLOszTo+R2vAL4Sp9UxuRdLkFLfnq4=",
30
+ "packages/components/empty-state/empty-state.tsx": "sha256-uCKqYpr9vkozXZHpdl4GFtdX9ijcGOriCHvgyarBqRw=",
31
+ "packages/components/hover-card/hover-card.tsx": "sha256-AyRbxfqDOYBgGlgjgrtQZm5DipArLEzQn6XnzjPd0ro=",
32
32
  "packages/components/input/input.tsx": "sha256-lbbheo+ydQTzyujTf0W7DD96TogOjifCyqj9c3h3W0g=",
33
33
  "packages/components/kbd/kbd.tsx": "sha256-wrPqzn1e82zJYoKzO7u94Bgq4+nPc0DXLc8GJ/4/3nY=",
34
34
  "packages/components/modal/modal.tsx": "sha256-vybxgBGYCrMYvJRnO1wDAc2VFQylu+mQIf5OZIcabX4=",
35
- "packages/components/number-input/number-input.tsx": "sha256-D+8jH2YaW4lCv/F02qvkc9Huhxvz0y1idzgOBwsjiN8=",
35
+ "packages/components/number-input/number-input.tsx": "sha256-BDOyvCNtMULD/HK7uWHsYbGg79lrRDrVqtGQQrYHpmM=",
36
36
  "packages/components/number-input/number.ts": "sha256-IrHCQrEhI5ein9ifMFuSViSoxQUBoT3tNmmCu/paGGM=",
37
- "packages/components/otp-input/otp-input.tsx": "sha256-MWDyTP99wVAQSYJsF78ol1Kv9LKOj1lybmp2zOomVqo=",
37
+ "packages/components/otp-input/otp-input.tsx": "sha256-QURn8g5HqCQXBTdeENPbVxoPlqs3ZtoAPLRlFjQDxmI=",
38
38
  "packages/components/pagination/pagination.tsx": "sha256-FBwk8tq4JcsK2D0dlWEYiDSRx3owgBmGzEIlVRaNBoE=",
39
39
  "packages/components/popover/popover.tsx": "sha256-/kTiAuFklU4IuP97XaiCUGdEpKtNyMPLPS359mfzPHw=",
40
40
  "packages/components/priority-icon/labels.ts": "sha256-zhmJIb7/gS16wU+xCJ7hkReBi9q5RI3SMwdjkJvvvKU=",
41
41
  "packages/components/priority-icon/priority-icon.tsx": "sha256-3D6hFRpht3YuWouLhakE2DFdldavmJx0B/povEDwrtQ=",
42
- "packages/components/progress/progress.tsx": "sha256-3SOA1JX54msiq/pF2UolesQqQZm0zSVyZQi6W38lOOA=",
42
+ "packages/components/progress/progress.tsx": "sha256-9OjecRNPWn7Bfe+C5IyXaKsW1NJae6X5gco1o0X5XkE=",
43
43
  "packages/components/radio-group/radio-group.tsx": "sha256-jhnSEBLkdpe8dv5us/Q1sMsQF4sKGRgDXWGKm2JSFew=",
44
44
  "packages/components/rating/rating.tsx": "sha256-zgPOQXzAiRE4qGNPQ8qat3SP1XSI/7567kA5VaT51P0=",
45
45
  "packages/components/resizable/resizable.tsx": "sha256-40sK2UyIR9Ek72KAu5nvbr44tKEY6JhC5Ftb++Wf6cI=",
46
- "packages/components/segmented-control/segmented-control.tsx": "sha256-sExMN2WHtzxcaCu7xN/1jaP+sRVwJ2OAW1WkKGhNYnU=",
47
- "packages/components/select/select.tsx": "sha256-MWHL/mlAO2ob4H3EmYSBZcC1qqy+M3cF1Z4s0R+frrc=",
46
+ "packages/components/segmented-control/segmented-control.tsx": "sha256-MCBG5XHFWCwbACctsYKbqe8mvSSR6pf60tyRgdCDKDA=",
47
+ "packages/components/select/select.tsx": "sha256-m8QmKUGC4386XcklosSntal0XwUAFjKosTBQSF0dduQ=",
48
48
  "packages/components/sheet/sheet.tsx": "sha256-EIn3OeRDmXGHbYf/uihRh8aw3LmzarIFiyC9DUsL1jg=",
49
49
  "packages/components/skeleton/skeleton.tsx": "sha256-2Fk72NbLr3+CrXH7jGbHNsc3t+vskMqSyySe/UwbGCs=",
50
50
  "packages/components/slider/slider.tsx": "sha256-l+VrBqPYHIIcemiVixxwDxNDB0HEMmYRZekx/H5HroQ=",
@@ -53,15 +53,15 @@
53
53
  "packages/components/stepper/stepper.tsx": "sha256-uI/dRmkbijlixHiGUceWcpaDzpEixrzQ1UTmoPbGZAY=",
54
54
  "packages/components/table/table.tsx": "sha256-ed1Qn2sIpxRvGdPDAb5ih5AcXr5PmHTHdlrJLYDYRp8=",
55
55
  "packages/components/tabs/tabs.tsx": "sha256-kWAQjE9mshqZL7V3NJTUiurp6M5Nu3vx5O1LsIgdV5Y=",
56
- "packages/components/tag-input/tag-input.tsx": "sha256-+R1f2XljDYInsfuggKkpqz5m9TqEa0g3QYKMX2TE6h0=",
57
- "packages/components/textarea/textarea.tsx": "sha256-jeyvAlzFr25eWZ/Inkxo1UEOHXlCPB/QHeId124SfR8=",
58
- "packages/components/time-picker/time-picker.tsx": "sha256-OyIfJN6xVv6TwUYtRirkemJe5VMcu2HXxjaJj8fvu9I=",
56
+ "packages/components/tag-input/tag-input.tsx": "sha256-EK7xJTe3Y/S5Kn3vS0zBQg9lC+RVoGvky6HOAt+DnAw=",
57
+ "packages/components/textarea/textarea.tsx": "sha256-C9jMYZ63w+m6bgdLvHjvULRuoCe26q5zbgS4+ahWzr8=",
58
+ "packages/components/time-picker/time-picker.tsx": "sha256-og8Cbf+e8oFYewfagUgjRzLHSIIlAZh+1OpAn9yNq70=",
59
59
  "packages/components/time-picker/times.ts": "sha256-V/4uqFZPn9gVnyx8cB9vwp/RocmTYaPmLxjfy8DPQOI=",
60
- "packages/components/timeline/timeline.tsx": "sha256-FDZx+ihL5tbIY/sEl0444uj27JWHE5xl0vl8IqRUUPY=",
61
- "packages/components/toast/toast.tsx": "sha256-RrqUAAfKavEkZaLjywJHc0iTNHC1DHlIGVX97kftDkg=",
62
- "packages/components/toggle/toggle.tsx": "sha256-zwTbHMSs3n0eg5XIpREDnySYLXHaLly1Ew4FWlzvgJY=",
60
+ "packages/components/timeline/timeline.tsx": "sha256-nVg7lBcAUAzqEb0YrHagJXSaHE2aWWE9bUbQMWQY6PU=",
61
+ "packages/components/toast/toast.tsx": "sha256-FvZnwN6Lk/QEHmcNxo7sVlEvR3Lx54aT5cH9lwrthhE=",
62
+ "packages/components/toggle/toggle.tsx": "sha256-8qPHeSliHNdzQS7OLJ47BrvRR75MYyUGJlK4s21XS/U=",
63
63
  "packages/components/tooltip/tooltip.tsx": "sha256-NbaOUmlUbBlUIdovchrWwH2QqzuvXB97DjnJnrHf4Gc=",
64
- "packages/components/tour/tour.tsx": "sha256-1aSh7hMVt/DM/kH3WGJzJMGNsNqYEOmnwAZwc8dwRv8=",
64
+ "packages/components/tour/tour.tsx": "sha256-mrAE8aVDBVIibGvQah9Gh3mzIiWDFxB6aObzqFo5xXU=",
65
65
  "packages/components/virtual-list/virtual-list.tsx": "sha256-raGEFn4HlAOIohqZcy0DgptsnUIPS/6bWLYeTe+M9E8=",
66
66
  "packages/components/virtual-list/virtual.ts": "sha256-PyDIj3C2Vwb1i4ZssxSlYjgKmZPBIFLZFNq8YCW2Z78="
67
67
  }