@dashforge/tw 0.9.1-beta → 0.10.0-beta

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 (37) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/dist/index.esm.js +1152 -336
  3. package/dist/src/components/DateRangePicker/DateRangePicker.d.ts +13 -0
  4. package/dist/src/components/DateRangePicker/DateRangePicker.d.ts.map +1 -0
  5. package/dist/src/components/DateRangePicker/dateRangePicker.types.d.ts +85 -0
  6. package/dist/src/components/DateRangePicker/dateRangePicker.types.d.ts.map +1 -0
  7. package/dist/src/components/DateRangePicker/dateRangePicker.variants.d.ts +183 -0
  8. package/dist/src/components/DateRangePicker/dateRangePicker.variants.d.ts.map +1 -0
  9. package/dist/src/components/DateTimePicker/DateTimePicker.d.ts +7 -43
  10. package/dist/src/components/DateTimePicker/DateTimePicker.d.ts.map +1 -1
  11. package/dist/src/components/DateTimePicker/dateTimePicker.types.d.ts +50 -55
  12. package/dist/src/components/DateTimePicker/dateTimePicker.types.d.ts.map +1 -1
  13. package/dist/src/components/DateTimePicker/dateTimePicker.variants.d.ts +36 -94
  14. package/dist/src/components/DateTimePicker/dateTimePicker.variants.d.ts.map +1 -1
  15. package/dist/src/components/TimePicker/TimePicker.d.ts +12 -0
  16. package/dist/src/components/TimePicker/TimePicker.d.ts.map +1 -0
  17. package/dist/src/components/TimePicker/timePicker.types.d.ts +80 -0
  18. package/dist/src/components/TimePicker/timePicker.types.d.ts.map +1 -0
  19. package/dist/src/components/TimePicker/timePicker.variants.d.ts +103 -0
  20. package/dist/src/components/TimePicker/timePicker.variants.d.ts.map +1 -0
  21. package/dist/src/index.d.ts +10 -4
  22. package/dist/src/index.d.ts.map +1 -1
  23. package/package.json +4 -4
  24. package/src/components/DateRangePicker/DateRangePicker.test.tsx +92 -0
  25. package/src/components/DateRangePicker/DateRangePicker.tsx +510 -0
  26. package/src/components/DateRangePicker/dateRangePicker.types.ts +74 -0
  27. package/src/components/DateRangePicker/dateRangePicker.variants.ts +101 -0
  28. package/src/components/DateTimePicker/DateTimePicker.test.tsx +60 -186
  29. package/src/components/DateTimePicker/DateTimePicker.tsx +209 -181
  30. package/src/components/DateTimePicker/dateTimePicker.types.ts +50 -54
  31. package/src/components/DateTimePicker/dateTimePicker.variants.ts +22 -51
  32. package/src/components/TimePicker/TimePicker.test.tsx +65 -0
  33. package/src/components/TimePicker/TimePicker.tsx +274 -0
  34. package/src/components/TimePicker/timePicker.types.ts +69 -0
  35. package/src/components/TimePicker/timePicker.variants.ts +53 -0
  36. package/src/index.ts +28 -16
  37. package/src/components/DateTimePicker/DateTimePicker.perf.test.tsx +0 -153
