@human-kit/svelte-components 1.0.0-alpha.3 → 1.0.0-alpha.4

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 (110) hide show
  1. package/dist/FOCUS_STATE_CONTRACT.md +12 -0
  2. package/dist/calendar/body-cell/README.md +15 -0
  3. package/dist/calendar/grid/README.md +13 -0
  4. package/dist/calendar/grid-body/README.md +13 -0
  5. package/dist/calendar/grid-header/README.md +13 -0
  6. package/dist/calendar/header-cell/README.md +14 -0
  7. package/dist/calendar/heading/README.md +13 -0
  8. package/dist/calendar/root/README.md +24 -0
  9. package/dist/calendar/trigger-next/README.md +14 -0
  10. package/dist/calendar/trigger-previous/README.md +14 -0
  11. package/dist/clock/README.md +75 -0
  12. package/dist/clock/axis/README.md +24 -0
  13. package/dist/clock/axis/clock-axis.svelte +37 -0
  14. package/dist/clock/axis/clock-axis.svelte.d.ts +8 -0
  15. package/dist/clock/hooks/use-wheel-scroll.svelte.d.ts +16 -0
  16. package/dist/clock/hooks/use-wheel-scroll.svelte.js +336 -0
  17. package/dist/clock/index.d.ts +10 -0
  18. package/dist/clock/index.js +10 -0
  19. package/dist/clock/index.parts.d.ts +4 -0
  20. package/dist/clock/index.parts.js +4 -0
  21. package/dist/clock/root/README.md +38 -0
  22. package/dist/clock/root/clock-root-test.svelte +62 -0
  23. package/dist/clock/root/clock-root-test.svelte.d.ts +14 -0
  24. package/dist/clock/root/clock-root.svelte +329 -0
  25. package/dist/clock/root/clock-root.svelte.d.ts +25 -0
  26. package/dist/clock/root/context.d.ts +22 -0
  27. package/dist/clock/root/context.js +15 -0
  28. package/dist/clock/root/resolve-visible-columns.d.ts +7 -0
  29. package/dist/clock/root/resolve-visible-columns.js +16 -0
  30. package/dist/clock/root/time-utils.d.ts +48 -0
  31. package/dist/clock/root/time-utils.js +314 -0
  32. package/dist/clock/root/wheel-options.d.ts +17 -0
  33. package/dist/clock/root/wheel-options.js +63 -0
  34. package/dist/clock/wheel-column/README.md +25 -0
  35. package/dist/clock/wheel-column/clock-wheel-column-bindable-test.svelte +16 -0
  36. package/dist/clock/wheel-column/clock-wheel-column-bindable-test.svelte.d.ts +3 -0
  37. package/dist/clock/wheel-column/clock-wheel-column-custom-snippet-test.svelte +29 -0
  38. package/dist/clock/wheel-column/clock-wheel-column-custom-snippet-test.svelte.d.ts +6 -0
  39. package/dist/clock/wheel-column/clock-wheel-column-default-height-test.svelte +11 -0
  40. package/dist/clock/wheel-column/clock-wheel-column-default-height-test.svelte.d.ts +3 -0
  41. package/dist/clock/wheel-column/clock-wheel-column-test.svelte +38 -0
  42. package/dist/clock/wheel-column/clock-wheel-column-test.svelte.d.ts +12 -0
  43. package/dist/clock/wheel-column/clock-wheel-column-tp-test.svelte +38 -0
  44. package/dist/clock/wheel-column/clock-wheel-column-tp-test.svelte.d.ts +12 -0
  45. package/dist/clock/wheel-column/clock-wheel-column-untagged-snippet-test.svelte +29 -0
  46. package/dist/clock/wheel-column/clock-wheel-column-untagged-snippet-test.svelte.d.ts +6 -0
  47. package/dist/clock/wheel-column/clock-wheel-column.svelte +499 -0
  48. package/dist/clock/wheel-column/clock-wheel-column.svelte.d.ts +17 -0
  49. package/dist/clock/wheel-item/README.md +17 -0
  50. package/dist/clock/wheel-item/clock-wheel-item.svelte +49 -0
  51. package/dist/clock/wheel-item/clock-wheel-item.svelte.d.ts +17 -0
  52. package/dist/datepicker/TODO.md +2 -2
  53. package/dist/datepicker/calendar/README.md +19 -0
  54. package/dist/datepicker/input/README.md +15 -0
  55. package/dist/datepicker/popover/README.md +20 -0
  56. package/dist/datepicker/root/README.md +38 -0
  57. package/dist/datepicker/segment/README.md +14 -0
  58. package/dist/datepicker/trigger/README.md +14 -0
  59. package/dist/index.d.ts +5 -0
  60. package/dist/index.js +5 -0
  61. package/dist/primitives/focus-trap.js +11 -12
  62. package/dist/primitives/input-modality.js +10 -1
  63. package/dist/timepicker/IMPLEMENTATION_PLAN.md +254 -0
  64. package/dist/timepicker/README.md +97 -0
  65. package/dist/timepicker/TODO.md +86 -0
  66. package/dist/timepicker/clock/README.md +14 -0
  67. package/dist/timepicker/clock/time-picker-clock-test.svelte +45 -0
  68. package/dist/timepicker/clock/time-picker-clock-test.svelte.d.ts +11 -0
  69. package/dist/timepicker/clock/time-picker-clock.svelte +65 -0
  70. package/dist/timepicker/clock/time-picker-clock.svelte.d.ts +10 -0
  71. package/dist/timepicker/index.d.ts +14 -0
  72. package/dist/timepicker/index.js +14 -0
  73. package/dist/timepicker/index.parts.d.ts +8 -0
  74. package/dist/timepicker/index.parts.js +8 -0
  75. package/dist/timepicker/input/README.md +15 -0
  76. package/dist/timepicker/input/time-picker-input-forwarding-test.svelte +40 -0
  77. package/dist/timepicker/input/time-picker-input-forwarding-test.svelte.d.ts +3 -0
  78. package/dist/timepicker/input/time-picker-input.svelte +109 -0
  79. package/dist/timepicker/input/time-picker-input.svelte.d.ts +11 -0
  80. package/dist/timepicker/internal/strict-props.d.ts +4 -0
  81. package/dist/timepicker/internal/strict-props.js +51 -0
  82. package/dist/timepicker/popover/README.md +20 -0
  83. package/dist/timepicker/popover/time-picker-popover-unsafe-props-test.svelte +22 -0
  84. package/dist/timepicker/popover/time-picker-popover-unsafe-props-test.svelte.d.ts +3 -0
  85. package/dist/timepicker/popover/time-picker-popover.svelte +89 -0
  86. package/dist/timepicker/popover/time-picker-popover.svelte.d.ts +7 -0
  87. package/dist/timepicker/root/README.md +42 -0
  88. package/dist/timepicker/root/context.d.ts +51 -0
  89. package/dist/timepicker/root/context.js +15 -0
  90. package/dist/timepicker/root/time-picker-12h-test.svelte +22 -0
  91. package/dist/timepicker/root/time-picker-12h-test.svelte.d.ts +3 -0
  92. package/dist/timepicker/root/time-picker-bindable-test.svelte +25 -0
  93. package/dist/timepicker/root/time-picker-bindable-test.svelte.d.ts +3 -0
  94. package/dist/timepicker/root/time-picker-empty-test.svelte +20 -0
  95. package/dist/timepicker/root/time-picker-empty-test.svelte.d.ts +3 -0
  96. package/dist/timepicker/root/time-picker-root.svelte +625 -0
  97. package/dist/timepicker/root/time-picker-root.svelte.d.ts +28 -0
  98. package/dist/timepicker/root/time-picker-test.svelte +72 -0
  99. package/dist/timepicker/root/time-picker-test.svelte.d.ts +15 -0
  100. package/dist/timepicker/root/time-utils.d.ts +1 -0
  101. package/dist/timepicker/root/time-utils.js +3 -0
  102. package/dist/timepicker/segment/README.md +14 -0
  103. package/dist/timepicker/segment/time-picker-segment.svelte +365 -0
  104. package/dist/timepicker/segment/time-picker-segment.svelte.d.ts +9 -0
  105. package/dist/timepicker/trigger/README.md +14 -0
  106. package/dist/timepicker/trigger/time-picker-trigger-forwarding-test.svelte +35 -0
  107. package/dist/timepicker/trigger/time-picker-trigger-forwarding-test.svelte.d.ts +3 -0
  108. package/dist/timepicker/trigger/time-picker-trigger.svelte +122 -0
  109. package/dist/timepicker/trigger/time-picker-trigger.svelte.d.ts +9 -0
  110. package/package.json +11 -1
