@charcuterie/ui 3.33.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -0
- package/dist/Board/Board.d.ts.map +1 -1
- package/dist/Board/Board.js +2 -2
- package/dist/Board/Board.js.map +1 -1
- package/dist/Main/Main.d.ts +18 -19
- package/dist/Main/Main.d.ts.map +1 -1
- package/dist/Main/Main.js +21 -10
- package/dist/Main/Main.js.map +1 -1
- package/dist/Main/ScrollMemoryProvider.d.ts +38 -0
- package/dist/Main/ScrollMemoryProvider.d.ts.map +1 -0
- package/dist/Main/ScrollMemoryProvider.js +30 -0
- package/dist/Main/ScrollMemoryProvider.js.map +1 -0
- package/dist/Main/scrollMemory.d.ts +54 -28
- package/dist/Main/scrollMemory.d.ts.map +1 -1
- package/dist/Main/scrollMemory.js +81 -40
- package/dist/Main/scrollMemory.js.map +1 -1
- package/dist/Main/scrollMemoryContext.d.ts +18 -0
- package/dist/Main/scrollMemoryContext.d.ts.map +1 -0
- package/dist/Main/scrollMemoryContext.js +18 -0
- package/dist/Main/scrollMemoryContext.js.map +1 -0
- package/dist/Main/useScrollMemory.d.ts +20 -18
- package/dist/Main/useScrollMemory.d.ts.map +1 -1
- package/dist/Main/useScrollMemory.js +69 -42
- package/dist/Main/useScrollMemory.js.map +1 -1
- package/dist/RangeSlider/RangeSlider.d.ts +117 -0
- package/dist/RangeSlider/RangeSlider.d.ts.map +1 -0
- package/dist/RangeSlider/RangeSlider.js +242 -0
- package/dist/RangeSlider/RangeSlider.js.map +1 -0
- package/dist/RangeSlider/rangeSliderValue.d.ts +102 -0
- package/dist/RangeSlider/rangeSliderValue.d.ts.map +1 -0
- package/dist/RangeSlider/rangeSliderValue.js +116 -0
- package/dist/RangeSlider/rangeSliderValue.js.map +1 -0
- package/dist/SegmentedControl/SegmentedControl.d.ts +12 -1
- package/dist/SegmentedControl/SegmentedControl.d.ts.map +1 -1
- package/dist/SegmentedControl/SegmentedControl.js +3 -3
- package/dist/SegmentedControl/SegmentedControl.js.map +1 -1
- package/dist/SegmentedControl/SegmentedOption.d.ts +2 -1
- package/dist/SegmentedControl/SegmentedOption.d.ts.map +1 -1
- package/dist/SegmentedControl/SegmentedOption.js +7 -2
- package/dist/SegmentedControl/SegmentedOption.js.map +1 -1
- package/dist/Slider/Slider.d.ts +6 -1
- package/dist/Slider/Slider.d.ts.map +1 -1
- package/dist/Slider/Slider.js +8 -22
- package/dist/Slider/Slider.js.map +1 -1
- package/dist/TimecodeInput/TimecodeInput.d.ts +88 -0
- package/dist/TimecodeInput/TimecodeInput.d.ts.map +1 -0
- package/dist/TimecodeInput/TimecodeInput.js +258 -0
- package/dist/TimecodeInput/TimecodeInput.js.map +1 -0
- package/dist/TimecodeInput/timecode.d.ts +132 -0
- package/dist/TimecodeInput/timecode.d.ts.map +1 -0
- package/dist/TimecodeInput/timecode.js +246 -0
- package/dist/TimecodeInput/timecode.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/reactRouter/ReactRouterAdapter.d.ts +43 -0
- package/dist/reactRouter/ReactRouterAdapter.d.ts.map +1 -0
- package/dist/reactRouter/ReactRouterAdapter.js +50 -0
- package/dist/reactRouter/ReactRouterAdapter.js.map +1 -0
- package/dist/reactRouter/index.d.ts +14 -8
- package/dist/reactRouter/index.d.ts.map +1 -1
- package/dist/reactRouter/index.js +13 -8
- package/dist/reactRouter/index.js.map +1 -1
- package/dist/sliderStyles.d.ts +39 -0
- package/dist/sliderStyles.d.ts.map +1 -0
- package/dist/sliderStyles.js +50 -0
- package/dist/sliderStyles.js.map +1 -0
- package/package.json +1 -1
- package/src/Board/Board.test.tsx +8 -0
- package/src/Board/Board.tsx +7 -1
- package/src/Main/Main.mdx +35 -7
- package/src/Main/Main.stories.tsx +129 -64
- package/src/Main/Main.test.tsx +58 -0
- package/src/Main/Main.tsx +20 -20
- package/src/Main/ScrollMemoryProvider.tsx +48 -0
- package/src/Main/scrollMemory.test.ts +70 -16
- package/src/Main/scrollMemory.ts +98 -40
- package/src/Main/scrollMemoryContext.ts +21 -0
- package/src/Main/useScrollMemory.ts +83 -43
- package/src/RangeSlider/RangeSlider.mdx +85 -0
- package/src/RangeSlider/RangeSlider.stories.tsx +346 -0
- package/src/RangeSlider/RangeSlider.test.tsx +452 -0
- package/src/RangeSlider/RangeSlider.tsx +595 -0
- package/src/RangeSlider/rangeSliderValue.test.ts +217 -0
- package/src/RangeSlider/rangeSliderValue.ts +185 -0
- package/src/RouterLink/RouterLink.mdx +53 -16
- package/src/SegmentedControl/SegmentedControl.stories.tsx +50 -0
- package/src/SegmentedControl/SegmentedControl.test.tsx +36 -1
- package/src/SegmentedControl/SegmentedControl.tsx +14 -0
- package/src/SegmentedControl/SegmentedOption.tsx +7 -0
- package/src/Slider/Slider.tsx +23 -29
- package/src/TimecodeInput/TimecodeInput.mdx +155 -0
- package/src/TimecodeInput/TimecodeInput.stories.tsx +304 -0
- package/src/TimecodeInput/TimecodeInput.test.tsx +279 -0
- package/src/TimecodeInput/TimecodeInput.tsx +493 -0
- package/src/TimecodeInput/timecode.test.ts +236 -0
- package/src/TimecodeInput/timecode.ts +391 -0
- package/src/index.ts +21 -0
- package/src/reactRouter/ReactRouterAdapter.stories.tsx +120 -0
- package/src/reactRouter/ReactRouterAdapter.test.tsx +92 -0
- package/src/reactRouter/ReactRouterAdapter.tsx +66 -0
- package/src/reactRouter/index.ts +14 -8
- package/src/sliderStyles.ts +69 -0
- package/src/sourceRules.test.ts +29 -3
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
import { composeStories } from "@storybook/react"
|
|
2
|
+
import { expect, userEvent, waitFor } from "storybook/test"
|
|
3
|
+
import { test } from "vitest"
|
|
4
|
+
|
|
5
|
+
import { expectNoAxeViolations } from "../expectNoAxeViolations.testHelpers.ts"
|
|
6
|
+
import { mountStory } from "../mountStory.testHelpers.ts"
|
|
7
|
+
import { expectAgentDrivable } from "../testing/index.ts"
|
|
8
|
+
import * as stories from "./TimecodeInput.stories.tsx"
|
|
9
|
+
|
|
10
|
+
const { AllStates, Interactive } = composeStories(stories)
|
|
11
|
+
|
|
12
|
+
const mountInteractive = async () => {
|
|
13
|
+
const mounted = await mountStory(Interactive)
|
|
14
|
+
|
|
15
|
+
return {
|
|
16
|
+
...mounted,
|
|
17
|
+
end: expectAgentDrivable(mounted.canvas, {
|
|
18
|
+
name: "Play section end",
|
|
19
|
+
role: "textbox",
|
|
20
|
+
}),
|
|
21
|
+
single: expectAgentDrivable(mounted.canvas, {
|
|
22
|
+
name: "Start at",
|
|
23
|
+
role: "textbox",
|
|
24
|
+
}),
|
|
25
|
+
start: expectAgentDrivable(mounted.canvas, {
|
|
26
|
+
name: "Play section start",
|
|
27
|
+
role: "textbox",
|
|
28
|
+
}),
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
test("a typed position is echoed in full, and does NOT commit until Enter", async () => {
|
|
33
|
+
const { canvas, single } = await mountInteractive()
|
|
34
|
+
|
|
35
|
+
await userEvent.click(single)
|
|
36
|
+
|
|
37
|
+
await userEvent.type(single, "90")
|
|
38
|
+
|
|
39
|
+
// The whole anti-silent-guess mechanism: `90` is ninety seconds,
|
|
40
|
+
// and the field says so in a live region before anything is
|
|
41
|
+
// stored.
|
|
42
|
+
await waitFor(() => {
|
|
43
|
+
expect(
|
|
44
|
+
canvas.getByText("00:01:30.000"),
|
|
45
|
+
).toBeInTheDocument()
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
await expect(
|
|
49
|
+
canvas.getByText("Chosen: —"),
|
|
50
|
+
).toBeInTheDocument()
|
|
51
|
+
|
|
52
|
+
await userEvent.keyboard("{Enter}")
|
|
53
|
+
|
|
54
|
+
await waitFor(() => {
|
|
55
|
+
expect(
|
|
56
|
+
canvas.getByText("Chosen: 00:01:30.000"),
|
|
57
|
+
).toBeInTheDocument()
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
// Committed in the one canonical spelling, so a re-read of the
|
|
61
|
+
// field parses to the number that produced it.
|
|
62
|
+
await expect(single).toHaveValue("00:01:30.000")
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
test("an overflowed field is refused by name, and the typed text survives", async () => {
|
|
66
|
+
const { canvas, canvasElement, single } =
|
|
67
|
+
await mountInteractive()
|
|
68
|
+
|
|
69
|
+
await userEvent.click(single)
|
|
70
|
+
|
|
71
|
+
await userEvent.type(single, "1:90")
|
|
72
|
+
|
|
73
|
+
await waitFor(() => {
|
|
74
|
+
expect(
|
|
75
|
+
canvas.getByText(/A minute has 60 seconds/),
|
|
76
|
+
).toBeInTheDocument()
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
await expect(single).toHaveAttribute(
|
|
80
|
+
"aria-invalid",
|
|
81
|
+
"true",
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
await userEvent.keyboard("{Enter}")
|
|
85
|
+
|
|
86
|
+
// Nothing committed, and the text was neither cleared nor carried
|
|
87
|
+
// to a position the person did not ask for.
|
|
88
|
+
await expect(
|
|
89
|
+
canvas.getByText("Chosen: —"),
|
|
90
|
+
).toBeInTheDocument()
|
|
91
|
+
|
|
92
|
+
await expect(single).toHaveValue("1:90")
|
|
93
|
+
|
|
94
|
+
await expectNoAxeViolations(canvasElement)
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
test("Escape puts the last committed value back", async () => {
|
|
98
|
+
const { single } = await mountInteractive()
|
|
99
|
+
|
|
100
|
+
await userEvent.click(single)
|
|
101
|
+
|
|
102
|
+
await userEvent.type(single, "90{Enter}")
|
|
103
|
+
|
|
104
|
+
await expect(single).toHaveValue("00:01:30.000")
|
|
105
|
+
|
|
106
|
+
await userEvent.clear(single)
|
|
107
|
+
|
|
108
|
+
await userEvent.type(single, "7")
|
|
109
|
+
|
|
110
|
+
await userEvent.keyboard("{Escape}")
|
|
111
|
+
|
|
112
|
+
await expect(single).toHaveValue("00:01:30.000")
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
test("the arrows step by stepMs, and Shift multiplies that by ten", async () => {
|
|
116
|
+
const { canvas, single } = await mountInteractive()
|
|
117
|
+
|
|
118
|
+
await userEvent.click(single)
|
|
119
|
+
|
|
120
|
+
await userEvent.keyboard("{ArrowUp}")
|
|
121
|
+
|
|
122
|
+
// A step commits, because it is a whole gesture on a value rather
|
|
123
|
+
// than a draft the echo is reporting on.
|
|
124
|
+
await waitFor(() => {
|
|
125
|
+
expect(
|
|
126
|
+
canvas.getByText("Chosen: 00:00:01.000"),
|
|
127
|
+
).toBeInTheDocument()
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
await userEvent.keyboard("{Shift>}{ArrowUp}{/Shift}")
|
|
131
|
+
|
|
132
|
+
await expect(single).toHaveValue("00:00:11.000")
|
|
133
|
+
|
|
134
|
+
await userEvent.keyboard("{ArrowDown}")
|
|
135
|
+
|
|
136
|
+
await expect(single).toHaveValue("00:00:10.000")
|
|
137
|
+
|
|
138
|
+
// The floor is the start of the media, not a negative position.
|
|
139
|
+
await userEvent.keyboard(
|
|
140
|
+
"{Shift>}{ArrowDown}{ArrowDown}{/Shift}",
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
await expect(single).toHaveValue("00:00:00.000")
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
test("a position past the end of the media says so, then commits clamped", async () => {
|
|
147
|
+
const { canvas, single } = await mountInteractive()
|
|
148
|
+
|
|
149
|
+
await userEvent.click(single)
|
|
150
|
+
|
|
151
|
+
await userEvent.type(single, "1:00:00")
|
|
152
|
+
|
|
153
|
+
await waitFor(() => {
|
|
154
|
+
expect(
|
|
155
|
+
canvas.getByText(/commits as 00:45:12\.000/),
|
|
156
|
+
).toBeInTheDocument()
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
await userEvent.keyboard("{Enter}")
|
|
160
|
+
|
|
161
|
+
await expect(single).toHaveValue("00:45:12.000")
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
test("an inverted section swaps, rather than throwing away the boundary just typed", async () => {
|
|
165
|
+
const { canvas, canvasElement, end } =
|
|
166
|
+
await mountInteractive()
|
|
167
|
+
|
|
168
|
+
await userEvent.click(end)
|
|
169
|
+
|
|
170
|
+
// The seeded start is 00:05:00.000, so this end sits before it.
|
|
171
|
+
await userEvent.type(end, "1:00{Enter}")
|
|
172
|
+
|
|
173
|
+
await waitFor(() => {
|
|
174
|
+
expect(
|
|
175
|
+
canvas.getByText(
|
|
176
|
+
"Chosen: 00:01:00.000 to 00:05:00.000",
|
|
177
|
+
),
|
|
178
|
+
).toBeInTheDocument()
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
await expectNoAxeViolations(canvasElement)
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
test("a zero-length section is refused by name, because it plays nothing", async () => {
|
|
185
|
+
const { canvas, canvasElement, end } =
|
|
186
|
+
await mountInteractive()
|
|
187
|
+
|
|
188
|
+
await userEvent.click(end)
|
|
189
|
+
|
|
190
|
+
await userEvent.type(end, "5:00{Enter}")
|
|
191
|
+
|
|
192
|
+
await waitFor(() => {
|
|
193
|
+
expect(
|
|
194
|
+
canvas.getByText(/A section needs a length/),
|
|
195
|
+
).toBeInTheDocument()
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
await expect(end).toHaveAttribute("aria-invalid", "true")
|
|
199
|
+
|
|
200
|
+
// The start is untouched and the end is still open — nothing was
|
|
201
|
+
// committed, and the text is still what was typed.
|
|
202
|
+
await expect(
|
|
203
|
+
canvas.getByText("Chosen: 00:05:00.000 to the end"),
|
|
204
|
+
).toBeInTheDocument()
|
|
205
|
+
|
|
206
|
+
await expect(end).toHaveValue("5:00")
|
|
207
|
+
|
|
208
|
+
await expectNoAxeViolations(canvasElement)
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
test("both ends are independently optional, and clearing one leaves the other alone", async () => {
|
|
212
|
+
const { canvas, end, start } = await mountInteractive()
|
|
213
|
+
|
|
214
|
+
await userEvent.click(end)
|
|
215
|
+
|
|
216
|
+
await userEvent.type(end, "10:00{Enter}")
|
|
217
|
+
|
|
218
|
+
await waitFor(() => {
|
|
219
|
+
expect(
|
|
220
|
+
canvas.getByText(
|
|
221
|
+
"Chosen: 00:05:00.000 to 00:10:00.000",
|
|
222
|
+
),
|
|
223
|
+
).toBeInTheDocument()
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
await userEvent.clear(start)
|
|
227
|
+
|
|
228
|
+
// Tab commits by blurring, which is the other half of "a keystroke
|
|
229
|
+
// never commits".
|
|
230
|
+
await userEvent.tab()
|
|
231
|
+
|
|
232
|
+
await waitFor(() => {
|
|
233
|
+
expect(
|
|
234
|
+
canvas.getByText(
|
|
235
|
+
"Chosen: the beginning to 00:10:00.000",
|
|
236
|
+
),
|
|
237
|
+
).toBeInTheDocument()
|
|
238
|
+
})
|
|
239
|
+
})
|
|
240
|
+
|
|
241
|
+
test("the four states of a section all render as values, not as half-finished input", async () => {
|
|
242
|
+
const { canvas, canvasElement } =
|
|
243
|
+
await mountStory(AllStates)
|
|
244
|
+
|
|
245
|
+
expectAgentDrivable(canvas, {
|
|
246
|
+
name: "From here on start",
|
|
247
|
+
role: "textbox",
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
expectAgentDrivable(canvas, {
|
|
251
|
+
name: "Up to here end",
|
|
252
|
+
role: "textbox",
|
|
253
|
+
})
|
|
254
|
+
|
|
255
|
+
// "empty" and "no window" are the same reading of the same
|
|
256
|
+
// absence, one in each mode — which is the point: an unset section
|
|
257
|
+
// is not a broken one.
|
|
258
|
+
await expect(
|
|
259
|
+
canvas.getAllByText("Chosen: —"),
|
|
260
|
+
).toHaveLength(2)
|
|
261
|
+
|
|
262
|
+
await expect(
|
|
263
|
+
canvas.getByText("Chosen: 00:05:00.000 to the end"),
|
|
264
|
+
).toBeInTheDocument()
|
|
265
|
+
|
|
266
|
+
await expect(
|
|
267
|
+
canvas.getByText(
|
|
268
|
+
"Chosen: the beginning to 00:05:00.000",
|
|
269
|
+
),
|
|
270
|
+
).toBeInTheDocument()
|
|
271
|
+
|
|
272
|
+
await expect(
|
|
273
|
+
canvas.getByText(
|
|
274
|
+
"Chosen: 00:05:00.000 to 00:15:00.000",
|
|
275
|
+
),
|
|
276
|
+
).toBeInTheDocument()
|
|
277
|
+
|
|
278
|
+
await expectNoAxeViolations(canvasElement)
|
|
279
|
+
})
|