@@ -1,153 +0,0 @@
1
- // @vitest-environment jsdom
2
- /**
3
- * Performance + re-render budget tests for `<DateTimePicker>`.
4
- *
5
- * Guardrails — not absolute benchmarks. They protect against:
6
- *
7
- * 1. Pathological mount cost (an accidental quadratic in a future
8
- * refactor — much less likely here than in `<Autocomplete>` because
9
- * there's no option list, but the test still pins the budget).
10
- *
11
- * 2. Sibling re-renders on every keystroke. The bridge subscription
12
- * model (`useDashFieldMeta(name)`) must remain GRANULAR — only the
13
- * field whose value changed re-renders, never a sibling.
14
- *
15
- * 3. Linear render budget per keystroke for THIS field. Aim for 1
16
- * render per `onChange`; tolerate 2 (RHF / engine reactive snapshot
17
- * batching).
18
- *
19
- * 4. Mode switch isn't accidentally expensive (cycling between
20
- * `date` / `time` / `datetime` mounts a NEW native input each time
21
- * — but the component itself must not allocate per-key arrays etc.).
22
- */
23
- import { describe, it, expect } from 'vitest';
24
- import { render, fireEvent, screen } from '@testing-library/react';
25
- import { DashFormProvider } from '@dashforge/forms';
26
- import { DateTimePicker, TextField } from '../../index.js';
27
-
28
- function withRenderCounter<P>(
29
- Component: React.ComponentType<P>,
30
- counterRef: { count: number }
31
- ): React.FC<P> {
32
- return function Counted(props: P) {
33
- counterRef.count++;
34
- return <Component {...(props as P & object)} />;
35
- };
36
- }
37
-
38
- describe('<DateTimePicker> performance', () => {
39
- it('mounts well under 50ms (standalone, no bridge)', () => {
40
- const t0 = performance.now();
41
- render(<DateTimePicker name="d" label="Date" />);
42
- const t1 = performance.now();
43
- expect(t1 - t0).toBeLessThan(50);
44
- });
45
-
46
- it('mounts under 100ms inside DashFormProvider (with bridge wiring)', () => {
47
- const t0 = performance.now();
48
- render(
49
- <DashFormProvider defaultValues={{ d: '2026-05-16' }}>
50
- <DateTimePicker name="d" label="Date" />
51
- </DashFormProvider>
52
- );
53
- const t1 = performance.now();
54
- expect(t1 - t0).toBeLessThan(100);
55
- });
56
-
57
- it('processes a value change in under 20ms', () => {
58
- render(<DateTimePicker name="d" label="Date" />);
59
- const input = screen.getByLabelText('Date') as HTMLInputElement;
60
- const t0 = performance.now();
61
- fireEvent.change(input, { target: { value: '2026-12-25' } });
62
- const t1 = performance.now();
63
- expect(t1 - t0).toBeLessThan(20);
64
- });
65
- });
66
-
67
- describe('<DateTimePicker> re-render budget', () => {
68
- it('renders at most 2 times on mount', () => {
69
- const counter = { count: 0 };
70
- const Counted = withRenderCounter(DateTimePicker, counter);
71
- render(<Counted name="d" label="Date" />);
72
- // 1 initial + (tolerate 1 follow-up commit for any setState
73
- // scheduled in a useEffect; today we don't schedule any, but the
74
- // budget allows headroom).
75
- expect(counter.count).toBeLessThanOrEqual(2);
76
- });
77
-
78
- it('keeps controlled re-renders ≤ 1 per value prop change', () => {
79
- const counter = { count: 0 };
80
- const Counted = withRenderCounter(DateTimePicker, counter);
81
- const { rerender } = render(
82
- <Counted name="d" label="Date" value="2026-01-01" />
83
- );
84
- const baseline = counter.count;
85
- rerender(<Counted name="d" label="Date" value="2026-01-02" />);
86
- rerender(<Counted name="d" label="Date" value="2026-01-03" />);
87
- rerender(<Counted name="d" label="Date" value="2026-01-04" />);
88
- // 3 prop changes ⇒ ≤ 4 renders (one per change + tolerance).
89
- expect(counter.count - baseline).toBeLessThanOrEqual(4);
90
- });
91
-
92
- it('does NOT re-render an unrelated sibling on each value change', () => {
93
- // Critical perf property: typing into one DateTimePicker must not
94
- // cause sibling components to re-render. Granular per-field
95
- // subscription via `useDashFieldMeta(name)` is what enforces this.
96
- const sibCount = { count: 0 };
97
- const Sibling = () => {
98
- sibCount.count++;
99
- return <div data-testid="sib" />;
100
- };
101
- render(
102
- <DashFormProvider defaultValues={{ d: '2026-01-01', other: 'x' }}>
103
- <DateTimePicker name="d" label="Date" />
104
- <Sibling />
105
- </DashFormProvider>
106
- );
107
- const before = sibCount.count;
108
- const input = screen.getByLabelText('Date') as HTMLInputElement;
109
- for (let i = 1; i <= 5; i++) {
110
- fireEvent.change(input, {
111
- target: { value: `2026-01-0${i + 1}` },
112
- });
113
- }
114
- expect(sibCount.count).toBe(before);
115
- });
116
-
117
- it('does NOT re-render when a sibling field changes (per-field subscription)', () => {
118
- // Inverse of the above: changing the OTHER field in the bridge
119
- // must not re-render the DateTimePicker. Verifies the granular
120
- // `useDashFieldMeta` subscription is filtering by name.
121
- const dtCount = { count: 0 };
122
- const Counted = withRenderCounter(DateTimePicker, dtCount);
123
- render(
124
- <DashFormProvider defaultValues={{ d: '2026-01-01', other: '' }}>
125
- <Counted name="d" label="Date" />
126
- <TextField name="other" label="Other" />
127
- </DashFormProvider>
128
- );
129
- const baseline = dtCount.count;
130
- const other = screen.getByLabelText('Other') as HTMLInputElement;
131
- for (let i = 0; i < 5; i++) {
132
- fireEvent.change(other, { target: { value: 'a'.repeat(i + 1) } });
133
- }
134
- // The DateTimePicker may re-render once on initial mount of the
135
- // sibling (parent re-render propagation) but NOT on each sibling
136
- // keystroke. Budget: 0 additional renders.
137
- expect(dtCount.count - baseline).toBeLessThanOrEqual(1);
138
- });
139
-
140
- it('mode switch (date ⇄ datetime) does not leak the previous native input', () => {
141
- // Switching `mode` changes the native element's `type` attribute —
142
- // React reuses the same DOM node (no remount). Assert that the
143
- // input element identity is preserved.
144
- const { rerender, container } = render(
145
- <DateTimePicker name="d" label="X" mode="date" />
146
- );
147
- const inputBefore = container.querySelector('input');
148
- rerender(<DateTimePicker name="d" label="X" mode="datetime" />);
149
- const inputAfter = container.querySelector('input');
150
- expect(inputBefore).toBe(inputAfter);
151
- expect((inputAfter as HTMLInputElement).type).toBe('datetime-local');
152
- });
153
- });