@@ -0,0 +1,38 @@
1
+ # DatePicker Root
2
+
3
+ ## API reference
4
+
5
+ ### DatePicker.Root
6
+
7
+ Name: `DatePicker.Root`
8
+ Description: State container for segmented date input, popover lifecycle, calendar selection, and validation constraints.
9
+
10
+ | Prop | Type | Default | Description |
11
+ | ------------------- | ---------------------------------------------- | ----------- | --------------------------------------------------------- |
12
+ | `value` | `DatePickerDateValue \| null` | `bindable` | Controlled ISO date value (`YYYY-MM-DD`). |
13
+ | `defaultValue` | `DatePickerDateValue \| null` | `undefined` | Initial date value for uncontrolled mode. |
14
+ | `onChange` | `(value: DatePickerDateValue \| null) => void` | `undefined` | Called when committed date value changes. |
15
+ | `open` | `boolean` | `bindable` | Controlled open state for the popover panel. |
16
+ | `defaultOpen` | `boolean` | `false` | Initial open state in uncontrolled mode. |
17
+ | `onOpenChange` | `(open: boolean, details) => void` | `undefined` | Called on open-state transitions (supports cancellation). |
18
+ | `closeOnSelect` | `boolean` | `true` | Closes the popover after calendar selection. |
19
+ | `minValue` | `DatePickerDateValue` | `undefined` | Optional lower bound for selectable dates. |
20
+ | `maxValue` | `DatePickerDateValue` | `undefined` | Optional upper bound for selectable dates. |
21
+ | `isDateUnavailable` | `(date: DatePickerDateValue) => boolean` | `undefined` | Optional predicate to block unavailable dates. |
22
+ | `isDisabled` | `boolean` | `false` | Disables interaction and selection. |
23
+ | `isReadOnly` | `boolean` | `false` | Prevents value changes while preserving navigation. |
24
+ | `children` | `Snippet` | `undefined` | Composed `DatePicker` parts. |
25
+ | `class` | `string` | `''` | CSS class names for root wrapper. |
26
+ | `aria-label` | `string` | `undefined` | Accessible label for the root wrapper. |
27
+
28
+ ### Context utilities
29
+
30
+ Name: `setDatePickerContext` / `getDatePickerContext` / `useDatePickerContext`
31
+ Description: Context helpers for internal/public `DatePicker` part composition.
32
+
33
+ | Prop | Type | Default | Description |
34
+ | ---------------------- | -------------------------------------- | ------- | --------------------------------------------------- |
35
+ | `setDatePickerContext` | `(ctx: DatePickerContext) => void` | `-` | Publishes root context. |
36
+ | `getDatePickerContext` | `() => DatePickerContext \| undefined` | `-` | Reads context when available. |
37
+ | `useDatePickerContext` | `() => DatePickerContext` | `-` | Reads context and throws outside `DatePicker.Root`. |
38
+ | `DatePickerContext` | `type` | `-` | Shared context contract for all datepicker parts. |
@@ -0,0 +1,14 @@
1
+ # DatePicker Segment
2
+
3
+ ## API reference
4
+
5
+ ### DatePicker.Segment
6
+
7
+ Name: `DatePicker.Segment`
8
+ Description: Editable (or literal) date segment renderer used inside `DatePicker.Input`.
9
+
10
+ | Prop | Type | Default | Description |
11
+ | -------------- | --------------------------------- | ---------- | ------------------------------------------------------- |
12
+ | `segment` | `DatePickerSegmentPart` | `required` | Segment metadata and text payload. |
13
+ | `class` | `string` | `''` | CSS class names for the segment span element. |
14
+ | `...restProps` | `HTMLAttributes<HTMLSpanElement>` | `-` | Additional attributes forwarded to the segment element. |
@@ -0,0 +1,14 @@
1
+ # DatePicker Trigger
2
+
3
+ ## API reference
4
+
5
+ ### DatePicker.Trigger
6
+
7
+ Name: `DatePicker.Trigger`
8
+ Description: Button part that toggles `DatePicker.Popover` and anchors positioning/focus behavior.
9
+
10
+ | Prop | Type | Default | Description |
11
+ | -------------- | ---------------------- | ----------- | ------------------------------------------------------ |
12
+ | `children` | `Snippet` | `undefined` | Optional trigger content. |
13
+ | `class` | `string` | `''` | CSS class names for the trigger button. |
14
+ | `...restProps` | `HTMLButtonAttributes` | `-` | Additional button attributes forwarded to the trigger. |
package/dist/index.d.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  export { ComboBox } from './combobox/index.ts';
2
2
  export { Calendar } from './calendar/index.ts';
