@aglyn/plugins-mui 1.0.0-beta.143
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/LICENSE +201 -0
- package/README.md +22 -0
- package/package.json +47 -0
- package/src/index.d.ts +21 -0
- package/src/index.js +22 -0
- package/src/index.js.map +1 -0
- package/src/lib/components/accordion.d.ts +96 -0
- package/src/lib/components/accordion.js +392 -0
- package/src/lib/components/accordion.js.map +1 -0
- package/src/lib/components/app-bar.d.ts +32 -0
- package/src/lib/components/app-bar.js +86 -0
- package/src/lib/components/app-bar.js.map +1 -0
- package/src/lib/components/blocks.d.ts +46 -0
- package/src/lib/components/blocks.js +1007 -0
- package/src/lib/components/blocks.js.map +1 -0
- package/src/lib/components/box.d.ts +47 -0
- package/src/lib/components/box.js +125 -0
- package/src/lib/components/box.js.map +1 -0
- package/src/lib/components/breadcrumbs.d.ts +44 -0
- package/src/lib/components/breadcrumbs.js +172 -0
- package/src/lib/components/breadcrumbs.js.map +1 -0
- package/src/lib/components/button.d.ts +89 -0
- package/src/lib/components/button.js +272 -0
- package/src/lib/components/button.js.map +1 -0
- package/src/lib/components/card.d.ts +61 -0
- package/src/lib/components/card.js +336 -0
- package/src/lib/components/card.js.map +1 -0
- package/src/lib/components/collection.d.ts +514 -0
- package/src/lib/components/collection.js +2999 -0
- package/src/lib/components/collection.js.map +1 -0
- package/src/lib/components/container.d.ts +42 -0
- package/src/lib/components/container.js +169 -0
- package/src/lib/components/container.js.map +1 -0
- package/src/lib/components/custom-html.d.ts +78 -0
- package/src/lib/components/custom-html.js +219 -0
- package/src/lib/components/custom-html.js.map +1 -0
- package/src/lib/components/data-table.d.ts +57 -0
- package/src/lib/components/data-table.js +216 -0
- package/src/lib/components/data-table.js.map +1 -0
- package/src/lib/components/document-root.d.ts +77 -0
- package/src/lib/components/document-root.js +118 -0
- package/src/lib/components/document-root.js.map +1 -0
- package/src/lib/components/drawer.d.ts +93 -0
- package/src/lib/components/drawer.js +566 -0
- package/src/lib/components/drawer.js.map +1 -0
- package/src/lib/components/function-controls.d.ts +66 -0
- package/src/lib/components/function-controls.js +207 -0
- package/src/lib/components/function-controls.js.map +1 -0
- package/src/lib/components/function-scope.d.ts +110 -0
- package/src/lib/components/function-scope.js +478 -0
- package/src/lib/components/function-scope.js.map +1 -0
- package/src/lib/components/function-widget.d.ts +75 -0
- package/src/lib/components/function-widget.js +307 -0
- package/src/lib/components/function-widget.js.map +1 -0
- package/src/lib/components/grid.d.ts +91 -0
- package/src/lib/components/grid.js +322 -0
- package/src/lib/components/grid.js.map +1 -0
- package/src/lib/components/icon.d.ts +54 -0
- package/src/lib/components/icon.js +163 -0
- package/src/lib/components/icon.js.map +1 -0
- package/src/lib/components/image-list.d.ts +58 -0
- package/src/lib/components/image-list.js +336 -0
- package/src/lib/components/image-list.js.map +1 -0
- package/src/lib/components/image.d.ts +154 -0
- package/src/lib/components/image.js +451 -0
- package/src/lib/components/image.js.map +1 -0
- package/src/lib/components/inline-text.d.ts +74 -0
- package/src/lib/components/inline-text.js +407 -0
- package/src/lib/components/inline-text.js.map +1 -0
- package/src/lib/components/language-switcher.d.ts +35 -0
- package/src/lib/components/language-switcher.js +179 -0
- package/src/lib/components/language-switcher.js.map +1 -0
- package/src/lib/components/layout-slot.d.ts +68 -0
- package/src/lib/components/layout-slot.js +192 -0
- package/src/lib/components/layout-slot.js.map +1 -0
- package/src/lib/components/link-box-accessible-name.d.ts +79 -0
- package/src/lib/components/link-box-accessible-name.js +158 -0
- package/src/lib/components/link-box-accessible-name.js.map +1 -0
- package/src/lib/components/link-box.d.ts +66 -0
- package/src/lib/components/link-box.js +212 -0
- package/src/lib/components/link-box.js.map +1 -0
- package/src/lib/components/list-item-text.d.ts +22 -0
- package/src/lib/components/list-item-text.js +86 -0
- package/src/lib/components/list-item-text.js.map +1 -0
- package/src/lib/components/list-item.d.ts +32 -0
- package/src/lib/components/list-item.js +127 -0
- package/src/lib/components/list-item.js.map +1 -0
- package/src/lib/components/list.d.ts +34 -0
- package/src/lib/components/list.js +108 -0
- package/src/lib/components/list.js.map +1 -0
- package/src/lib/components/markdown.d.ts +129 -0
- package/src/lib/components/markdown.js +853 -0
- package/src/lib/components/markdown.js.map +1 -0
- package/src/lib/components/nav-menu.d.ts +68 -0
- package/src/lib/components/nav-menu.js +683 -0
- package/src/lib/components/nav-menu.js.map +1 -0
- package/src/lib/components/pagination.d.ts +59 -0
- package/src/lib/components/pagination.js +243 -0
- package/src/lib/components/pagination.js.map +1 -0
- package/src/lib/components/paper.d.ts +47 -0
- package/src/lib/components/paper.js +202 -0
- package/src/lib/components/paper.js.map +1 -0
- package/src/lib/components/plugin-frame.d.ts +64 -0
- package/src/lib/components/plugin-frame.js +335 -0
- package/src/lib/components/plugin-frame.js.map +1 -0
- package/src/lib/components/plugin.d.ts +95 -0
- package/src/lib/components/plugin.js +304 -0
- package/src/lib/components/plugin.js.map +1 -0
- package/src/lib/components/product.d.ts +40 -0
- package/src/lib/components/product.js +287 -0
- package/src/lib/components/product.js.map +1 -0
- package/src/lib/components/reusable-instance.d.ts +57 -0
- package/src/lib/components/reusable-instance.js +126 -0
- package/src/lib/components/reusable-instance.js.map +1 -0
- package/src/lib/components/screen-link.d.ts +85 -0
- package/src/lib/components/screen-link.js +324 -0
- package/src/lib/components/screen-link.js.map +1 -0
- package/src/lib/components/search-box.d.ts +41 -0
- package/src/lib/components/search-box.js +125 -0
- package/src/lib/components/search-box.js.map +1 -0
- package/src/lib/components/section.d.ts +56 -0
- package/src/lib/components/section.js +132 -0
- package/src/lib/components/section.js.map +1 -0
- package/src/lib/components/stack.d.ts +55 -0
- package/src/lib/components/stack.js +309 -0
- package/src/lib/components/stack.js.map +1 -0
- package/src/lib/components/tabs.d.ts +288 -0
- package/src/lib/components/tabs.js +788 -0
- package/src/lib/components/tabs.js.map +1 -0
- package/src/lib/components/theme-mode-switcher.d.ts +38 -0
- package/src/lib/components/theme-mode-switcher.js +168 -0
- package/src/lib/components/theme-mode-switcher.js.map +1 -0
- package/src/lib/components/toolbar.d.ts +37 -0
- package/src/lib/components/toolbar.js +117 -0
- package/src/lib/components/toolbar.js.map +1 -0
- package/src/lib/components/typography.d.ts +33 -0
- package/src/lib/components/typography.js +433 -0
- package/src/lib/components/typography.js.map +1 -0
- package/src/lib/components/video-lightbox-controls.d.ts +131 -0
- package/src/lib/components/video-lightbox-controls.js +536 -0
- package/src/lib/components/video-lightbox-controls.js.map +1 -0
- package/src/lib/components/video-lightbox.d.ts +60 -0
- package/src/lib/components/video-lightbox.js +247 -0
- package/src/lib/components/video-lightbox.js.map +1 -0
- package/src/lib/components/video-playback-beacon.d.ts +80 -0
- package/src/lib/components/video-playback-beacon.js +128 -0
- package/src/lib/components/video-playback-beacon.js.map +1 -0
- package/src/lib/components/video-player-frame.d.ts +58 -0
- package/src/lib/components/video-player-frame.js +51 -0
- package/src/lib/components/video-player-frame.js.map +1 -0
- package/src/lib/components/video.d.ts +220 -0
- package/src/lib/components/video.js +874 -0
- package/src/lib/components/video.js.map +1 -0
- package/src/lib/constants/bundle-common.d.ts +17 -0
- package/src/lib/constants/bundle-common.js +18 -0
- package/src/lib/constants/bundle-common.js.map +1 -0
- package/src/lib/constants/field-presets.d.ts +93 -0
- package/src/lib/constants/field-presets.js +248 -0
- package/src/lib/constants/field-presets.js.map +1 -0
- package/src/lib/plugin.d.ts +80 -0
- package/src/lib/plugin.js +599 -0
- package/src/lib/plugin.js.map +1 -0
- package/src/lib/utils/author-html-label.d.ts +73 -0
- package/src/lib/utils/author-html-label.js +89 -0
- package/src/lib/utils/author-html-label.js.map +1 -0
- package/src/lib/utils/drop-cleared-props.d.ts +18 -0
- package/src/lib/utils/drop-cleared-props.js +74 -0
- package/src/lib/utils/drop-cleared-props.js.map +1 -0
- package/src/lib/utils/element-picker.d.ts +99 -0
- package/src/lib/utils/element-picker.js +140 -0
- package/src/lib/utils/element-picker.js.map +1 -0
- package/src/lib/utils/generate-preset-id.d.ts +19 -0
- package/src/lib/utils/generate-preset-id.js +25 -0
- package/src/lib/utils/generate-preset-id.js.map +1 -0
- package/src/lib/utils/link-target-props.d.ts +53 -0
- package/src/lib/utils/link-target-props.js +53 -0
- package/src/lib/utils/link-target-props.js.map +1 -0
|
@@ -0,0 +1,874 @@
|
|
|
1
|
+
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
2
|
+
import { _ as _object_without_properties_loose } from "@swc/helpers/_/_object_without_properties_loose";
|
|
3
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
4
|
+
/**
|
|
5
|
+
* @license
|
|
6
|
+
* Copyright 2026 Aglyn LLC
|
|
7
|
+
*
|
|
8
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
9
|
+
* you may not use this file except in compliance with the License.
|
|
10
|
+
* You may obtain a copy of the License at
|
|
11
|
+
*
|
|
12
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
13
|
+
*
|
|
14
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
15
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
16
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
17
|
+
* See the License for the specific language governing permissions and
|
|
18
|
+
* limitations under the License.
|
|
19
|
+
*/ import * as Aglyn from "@aglyn/aglyn";
|
|
20
|
+
import { readStoredVisitorConsent, VISITOR_CONSENT_CHANGED_EVENT } from "@aglyn/aglyn/app-utils/visitor-consent";
|
|
21
|
+
import { wistiaMediaId, wistiaPlayerSrc } from "@aglyn/aglyn/app-utils/wistia-embed";
|
|
22
|
+
import { mdiPlay, mdiVideo } from "@aglyn/shared-data-mdi";
|
|
23
|
+
import { MdiIcon } from "@aglyn/shared-ui-jsx";
|
|
24
|
+
import Box from "@mui/material/Box";
|
|
25
|
+
import useEventCallback from "@mui/utils/useEventCallback";
|
|
26
|
+
import useForkRef from "@mui/utils/useForkRef";
|
|
27
|
+
import { Suspense, forwardRef, lazy, useCallback, useEffect, useState } from "react";
|
|
28
|
+
import { BUNDLE_ID } from "../constants/bundle-common.js";
|
|
29
|
+
import { generatePresetId } from "../utils/generate-preset-id.js";
|
|
30
|
+
import { useVideoPlaybackBeacon } from "./video-playback-beacon.js";
|
|
31
|
+
import { VideoPlayerFrame } from "./video-player-frame.js";
|
|
32
|
+
/**
|
|
33
|
+
* The lightbox, behind a lazy boundary rather than a plain import (AGL-2744).
|
|
34
|
+
*
|
|
35
|
+
* `video-lightbox.tsx` is the only module in the tenant's component graph that
|
|
36
|
+
* names `@mui/material/Dialog`, and `plugin.ts` imports every element in this
|
|
37
|
+
* bundle eagerly — so naming it here would put the whole MUI Dialog stack on
|
|
38
|
+
* every published page of every customer site, which is precisely what
|
|
39
|
+
* AGL-1290 took OFF those pages by withholding `dialog-confirm` and
|
|
40
|
+
* `navigation-drawer` from the shared JSX barrel.
|
|
41
|
+
*
|
|
42
|
+
* Behind `lazy()` it is a chunk nothing on a first paint requests: not on a
|
|
43
|
+
* page without a video, not on a page with one, only for a visitor who has
|
|
44
|
+
* asked to watch. Same shape as `product-detail.tsx`'s Payment Element, for
|
|
45
|
+
* the same reason.
|
|
46
|
+
*/ const VideoLightbox = /*#__PURE__*/ lazy(()=>import("./video-lightbox.js").then((module)=>({
|
|
47
|
+
default: module.VideoLightbox
|
|
48
|
+
})));
|
|
49
|
+
// Component ids are persisted in screen documents; never rename.
|
|
50
|
+
export const ID = 'video';
|
|
51
|
+
/**
|
|
52
|
+
* The width the poster is requested at.
|
|
53
|
+
*
|
|
54
|
+
* `<video poster>` takes ONE url and no `srcset`, so unlike `image.tsx` there
|
|
55
|
+
* is no candidate list to hand the browser and no `sizes` to resolve against —
|
|
56
|
+
* a single width has to serve every viewport. Asking for it is what turns a
|
|
57
|
+
* 335 KB PNG poster into the 4 KB WebP the DAM already generated.
|
|
58
|
+
*
|
|
59
|
+
* Read from `@aglyn/aglyn` rather than restated: the page's `thumbnailUrl`
|
|
60
|
+
* asks for the same width, and a crawler that fetches a different image from
|
|
61
|
+
* the one a visitor sees is describing a different picture.
|
|
62
|
+
*/ const POSTER_REQUEST_WIDTH = Aglyn.MEDIA_CDN_POSTER_WIDTH;
|
|
63
|
+
/**
|
|
64
|
+
* What `preload` an unset field resolves to, given the rest of the node.
|
|
65
|
+
*
|
|
66
|
+
* Exported for the spec, because the whole point of this element is what it
|
|
67
|
+
* does NOT download and a rule that is only asserted through rendered markup
|
|
68
|
+
* is a rule three refactors from being an accident.
|
|
69
|
+
*
|
|
70
|
+
* * **Autoplay wins first.** A video that starts on its own needs its bytes
|
|
71
|
+
* by definition; `none` there is a hint every browser overrides anyway, and
|
|
72
|
+
* stating it would only mislead the next reader.
|
|
73
|
+
* * **A poster buys `none`.** This is the saving the element exists for — the
|
|
74
|
+
* poster carries the first paint at image cost, and not one byte of video
|
|
75
|
+
* is fetched until a visitor presses play.
|
|
76
|
+
* * **No poster keeps `metadata`.** With neither a poster nor metadata a
|
|
77
|
+
* `<video>` paints a black rectangle, so deferring an unpostered video
|
|
78
|
+
* trades a real transfer for a real regression. `metadata` is also what
|
|
79
|
+
* every document authored before this rendered, which is what makes the
|
|
80
|
+
* change safe for sites already published.
|
|
81
|
+
*/ export function resolveVideoPreload(options) {
|
|
82
|
+
const { preload, poster, autoPlay } = options != null ? options : {};
|
|
83
|
+
if (preload === 'none' || preload === 'metadata' || preload === 'auto') return preload;
|
|
84
|
+
if (autoPlay) return 'auto';
|
|
85
|
+
return poster ? 'none' : 'metadata';
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Video element (AGL-162, AGL-2741): plays a media-library upload, any URL or
|
|
89
|
+
* a Wistia video (AGL-2826), poster first.
|
|
90
|
+
*
|
|
91
|
+
* The default is now to download NOTHING until a visitor asks. A poster is an
|
|
92
|
+
* image — a few tens of kilobytes through the DAM's WebP variants, edge-cached
|
|
93
|
+
* — while the video behind it is not edge-cacheable at all and is measured in
|
|
94
|
+
* megabytes. `preload` decides which of those a page pays for on first paint,
|
|
95
|
+
* and {@link resolveVideoPreload} explains what an unset field resolves to.
|
|
96
|
+
*
|
|
97
|
+
* An empty src shows a labeled placeholder so the element stays selectable in
|
|
98
|
+
* the editor.
|
|
99
|
+
*/ const Video = /*#__PURE__*/ forwardRef((props, ref)=>{
|
|
100
|
+
const { src: storedSrc, poster: storedPoster, posterFromSource, title, // Structured-data-only fields. Every one of them MUST be destructured:
|
|
101
|
+
// what is left in `rest` is spread onto the `<video>`, where a
|
|
102
|
+
// `description` or an `uploadDate` becomes an invalid DOM attribute in
|
|
103
|
+
// the published HTML.
|
|
104
|
+
description: _description, uploadDate: _uploadDate, durationSeconds: _durationSeconds, lightbox, loadPlayer, captionsSrc, captionsLabel, captionsLang, autoPlay, loop, muted, controls, playsInline, preload, width, height, intrinsicWidth, intrinsicHeight, radius, children: _children, // Pulled out of the spread: the literals below are composed AFTER
|
|
105
|
+
// `{...rest}`, so leaving it there REPLACES every style the author set
|
|
106
|
+
// from the Styles panel (AGL-1240, the same trap image.tsx documents).
|
|
107
|
+
sx: nodeSxProp } = props, rest = _object_without_properties_loose(props, [
|
|
108
|
+
"src",
|
|
109
|
+
"poster",
|
|
110
|
+
"posterFromSource",
|
|
111
|
+
"title",
|
|
112
|
+
"description",
|
|
113
|
+
"uploadDate",
|
|
114
|
+
"durationSeconds",
|
|
115
|
+
"lightbox",
|
|
116
|
+
"loadPlayer",
|
|
117
|
+
"captionsSrc",
|
|
118
|
+
"captionsLabel",
|
|
119
|
+
"captionsLang",
|
|
120
|
+
"autoPlay",
|
|
121
|
+
"loop",
|
|
122
|
+
"muted",
|
|
123
|
+
"controls",
|
|
124
|
+
"playsInline",
|
|
125
|
+
"preload",
|
|
126
|
+
"width",
|
|
127
|
+
"height",
|
|
128
|
+
"intrinsicWidth",
|
|
129
|
+
"intrinsicHeight",
|
|
130
|
+
"radius",
|
|
131
|
+
"children",
|
|
132
|
+
"sx"
|
|
133
|
+
]);
|
|
134
|
+
const nodeSx = Array.isArray(nodeSxProp) ? nodeSxProp : nodeSxProp ? [
|
|
135
|
+
nodeSxProp
|
|
136
|
+
] : [];
|
|
137
|
+
// Both fields are media-picker targets, so both can hold a media
|
|
138
|
+
// reference (AGL-1215) as well as any of the URL forms that predate it.
|
|
139
|
+
// Resolving here is not optional: the picker hands back one string for
|
|
140
|
+
// whichever attribute asked for it, so every field it can write to has to
|
|
141
|
+
// understand the same value.
|
|
142
|
+
const { hostId } = Aglyn.useSite();
|
|
143
|
+
/**
|
|
144
|
+
* The delivery copy, not the master (AGL-2753).
|
|
145
|
+
*
|
|
146
|
+
* `videoDeliverySrc` is `resolveMediaSrc` plus `?r=auto`, which asks the CDN
|
|
147
|
+
* for the best encoding it holds instead of naming one. The element cannot
|
|
148
|
+
* name one: renditions are produced out of band by
|
|
149
|
+
* `tools/scripts/generate-video-renditions.mjs`, minutes or days after the
|
|
150
|
+
* upload, so at pick time an asset usually has none and a copied list would
|
|
151
|
+
* be empty for exactly the videos it exists to serve. Asking rather than
|
|
152
|
+
* naming is what lets an encoding made tomorrow reach a video placed today,
|
|
153
|
+
* with no re-pick and nothing rewritten in a published document.
|
|
154
|
+
*
|
|
155
|
+
* Nothing here is conditional on the asset having renditions, because the
|
|
156
|
+
* URL degrades to the master by construction — which is the file this
|
|
157
|
+
* served before, so a page cannot get worse by adopting it.
|
|
158
|
+
*/ const src = Aglyn.videoDeliverySrc(storedSrc, {
|
|
159
|
+
hostId
|
|
160
|
+
});
|
|
161
|
+
// Two candidates, one rule, shared with the page's `thumbnailUrl`: the
|
|
162
|
+
// author's own poster wins, and the DAM's generated frame is used only when
|
|
163
|
+
// the node records that one exists (AGL-2749).
|
|
164
|
+
const posterFor = (width)=>Aglyn.videoPosterSrc({
|
|
165
|
+
hostId,
|
|
166
|
+
poster: storedPoster,
|
|
167
|
+
src: storedSrc,
|
|
168
|
+
generated: posterFromSource,
|
|
169
|
+
width
|
|
170
|
+
});
|
|
171
|
+
/** Full size — what an `<img>` wants beside a `srcSet` of widths. */ const posterBase = posterFor();
|
|
172
|
+
/** One width — what `<video poster>` and a `thumbnailUrl` have to take. */ const poster = posterFor(POSTER_REQUEST_WIDTH);
|
|
173
|
+
const captions = Aglyn.resolveMediaSrc(captionsSrc, {
|
|
174
|
+
hostId
|
|
175
|
+
});
|
|
176
|
+
/**
|
|
177
|
+
* The lightbox's two pieces of state, and they are deliberately two.
|
|
178
|
+
*
|
|
179
|
+
* `open` is what the dialog reads. `armed` is whether the chunk has been
|
|
180
|
+
* ASKED for, which happens on the first hover or focus rather than on the
|
|
181
|
+
* click — so by the time a visitor's pointer has travelled from the poster
|
|
182
|
+
* to the play badge, the module is usually already there and the dialog
|
|
183
|
+
* opens on the same frame as the click. A visitor who never goes near the
|
|
184
|
+
* poster never arms it and never fetches it, which is the whole bargain.
|
|
185
|
+
*
|
|
186
|
+
* `armed` never goes back to false. Re-mounting `lazy()` after a close
|
|
187
|
+
* would not re-download anything (the module is resolved), and leaving it
|
|
188
|
+
* mounted is what lets a second open be instant.
|
|
189
|
+
*/ const [open, setOpen] = useState(false);
|
|
190
|
+
const [armed, setArmed] = useState(false);
|
|
191
|
+
const arm = useCallback(()=>setArmed(true), []);
|
|
192
|
+
const closeLightbox = useCallback(()=>setOpen(false), []);
|
|
193
|
+
const openLightbox = useCallback(()=>{
|
|
194
|
+
setArmed(true);
|
|
195
|
+
setOpen(true);
|
|
196
|
+
}, []);
|
|
197
|
+
/**
|
|
198
|
+
* The besigner canvas is inert (AGL-830): opening a focus-trapping dialog
|
|
199
|
+
* inside the editor would steal the author's keyboard mid-edit, and the
|
|
200
|
+
* click on a poster there means "select this node", not "watch this".
|
|
201
|
+
* The trigger still RENDERS as a button, because what an author sees on the
|
|
202
|
+
* canvas has to be what a visitor gets.
|
|
203
|
+
*/ const { editorInert, suppressNavigation } = Aglyn.useScreenLink(undefined);
|
|
204
|
+
/**
|
|
205
|
+
* What a play is counted against (AGL-2781): the site, the asset the STORED
|
|
206
|
+
* value names, and whether this surface is an editor. Handed to the
|
|
207
|
+
* lightbox as data rather than as handlers, because a play there belongs to
|
|
208
|
+
* one open of the dialog and only the dialog knows when that begins.
|
|
209
|
+
*/ const playback = {
|
|
210
|
+
hostId,
|
|
211
|
+
src: storedSrc,
|
|
212
|
+
suppressed: suppressNavigation
|
|
213
|
+
};
|
|
214
|
+
const playbackHandlers = useVideoPlaybackBeacon(playback);
|
|
215
|
+
/**
|
|
216
|
+
* A Wistia video (AGL-2826), and the player address a press produced.
|
|
217
|
+
*
|
|
218
|
+
* Unless the page loads the player (see `pageLoadSrc` below), nothing of
|
|
219
|
+
* Wistia's renders before that press. The poster is a button, as it is for
|
|
220
|
+
* the lightbox, so a visitor who only looks at the page makes no request to
|
|
221
|
+
* Wistia and is handed no storage by it. `playerSrc` stays empty until the
|
|
222
|
+
* press, and setting it is what brings the frame in.
|
|
223
|
+
*/ const wistiaId = wistiaMediaId(storedSrc);
|
|
224
|
+
const [playerSrc, setPlayerSrc] = useState();
|
|
225
|
+
/**
|
|
226
|
+
* The press. Consent is read here and not during render: the record lives
|
|
227
|
+
* in this browser's storage, which a cached page cannot vary on, and a
|
|
228
|
+
* visitor may answer the banner between the first paint and the click.
|
|
229
|
+
*
|
|
230
|
+
* Wistia records the viewing only when this visitor's analytics consent is
|
|
231
|
+
* on record. A visitor without that record — still deciding, refused,
|
|
232
|
+
* opted out by GPC, or visiting a site that runs no consent banner — gets
|
|
233
|
+
* the player with `doNotTrack`.
|
|
234
|
+
*/ const playWistia = useCallback(()=>{
|
|
235
|
+
var _readStoredVisitorConsent;
|
|
236
|
+
setPlayerSrc(wistiaPlayerSrc(storedSrc, {
|
|
237
|
+
doNotTrack: !((_readStoredVisitorConsent = readStoredVisitorConsent(hostId)) == null ? void 0 : _readStoredVisitorConsent.analytics),
|
|
238
|
+
muted: Boolean(muted),
|
|
239
|
+
loop: Boolean(loop)
|
|
240
|
+
}));
|
|
241
|
+
}, [
|
|
242
|
+
storedSrc,
|
|
243
|
+
hostId,
|
|
244
|
+
muted,
|
|
245
|
+
loop
|
|
246
|
+
]);
|
|
247
|
+
/**
|
|
248
|
+
* The player address "Load the player with the page" produced (AGL-2962):
|
|
249
|
+
* Wistia's player without autoplay, in place of the poster before anyone has
|
|
250
|
+
* pressed anything, so a search engine rendering the page finds the player
|
|
251
|
+
* in it.
|
|
252
|
+
*
|
|
253
|
+
* Only for a visitor whose stored consent grants analytics, which is why the
|
|
254
|
+
* address never carries `doNotTrack`. The unpressed frame is not free of
|
|
255
|
+
* storage: measured on 2026-09-14 in headless Chrome, it set no cookie but
|
|
256
|
+
* wrote two keys to its own `localStorage`, a random resume key and a record
|
|
257
|
+
* of its load times, with `doNotTrack` on as well as off. A visitor in a
|
|
258
|
+
* prior-consent region has to say yes before that. Everyone else keeps the
|
|
259
|
+
* poster, and a press on it loads the player as it does with the switch
|
|
260
|
+
* off, `doNotTrack` included.
|
|
261
|
+
*
|
|
262
|
+
* Set from an effect and never during render, for the reason a press reads
|
|
263
|
+
* consent at the click: the record is in this browser's storage, which the
|
|
264
|
+
* server HTML and the first paint cannot vary on, so both are the poster for
|
|
265
|
+
* every visitor. The effect reads the record on mount and again on every
|
|
266
|
+
* consent change, because the decision can land after the element mounts:
|
|
267
|
+
* the implied default is recorded when the region lookup answers, and a
|
|
268
|
+
* banner is answered whenever the visitor gets to it. A withdrawal before
|
|
269
|
+
* any press on the element puts the poster back, even over a film started
|
|
270
|
+
* from the play button inside Wistia's frame, which the element cannot see.
|
|
271
|
+
*
|
|
272
|
+
* Never on the besigner canvas, where the poster button is inert too, and
|
|
273
|
+
* never for a Wistia video without a poster, which has no first paint to
|
|
274
|
+
* give. A press ends it: from then on the pressed address is the one in the
|
|
275
|
+
* frame, and a later change of consent moves nothing, as it moves nothing
|
|
276
|
+
* for a player a press loaded with the switch off.
|
|
277
|
+
*/ const [pageLoadSrc, setPageLoadSrc] = useState();
|
|
278
|
+
useEffect(()=>{
|
|
279
|
+
if (!loadPlayer || !wistiaId || !poster || editorInert || playerSrc) {
|
|
280
|
+
return undefined;
|
|
281
|
+
}
|
|
282
|
+
const sync = ()=>{
|
|
283
|
+
var _readStoredVisitorConsent;
|
|
284
|
+
return setPageLoadSrc(((_readStoredVisitorConsent = readStoredVisitorConsent(hostId)) == null ? void 0 : _readStoredVisitorConsent.analytics) ? wistiaPlayerSrc(storedSrc, {
|
|
285
|
+
autoPlay: false,
|
|
286
|
+
muted: Boolean(muted),
|
|
287
|
+
loop: Boolean(loop)
|
|
288
|
+
}) : undefined);
|
|
289
|
+
};
|
|
290
|
+
sync();
|
|
291
|
+
window.addEventListener(VISITOR_CONSENT_CHANGED_EVENT, sync);
|
|
292
|
+
return ()=>window.removeEventListener(VISITOR_CONSENT_CHANGED_EVENT, sync);
|
|
293
|
+
}, [
|
|
294
|
+
loadPlayer,
|
|
295
|
+
wistiaId,
|
|
296
|
+
poster,
|
|
297
|
+
editorInert,
|
|
298
|
+
playerSrc,
|
|
299
|
+
hostId,
|
|
300
|
+
storedSrc,
|
|
301
|
+
muted,
|
|
302
|
+
loop
|
|
303
|
+
]);
|
|
304
|
+
/** Read through the switch, so switching it off removes a loaded player. */ const loadedSrc = loadPlayer ? pageLoadSrc : undefined;
|
|
305
|
+
/**
|
|
306
|
+
* The address of the Wistia player standing in place of the poster, if one
|
|
307
|
+
* is: a pressed player while the lightbox is off, and a player the page
|
|
308
|
+
* loaded whatever the lightbox is set to. The pressed address outranks the
|
|
309
|
+
* loaded one, because it is the one that plays.
|
|
310
|
+
*/ const inPlaceSrc = wistiaId && (!lightbox || loadedSrc) ? playerSrc != null ? playerSrc : loadedSrc : undefined;
|
|
311
|
+
/**
|
|
312
|
+
* What pressing the poster does. The poster's own button and a press sent
|
|
313
|
+
* from outside the element both call this one function, so the two cannot
|
|
314
|
+
* come to disagree about what a press is.
|
|
315
|
+
*
|
|
316
|
+
* A player the page loaded is already in place, so a press plays it there
|
|
317
|
+
* and never opens the lightbox.
|
|
318
|
+
*/ const press = ()=>{
|
|
319
|
+
if (wistiaId) playWistia();
|
|
320
|
+
if (lightbox && !loadedSrc) openLightbox();
|
|
321
|
+
};
|
|
322
|
+
/**
|
|
323
|
+
* A press sent from outside the element (AGL-2867) — a "Play a video"
|
|
324
|
+
* interaction on a button elsewhere on the page — answered as the markup
|
|
325
|
+
* rendered below would answer a press on it, branch for branch:
|
|
326
|
+
*
|
|
327
|
+
* - nothing that can play (no source, or a Wistia video with no poster):
|
|
328
|
+
* nothing happens;
|
|
329
|
+
* - a Wistia player a press put in place of its poster: it is playing
|
|
330
|
+
* already;
|
|
331
|
+
* - a Wistia player the page loaded and nothing has pressed: `press()`,
|
|
332
|
+
* whose autoplaying address replaces the loaded one. That is what the play
|
|
333
|
+
* button inside the frame does, and the element cannot reach that button
|
|
334
|
+
* across Wistia's origin, so a film already started from it starts over;
|
|
335
|
+
* - a poster that is a button: that button's own press, which reads consent
|
|
336
|
+
* at that moment and is the only thing that ever loads the lightbox chunk
|
|
337
|
+
* or a player the page did not load;
|
|
338
|
+
* - the inline player: `play()`, which is what a press on it does.
|
|
339
|
+
*
|
|
340
|
+
* Inert on the besigner canvas, as the poster button is, for the same
|
|
341
|
+
* reason: nothing may load a player or open a dialog inside the editor.
|
|
342
|
+
*
|
|
343
|
+
* `useEventCallback`, not React's `useEffectEvent`: in the React this repo
|
|
344
|
+
* runs, an effect event declared in a `forwardRef` component is never
|
|
345
|
+
* handed the implementation from later renders, so a listener added once
|
|
346
|
+
* would go on answering with the props of the first one.
|
|
347
|
+
*/ const answerVideoCommand = useEventCallback((element)=>{
|
|
348
|
+
if (editorInert || !src) return;
|
|
349
|
+
if (wistiaId && !poster) return;
|
|
350
|
+
if (inPlaceSrc && playerSrc) return;
|
|
351
|
+
if ((lightbox || wistiaId) && poster) return press();
|
|
352
|
+
if (element instanceof HTMLVideoElement) {
|
|
353
|
+
// A play the browser refuses (a trigger that was not a press, on an
|
|
354
|
+
// unmuted film) is the browser's decision to make, not an error.
|
|
355
|
+
void Promise.resolve(element.play()).catch(()=>undefined);
|
|
356
|
+
}
|
|
357
|
+
});
|
|
358
|
+
/**
|
|
359
|
+
* The element's own DOM root, which receives the command. State rather than
|
|
360
|
+
* a ref object, because the root is a different element in different
|
|
361
|
+
* branches — the inline `<video>`, or the box around a poster — and the
|
|
362
|
+
* listener has to follow it.
|
|
363
|
+
*/ const [root, setRoot] = useState(null);
|
|
364
|
+
const rootRef = useForkRef(ref, setRoot);
|
|
365
|
+
useEffect(()=>{
|
|
366
|
+
if (!root) return undefined;
|
|
367
|
+
return Aglyn.subscribeVideoCommands(root, ()=>answerVideoCommand(root));
|
|
368
|
+
}, [
|
|
369
|
+
root,
|
|
370
|
+
answerVideoCommand
|
|
371
|
+
]);
|
|
372
|
+
/**
|
|
373
|
+
* The labeled box the element shows when it has nothing it can play. The
|
|
374
|
+
* label is for the author, so only editing surfaces draw it; a published
|
|
375
|
+
* page renders the bare element.
|
|
376
|
+
*/ const placeholder = (label)=>suppressNavigation ? /*#__PURE__*/ _jsx(Box, _extends({
|
|
377
|
+
ref: rootRef
|
|
378
|
+
}, rest, {
|
|
379
|
+
sx: [
|
|
380
|
+
{
|
|
381
|
+
width: width || '100%',
|
|
382
|
+
height: height || 180,
|
|
383
|
+
display: 'flex',
|
|
384
|
+
alignItems: 'center',
|
|
385
|
+
justifyContent: 'center',
|
|
386
|
+
border: '1px dashed',
|
|
387
|
+
borderColor: 'divider',
|
|
388
|
+
borderRadius: radius != null ? `${radius}px` : undefined,
|
|
389
|
+
color: 'text.secondary',
|
|
390
|
+
fontSize: 12,
|
|
391
|
+
fontFamily: 'system-ui, sans-serif'
|
|
392
|
+
},
|
|
393
|
+
...nodeSx
|
|
394
|
+
],
|
|
395
|
+
children: label
|
|
396
|
+
})) : /*#__PURE__*/ _jsx(Box, _extends({
|
|
397
|
+
ref: rootRef
|
|
398
|
+
}, rest, {
|
|
399
|
+
sx: nodeSx
|
|
400
|
+
}));
|
|
401
|
+
if (!src) return placeholder('Video — set a source URL');
|
|
402
|
+
/**
|
|
403
|
+
* The reserved box, as a ratio rather than a size.
|
|
404
|
+
*
|
|
405
|
+
* Both-or-neither and finite-and-positive for the reasons `image.tsx` gives
|
|
406
|
+
* at its own pair: a lone dimension describes no shape, and a capture may
|
|
407
|
+
* legitimately be `0` or missing. Suppressed when the author pinned a
|
|
408
|
+
* `height`, because then the box is already definite and a ratio would only
|
|
409
|
+
* fight the value they typed.
|
|
410
|
+
*/ const usable = (value)=>typeof value === 'number' && Number.isFinite(value) && value > 0;
|
|
411
|
+
const aspectRatio = !height && usable(intrinsicWidth) && usable(intrinsicHeight) ? `${intrinsicWidth} / ${intrinsicHeight}` : undefined;
|
|
412
|
+
const track = captions ? // `default` so the track is selected without the visitor hunting for it
|
|
413
|
+
// in the player's menu. A captions file an author took the trouble to
|
|
414
|
+
// attach is one they meant people to see, and the browser still offers
|
|
415
|
+
// the switch that turns it off.
|
|
416
|
+
/*#__PURE__*/ _jsx("track", {
|
|
417
|
+
kind: "captions",
|
|
418
|
+
src: captions,
|
|
419
|
+
srcLang: captionsLang || undefined,
|
|
420
|
+
label: captionsLabel || undefined,
|
|
421
|
+
default: true
|
|
422
|
+
}) : null;
|
|
423
|
+
/**
|
|
424
|
+
* A Wistia video with no poster has nothing to press, and the alternative —
|
|
425
|
+
* loading the player up front for every visitor — is the request this
|
|
426
|
+
* element exists to withhold. So it says what it needs instead, with "Load
|
|
427
|
+
* the player with the page" on as well: the poster is that switch's server
|
|
428
|
+
* HTML and first paint, and what every visitor without analytics consent
|
|
429
|
+
* goes on seeing.
|
|
430
|
+
*/ if (wistiaId && !poster) {
|
|
431
|
+
return placeholder('Video — add a poster image to play this Wistia video');
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* The Wistia player in place of the poster: after a press when the lightbox
|
|
435
|
+
* is off, or loaded with the page (AGL-2962). The wrapper is the same element
|
|
436
|
+
* the poster sat in, so the node's styles and ref stay where they were and
|
|
437
|
+
* only its contents change.
|
|
438
|
+
*/ if (inPlaceSrc) {
|
|
439
|
+
return /*#__PURE__*/ _jsx(Box, _extends({
|
|
440
|
+
ref: rootRef
|
|
441
|
+
}, rest, {
|
|
442
|
+
sx: [
|
|
443
|
+
{
|
|
444
|
+
display: 'block',
|
|
445
|
+
position: 'relative',
|
|
446
|
+
width: width || '100%',
|
|
447
|
+
height: height || 'auto'
|
|
448
|
+
},
|
|
449
|
+
...nodeSx
|
|
450
|
+
],
|
|
451
|
+
children: /*#__PURE__*/ _jsx(VideoPlayerFrame, {
|
|
452
|
+
src: inPlaceSrc,
|
|
453
|
+
title: title,
|
|
454
|
+
aspectRatio: aspectRatio,
|
|
455
|
+
height: height,
|
|
456
|
+
radius: radius,
|
|
457
|
+
// Focus follows a press into the player. A player the page loaded
|
|
458
|
+
// takes none: nobody asked for it, and focusing a frame as the page
|
|
459
|
+
// loads would scroll the page to it and take the keyboard with it.
|
|
460
|
+
focusOnMount: Boolean(playerSrc)
|
|
461
|
+
}, playerSrc ? 'pressed' : 'loaded')
|
|
462
|
+
}));
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* Poster-first, dialog on click (AGL-2744).
|
|
466
|
+
*
|
|
467
|
+
* Requires a poster, and falls back to the inline player without one rather
|
|
468
|
+
* than rendering a button with no face. That is not a limitation to work
|
|
469
|
+
* around — a lightbox trigger with nothing to show is a blank rectangle
|
|
470
|
+
* that claims to be a film.
|
|
471
|
+
*
|
|
472
|
+
* A Wistia video takes this branch until a player stands in place of its
|
|
473
|
+
* poster (AGL-2826), which includes the server HTML and first paint of one
|
|
474
|
+
* that loads its player with the page. With the lightbox off its press swaps
|
|
475
|
+
* the player in above, and the lazy dialog is never armed, because nothing is
|
|
476
|
+
* going to open it.
|
|
477
|
+
*/ if ((lightbox || wistiaId) && poster) {
|
|
478
|
+
const playsInPlace = Boolean(wistiaId) && !lightbox;
|
|
479
|
+
return /*#__PURE__*/ _jsxs(Box, _extends({
|
|
480
|
+
ref: rootRef
|
|
481
|
+
}, rest, {
|
|
482
|
+
sx: [
|
|
483
|
+
{
|
|
484
|
+
display: 'block',
|
|
485
|
+
position: 'relative',
|
|
486
|
+
width: width || '100%',
|
|
487
|
+
height: height || 'auto'
|
|
488
|
+
},
|
|
489
|
+
...nodeSx
|
|
490
|
+
],
|
|
491
|
+
children: [
|
|
492
|
+
/*#__PURE__*/ _jsxs(Box, {
|
|
493
|
+
component: "button",
|
|
494
|
+
type: "button",
|
|
495
|
+
// Inert on the canvas: a focus-trapping dialog opened inside the
|
|
496
|
+
// editor would take the author's keyboard away mid-edit, and the
|
|
497
|
+
// click there means "select this node". Same shape as the Drawer's
|
|
498
|
+
// `editorInert ? undefined : handler`. It is also why a hosted
|
|
499
|
+
// player never loads into the canvas.
|
|
500
|
+
onClick: editorInert ? undefined : press,
|
|
501
|
+
// Arming on hover and on focus, not on click, is what makes the
|
|
502
|
+
// dialog open on the same frame as the press — and it costs a
|
|
503
|
+
// visitor who never approaches the poster nothing at all.
|
|
504
|
+
onPointerEnter: editorInert || playsInPlace ? undefined : arm,
|
|
505
|
+
onFocus: editorInert || playsInPlace ? undefined : arm,
|
|
506
|
+
// The button IS the play control, so it says so. Without a title it
|
|
507
|
+
// still announces its purpose rather than reading as an image.
|
|
508
|
+
"aria-label": title ? `Play video: ${title}` : 'Play video',
|
|
509
|
+
sx: {
|
|
510
|
+
// A button reset: the poster is the control, so none of the
|
|
511
|
+
// browser's own chrome should show through it.
|
|
512
|
+
appearance: 'none',
|
|
513
|
+
border: 0,
|
|
514
|
+
p: 0,
|
|
515
|
+
m: 0,
|
|
516
|
+
display: 'block',
|
|
517
|
+
width: '100%',
|
|
518
|
+
font: 'inherit',
|
|
519
|
+
color: 'inherit',
|
|
520
|
+
background: 'none',
|
|
521
|
+
cursor: editorInert ? 'default' : 'pointer',
|
|
522
|
+
position: 'relative',
|
|
523
|
+
overflow: 'hidden',
|
|
524
|
+
borderRadius: radius != null ? `${radius}px` : undefined,
|
|
525
|
+
// The focus ring is the theme's, not a literal: a keyboard
|
|
526
|
+
// visitor has to be able to see where they are on a poster of
|
|
527
|
+
// unknown brightness.
|
|
528
|
+
'&:focus-visible': {
|
|
529
|
+
outline: '2px solid',
|
|
530
|
+
outlineColor: 'primary.dark',
|
|
531
|
+
outlineOffset: 2
|
|
532
|
+
}
|
|
533
|
+
},
|
|
534
|
+
children: [
|
|
535
|
+
/*#__PURE__*/ _jsx(Box, {
|
|
536
|
+
component: "img",
|
|
537
|
+
src: posterBase,
|
|
538
|
+
// The poster is an `<img>` here rather than a `<video poster>`,
|
|
539
|
+
// which is the one advantage this mode has over the inline one:
|
|
540
|
+
// an `<img>` takes the DAM's whole candidate list, so a phone
|
|
541
|
+
// downloads `?w=320` where the inline player has to be handed a
|
|
542
|
+
// single width for every viewport.
|
|
543
|
+
srcSet: Aglyn.isMediaCdnUrl(posterBase) ? Aglyn.MEDIA_CDN_VARIANT_WIDTHS.map((variant)=>{
|
|
544
|
+
// Built through the same rule as the src, not by pasting
|
|
545
|
+
// `?w=` onto it: a generated poster's url already carries
|
|
546
|
+
// `?poster=1`, so a hand-appended query would produce
|
|
547
|
+
// `?poster=1?w=320` and 404 every candidate.
|
|
548
|
+
const candidate = posterFor(variant);
|
|
549
|
+
return candidate ? `${candidate} ${variant}w` : '';
|
|
550
|
+
}).filter(Boolean).join(', ') : undefined,
|
|
551
|
+
sizes: Aglyn.isMediaCdnUrl(posterBase) ? '100vw' : undefined,
|
|
552
|
+
// Empty by design. The button's `aria-label` already names the
|
|
553
|
+
// film and its purpose; alt text here would make a screen reader
|
|
554
|
+
// read the same thing twice, once as a control and once as a
|
|
555
|
+
// picture.
|
|
556
|
+
alt: "",
|
|
557
|
+
// No `loading` hint, deliberately — see `deferred-images.spec`,
|
|
558
|
+
// where this element is exempt. A poster is the entire visual of
|
|
559
|
+
// a media element an author placed on purpose, and a browser
|
|
560
|
+
// fetches `<video poster>` eagerly too, so deferring it would be
|
|
561
|
+
// a regression against what this element rendered yesterday
|
|
562
|
+
// rather than a saving.
|
|
563
|
+
decoding: "async",
|
|
564
|
+
sx: {
|
|
565
|
+
display: 'block',
|
|
566
|
+
width: '100%',
|
|
567
|
+
height: height || 'auto',
|
|
568
|
+
objectFit: 'cover',
|
|
569
|
+
aspectRatio
|
|
570
|
+
}
|
|
571
|
+
}),
|
|
572
|
+
/*#__PURE__*/ _jsx(Box, {
|
|
573
|
+
"aria-hidden": true,
|
|
574
|
+
sx: {
|
|
575
|
+
position: 'absolute',
|
|
576
|
+
top: '50%',
|
|
577
|
+
left: '50%',
|
|
578
|
+
transform: 'translate(-50%, -50%)',
|
|
579
|
+
display: 'flex',
|
|
580
|
+
alignItems: 'center',
|
|
581
|
+
justifyContent: 'center',
|
|
582
|
+
// A poster is a frame of unknown brightness, and this control
|
|
583
|
+
// has to separate from all of them. A solid disc does; the
|
|
584
|
+
// translucent scrim it replaced sank into any dark frame.
|
|
585
|
+
width: {
|
|
586
|
+
xs: 64,
|
|
587
|
+
sm: 80,
|
|
588
|
+
md: 96
|
|
589
|
+
},
|
|
590
|
+
height: {
|
|
591
|
+
xs: 64,
|
|
592
|
+
sm: 80,
|
|
593
|
+
md: 96
|
|
594
|
+
},
|
|
595
|
+
borderRadius: '50%',
|
|
596
|
+
// The site's accent, not a literal. A play control is the one
|
|
597
|
+
// mark on a poster that should read as the brand's, so it takes
|
|
598
|
+
// the theme's primary the way every other accented glyph does.
|
|
599
|
+
color: 'primary.main',
|
|
600
|
+
bgcolor: 'common.white',
|
|
601
|
+
boxShadow: 6,
|
|
602
|
+
// Gated in CSS rather than behind a JS branch, so toggling the
|
|
603
|
+
// OS setting after load resolves in BOTH directions — the same
|
|
604
|
+
// reason `element-animation-assets` puts every rule it has
|
|
605
|
+
// inside this query.
|
|
606
|
+
'@media (prefers-reduced-motion: no-preference)': {
|
|
607
|
+
transition: 'transform 150ms, color 150ms',
|
|
608
|
+
'button:hover &, button:focus-visible &': {
|
|
609
|
+
transform: 'translate(-50%, -50%) scale(1.08)',
|
|
610
|
+
color: 'primary.dark'
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
},
|
|
614
|
+
children: /*#__PURE__*/ _jsx(MdiIcon, {
|
|
615
|
+
path: mdiPlay.path,
|
|
616
|
+
sx: {
|
|
617
|
+
fontSize: {
|
|
618
|
+
xs: 32,
|
|
619
|
+
sm: 40,
|
|
620
|
+
md: 48
|
|
621
|
+
},
|
|
622
|
+
ml: '4px'
|
|
623
|
+
}
|
|
624
|
+
})
|
|
625
|
+
})
|
|
626
|
+
]
|
|
627
|
+
}),
|
|
628
|
+
armed ? // `fallback={null}`: the poster is still on screen underneath, so
|
|
629
|
+
// there is nothing to cover and a spinner would only flash. The
|
|
630
|
+
// chunk is usually resolved before the click anyway.
|
|
631
|
+
/*#__PURE__*/ _jsx(Suspense, {
|
|
632
|
+
fallback: null,
|
|
633
|
+
children: /*#__PURE__*/ _jsx(VideoLightbox, {
|
|
634
|
+
open: open,
|
|
635
|
+
onClose: closeLightbox,
|
|
636
|
+
title: title,
|
|
637
|
+
src: src,
|
|
638
|
+
poster: poster,
|
|
639
|
+
aspectRatio: aspectRatio,
|
|
640
|
+
loop: loop,
|
|
641
|
+
muted: muted,
|
|
642
|
+
captions: track,
|
|
643
|
+
playback: playback,
|
|
644
|
+
embedSrc: wistiaId ? playerSrc : undefined
|
|
645
|
+
})
|
|
646
|
+
}) : null
|
|
647
|
+
]
|
|
648
|
+
}));
|
|
649
|
+
}
|
|
650
|
+
return /*#__PURE__*/ _jsx(Box, _extends({
|
|
651
|
+
ref: rootRef,
|
|
652
|
+
component: "video",
|
|
653
|
+
src: src,
|
|
654
|
+
poster: poster || undefined,
|
|
655
|
+
title: title || undefined,
|
|
656
|
+
autoPlay: Boolean(autoPlay),
|
|
657
|
+
loop: Boolean(loop),
|
|
658
|
+
// Browsers block unmuted autoplay.
|
|
659
|
+
muted: Boolean(muted) || Boolean(autoPlay),
|
|
660
|
+
controls: controls !== false,
|
|
661
|
+
playsInline: playsInline !== false,
|
|
662
|
+
preload: resolveVideoPreload({
|
|
663
|
+
preload,
|
|
664
|
+
poster,
|
|
665
|
+
autoPlay
|
|
666
|
+
})
|
|
667
|
+
}, rest, {
|
|
668
|
+
// After the spread, so a stray prop from the tree cannot unwire the
|
|
669
|
+
// counters.
|
|
670
|
+
onPlay: playbackHandlers.onPlay,
|
|
671
|
+
onTimeUpdate: playbackHandlers.onTimeUpdate,
|
|
672
|
+
onEnded: playbackHandlers.onEnded,
|
|
673
|
+
sx: [
|
|
674
|
+
{
|
|
675
|
+
display: 'block',
|
|
676
|
+
width: width || '100%',
|
|
677
|
+
height: height || 'auto',
|
|
678
|
+
aspectRatio,
|
|
679
|
+
borderRadius: radius != null ? `${radius}px` : undefined
|
|
680
|
+
},
|
|
681
|
+
...nodeSx
|
|
682
|
+
],
|
|
683
|
+
children: track
|
|
684
|
+
}));
|
|
685
|
+
});
|
|
686
|
+
Video.displayName = 'Video';
|
|
687
|
+
/**
|
|
688
|
+
* The caption fields say nothing until there is a captions file to label.
|
|
689
|
+
*
|
|
690
|
+
* `isNotEmpty` rather than an inverted `is: ''` — the two are not the same
|
|
691
|
+
* question for a field nobody has touched. An unset attribute is `undefined`,
|
|
692
|
+
* which is not equal to `''`, so `{ is: '', notMatch: true }` would read as
|
|
693
|
+
* "not empty" and show both controls on every fresh Video element.
|
|
694
|
+
*/ const HAS_CAPTIONS = {
|
|
695
|
+
when: 'captionsSrc',
|
|
696
|
+
isNotEmpty: true
|
|
697
|
+
};
|
|
698
|
+
export const schema = {
|
|
699
|
+
$id: ID,
|
|
700
|
+
pluginId: BUNDLE_ID,
|
|
701
|
+
displayName: 'Video',
|
|
702
|
+
description: 'Plays a video file from your media library or any URL.',
|
|
703
|
+
category: Aglyn.ComponentCategory.MEDIA,
|
|
704
|
+
icon: {
|
|
705
|
+
path: mdiVideo.path,
|
|
706
|
+
sx: {
|
|
707
|
+
color: '#7b1fa2'
|
|
708
|
+
}
|
|
709
|
+
},
|
|
710
|
+
flags: {
|
|
711
|
+
selfClosing: Aglyn.FEATURE_FLAG.ENABLED
|
|
712
|
+
},
|
|
713
|
+
attributes: [
|
|
714
|
+
{
|
|
715
|
+
name: 'src',
|
|
716
|
+
description: 'Pick the film from your media library with "Browse media", or ' + 'paste the URL of a video hosted somewhere else. A Wistia media ' + "link plays in Wistia's player, which loads when a visitor presses " + 'play. Load the player with the page can load it sooner.',
|
|
717
|
+
component: Aglyn.FieldComponentType.TEXT_FIELD,
|
|
718
|
+
label: 'Video source'
|
|
719
|
+
},
|
|
720
|
+
{
|
|
721
|
+
name: 'poster',
|
|
722
|
+
description: 'The still shown before anyone presses play. Videos from your media ' + 'library already have one; pick a different image here to override ' + 'it. A poster is what keeps the video off the wire until it is asked for. ' + 'A Wistia video needs one: it is the button a visitor presses.',
|
|
723
|
+
component: Aglyn.FieldComponentType.TEXT_FIELD,
|
|
724
|
+
label: 'Poster image'
|
|
725
|
+
},
|
|
726
|
+
{
|
|
727
|
+
name: 'title',
|
|
728
|
+
description: 'Names the video for screen readers, for the tooltip, and for ' + 'search engines. Write it as a headline, not a file name.',
|
|
729
|
+
component: Aglyn.FieldComponentType.TEXT_FIELD,
|
|
730
|
+
label: 'Video title'
|
|
731
|
+
},
|
|
732
|
+
{
|
|
733
|
+
name: 'description',
|
|
734
|
+
description: 'A sentence or two about what the video shows, different for each ' + 'video. Never displayed on the page. Search engines read it, and ' + 'can still list the video in results when it is blank.',
|
|
735
|
+
component: Aglyn.FieldComponentType.TEXTAREA,
|
|
736
|
+
label: 'Video description'
|
|
737
|
+
},
|
|
738
|
+
{
|
|
739
|
+
name: 'uploadDate',
|
|
740
|
+
description: 'The day the video was first published, as YYYY-MM-DD. Search ' + 'engines need this and cannot use the page date instead.',
|
|
741
|
+
component: Aglyn.FieldComponentType.TEXT_FIELD,
|
|
742
|
+
label: 'Publication date'
|
|
743
|
+
},
|
|
744
|
+
{
|
|
745
|
+
name: 'durationSeconds',
|
|
746
|
+
description: 'How long the video runs, in seconds. Filled in from the media ' + 'library when it knows; otherwise type it, or leave it blank.',
|
|
747
|
+
component: Aglyn.FieldComponentType.TEXT_FIELD,
|
|
748
|
+
label: 'Duration (seconds)',
|
|
749
|
+
type: 'number'
|
|
750
|
+
},
|
|
751
|
+
{
|
|
752
|
+
name: 'lightbox',
|
|
753
|
+
description: 'Show the poster as a play button and open the film full size in a ' + 'dialog. Needs a poster image; without one the player stays in the ' + "page. A Wistia video's poster waits for a press either way, and the " + 'video plays in place of the poster when this is off. A Wistia player ' + 'that loads with the page stays in the page.',
|
|
754
|
+
component: Aglyn.FieldComponentType.SWITCH,
|
|
755
|
+
label: 'Open in a lightbox'
|
|
756
|
+
},
|
|
757
|
+
{
|
|
758
|
+
name: 'loadPlayer',
|
|
759
|
+
description: "For a Wistia video: show Wistia's player as soon as the page loads, " + 'instead of a poster to press, to visitors whose privacy choices ' + 'allow analytics. Everyone else still gets the poster. Use it on a ' + 'page built to show this one video, so search engines can find the ' + 'player. The player stays in the page even with the lightbox on.',
|
|
760
|
+
component: Aglyn.FieldComponentType.SWITCH,
|
|
761
|
+
label: 'Load the player with the page'
|
|
762
|
+
},
|
|
763
|
+
{
|
|
764
|
+
name: 'preload',
|
|
765
|
+
description: 'How much to fetch before anyone presses play. Leave unset and a ' + 'video with a poster fetches nothing at all.',
|
|
766
|
+
component: Aglyn.FieldComponentType.SELECT,
|
|
767
|
+
label: 'Preload',
|
|
768
|
+
// Real sentinels, never `''` (AGL-1451): each of these is a member of
|
|
769
|
+
// the declared `VideoPreload` union, so a video switched to Metadata
|
|
770
|
+
// can be switched back.
|
|
771
|
+
options: [
|
|
772
|
+
{
|
|
773
|
+
value: 'none',
|
|
774
|
+
label: 'Nothing until play (default with a poster)'
|
|
775
|
+
},
|
|
776
|
+
{
|
|
777
|
+
value: 'metadata',
|
|
778
|
+
label: 'Length and dimensions only'
|
|
779
|
+
},
|
|
780
|
+
{
|
|
781
|
+
value: 'auto',
|
|
782
|
+
label: 'As much as the browser likes'
|
|
783
|
+
}
|
|
784
|
+
]
|
|
785
|
+
},
|
|
786
|
+
{
|
|
787
|
+
name: 'controls',
|
|
788
|
+
description: 'Show the browser playback controls (on by default).',
|
|
789
|
+
component: Aglyn.FieldComponentType.CHECKBOX,
|
|
790
|
+
label: 'Controls'
|
|
791
|
+
},
|
|
792
|
+
{
|
|
793
|
+
name: 'autoPlay',
|
|
794
|
+
description: 'Start playback automatically (mutes the video).',
|
|
795
|
+
component: Aglyn.FieldComponentType.CHECKBOX,
|
|
796
|
+
label: 'Autoplay'
|
|
797
|
+
},
|
|
798
|
+
{
|
|
799
|
+
name: 'loop',
|
|
800
|
+
description: 'Restart the video when it ends.',
|
|
801
|
+
component: Aglyn.FieldComponentType.CHECKBOX,
|
|
802
|
+
label: 'Loop'
|
|
803
|
+
},
|
|
804
|
+
{
|
|
805
|
+
name: 'muted',
|
|
806
|
+
description: 'Play without sound.',
|
|
807
|
+
component: Aglyn.FieldComponentType.CHECKBOX,
|
|
808
|
+
label: 'Muted'
|
|
809
|
+
},
|
|
810
|
+
{
|
|
811
|
+
name: 'playsInline',
|
|
812
|
+
description: 'Keep the video in the page on a phone instead of taking over the ' + 'whole screen (on by default).',
|
|
813
|
+
component: Aglyn.FieldComponentType.CHECKBOX,
|
|
814
|
+
label: 'Play inline on phones'
|
|
815
|
+
},
|
|
816
|
+
{
|
|
817
|
+
name: 'captionsSrc',
|
|
818
|
+
description: 'A WebVTT captions file (.vtt) for this video — pick it with ' + '"Browse media" or paste its URL.',
|
|
819
|
+
component: Aglyn.FieldComponentType.TEXT_FIELD,
|
|
820
|
+
label: 'Captions file'
|
|
821
|
+
},
|
|
822
|
+
{
|
|
823
|
+
name: 'captionsLabel',
|
|
824
|
+
description: 'What to call the captions track in the player menu, e.g. English.',
|
|
825
|
+
component: Aglyn.FieldComponentType.TEXT_FIELD,
|
|
826
|
+
label: 'Captions label',
|
|
827
|
+
condition: HAS_CAPTIONS
|
|
828
|
+
},
|
|
829
|
+
{
|
|
830
|
+
name: 'captionsLang',
|
|
831
|
+
description: 'The language code of the captions, e.g. en or es — it tells the ' + 'browser which track to offer.',
|
|
832
|
+
component: Aglyn.FieldComponentType.TEXT_FIELD,
|
|
833
|
+
label: 'Captions language',
|
|
834
|
+
condition: HAS_CAPTIONS
|
|
835
|
+
},
|
|
836
|
+
{
|
|
837
|
+
name: 'width',
|
|
838
|
+
description: 'Width of the player — a number plus a unit, e.g. 100% or 640px.',
|
|
839
|
+
component: Aglyn.FieldComponentType.CSS_DIMENSION,
|
|
840
|
+
label: 'Width'
|
|
841
|
+
},
|
|
842
|
+
{
|
|
843
|
+
name: 'height',
|
|
844
|
+
description: 'Height of the player. Leave empty for auto.',
|
|
845
|
+
component: Aglyn.FieldComponentType.CSS_DIMENSION,
|
|
846
|
+
label: 'Height'
|
|
847
|
+
}
|
|
848
|
+
]
|
|
849
|
+
};
|
|
850
|
+
export const presets = [
|
|
851
|
+
{
|
|
852
|
+
$id: generatePresetId(ID),
|
|
853
|
+
type: Aglyn.NodeType.PRESET,
|
|
854
|
+
displayName: 'Video',
|
|
855
|
+
pluginId: BUNDLE_ID,
|
|
856
|
+
description: 'Video from your media library or any URL',
|
|
857
|
+
category: Aglyn.ComponentCategory.MEDIA,
|
|
858
|
+
icon: {
|
|
859
|
+
path: mdiVideo.path,
|
|
860
|
+
sx: {
|
|
861
|
+
color: '#7b1fa2'
|
|
862
|
+
}
|
|
863
|
+
},
|
|
864
|
+
data: {
|
|
865
|
+
$id: null,
|
|
866
|
+
componentId: ID,
|
|
867
|
+
pluginId: BUNDLE_ID,
|
|
868
|
+
props: {}
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
];
|
|
872
|
+
export default Video;
|
|
873
|
+
|
|
874
|
+
//# sourceMappingURL=video.js.map
|