@nexxtmove/ui 2.6.0 → 2.8.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/dist/components/editor/molecules/EditorColor/EditorColor.vue.d.ts +7 -5
- package/dist/components/editor/molecules/EditorColor/_ColorField.vue.d.ts +4 -1
- package/dist/components/organisms/ColorPicker/ColorPicker.vue.d.ts +3 -1
- package/dist/index.js +95 -172
- package/dist/nuxt.js +0 -1
- package/package.json +38 -36
- package/src/attribute-contract.test.ts +151 -0
- package/src/components/atoms/AnimatedNumber/AnimatedNumber.stories.ts +36 -0
- package/src/components/atoms/AnimatedNumber/AnimatedNumber.test.ts +21 -0
- package/src/components/atoms/Avatar/Avatar.stories.ts +122 -0
- package/src/components/atoms/Avatar/Avatar.test.ts +134 -0
- package/src/components/atoms/Button/Button.stories.ts +290 -0
- package/src/components/atoms/Button/Button.test.ts +406 -0
- package/src/components/atoms/Card/Card.stories.ts +45 -0
- package/src/components/atoms/Card/Card.test.ts +46 -0
- package/src/components/atoms/Checkbox/Checkbox.stories.ts +35 -0
- package/src/components/atoms/Checkbox/Checkbox.test.ts +55 -0
- package/src/components/atoms/Chip/Chip.stories.ts +274 -0
- package/src/components/atoms/Chip/Chip.test.ts +230 -0
- package/src/components/atoms/EnergyLabel/EnergyLabel.stories.ts +189 -0
- package/src/components/atoms/EnergyLabel/EnergyLabel.test.ts +100 -0
- package/src/components/atoms/FloatingPanel/FloatingPanel.stories.ts +677 -0
- package/src/components/atoms/FloatingPanel/FloatingPanel.test.ts +864 -0
- package/src/components/atoms/Icon/Icon.stories.ts +63 -0
- package/src/components/atoms/Icon/Icon.test.ts +89 -0
- package/src/components/atoms/Loader/Loader.stories.ts +36 -0
- package/src/components/atoms/Loader/Loader.test.ts +40 -0
- package/src/components/atoms/PickerItem/PickerItem.stories.ts +172 -0
- package/src/components/atoms/PickerItem/PickerItem.test.ts +142 -0
- package/src/components/atoms/PickerPreview/PickerPreview.stories.ts +183 -0
- package/src/components/atoms/PickerPreview/PickerPreview.test.ts +137 -0
- package/src/components/atoms/PresenceIndicator/PresenceIndicator.stories.ts +21 -0
- package/src/components/atoms/PropertyListing/PropertyListing.stories.ts +38 -0
- package/src/components/atoms/PropertyListing/PropertyListing.test.ts +46 -0
- package/src/components/atoms/SelectableOption/SelectableOption.stories.ts +131 -0
- package/src/components/atoms/SelectableOption/SelectableOption.test.ts +96 -0
- package/src/components/atoms/Skeleton/Skeleton.stories.ts +27 -0
- package/src/components/atoms/Skeleton/Skeleton.test.ts +28 -0
- package/src/components/atoms/Slider/Slider.stories.ts +151 -0
- package/src/components/atoms/Slider/Slider.test.ts +191 -0
- package/src/components/atoms/Squircle/Squircle.stories.ts +114 -0
- package/src/components/atoms/Squircle/Squircle.test.ts +70 -0
- package/src/components/atoms/Switch/Switch.stories.ts +91 -0
- package/src/components/atoms/Switch/Switch.test.ts +183 -0
- package/src/components/atoms/Tooltip/Tooltip.stories.ts +158 -0
- package/src/components/atoms/Tooltip/Tooltip.test.ts +134 -0
- package/src/components/editor/atoms/EditorDimensionField/EditorDimensionField.stories.ts +124 -0
- package/src/components/editor/atoms/EditorDimensionField/EditorDimensionField.test.ts +464 -0
- package/src/components/editor/atoms/EditorSettingsBlock/EditorSettingsBlock.stories.ts +266 -0
- package/src/components/editor/atoms/EditorSettingsBlock/EditorSettingsBlock.test.ts +421 -0
- package/src/components/editor/atoms/EditorSettingsGroup/EditorSettingsGroup.stories.ts +365 -0
- package/src/components/editor/atoms/EditorSettingsGroup/EditorSettingsGroup.test.ts +484 -0
- package/src/components/editor/composables/useRadioGrid.test.ts +261 -0
- package/src/components/editor/molecules/EditorAlignItems/EditorAlignItems.stories.ts +73 -0
- package/src/components/editor/molecules/EditorAlignItems/EditorAlignItems.test.ts +189 -0
- package/src/components/editor/molecules/EditorAlignmentGrid/EditorAlignmentGrid.stories.ts +309 -0
- package/src/components/editor/molecules/EditorAlignmentGrid/EditorAlignmentGrid.test.ts +359 -0
- package/src/components/editor/molecules/EditorAnchor/EditorAnchor.stories.ts +138 -0
- package/src/components/editor/molecules/EditorAnchor/EditorAnchor.test.ts +344 -0
- package/src/components/editor/molecules/EditorBackgroundSize/EditorBackgroundSize.stories.ts +87 -0
- package/src/components/editor/molecules/EditorBackgroundSize/EditorBackgroundSize.test.ts +262 -0
- package/src/components/editor/molecules/EditorBlur/EditorBlur.stories.ts +115 -0
- package/src/components/editor/molecules/EditorBlur/EditorBlur.test.ts +243 -0
- package/src/components/editor/molecules/EditorBorder/EditorBorder.stories.ts +187 -0
- package/src/components/editor/molecules/EditorBorder/EditorBorder.test.ts +456 -0
- package/src/components/editor/molecules/EditorColor/EditorColor.stories.ts +633 -0
- package/src/components/editor/molecules/EditorColor/EditorColor.test.ts +776 -0
- package/src/components/editor/molecules/EditorColor/EditorColor.vue +7 -5
- package/src/components/editor/molecules/EditorColor/EditorColorGradient.test.ts +832 -0
- package/src/components/editor/molecules/EditorColor/_ColorField.vue +20 -15
- package/src/components/editor/molecules/EditorColor/gradientClass.test.ts +303 -0
- package/src/components/editor/molecules/EditorDashPattern/EditorDashPattern.stories.ts +72 -0
- package/src/components/editor/molecules/EditorDashPattern/EditorDashPattern.test.ts +124 -0
- package/src/components/editor/molecules/EditorFlexDirection/EditorFlexDirection.stories.ts +46 -0
- package/src/components/editor/molecules/EditorFlexDirection/EditorFlexDirection.test.ts +97 -0
- package/src/components/editor/molecules/EditorFontSize/EditorFontSize.stories.ts +248 -0
- package/src/components/editor/molecules/EditorFontSize/EditorFontSize.test.ts +288 -0
- package/src/components/editor/molecules/EditorGap/EditorGap.stories.ts +129 -0
- package/src/components/editor/molecules/EditorGap/EditorGap.test.ts +253 -0
- package/src/components/editor/molecules/EditorHeadingType/EditorHeadingType.stories.ts +68 -0
- package/src/components/editor/molecules/EditorHeadingType/EditorHeadingType.test.ts +150 -0
- package/src/components/editor/molecules/EditorJustifyContent/EditorJustifyContent.stories.ts +71 -0
- package/src/components/editor/molecules/EditorJustifyContent/EditorJustifyContent.test.ts +161 -0
- package/src/components/editor/molecules/EditorLetterSpacing/EditorLetterSpacing.stories.ts +204 -0
- package/src/components/editor/molecules/EditorLetterSpacing/EditorLetterSpacing.test.ts +238 -0
- package/src/components/editor/molecules/EditorLineHeight/EditorLineHeight.stories.ts +257 -0
- package/src/components/editor/molecules/EditorLineHeight/EditorLineHeight.test.ts +286 -0
- package/src/components/editor/molecules/EditorLineStyle/EditorLineStyle.stories.ts +135 -0
- package/src/components/editor/molecules/EditorLineStyle/EditorLineStyle.test.ts +230 -0
- package/src/components/editor/molecules/EditorMaxWidth/EditorMaxWidth.stories.ts +154 -0
- package/src/components/editor/molecules/EditorMaxWidth/EditorMaxWidth.test.ts +112 -0
- package/src/components/editor/molecules/EditorOverrideStatus/EditorOverrideStatus.stories.ts +206 -0
- package/src/components/editor/molecules/EditorOverrideStatus/EditorOverrideStatus.test.ts +332 -0
- package/src/components/editor/molecules/EditorPadding/EditorPadding.stories.ts +374 -0
- package/src/components/editor/molecules/EditorPadding/EditorPadding.test.ts +712 -0
- package/src/components/editor/molecules/EditorRadius/EditorRadius.stories.ts +64 -0
- package/src/components/editor/molecules/EditorRadius/EditorRadius.test.ts +137 -0
- package/src/components/editor/molecules/EditorShadow/EditorShadow.stories.ts +103 -0
- package/src/components/editor/molecules/EditorShadow/EditorShadow.test.ts +168 -0
- package/src/components/editor/molecules/EditorSize/EditorSize.stories.ts +84 -0
- package/src/components/editor/molecules/EditorSize/EditorSize.test.ts +253 -0
- package/src/components/editor/molecules/EditorSizeLimit/EditorSizeLimit.stories.ts +49 -0
- package/src/components/editor/molecules/EditorSizeLimit/EditorSizeLimit.test.ts +43 -0
- package/src/components/editor/molecules/EditorTextAlign/EditorTextAlign.stories.ts +69 -0
- package/src/components/editor/molecules/EditorTextAlign/EditorTextAlign.test.ts +155 -0
- package/src/components/molecules/Announcement/Announcement.stories.ts +224 -0
- package/src/components/molecules/Announcement/Announcement.test.ts +161 -0
- package/src/components/molecules/ContactInfoCard/ContactInfoCard.stories.ts +99 -0
- package/src/components/molecules/ContactInfoCard/ContactInfoCard.test.ts +70 -0
- package/src/components/molecules/CustomerJourneyTile/CustomerJourneyTile.stories.ts +148 -0
- package/src/components/molecules/CustomerJourneyTile/CustomerJourneyTile.test.ts +271 -0
- package/src/components/molecules/DoughnutChart/DoughnutChart.stories.ts +45 -0
- package/src/components/molecules/DoughnutChart/DoughnutChart.test.ts +205 -0
- package/src/components/molecules/InfoBlock/InfoBlock.stories.ts +84 -0
- package/src/components/molecules/InfoBlock/InfoBlock.test.ts +101 -0
- package/src/components/molecules/InputField/InputField.stories.ts +117 -0
- package/src/components/molecules/InputField/InputField.test.ts +138 -0
- package/src/components/molecules/InputSelect/InputSelect.stories.ts +116 -0
- package/src/components/molecules/InputSelect/InputSelect.test.ts +127 -0
- package/src/components/molecules/ModalFooter/ModalFooter.stories.ts +204 -0
- package/src/components/molecules/ModalFooter/ModalFooter.test.ts +276 -0
- package/src/components/molecules/PageCard/PageCard.stories.ts +100 -0
- package/src/components/molecules/PageCard/PageCard.test.ts +91 -0
- package/src/components/molecules/PageContainer/PageContainer.stories.ts +90 -0
- package/src/components/molecules/PageContainer/PageContainer.test.ts +50 -0
- package/src/components/molecules/PageMockup/PageMockup.stories.ts +38 -0
- package/src/components/molecules/PageMockup/PageMockup.test.ts +20 -0
- package/src/components/molecules/PageTypeTile/PageTypeTile.stories.ts +63 -0
- package/src/components/molecules/PageTypeTile/PageTypeTile.test.ts +41 -0
- package/src/components/molecules/Pagination/Pagination.stories.ts +72 -0
- package/src/components/molecules/Pagination/Pagination.test.ts +130 -0
- package/src/components/molecules/PickerGroup/PickerGroup.stories.ts +90 -0
- package/src/components/molecules/PickerGroup/PickerGroup.test.ts +145 -0
- package/src/components/molecules/PickerList/PickerList.stories.ts +436 -0
- package/src/components/molecules/PickerList/PickerList.test.ts +870 -0
- package/src/components/molecules/PlaceholderInput/PlaceholderInput.stories.ts +48 -0
- package/src/components/molecules/PlaceholderInput/PlaceholderInput.test.ts +107 -0
- package/src/components/molecules/PropertyCard/PropertyCard.stories.ts +46 -0
- package/src/components/molecules/PropertyCard/PropertyCard.test.ts +59 -0
- package/src/components/molecules/Select/Select.stories.ts +264 -0
- package/src/components/molecules/Select/Select.test.ts +315 -0
- package/src/components/molecules/SidebarMenuItem/SidebarMenuItem.stories.ts +84 -0
- package/src/components/molecules/SidebarMenuItem/SidebarMenuItem.test.ts +103 -0
- package/src/components/molecules/SkeletonPropertyCard/SkeletonPropertyCard.stories.ts +11 -0
- package/src/components/molecules/SkeletonPropertyCard/SkeletonPropertyCard.test.ts +38 -0
- package/src/components/molecules/SocialIcons/SocialIcons.stories.ts +28 -0
- package/src/components/molecules/SocialIcons/SocialIcons.test.ts +58 -0
- package/src/components/molecules/SocialMediaCustomTemplate/SocialMediaCustomTemplate.stories.ts +11 -0
- package/src/components/molecules/SocialMediaCustomTemplate/SocialMediaCustomTemplate.test.ts +116 -0
- package/src/components/molecules/SocialMediaType/SocialMediaType.stories.ts +37 -0
- package/src/components/molecules/SocialMediaType/SocialMediaType.test.ts +126 -0
- package/src/components/molecules/Source/Source.stories.ts +35 -0
- package/src/components/molecules/Source/Source.test.ts +45 -0
- package/src/components/molecules/TabbedContent/TabbedContent.stories.ts +96 -0
- package/src/components/molecules/TabbedContent/TabbedContent.test.ts +109 -0
- package/src/components/molecules/Tabs/Tabs.stories.ts +230 -0
- package/src/components/molecules/Tabs/Tabs.test.ts +280 -0
- package/src/components/molecules/TemplateCard/TemplateCard.stories.ts +327 -0
- package/src/components/molecules/TemplateCard/TemplateCard.test.ts +339 -0
- package/src/components/molecules/Textarea/Textarea.stories.ts +216 -0
- package/src/components/molecules/Textarea/Textarea.test.ts +191 -0
- package/src/components/molecules/TimelineEvent/TimelineEvent.stories.ts +319 -0
- package/src/components/molecules/TimelineEvent/TimelineEvent.test.ts +224 -0
- package/src/components/molecules/TimelineLanes/TimelineLanes.stories.ts +29 -0
- package/src/components/molecules/TimelineLanes/TimelineLanes.test.ts +30 -0
- package/src/components/molecules/Toast/Toast.stories.ts +110 -0
- package/src/components/molecules/Toast/Toast.test.ts +103 -0
- package/src/components/molecules/ToggleGroup/ToggleGroup.stories.ts +297 -0
- package/src/components/molecules/ToggleGroup/ToggleGroup.test.ts +578 -0
- package/src/components/molecules/Tree/Tree.stories.ts +884 -0
- package/src/components/molecules/Tree/Tree.test.ts +1247 -0
- package/src/components/molecules/Tree/tree-drop.test.ts +223 -0
- package/src/components/organisms/Calendar/Calendar.stories.ts +108 -0
- package/src/components/organisms/Calendar/Calendar.test.ts +279 -0
- package/src/components/organisms/Calendar/_CalendarDayView.test.ts +104 -0
- package/src/components/organisms/Calendar/_CalendarHeader.test.ts +86 -0
- package/src/components/organisms/Calendar/_CalendarMonthView.test.ts +52 -0
- package/src/components/organisms/Calendar/_CalendarTimePicker.test.ts +89 -0
- package/src/components/organisms/Calendar/_CalendarYearView.test.ts +56 -0
- package/src/components/organisms/ColorPicker/ColorPicker.stories.ts +229 -0
- package/src/components/organisms/ColorPicker/ColorPicker.test.ts +696 -0
- package/src/components/organisms/ColorPicker/ColorPicker.vue +12 -3
- package/src/components/organisms/ColorPicker/gradient.test.ts +67 -0
- package/src/components/organisms/ColorPicker/gradientGeometry.test.ts +206 -0
- package/src/components/organisms/Combobox/Combobox.stories.ts +234 -0
- package/src/components/organisms/Combobox/Combobox.test.ts +512 -0
- package/src/components/organisms/Combobox/Combobox.vue +1 -1
- package/src/components/organisms/ComboboxPanel/ComboboxPanel.stories.ts +158 -0
- package/src/components/organisms/ComboboxPanel/ComboboxPanel.test.ts +524 -0
- package/src/components/organisms/ComboboxPanel/ComboboxPanel.vue +1 -1
- package/src/components/organisms/CountrySelect/CountrySelect.stories.ts +139 -0
- package/src/components/organisms/CountrySelect/CountrySelect.test.ts +149 -0
- package/src/components/organisms/DatePicker/DatePicker.stories.ts +163 -0
- package/src/components/organisms/DatePicker/DatePicker.test.ts +214 -0
- package/src/components/organisms/DropdownButton/DropdownButton.stories.ts +123 -0
- package/src/components/organisms/DropdownButton/DropdownButton.test.ts +379 -0
- package/src/components/organisms/FontPicker/FontPicker.stories.ts +476 -0
- package/src/components/organisms/FontPicker/FontPicker.test.ts +873 -0
- package/src/components/organisms/Header/Header.stories.ts +75 -0
- package/src/components/organisms/Header/Header.test.ts +233 -0
- package/src/components/organisms/IconPicker/IconPicker.stories.ts +734 -0
- package/src/components/organisms/IconPicker/IconPicker.test.ts +669 -0
- package/src/components/organisms/IconPicker/_IconPickerGrid.test.ts +184 -0
- package/src/components/organisms/IconPicker/_IconPickerRail.test.ts +102 -0
- package/src/components/organisms/IconPicker/useIconPickerNavigation.test.ts +215 -0
- package/src/components/organisms/Modal/Modal.stories.ts +350 -0
- package/src/components/organisms/Modal/Modal.test.ts +275 -0
- package/src/components/organisms/PickerPanel/PickerPanel.stories.ts +452 -0
- package/src/components/organisms/PickerPanel/PickerPanel.test.ts +358 -0
- package/src/components/organisms/ProgressBar/ProgressBar.stories.ts +100 -0
- package/src/components/organisms/ProgressBar/ProgressBar.test.ts +417 -0
- package/src/components/organisms/ReferenceCard/ReferenceCard.stories.ts +191 -0
- package/src/components/organisms/ReferenceCard/ReferenceCard.test.ts +165 -0
- package/src/components/organisms/SocialMediaTemplate/SocialMediaTemplate.stories.ts +65 -0
- package/src/components/organisms/SocialMediaTemplate/SocialMediaTemplate.test.ts +466 -0
- package/src/components/organisms/StepperHeader/StepperHeader.stories.ts +91 -0
- package/src/components/organisms/StepperHeader/StepperHeader.test.ts +279 -0
- package/src/components/organisms/Table/Table.stories.ts +332 -0
- package/src/components/organisms/Table/Table.test.ts +320 -0
- package/src/components/organisms/TimelinePhaseblock/TimelinePhaseblock.stories.ts +198 -0
- package/src/components/organisms/TimelinePhaseblock/TimelinePhaseblock.test.ts +283 -0
- package/src/components/organisms/TimelineTrack/TimelineTrack.stories.ts +120 -0
- package/src/components/organisms/TimelineTrack/TimelineTrack.test.ts +91 -0
- package/src/components/organisms/Toaster/Toaster.stories.ts +51 -0
- package/src/components/organisms/Toaster/Toaster.test.ts +75 -0
- package/src/components/organisms/VideoControls/VideoControls.stories.ts +792 -0
- package/src/components/organisms/VideoControls/VideoControls.test.ts +1056 -0
- package/src/components/organisms/VideoPlayer/VideoPlayer.stories.ts +327 -0
- package/src/components/organisms/VideoPlayer/VideoPlayer.test.ts +439 -0
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/vue3-vite'
|
|
2
|
+
import { expect, userEvent, waitFor, within } from 'storybook/test'
|
|
3
|
+
import VideoPlayer from './VideoPlayer.vue'
|
|
4
|
+
|
|
5
|
+
/** A 14-second VP9 tour from .storybook/public; WebM, so the Chromium of CI plays it too. */
|
|
6
|
+
const VIDEO = '/demo-tour.webm'
|
|
7
|
+
/** The first frame of VIDEO. */
|
|
8
|
+
const POSTER = '/demo-tour-poster.jpg'
|
|
9
|
+
/** The same tour cut to 9:16 (540×960), as a phone or TikTok video, and its first frame. */
|
|
10
|
+
const VERTICAL_VIDEO = '/demo-tour-vertical.webm'
|
|
11
|
+
const VERTICAL_POSTER = '/demo-tour-vertical-poster.jpg'
|
|
12
|
+
|
|
13
|
+
const meta = {
|
|
14
|
+
title: 'Components/Organisms/VideoPlayer',
|
|
15
|
+
component: VideoPlayer,
|
|
16
|
+
decorators: [() => ({ template: '<div class="w-120"><story /></div>' })],
|
|
17
|
+
// The rebuilt player is clean: axe runs as a gate here, not in the repo-wide reporting mode
|
|
18
|
+
parameters: { a11y: { test: 'error' } },
|
|
19
|
+
args: {
|
|
20
|
+
src: VIDEO,
|
|
21
|
+
name: 'Rondleiding',
|
|
22
|
+
controls: 'full',
|
|
23
|
+
},
|
|
24
|
+
argTypes: {
|
|
25
|
+
src: { control: 'text' },
|
|
26
|
+
poster: { control: 'text' },
|
|
27
|
+
name: { control: 'text' },
|
|
28
|
+
autoplay: { control: 'boolean' },
|
|
29
|
+
muted: { control: 'boolean' },
|
|
30
|
+
loop: { control: 'boolean' },
|
|
31
|
+
controls: { control: 'inline-radio', options: ['full', 'toggle', 'none'] },
|
|
32
|
+
},
|
|
33
|
+
} satisfies Meta<typeof VideoPlayer>
|
|
34
|
+
|
|
35
|
+
export default meta
|
|
36
|
+
|
|
37
|
+
type Story = StoryObj<typeof meta>
|
|
38
|
+
|
|
39
|
+
/** sRGB of a CSS colour over `backdrop`, via a 1px canvas (handles oklch and alpha). */
|
|
40
|
+
const composite = (color: string, backdrop = 'transparent') => {
|
|
41
|
+
const context = document.createElement('canvas').getContext('2d')!
|
|
42
|
+
context.fillStyle = backdrop
|
|
43
|
+
context.fillRect(0, 0, 1, 1)
|
|
44
|
+
context.fillStyle = color
|
|
45
|
+
context.fillRect(0, 0, 1, 1)
|
|
46
|
+
return [...context.getImageData(0, 0, 1, 1).data.slice(0, 3)]
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const contrast = (a: number[], b: number[]) => {
|
|
50
|
+
const luminance = (rgb: number[]) => {
|
|
51
|
+
const [r, g, b] = rgb.map((channel) => {
|
|
52
|
+
const c = channel / 255
|
|
53
|
+
return c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4
|
|
54
|
+
})
|
|
55
|
+
return 0.2126 * r + 0.7152 * g + 0.0722 * b
|
|
56
|
+
}
|
|
57
|
+
const [hi, lo] = [luminance(a), luminance(b)].sort((x, y) => y - x)
|
|
58
|
+
return (hi + 0.05) / (lo + 0.05)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* The big play button's focus ring lies on the frame: blue-600 against the white band holds
|
|
63
|
+
* 3:1, and on a white or a black frame at least one band does.
|
|
64
|
+
*/
|
|
65
|
+
const expectStartRing = async (button: HTMLElement) => {
|
|
66
|
+
const style = getComputedStyle(button)
|
|
67
|
+
await expect(style.outlineStyle).toBe('solid')
|
|
68
|
+
const ring = style.boxShadow
|
|
69
|
+
.split(/,(?![^(]*\))/)
|
|
70
|
+
.find((shadow) => / 2px$/.test(shadow.trim()))
|
|
71
|
+
?.match(/(?:rgba?|oklch|oklab|color)\([^)]*\)/)?.[0]
|
|
72
|
+
await expect(ring).toBeDefined()
|
|
73
|
+
const [outline, band] = [composite(style.outlineColor), composite(ring!)]
|
|
74
|
+
await expect(contrast(outline, band)).toBeGreaterThanOrEqual(3)
|
|
75
|
+
for (const frame of ['#ffffff', '#000000']) {
|
|
76
|
+
const backdrop = composite(frame)
|
|
77
|
+
await expect(
|
|
78
|
+
Math.max(contrast(outline, backdrop), contrast(band, backdrop)),
|
|
79
|
+
).toBeGreaterThanOrEqual(3)
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Before the first play a big play button stands over the video, the one tab stop; the bar
|
|
85
|
+
* waits behind it. Started, the button fades and the focus goes to the bar's play button.
|
|
86
|
+
*/
|
|
87
|
+
export const Full: Story = {
|
|
88
|
+
play: async ({ canvasElement }) => {
|
|
89
|
+
const canvas = within(canvasElement)
|
|
90
|
+
const root = canvasElement.querySelector<HTMLElement>('[data-video-player]')!
|
|
91
|
+
const video = canvasElement.querySelector('video')!
|
|
92
|
+
|
|
93
|
+
// The begin state: only the big button is reachable
|
|
94
|
+
const start = canvas.getByRole('button', { name: 'Afspelen: Rondleiding' })
|
|
95
|
+
await expect(canvas.queryByRole('group')).toBeNull()
|
|
96
|
+
await expect(start.getBoundingClientRect().width).toBe(64)
|
|
97
|
+
await userEvent.tab()
|
|
98
|
+
await expect(start).toHaveFocus()
|
|
99
|
+
await expectStartRing(start)
|
|
100
|
+
|
|
101
|
+
// Muted here only: a test has no real gesture, and the browser plays sound only after one
|
|
102
|
+
video.muted = true
|
|
103
|
+
|
|
104
|
+
// Enter starts it; the focus lands on the bar's play button, and as keyboard focus the bar
|
|
105
|
+
// stays while the video plays
|
|
106
|
+
await userEvent.keyboard('{Enter}')
|
|
107
|
+
await waitFor(() => expect(video.paused).toBe(false), { timeout: 5000 })
|
|
108
|
+
const barPlay = canvas.getByRole('button', { name: 'Pauzeren' })
|
|
109
|
+
await waitFor(() => expect(barPlay).toHaveFocus())
|
|
110
|
+
await waitFor(() => expect(canvasElement.querySelector('[data-video-start]')).toBeNull())
|
|
111
|
+
const bar = canvas.getByRole('group', { name: 'Videobediening: Rondleiding' })
|
|
112
|
+
// Past the hide delay on the video's own clock, rather than a fixed sleep
|
|
113
|
+
const playedFrom = video.currentTime
|
|
114
|
+
await waitFor(() => expect(video.currentTime - playedFrom).toBeGreaterThan(2.8), {
|
|
115
|
+
timeout: 10000,
|
|
116
|
+
})
|
|
117
|
+
await expect(getComputedStyle(bar).opacity).toBe('1')
|
|
118
|
+
video.pause()
|
|
119
|
+
|
|
120
|
+
// Paused again, the big button stays away
|
|
121
|
+
await expect(canvasElement.querySelector('[data-video-start]')).toBeNull()
|
|
122
|
+
// Loaded: the bar knows the duration
|
|
123
|
+
await waitFor(
|
|
124
|
+
() =>
|
|
125
|
+
expect(canvas.getByRole('slider', { name: 'Tijdlijn' })).toHaveAttribute(
|
|
126
|
+
'aria-valuetext',
|
|
127
|
+
expect.stringMatching(/ van 0:13$/),
|
|
128
|
+
),
|
|
129
|
+
{ timeout: 5000 },
|
|
130
|
+
)
|
|
131
|
+
await expect(getComputedStyle(root).borderTopLeftRadius).toBe('8px')
|
|
132
|
+
},
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** The round play/pause button, with mute beside it on hover; a click on the video does nothing. */
|
|
136
|
+
export const Toggle: Story = {
|
|
137
|
+
args: { controls: 'toggle' },
|
|
138
|
+
play: async ({ canvasElement }) => {
|
|
139
|
+
const canvas = within(canvasElement)
|
|
140
|
+
const video = canvasElement.querySelector('video')!
|
|
141
|
+
|
|
142
|
+
await waitFor(() => expect(video.readyState).toBeGreaterThanOrEqual(1), { timeout: 5000 })
|
|
143
|
+
await expect(canvas.queryByRole('alert')).toBeNull()
|
|
144
|
+
|
|
145
|
+
// Before the first play only the big button is reachable; a click on it starts the video
|
|
146
|
+
await expect(canvas.getAllByRole('button')).toHaveLength(1)
|
|
147
|
+
// Muted here only: a test has no real gesture, and the browser plays sound only after one
|
|
148
|
+
video.muted = true
|
|
149
|
+
await userEvent.click(canvas.getByRole('button', { name: 'Afspelen: Rondleiding' }))
|
|
150
|
+
await waitFor(() => expect(video.paused).toBe(false), { timeout: 5000 })
|
|
151
|
+
await waitFor(() => expect(canvasElement.querySelector('[data-video-start]')).toBeNull())
|
|
152
|
+
|
|
153
|
+
// After a mouse click the focus moves into the corner, but the corner still hides
|
|
154
|
+
const corner = canvasElement.querySelector<HTMLElement>('[data-video-controls]')!
|
|
155
|
+
await expect(corner.contains(document.activeElement)).toBe(true)
|
|
156
|
+
await userEvent.unhover(video)
|
|
157
|
+
await waitFor(() => expect(getComputedStyle(corner).opacity).toBe('0'), { timeout: 4000 })
|
|
158
|
+
video.pause()
|
|
159
|
+
|
|
160
|
+
await waitFor(() =>
|
|
161
|
+
expect(canvas.getAllByRole('button').map((b) => b.getAttribute('aria-label'))).toEqual([
|
|
162
|
+
'Geluid aan: Rondleiding',
|
|
163
|
+
'Afspelen: Rondleiding',
|
|
164
|
+
]),
|
|
165
|
+
)
|
|
166
|
+
},
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* No visible controls, and no click handler: a click on the video reaches the parent (the
|
|
171
|
+
* media library selects with it). Played and paused from outside, as on hover there.
|
|
172
|
+
*/
|
|
173
|
+
export const None: Story = {
|
|
174
|
+
args: { controls: 'none', autoplay: true, muted: true, loop: true },
|
|
175
|
+
play: async ({ canvasElement }) => {
|
|
176
|
+
const video = canvasElement.querySelector('video')!
|
|
177
|
+
await expect(within(canvasElement).queryByRole('button')).toBeNull()
|
|
178
|
+
await waitFor(() => expect(video.paused).toBe(false), { timeout: 5000 })
|
|
179
|
+
await expect(video.muted).toBe(true)
|
|
180
|
+
video.pause()
|
|
181
|
+
},
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* With a poster nothing is preloaded: the poster stands, with the big play button over it,
|
|
186
|
+
* until the video plays.
|
|
187
|
+
*/
|
|
188
|
+
export const Poster: Story = {
|
|
189
|
+
args: { poster: POSTER },
|
|
190
|
+
play: async ({ canvasElement }) => {
|
|
191
|
+
const video = canvasElement.querySelector('video')!
|
|
192
|
+
await expect(
|
|
193
|
+
within(canvasElement).getByRole('button', { name: 'Afspelen: Rondleiding' }),
|
|
194
|
+
).toBeVisible()
|
|
195
|
+
await expect(video).toHaveAttribute('preload', 'none')
|
|
196
|
+
await expect(video).toHaveAttribute('poster', POSTER)
|
|
197
|
+
},
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/** A source that does not load: a message, never the URL. */
|
|
201
|
+
export const Error: Story = {
|
|
202
|
+
args: { src: '/missing-video.webm' },
|
|
203
|
+
play: async ({ canvasElement }) => {
|
|
204
|
+
const alert = await within(canvasElement).findByRole('alert', {}, { timeout: 5000 })
|
|
205
|
+
await expect(alert).toHaveTextContent('De video kan niet worden geladen')
|
|
206
|
+
await expect(alert.innerHTML).not.toContain('missing-video')
|
|
207
|
+
await expect(within(canvasElement).queryByRole('group')).toBeNull()
|
|
208
|
+
},
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/** The radius is a default: a `rounded-*` class from the consumer wins. */
|
|
212
|
+
export const CustomRadius: Story = {
|
|
213
|
+
render: (args) => ({
|
|
214
|
+
components: { VideoPlayer },
|
|
215
|
+
setup: () => ({ args }),
|
|
216
|
+
template: '<VideoPlayer v-bind="args" class="rounded-3xl" />',
|
|
217
|
+
}),
|
|
218
|
+
play: async ({ canvasElement }) => {
|
|
219
|
+
const root = canvasElement.querySelector<HTMLElement>('[data-video-player]')!
|
|
220
|
+
await expect(getComputedStyle(root).borderTopLeftRadius).toBe('24px')
|
|
221
|
+
},
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/** Autoplay starts without sound; the bar has the sound button to turn it on. */
|
|
225
|
+
export const Autoplay: Story = {
|
|
226
|
+
args: { autoplay: true, muted: true, loop: true },
|
|
227
|
+
play: async ({ canvasElement }) => {
|
|
228
|
+
const canvas = within(canvasElement)
|
|
229
|
+
const video = canvasElement.querySelector('video')!
|
|
230
|
+
|
|
231
|
+
await waitFor(() => expect(video.paused).toBe(false), { timeout: 5000 })
|
|
232
|
+
await expect(video.muted).toBe(true)
|
|
233
|
+
|
|
234
|
+
// Untouched while it plays: the bar fades and the cursor goes with it
|
|
235
|
+
const root = canvasElement.querySelector<HTMLElement>('[data-video-player]')!
|
|
236
|
+
await waitFor(() => expect(getComputedStyle(root).cursor).toBe('none'), { timeout: 4000 })
|
|
237
|
+
|
|
238
|
+
// A hidden bar lets clicks through; moving the mouse brings it back first
|
|
239
|
+
await userEvent.hover(video)
|
|
240
|
+
await expect(getComputedStyle(root).cursor).not.toBe('none')
|
|
241
|
+
await userEvent.click(canvas.getByRole('button', { name: 'Geluid aan' }))
|
|
242
|
+
await expect(video.muted).toBe(false)
|
|
243
|
+
video.pause()
|
|
244
|
+
},
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* A 9:16 video in a portrait frame of 360 by 640, filled the way a consumer does it (as the
|
|
249
|
+
* media library): `size-full` on the player and `object-cover` on its video.
|
|
250
|
+
*/
|
|
251
|
+
const verticalRender =
|
|
252
|
+
(frameClass = 'w-90'): Story['render'] =>
|
|
253
|
+
(args) => ({
|
|
254
|
+
components: { VideoPlayer },
|
|
255
|
+
setup: () => ({ args, frameClass }),
|
|
256
|
+
template: `
|
|
257
|
+
<div data-frame class="aspect-9/16" :class="frameClass">
|
|
258
|
+
<VideoPlayer v-bind="args" class="size-full [&>video]:h-full [&>video]:object-cover" />
|
|
259
|
+
</div>
|
|
260
|
+
`,
|
|
261
|
+
})
|
|
262
|
+
|
|
263
|
+
/** Portrait with the full bar; the volume opens in a popover above the sound button. */
|
|
264
|
+
export const Vertical: Story = {
|
|
265
|
+
args: { src: VERTICAL_VIDEO, poster: VERTICAL_POSTER, muted: true },
|
|
266
|
+
render: verticalRender(),
|
|
267
|
+
play: async ({ canvasElement }) => {
|
|
268
|
+
const canvas = within(canvasElement)
|
|
269
|
+
const frame = canvasElement.querySelector<HTMLElement>('[data-frame]')!.getBoundingClientRect()
|
|
270
|
+
const video = canvasElement.querySelector('video')!
|
|
271
|
+
|
|
272
|
+
// Plays in portrait (also in the Chromium of CI), started with the big button
|
|
273
|
+
await userEvent.click(canvas.getByRole('button', { name: 'Afspelen: Rondleiding' }))
|
|
274
|
+
await waitFor(() => expect(video.currentTime).toBeGreaterThan(0.3), { timeout: 5000 })
|
|
275
|
+
await expect(video.videoHeight / video.videoWidth).toBeCloseTo(16 / 9, 2)
|
|
276
|
+
video.pause()
|
|
277
|
+
|
|
278
|
+
// Fills the frame, cropped rather than stretched
|
|
279
|
+
const box = video.getBoundingClientRect()
|
|
280
|
+
await expect([box.width, box.height]).toEqual([frame.width, frame.height])
|
|
281
|
+
await expect(getComputedStyle(video).objectFit).toBe('cover')
|
|
282
|
+
|
|
283
|
+
// The bar fits the width; the volume popover fits inside the frame, clear of the corners
|
|
284
|
+
const capsule = canvas.getByRole('group').getBoundingClientRect()
|
|
285
|
+
await expect(capsule.left).toBeGreaterThanOrEqual(frame.left)
|
|
286
|
+
await expect(capsule.right).toBeLessThanOrEqual(frame.right)
|
|
287
|
+
canvas.getByRole('button', { name: /^Geluid/ }).focus()
|
|
288
|
+
const popover = canvasElement.querySelector<HTMLElement>('[data-volume-popover]')!
|
|
289
|
+
await waitFor(() => expect(getComputedStyle(popover).opacity).toBe('1'))
|
|
290
|
+
const volumeBox = popover.firstElementChild!.getBoundingClientRect()
|
|
291
|
+
await expect(volumeBox.top).toBeGreaterThanOrEqual(frame.top + 8)
|
|
292
|
+
await expect(volumeBox.right).toBeLessThanOrEqual(frame.right)
|
|
293
|
+
await expect(canvas.getByRole('slider', { name: 'Tijdlijn' })).toBeVisible()
|
|
294
|
+
},
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* A narrow portrait player (216px) with the corner toggle. Below 240px wide the big play button
|
|
299
|
+
* is 48px instead of 64, centred over the poster.
|
|
300
|
+
*/
|
|
301
|
+
export const VerticalToggle: Story = {
|
|
302
|
+
args: { src: VERTICAL_VIDEO, poster: VERTICAL_POSTER, controls: 'toggle' },
|
|
303
|
+
render: verticalRender('w-54'),
|
|
304
|
+
play: async ({ canvasElement }) => {
|
|
305
|
+
const canvas = within(canvasElement)
|
|
306
|
+
const frame = canvasElement.querySelector<HTMLElement>('[data-frame]')!.getBoundingClientRect()
|
|
307
|
+
const start = canvas.getByRole('button', { name: 'Afspelen: Rondleiding' })
|
|
308
|
+
const box = start.getBoundingClientRect()
|
|
309
|
+
await expect(box.width).toBe(48)
|
|
310
|
+
await expect(
|
|
311
|
+
Math.abs(box.left + box.width / 2 - (frame.left + frame.width / 2)),
|
|
312
|
+
).toBeLessThanOrEqual(0.5)
|
|
313
|
+
|
|
314
|
+
// Muted here only: a test has no real gesture, and the browser plays sound only after one
|
|
315
|
+
canvasElement.querySelector('video')!.muted = true
|
|
316
|
+
await userEvent.click(start)
|
|
317
|
+
await waitFor(() => expect(canvasElement.querySelector('[data-video-start]')).toBeNull(), {
|
|
318
|
+
timeout: 5000,
|
|
319
|
+
})
|
|
320
|
+
canvasElement.querySelector('video')!.pause()
|
|
321
|
+
const corner = (
|
|
322
|
+
await canvas.findByRole('button', { name: 'Afspelen: Rondleiding' })
|
|
323
|
+
).getBoundingClientRect()
|
|
324
|
+
await expect(corner.right).toBeLessThanOrEqual(frame.right)
|
|
325
|
+
await expect(corner.bottom).toBeLessThanOrEqual(frame.bottom)
|
|
326
|
+
},
|
|
327
|
+
}
|