3
+ export { Clock } from './clock/index.ts';
3
4
  export { DatePicker } from './datepicker/index.ts';
5
+ export { TimePicker } from './timepicker/index.ts';
4
6
  export { Dialog } from './dialog/index.ts';
5
7
  export { ListBox } from './listbox/index.ts';
6
8
  export { Popover } from './popover/index.ts';
@@ -11,10 +13,13 @@ export { Portal } from './portal/index.ts';
11
13
  export * from './locale-provider/index.ts';
12
14
  export * from './combobox/index.ts';
13
15
  export * from './calendar/index.ts';
16
+ export * from './clock/index.ts';
14
17
  export * from './datepicker/index.ts';
18
+ export * from './timepicker/index.ts';
15
19
  export * from './dialog/index.ts';
16
20
  export * from './listbox/index.ts';
17
21
  export * from './popover/index.ts';
18
22
  export * from './primitives/index.ts';
19
23
  export { cn } from './utils/index.ts';
20
24
  export * from './utils/index.ts';
25
+ export * from './utils/index.ts';
package/dist/index.js CHANGED
@@ -2,7 +2,9 @@
2
2
  // Components (namespace exports)
3
3
  export { ComboBox } from './combobox/index.ts';
4
4
  export { Calendar } from './calendar/index.ts';
5
+ export { Clock } from './clock/index.ts';
5
6
  export { DatePicker } from './datepicker/index.ts';
7
+ export { TimePicker } from './timepicker/index.ts';
6
8
  export { Dialog } from './dialog/index.ts';
7
9
  export { ListBox } from './listbox/index.ts';
8
10
  export { Popover } from './popover/index.ts';
@@ -15,7 +17,9 @@ export * from './locale-provider/index.ts';
15
17
  // Re-export named exports from components
16
18
  export * from './combobox/index.ts';
17
19
  export * from './calendar/index.ts';
20
+ export * from './clock/index.ts';
18
21
  export * from './datepicker/index.ts';
22
+ export * from './timepicker/index.ts';
19
23
  export * from './dialog/index.ts';
20
24
  export * from './listbox/index.ts';
21
25
  export * from './popover/index.ts';
@@ -24,3 +28,4 @@ export * from './primitives/index.ts';
24
28
  // Utilities
25
29
  export { cn } from './utils/index.ts';
26
30
  export * from './utils/index.ts';
31
+ export * from './utils/index.ts';
@@ -2,6 +2,7 @@
2
2
  * Focus trap primitive.
3
3
  * Traps keyboard focus within a container element.
4
4
  */
5
+ import { focusWithModality, getInteractionModality } from './input-modality';
5
6
  const FOCUSABLE_SELECTOR = [
6
7
  'a[href]',
7
8
  'button:not([disabled])',
@@ -33,7 +34,7 @@ function resolveInitialFocus(container, initialFocus) {
33
34
  return initialFocus;
34
35
  }
35
36
  function getFocusableElements(container) {
36
- return Array.from(container.querySelectorAll(FOCUSABLE_SELECTOR)).filter((el) => el.offsetParent !== null);
37
+ return Array.from(container.querySelectorAll(FOCUSABLE_SELECTOR)).filter((element) => element.offsetParent !== null);
37
38
  }
38
39
  /**
39
40
  * Svelte action that traps focus within an element.
@@ -73,12 +74,11 @@ export function focusTrap(node, options = true) {
73
74
  event.preventDefault();
74
75
  lastElement.focus();
75
76
  }
77
+ return;
76
78
  }
77
- else {
78
- if (document.activeElement === lastElement || document.activeElement === node) {
79
- event.preventDefault();
80
- firstElement.focus();
81
- }
79
+ if (document.activeElement === lastElement || document.activeElement === node) {
80
+ event.preventDefault();
81
+ firstElement.focus();
82
82
  }
83
83
  }
84
84
  function activate() {
@@ -86,20 +86,19 @@ export function focusTrap(node, options = true) {
86
86
  if (!node.hasAttribute('tabindex')) {
87
87
  node.setAttribute('tabindex', '-1');
88
88
  }
89
- // Focus first focusable element, or the container if none
90
89
  requestAnimationFrame(() => {
91
90
  const initialFocusTarget = resolveInitialFocus(node, initialFocus);
91
+ const modality = getInteractionModality();
92
92
  if (initialFocusTarget && initialFocusTarget.isConnected) {
93
- initialFocusTarget.focus();
93
+ focusWithModality(initialFocusTarget, modality);
94
94
  return;
95
95
  }
96
96
  const focusableElements = getFocusableElements(node);
97
97
  if (focusableElements.length > 0) {
98
- focusableElements[0].focus();
99
- }
100
- else {
101
- node.focus();
98
+ focusWithModality(focusableElements[0], modality);
99
+ return;
102
100
  }
101
+ focusWithModality(node, modality);
103
102
  });
104
103
  document.addEventListener('keydown', handleKeydown, true);
105
104
  }
@@ -95,7 +95,16 @@ export function focusWithModality(target, modality, options) {
95
95
  forcedFocusTarget = target;
96
96
  forcedFocusModality = modality;
97
97
  currentModality = modality;
98
- target.focus(options);
98
+ if (modality === 'pointer') {
99
+ const pointerFocusOptions = {
100
+ ...(options ?? {}),
101
+ focusVisible: false
102
+ };
103
+ target.focus(pointerFocusOptions);
104
+ }
105
+ else {
106
+ target.focus(options);
107
+ }
99
108
  queueMicrotask(() => {
100
109
  if (forcedFocusTarget !== target)
101
110
  return;
@@ -0,0 +1,254 @@
1
+ # TimePicker Implementation Plan
2
+
3
+ ## Scope
4
+
5
+ Implement all open items from `TODO.md` in one delivery, prioritizing correctness and stability first, then accessibility/performance, and finally coverage hardening.
6
+
7
+ ## Goals
8
+
9
+ - Fix state/event correctness regressions first.
10
+ - Keep wheel interaction fluid while improving robustness.
11
+ - Add missing accessibility semantics without introducing noisy behavior.
12
+ - Apply low-risk performance improvements.
13
+ - Expand tests to protect the full wheel pipeline.
14
+
15
+ ## Phase 1 — Correctness (P0/P1 behavior)
16
+
17
+ ### 1a) Allow external clearing via `value = undefined`
18
+
19
+ **Files**
20
+
21
+ - `root/time-picker-root.svelte`
22
+
23
+ **Changes**
24
+
25
+ - Remove the `if (value === undefined) return;` early-return in the `$effect` that observes `value`.
26
+ - When `value` becomes `undefined`, treat it as an explicit clear: reset `segmentDraft` to empty, set `valueInternal = null`, and publish `null` via `publishCommittedValue`.
27
+
28
+ **Risk**
29
+
30
+ - Controlled/uncontrolled sync regressions — initial mount with `value={undefined}` must not trigger a spurious publish.
31
+
32
+ **Validation**
33
+
34
+ - `bun run test -- --run src/lib/timepicker/root/time-picker-root.test.ts`
35
+
36
+ ### 1b) Publish `null` when all segments are cleared
37
+
38
+ **Files**
39
+
40
+ - `root/time-picker-root.svelte`
41
+
42
+ **Changes**
43
+
44
+ - In `commitFromDraft`, when `buildTimePartsFromDraft` returns `null` **and** `hasAnyRequiredValue` is `false` (every segment is empty), call `publishCommittedValue(null)` instead of doing a silent return.
45
+ - This ensures `bind:value` transitions from a valid time to `null` when the user backspaces every segment.
46
+
47
+ **Risk**
48
+
49
+ - Must avoid re-publishing `null` on every keystroke while segments are partially filled — the `hasAnyRequiredValue === true` path already handles that correctly.
50
+
51
+ **Validation**
52
+
53
+ - `bun run test -- --run src/lib/timepicker/root/time-picker-root.test.ts`
54
+
55
+ ### 2) Event forwarding consistency
56
+
57
+ **Files**
58
+
59
+ - `input/time-picker-input.svelte`
60
+ - `trigger/time-picker-trigger.svelte`
61
+ **Changes**
62
+
63
+ - Compose external/internal handlers consistently.
64
+
65
+ - User handlers accidentally suppress internal logic.
66
+
67
+ - `bun run test -- --run src/lib/timepicker/input/time-picker-input.test.ts src/lib/timepicker/trigger/time-picker-trigger.test.ts`
68
+
69
+ - Add safety restoration to current draft value when drift is detected.
70
+
71
+ **Policy**
72
+
73
+ - Implement guard + restore strategy aligned with TODO note.
74
+ **Risk**
75
+
76
+ - IME/paste edge cases and cross-browser input events.
77
+ **Validation**
78
+
79
+ - `bun run test -- --run src/lib/timepicker/segment/time-picker-segment.test.ts`
80
+
81
+ **Files**
82
+
83
+ - `segment/time-picker-segment.svelte`
84
+
85
+ - Use explicit 12h boundaries (`1`/`12`) instead of relying on clamping side effects.
86
+
87
+ - Segment keyboard tests in same run as above.
88
+
89
+ ---
90
+
91
+ ## Phase 2 — Wheel stability and interaction quality
92
+
93
+ ### 5) Wheel settle behavior and anchor sync
94
+
95
+ **Files**
96
+ **Changes**
97
+
98
+ - Increase scroll debounce fallback from 64ms to 120ms. The debounce only fires in browsers without native `scrollend` support, so higher values do not affect perceived latency. 120ms is conservative enough to avoid interrupting touch momentum on iOS Safari.
99
+ **Risk**
100
+
101
+ - Async timing loops (`scroll`, `scrollend`, debounce). The silent flag must be cleared reliably (on `scrollend` or animation completion).
102
+ **Validation**
103
+
104
+ - `bun run test -- --run src/lib/clock/wheel-column/clock-wheel-column.test.ts src/lib/clock/panel/clock-panel.test.ts`
105
+
106
+ ### 6) ResizeObserver loop mitigation
107
+
108
+ **Files**
109
+
110
+ - `../clock/wheel-column/clock-wheel-column.svelte`
111
+ - Batch measurement updates through `requestAnimationFrame` to reduce observer-loop warnings.
112
+
113
+ **Validation**
114
+
115
+ - Re-run wheel and root tests:
116
+ - `bun run test -- --run src/lib/clock/wheel-column/clock-wheel-column.test.ts src/lib/timepicker/root/time-picker-root.test.ts`
117
+
118
+ ## Phase 3 — Accessibility and API surface
119
+
120
+ ### 7) Wheel announcements and semantics
121
+
122
+ - `../clock/wheel-column/clock-wheel-column.svelte`
123
+
124
+ **Changes**
125
+
126
+ - Add `aria-roledescription` on wheel spinbutton.
127
+
128
+ **Risk**
129
+
130
+ - Over-announcement/noise for SR users.
131
+
132
+ ### 8) `isRequired` propagation
133
+
134
+ **Files**
135
+
136
+ - `root/time-picker-root.svelte`
137
+ - `root/context.ts`
138
+ - `input/time-picker-input.svelte`
139
+ - `README.md`
140
+
141
+ - Add `isRequired` to root API and context.
142
+ - Expose `aria-required` on input group.
143
+ - Document API addition.
144
+
145
+ - Input/root tests + typecheck.
146
+
147
+ - `../clock/wheel-item/clock-wheel-item.svelte`
148
+
149
+ **Changes**
150
+
151
+ - Remove default visual inline styles (opacity, cursor, font-weight, etc.) entirely. The component becomes fully headless — consumers style via `data-selected`, `data-disabled`, `data-centered` attributes.
152
+ - This aligns WheelItem with the rest of the library where components ship zero visual opinions.
153
+ **Risk**
154
+
155
+ - Technically breaking for anyone relying on the default inline styles. Acceptable at current maturity — the component is new and the old styles were inconsistent (all dropped when `class` was passed).
156
+
157
+ ## Phase 4 — Performance and code cleanup
158
+
159
+ ### 10) Formatter and locale caching
160
+
161
+ - `root/time-picker-root.svelte`
162
+ - `root/time-utils.ts`
163
+ **Changes**
164
+
165
+ - Cache `Intl.DateTimeFormat` by `locale` + `hourCycle` + `granularity`.
166
+ - Cache system-locale fallback once per component lifetime.
167
+
168
+ **Files**
169
+
170
+ **Changes**
171
+
172
+ - Skip disabled candidate computation when no min/max is set.
173
+
174
+ ### 12) Low-risk code quality cleanups
175
+
176
+ **Files**
177
+
178
+ - `root/time-utils.ts`
179
+ - `root/time-picker-root.svelte`
180
+
181
+ **Changes**
182
+
183
+ - Remove unreachable min>max branch in `isTimeOutOfRange`.
184
+ - Replace meaningless ternary `type === 'hour' ? 2 : 2` with constant.
185
+
186
+ **Validation**
187
+
188
+ ## Phase 5 — Test hardening
189
+
190
+ ### 13) Wheel test matrix expansion
191
+
192
+ **Add coverage for**
193
+
194
+ - Open alignment and external value sync.
195
+ - Focus contract (`data-focus-within`, `data-focus-visible`).
196
+ - Min/max disabled behavior.
197
+
198
+ ### 14) End-to-end wheel integration test
199
+
200
+ **Files**
201
+
202
+ - `root/time-picker-root.test.ts` and/or `wheel-column/time-picker-wheel-column.test.ts`
203
+
204
+ **Flow to cover**
205
+
206
+ - Wheel interaction → snap → `selectWheelValue` → root commit → segment UI + bound value sync.
207
+
208
+ ### 15) Segment paste/IME resilience tests
209
+
210
+ **Files**
211
+
212
+ - `segment/time-picker-segment.test.ts`
213
+
214
+ **Cases**
215
+
216
+ - Paste attempts do not corrupt draft.
217
+ - Input DOM restores to draft when mutation bypass happens.
218
+
219
+ ---
220
+
221
+ ## Execution Order (Single PR)
222
+
223
+ 1. Phase 1
224
+ 2. Phase 2
225
+ 3. Phase 3
226
+ 4. Phase 4
227
+ 5. Phase 5
228
+
229
+ This order minimizes risk: behavior correctness first, wheel timing second, API/accessibility third, refactors fourth, broad tests last.
230
+
231
+ ## Verification Checklist
232
+
233
+ ### Targeted runs after each phase
234
+
235
+ - `bun run test -- --run src/lib/timepicker/root/time-picker-root.test.ts`
236
+ - `bun run test -- --run src/lib/timepicker/input/time-picker-input.test.ts src/lib/timepicker/trigger/time-picker-trigger.test.ts`
237
+ - `bun run test -- --run src/lib/timepicker/segment/time-picker-segment.test.ts`
238
+ - `bun run test -- --run src/lib/clock/wheel-column/clock-wheel-column.test.ts src/lib/clock/panel/clock-panel.test.ts`
239
+ - `bun run test -- --run src/lib/timepicker/root/time-utils.test.ts`
240
+
241
+ ### Final gate
242
+
243
+ - `bun run typecheck`
244
+ - `bun run test -- --run`
245
+ - `bun run lint`
246
+ - `bun run build`
247
+ - `bunx changeset` — generate changeset file (CI requires it for `packages/svelte/src/**` changes)
248
+
249
+ ## Notes
250
+
251
+ - Debounce increase (64→120ms) is safe because it only acts as a fallback for browsers without `scrollend`. Modern browsers use `scrollend` as the primary settle signal.
252
+ - Preserve existing public APIs except where TODO explicitly requests additions (`isRequired`) or removals (WheelItem default styles).
253
+ - Phase 5 tests must be written against the corrected behavior from Phases 1-4, not against the current (buggy) behavior.
254
+ - Update docs/tests together with behavior changes to keep CI stable.
@@ -0,0 +1,97 @@
1
+ # TimePicker
2
+
3
+ ## Description
4
+
5
+ `TimePicker` composes a segmented time input with a popover containing wheel-based spinbutton columns.
6
+
7
+ ## Anatomy
8
+
9
+ - `TimePicker.Root`
10
+ - `TimePicker.Input`
11
+ - `TimePicker.Segment`
12
+ - `TimePicker.Trigger`
13
+ - `TimePicker.Popover`
14
+ - `TimePicker.Clock`
15
+ - `TimePicker.WheelColumn`
16
+ - `TimePicker.WheelItem`
17
+
18
+ ```svelte
19
+ <TimePicker.Root>
20
+ <TimePicker.Input aria-label="Time input">
21
+ {#snippet children(segment)}
22
+ <TimePicker.Segment {segment} />
23
+ {/snippet}
24
+ </TimePicker.Input>
25
+ <TimePicker.Trigger />
26
+
27
+ <TimePicker.Popover>
28
+ <TimePicker.Clock />
29
+ </TimePicker.Popover>
30
+ </TimePicker.Root>
31
+ ```
32
+
33
+ ```svelte
34
+ <TimePicker.Popover>
35
+ <TimePicker.Clock class="flex gap-2">
36
+ {#snippet column(col)}
37
+ <TimePicker.WheelColumn type={col.type} class="h-44 rounded-md">
38
+ {#snippet children(option)}
39
+ <TimePicker.WheelItem type={col.type} {option} class="..." />
40
+ {/snippet}
41
+ </TimePicker.WheelColumn>
42
+ {/snippet}
43
+ </TimePicker.Clock>
44
+ </TimePicker.Popover>
45
+ ```
46
+
47
+ ## Root API
48
+
49
+ - `value?: string | null` (`HH:mm` or `HH:mm:ss`)
50
+ - `defaultValue?: string | null` (`HH:mm` or `HH:mm:ss`)
51
+ - `onChange?: (value: string | null) => void`
52
+ - `minValue?: string`
53
+ - `maxValue?: string`
54
+ - `hourCycle?: 12 | 24`
55
+ - `granularity?: 'hour' | 'minute' | 'second'`
56
+ - `hourStep?: number`
57
+ - `minuteStep?: number`
58
+ - `secondStep?: number`
59
+ - `isDisabled?: boolean`
60
+ - `isReadOnly?: boolean`
61
+ - `isRequired?: boolean`
62
+ - `open?: boolean`
63
+ - `defaultOpen?: boolean`
64
+ - `onOpenChange?: (open: boolean, details: { reason, event?, cancel(), isCanceled }) => void`
65
+ - Null-first empty contract: when `value` and `defaultValue` are omitted, the empty state is `null`.
66
+ - `TimePicker.Input` exposes `aria-invalid` and `data-invalid` when the current segment draft is not committeable.
67
+
68
+ ## Popover API
69
+
70
+ - `TimePicker.Popover` forwards `Popover.Content` props (for example `placement`, `offset`, `shouldFlip`, `boundaryElement`, `isNonModal`, and close behavior props).
71
+ - The following are controlled internally by `TimePicker` and are not accepted on `TimePicker.Popover`: `open`, `triggerRef`, `onOpenChange`, `id`.
72
+ - Defaults:
73
+ - `placement` defaults to `bottom`.
74
+ - `aria-label` defaults to `Time picker`.
75
+ - `initialFocus` defaults to the first wheel column (`role="spinbutton"`).
76
+
77
+ ## Wheel API
78
+
79
+ - `TimePicker.WheelColumn` renders one wheel (`role="spinbutton"`) for one editable segment (`hour`, `minute`, `second`, or `dayPeriod`).
80
+ - `TimePicker.WheelItem` is headless: it renders one item (`data-wheel-item`) with state attributes (`data-selected`, `data-disabled`, `data-centered`) and leaves all visual styling to consumers.
81
+
82
+ ## Clock API
83
+
84
+ - `TimePicker.Clock` resolves visible wheel columns from root state (`granularity`, `hourCycle`) in stable order: `hour → minute? → second? → dayPeriod?`.
85
+ - `class?: string` uses default layout (`flex gap-2`) when omitted.
86
+ - `column?: Snippet<[ClockColumnInfo]>` allows custom per-column rendering.
87
+ - `ClockColumnInfo` shape:
88
+ - `type: 'hour' | 'minute' | 'second' | 'dayPeriod'`
89
+ - `label?: string`
90
+
91
+ ## Notes
92
+
93
+ - Locale is read from `LocaleProvider` when available.
94
+ - Internally, values are normalized to 24-hour representation; 12-hour rendering only affects UI segments.
95
+ - `granularity='hour'` emits `HH:00` values.
96
+ - Min/max comparisons do not support midnight-wrapping ranges (`minValue > maxValue` is treated as out-of-range).
97
+ - Wheel selection commits immediately on snap; popover close is controlled by standard popover interactions (escape, outside press, programmatic close).
@@ -0,0 +1,86 @@
1
+ # TimePicker TODO
2
+
3
+ ## Goal
4
+
5
+ Track TimePicker work with a single mandatory TODO format.
6
+
7
+ ## Backlog
8
+
9
+ - [x] [M][P0][Area: Parsing][Owner: Unassigned][Target: TBD] Make time parsing strict so invalid strings like "1.5:30" or ":30" are rejected. _(Already implemented via `isValidTimePickerValue` regex. `parseTimePickerValue` now returns `null` for invalid input.)_
10
+ - [x] [S][P0][Area: Validation][Owner: Unassigned][Target: TBD] Guard 12h draft conversion against invalid hour values outside 1-12. _(Already implemented in `buildTimePartsFromDraft`.)_
11
+ - [x] [M][P0][Area: Input][Owner: Unassigned][Target: TBD] Apply step clamping on typed values when segment edit is confirmed. _(Already implemented in `setSegmentValue` via `clampToStep`.)_
12
+ - [x] [S][P0][Area: Formatting][Owner: Unassigned][Target: TBD] Force "HH:00" output for hour granularity regardless of leftover minutes. _(Already implemented in `formatTimePickerValue`.)_
13
+ - [x] [S][P0][Area: Validation][Owner: Unassigned][Target: TBD] Treat missing dayPeriod as invalid in 12h draft-to-24h conversion. _(Already implemented in `buildTimePartsFromDraft`.)_
14
+ - [x] [M][P1][Area: Locale][Owner: Unassigned][Target: TBD] Build segment order and literals from Intl formatToParts instead of hardcoded templates. _(Already implemented in `buildTimePickerSegments`.)_
15
+ - [x] [S][P1][Area: Validation][Owner: Unassigned][Target: TBD] Compare min/max using the active granularity to avoid false out-of-range states. _(Fixed: `isTimeOutOfRange` now accepts `granularity` and truncates before comparison.)_
16
+ - [x] [S][P1][Area: Accessibility][Owner: Unassigned][Target: TBD] Ensure time picker columns and options expose complete listbox/option ARIA contract. _(Already implemented in column/column-cell components.)_
17
+ - [x] [S][P1][Area: Composition][Owner: Unassigned][Target: TBD] Wire selection-close behavior so `shouldCloseOnSelect` controls whether column selection closes popover when draft is complete. _(Implemented in `selectColumnOption`; default is `false`.)_
18
+ - [x] [S][P2][Area: Accessibility][Owner: Unassigned][Target: TBD] Verify dayPeriod segment uses 0-1 ARIA bounds and localized value text. _(Fixed: segment now uses `timePicker.hourCycle` for correct `aria-valuemin`/`aria-valuemax`.)_
19
+ - [x] [S][P2][Area: Testing][Owner: Unassigned][Target: TBD] Ensure segment data attributes are consistent for styling and test selectors. _(Already implemented: `data-time-picker-segment="true"`.)_
20
+ - [x] [M][P2][Area: Testing][Owner: Unassigned][Target: TBD] Expand unit coverage for parsing, format output, 12h conversion, clamping, and draft evaluation pipeline. _(Added 47 unit tests in `time-utils.test.ts`.)_
21
+ - [x] [S][P2][Area: Documentation][Owner: Unassigned][Target: TBD] Add TimePicker references to focus contract documentation. _(Added component coverage section to `FOCUS_STATE_CONTRACT.md`.)_
22
+
23
+ ## Active
24
+
25
+ ### Bugs
26
+
27
+ - [x] [M][P0][Area: State][Owner: Unassigned][Target: TBD] Permitir que el componente se limpie a undefined/null externamente. En `time-picker-root.svelte`, la observación de Svelte 5 ($effect sobre value) hace un early return con `if (value === undefined) return;`. Esto impide que se limpie la hora si el developer resetea el form manualmente enviando value = undefined. **Fix:** Quitar el early return por `undefined` y despachar la asignación nula para vaciar el TimePicker de forma controlada.
28
+
29
+ - [x] [S][P0][Area: State][Owner: Unassigned][Target: TBD] Arreglar deshidratación al vaciar todos los segmentos (`commitFromDraft`). Al borrar backspace tras backspace, si el usuario vacía el _último_ segmento, la variable `hasAnyRequiredValue` rompe el flujo dando `false` lo que dispara un early return ocultando el borrado de `publishCommittedValue`. **Fix:** Quitar o condicionar el early return condicionado de hasAnyRequiredValue para que el componente notifique a los bindings de que la hora quedó completamente `null`.
30
+
31
+ - [x] [M][P0][Area: Events][Owner: Unassigned][Target: TBD] Reparar Event Forwarding y omisiones en componentes internos. Actualmente los wrappers tragan eventos porque fallan en reingeniar el event payload natural de Svelte 5.
32
+ 1. `Input`: Extrae variables pero no usa `composeEventHandlers`. Los onkey/onfocus externos mueren ahogados.
33
+ 2. `Trigger`: Esparce `...restProps` **después** de sus eventos atados a Svelte 5, lo que cancela las rutinas internas si el usuario pasa `<Trigger onmousedown>`
34
+ **Fix:** Extraer las declaraciones conflictivas de `$props()` y usar `composeEventHandlers` en línea para todas en vez de pisarlas u omitirlas.
35
+
36
+ - [x] [M][P0][Area: Input][Owner: Unassigned][Target: TBD] Guard segment `contenteditable` against paste, IME composition, and drag-drop. The `<span contenteditable>` in `time-picker-segment.svelte` has no `onbeforeinput`, `onpaste`, or `oncompositionend` handler. Ctrl+V, IME input, or drag-drop can modify the DOM directly without going through `typeSegmentDigit` / `setSegmentValue`, creating drift between the visible text and the internal `segmentDraft`. **Fix:** Add `onbeforeinput={(e) => e.preventDefault()}` to block all non-keyboard mutations. Alternatively, add an `oninput` handler that immediately restores the span's `textContent` to the current draft value as a safety net.
37
+
38
+ - [x] [M][P0][Area: Wheel][Owner: Unassigned][Target: TBD] Increase scroll debounce timeout in `use-wheel-scroll.svelte.ts` from 64ms to ~120-150ms. On touch devices with momentum scrolling (especially iOS Safari), gaps between `scroll` events during deceleration can exceed 64ms, causing premature snap that interrupts the user's inertia. Since browsers that support `scrollend` use it as the primary settle signal and the debounce only acts as a safety net, a higher value does not affect perceived latency. **File:** `hooks/use-wheel-scroll.svelte.ts` line ~215.
39
+
40
+ - [x] [M][P0][Area: Wheel][Owner: Unassigned][Target: TBD] Sync `lastCenteredIndex` when value changes externally while popover is open. `lastCenteredIndex` in `time-picker-wheel-column.svelte` is a plain `let` used as anchor for `moveBy()`. If the root value changes via `bind:value` while the popover is open, `lastCenteredIndex` still points to the old index. A subsequent `ArrowDown` jumps from the stale position instead of the current one. **Fix:** Add a `$effect` that observes `selectedValue` + `timePicker.open`: when both are truthy and `selectedIndex` changes, update `lastCenteredIndex = selectedIndex` and optionally scroll to it.
41
+
42
+ - [x] [S][P1][Area: Wheel][Owner: Unassigned][Target: TBD] Suppress unnecessary re-snap chain after `scrollToIndex('smooth')` when value is already committed. When `handleCenterRequest` (click on WheelItem) eagerly commits the value then calls `scrollToIndex(i, 'smooth')`, the ensuing scroll events trigger debounce → `snapToCenter` → `animateSnapTo` for micro-alignment → more scroll events. The chain is usually idempotent but can cause micro-jitter. **Fix:** Add a `silent` parameter to `scrollToIndex` that suppresses `snapToCenter` for the duration of that smooth scroll. Use it only for clicks where the commit already happened, NOT for corrective disabled-skip (which must snap and commit).
43
+
44
+ ### Accesibilidad
45
+
46
+ - [x] [S][P1][Area: Accessibility][Owner: Unassigned][Target: TBD] Add `aria-live` region to announce wheel value changes for screen readers. When the user scrolls a `WheelColumn`, `aria-valuenow` / `aria-valuetext` update, but screen readers don't automatically announce `aria-valuenow` changes on a `spinbutton`. **Fix:** Add a visually-hidden `<span role="status" aria-live="polite" class="sr-only">{valueText}</span>` inside each `WheelColumn` that updates when the selected value changes. This announces the new value after each snap.
47
+
48
+ - [x] [S][P1][Area: Accessibility][Owner: Unassigned][Target: TBD] Fix `Home`/`End` in segment to use correct boundary values for 12h mode. In `time-picker-segment.svelte`, `Home` sends `setSegmentValue('hour', '0')` and `End` sends `'23'` regardless of `hourCycle`. In 12h mode the valid range is 1-12. This currently works by accident (clamping corrects it), but the intent is wrong and fragile. **Fix:** Use `timePicker.hourCycle` to determine boundaries: `Home` → `hourCycle === 12 ? '1' : '0'`, `End` → `hourCycle === 12 ? '12' : '23'`.
49
+
50
+ - [x] [S][P2][Area: Accessibility][Owner: Unassigned][Target: TBD] Add `aria-roledescription` to `WheelColumn`. A generic `role="spinbutton"` doesn't communicate that the widget is a wheel/picker. Adding `aria-roledescription="wheel picker"` (or a localized equivalent) would improve the experience for screen reader users. Apply to the root `<div role="spinbutton">` in `time-picker-wheel-column.svelte`.
51
+
52
+ - [x] [S][P2][Area: Accessibility][Owner: Unassigned][Target: TBD] Add `isRequired` prop to Root and propagate `aria-required` to the Input group. There's currently no way to indicate the field is required for form validation. The prop should flow from Root → context → `time-picker-input.svelte` as `aria-required` on the `role="group"` element.
53
+
54
+ ### Performance
55
+
56
+ - [x] [M][P1][Area: Performance][Owner: Unassigned][Target: TBD] Cache `Intl.DateTimeFormat` instance across segment draft changes. `buildTimePickerSegments` in `time-utils.ts` creates a `new Intl.DateTimeFormat(locale, formatOptions)` on every call. This function is invoked from a `$derived` that depends on `segmentDraft`, so every keystroke recreates the formatter. The formatter only depends on `locale`, `hourCycle`, and `granularity`. **Fix:** In `time-picker-root.svelte`, derive the formatter once from those 3 variables and pass it to `buildTimePickerSegments` as a parameter.
57
+
58
+ - [x] [S][P2][Area: Performance][Owner: Unassigned][Target: TBD] Short-circuit disabled computation in `getWheelOptions` when no min/max range is set. `getWheelOptions` runs `buildTimePartsFromDraft` + `isTimeOutOfRange` for every option in every column (~122 iterations for minute granularity). When `normalizedMinValue` and `normalizedMaxValue` are both `undefined`, all options are always enabled. **Fix:** Add an early check: `if (!normalizedMinValue && !normalizedMaxValue)` → skip disabled computation and return `disabled: false` for all.
59
+
60
+ - [x] [S][P2][Area: Performance][Owner: Unassigned][Target: TBD] Cache system locale so `Intl.DateTimeFormat().resolvedOptions().locale` isn't called on every `$derived` reevaluation. In `time-picker-root.svelte`, `resolvedLocale` falls back to `Intl.DateTimeFormat().resolvedOptions().locale` when no context locale is provided. This creates a new formatter on each reevaluation. The system locale doesn't change during the component's lifetime. **Fix:** Capture it once with `untrack` at initialization and store in a constant.
61
+
62
+ ### Arquitectura / Robustez
63
+
64
+ - [x] [S][P2][Area: Wheel][Owner: Unassigned][Target: TBD] Batch `ResizeObserver` callbacks with `requestAnimationFrame` to avoid layout thrashing. The `ResizeObserver` in `time-picker-wheel-column.svelte` calls `syncMeasurements()` which updates `$state` variables (`itemHeight`, `spacerHeight`). This triggers Svelte re-render → layout shift → observer fires again, producing `ResizeObserver loop completed with undelivered notifications` warnings in tests. **Fix:** Wrap the observer callback: `resizeObserver = new ResizeObserver(() => { requestAnimationFrame(() => syncMeasurements()); });`
65
+
66
+ - [x] [S][P2][Area: Wheel][Owner: Unassigned][Target: TBD] Make `WheelItem` default visual style consistent with headless pattern. `time-picker-wheel-item.svelte` applies inline styles (opacity, cursor, font-weight, etc.) by default but drops ALL of them if the consumer passes `class`. This all-or-nothing behavior is inconsistent with the rest of the library (fully headless). **Fix:** Either remove default visual styles entirely (consumer styles via `data-selected`, `data-disabled` attributes — headless), or separate structural styles (always applied) from decorative styles (opt-out via prop). Preferred: go fully headless for consistency.
67
+
68
+ - [x] [S][P3][Area: Code Quality][Owner: Unassigned][Target: TBD] Remove dead code in `isTimeOutOfRange`. The `if (minValue && maxValue) { ... if (min > max) return true }` block at the end is unreachable — if `min > max`, any value `v` is already rejected by the individual `v < min` or `v > max` checks above. Remove to reduce reader confusion. **File:** `root/time-utils.ts` lines ~219-228.
69
+
70
+ - [x] [S][P3][Area: Code Quality][Owner: Unassigned][Target: TBD] Fix meaningless ternary in `setSegmentValue`. `const maxDigits = type === 'hour' ? 2 : 2;` — both branches are `2`. Was likely a draft for differentiating types. Replace with `const maxDigits = 2;`. **File:** `root/time-picker-root.svelte` line ~280.
71
+
72
+ ### Testing
73
+
74
+ - [x] [C][P1][Area: Testing][Owner: Unassigned][Target: TBD] Expand `WheelColumn` test coverage. Current: 4 tests (render, aria values, cross-column nav, spacers). Missing critical scenarios: (a) ArrowUp/Down keyboard changes value, (b) disabled item skip on scroll, (c) PageUp/PageDown/Home/End navigation, (d) scroll-to-selected on popover open, (e) external value change syncs wheel position, (f) full focus contract (`data-focus-within`, `data-focus-visible`), (g) behavior with `minValue`/`maxValue` producing disabled options.
75
+
76
+ - [x] [M][P1][Area: Testing][Owner: Unassigned][Target: TBD] Add integration test for wheel → root → segment sync. No test verifies the full flow: user scrolls wheel → snap detects centered item → `selectWheelValue` called → `commitFromDraft` runs → segment text updates → `bind:value` reflects new time. This is the core happy-path of the wheel and should be covered.
77
+
78
+ - [x] [S][P2][Area: Testing][Owner: Unassigned][Target: TBD] Add test for contenteditable paste/IME resilience in segment. Verify that pasting text into a segment does not corrupt `segmentDraft` and that the visible text is restored to the draft value. Important because `contenteditable` is inherently fragile.
79
+
80
+ ## Notes
81
+
82
+ TimePicker locale is sourced from LocaleProvider, not from a root locale prop.
83
+
84
+ ### Review context (2026-03-02)
85
+
86
+ The wheel architecture (`WheelColumn` / `WheelItem` / `use-wheel-scroll`) replaced the original column-based `listbox`/`option` architecture. The wheel uses JS-driven snapping (no CSS `scroll-snap-type`) with a 120ms ease-out animation. Selection occurs when scroll settles and the centered item is committed. Disabled items are auto-skipped. `shouldCloseOnSelect` was removed — popover closes only via Escape, click outside, or programmatically.
@@ -0,0 +1,14 @@
1
+ # TimePicker Clock
2
+
3
+ ## API reference
4
+
5
+ ### TimePicker.Clock
6
+
7
+ Name: `TimePicker.Clock`
8
+ Description: Clock panel composition part that resolves visible wheel columns from `TimePicker.Root` state.
9
+
10
+ | Prop | Type | Default | Description |
11
+ | -------------- | -------------------------------- | ---------------------------- | ------------------------------------------------------- |
12
+ | `column` | `Snippet<[ClockColumnInfo]>` | `undefined` | Optional custom per-column renderer. |
13
+ | `class` | `string` | `'flex items-stretch gap-2'` | CSS class names for the clock container. |
14
+ | `...restProps` | `HTMLAttributes<HTMLDivElement>` | `-` | Additional attributes forwarded to the clock container. |