@vectoriox/iox-ui 5.36.0 → 5.38.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/actions-core/index.d.ts +17 -3
- package/composite-core/index.d.ts +463 -31
- package/design-core/index.d.ts +130 -3
- package/fesm2022/vectoriox-iox-ui-actions-core.mjs +33 -3
- package/fesm2022/vectoriox-iox-ui-actions-core.mjs.map +1 -1
- package/fesm2022/vectoriox-iox-ui-composite-core.mjs +1062 -121
- package/fesm2022/vectoriox-iox-ui-composite-core.mjs.map +1 -1
- package/fesm2022/vectoriox-iox-ui-design-core.mjs +172 -3
- package/fesm2022/vectoriox-iox-ui-design-core.mjs.map +1 -1
- package/fesm2022/vectoriox-iox-ui-generation-core.mjs +410 -10
- package/fesm2022/vectoriox-iox-ui-generation-core.mjs.map +1 -1
- package/fesm2022/vectoriox-iox-ui-media-core.mjs +571 -0
- package/fesm2022/vectoriox-iox-ui-media-core.mjs.map +1 -0
- package/fesm2022/vectoriox-iox-ui.mjs +482 -120
- package/fesm2022/vectoriox-iox-ui.mjs.map +1 -1
- package/generation-core/index.d.ts +167 -4
- package/index.d.ts +209 -25
- package/media-core/index.d.ts +277 -0
- package/media-core/package.json +3 -0
- package/package.json +5 -1
- package/src/actions-core/action.model.ts +4 -2
- package/src/actions-core/element-actions.ts +37 -2
- package/src/composite-core/accordion-runtime.ts +117 -28
- package/src/composite-core/accordion.ts +89 -45
- package/src/composite-core/composite-kinds.ts +110 -0
- package/src/composite-core/composite-node.ts +15 -0
- package/src/composite-core/disclosure.ts +125 -0
- package/src/composite-core/lightbox.ts +192 -0
- package/src/composite-core/public-api.ts +7 -1
- package/src/composite-core/slider-runtime.ts +180 -0
- package/src/composite-core/slider.ts +378 -0
- package/src/composite-core/structure.ts +25 -5
- package/src/design-core/element-styles.ts +27 -0
- package/src/design-core/flavor-spec.generated.ts +146 -2
- package/src/generation-core/baseline-roles.ts +2 -0
- package/src/generation-core/compose-site.ts +80 -4
- package/src/generation-core/node-baselines.generated.ts +148 -4
- package/src/generation-core/patterns.ts +17 -0
- package/src/generation-core/site-spec.generated.ts +164 -0
- package/src/media-core/public-api.ts +9 -0
- package/src/media-core/video-runtime.ts +239 -0
- package/src/media-core/video.ts +443 -0
- package/styles/iox-design.css +96 -0
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import { ConsentCategory, ConsentDecision } from '@vectoriox/iox-ui/consent-core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* VIDEO (CC3 — complex-components plan §7b, board 5). A LEAF type with two sources:
|
|
5
|
+
* - an uploaded FILE renders as a real `<video>` (poster, `preload="metadata"`, captions, controls);
|
|
6
|
+
* - a YouTube / Vimeo address is ALWAYS a FACADE (D5): the studio's poster and a play button named after the video,
|
|
7
|
+
* behind the site's consent gate (Marketing). The player's `<iframe>` exists only after a click AND a grant —
|
|
8
|
+
* never in the server's HTML, never on the builder canvas.
|
|
9
|
+
*
|
|
10
|
+
* `planVideo(node, consent)` is the ONE decision — the only code that knows the URL shapes, the embed hosts and the
|
|
11
|
+
* consent category. Both engine paths and the canvas render from it (`videoInnerHtml`), and the runtime
|
|
12
|
+
* (`video-runtime.ts`) builds the player's address from the same plan.
|
|
13
|
+
*/
|
|
14
|
+
declare const VIDEO_NODE_TYPE = "Video";
|
|
15
|
+
declare enum VideoSource {
|
|
16
|
+
File = "file",
|
|
17
|
+
YouTube = "youtube",
|
|
18
|
+
Vimeo = "vimeo"
|
|
19
|
+
}
|
|
20
|
+
declare enum VideoMode {
|
|
21
|
+
/** In the page, with the player's controls. */
|
|
22
|
+
Inline = "inline",
|
|
23
|
+
/** Muted, looped, no controls, an always-visible pause button (WCAG 2.2.2) — an uploaded file only. */
|
|
24
|
+
Background = "background"
|
|
25
|
+
}
|
|
26
|
+
/** The node's traits (Bindings ▸ Media · Behaviour · Accessibility · Privacy). */
|
|
27
|
+
declare enum VideoTrait {
|
|
28
|
+
/** Not `source`: across the builder `source` names a DATA source (a Repeater's, a Slider's). */
|
|
29
|
+
Source = "videoSource",
|
|
30
|
+
Src = "src",
|
|
31
|
+
Poster = "poster",
|
|
32
|
+
Mode = "mode",
|
|
33
|
+
Autoplay = "autoplay",
|
|
34
|
+
Title = "title",
|
|
35
|
+
Captions = "captions",
|
|
36
|
+
CaptionsLang = "captionsLang",
|
|
37
|
+
Consent = "consentCategory"
|
|
38
|
+
}
|
|
39
|
+
/** What a plan renders. */
|
|
40
|
+
declare enum VideoKind {
|
|
41
|
+
/** An uploaded file: a real `<video>`. */
|
|
42
|
+
File = "file",
|
|
43
|
+
/** YouTube / Vimeo: poster + play, the player loaded on a click after consent. */
|
|
44
|
+
Facade = "facade",
|
|
45
|
+
/** No address yet: the poster blank (DS3) and nothing to play. */
|
|
46
|
+
Empty = "empty"
|
|
47
|
+
}
|
|
48
|
+
/** A recognised embed: which provider, its video id and (Vimeo, unlisted) its privacy hash. */
|
|
49
|
+
interface VideoRef {
|
|
50
|
+
source: VideoSource.YouTube | VideoSource.Vimeo;
|
|
51
|
+
id: string;
|
|
52
|
+
hash?: string;
|
|
53
|
+
}
|
|
54
|
+
declare const VIDEO_PROVIDER_LABEL: Readonly<Record<VideoRef['source'], string>>;
|
|
55
|
+
/** Who the visitor's IP address and cookies go to, named in the consent prompt. */
|
|
56
|
+
declare const VIDEO_PROVIDER_COMPANY: Readonly<Record<VideoRef['source'], string>>;
|
|
57
|
+
/** The two hosts a player ever loads from — youtube-nocookie and Vimeo's player (with Do Not Track). */
|
|
58
|
+
declare const VIDEO_EMBED_ORIGINS: ReadonlyArray<string>;
|
|
59
|
+
/** The embed a pasted address names — a YouTube or Vimeo watch, share, embed or short link — or null. */
|
|
60
|
+
declare function parseVideoUrl(raw: string | null | undefined): VideoRef | null;
|
|
61
|
+
/** The source a node plays: its declared source when it is one, else what its address is. */
|
|
62
|
+
declare function videoSourceOf(src: unknown, declared?: unknown): VideoSource;
|
|
63
|
+
/** The player's address — loaded only after a click and a grant. Autoplays (the visitor just pressed play). */
|
|
64
|
+
declare function videoEmbedUrl(ref: VideoRef): string;
|
|
65
|
+
/** Where the SERVER fetches an embed's thumbnail to store as the studio's own poster (never the visitor). */
|
|
66
|
+
interface VideoThumbnailRequest {
|
|
67
|
+
/** `image`: the url IS the picture; `oembed`: the url answers JSON whose `thumbnail_url` is the picture. */
|
|
68
|
+
kind: 'image' | 'oembed';
|
|
69
|
+
url: string;
|
|
70
|
+
/** The only hosts the picture itself may come from (the server refuses anything else). */
|
|
71
|
+
imageHosts: ReadonlyArray<string>;
|
|
72
|
+
}
|
|
73
|
+
declare function videoThumbnailRequest(ref: VideoRef): VideoThumbnailRequest;
|
|
74
|
+
/** A stored poster's object name — one per video, so fetching it again overwrites rather than piles up. */
|
|
75
|
+
declare function videoPosterFileName(ref: VideoRef): string;
|
|
76
|
+
interface VideoNodeLike {
|
|
77
|
+
type?: string;
|
|
78
|
+
props?: Record<string, unknown> | null;
|
|
79
|
+
traits?: ReadonlyArray<{
|
|
80
|
+
name?: string;
|
|
81
|
+
default?: unknown;
|
|
82
|
+
}> | null;
|
|
83
|
+
}
|
|
84
|
+
/** A content value of a Video node, saved (`props`) or builder (`traits[].default`) shape. */
|
|
85
|
+
declare function videoProp(node: VideoNodeLike | null | undefined, name: VideoTrait | string): unknown;
|
|
86
|
+
/** The consent category an embed waits for — Marketing by default (D5); an uploaded file waits for none. */
|
|
87
|
+
declare function videoConsentCategory(node: VideoNodeLike | null | undefined, source?: VideoSource): ConsentCategory | null;
|
|
88
|
+
interface VideoPlan {
|
|
89
|
+
kind: VideoKind;
|
|
90
|
+
source: VideoSource;
|
|
91
|
+
/** An embed's provider and id (Facade). */
|
|
92
|
+
ref: VideoRef | null;
|
|
93
|
+
/** An uploaded file's address (File). */
|
|
94
|
+
src: string;
|
|
95
|
+
/** The studio's own poster ('' = the token blank). Never a provider's thumbnail URL. */
|
|
96
|
+
poster: string;
|
|
97
|
+
/** The video's name — the play button's accessible name. */
|
|
98
|
+
title: string;
|
|
99
|
+
mode: VideoMode;
|
|
100
|
+
background: boolean;
|
|
101
|
+
/** Muted autoplay when motion is allowed (the runtime asks; the server never writes `autoplay`). */
|
|
102
|
+
autoplay: boolean;
|
|
103
|
+
muted: boolean;
|
|
104
|
+
loop: boolean;
|
|
105
|
+
controls: boolean;
|
|
106
|
+
/** A `.vtt` file ('' = none). */
|
|
107
|
+
captions: string;
|
|
108
|
+
captionsLang: string;
|
|
109
|
+
/** What the facade waits for (null for a file). */
|
|
110
|
+
consentCategory: ConsentCategory | null;
|
|
111
|
+
/** The visitor's decision grants it. */
|
|
112
|
+
granted: boolean;
|
|
113
|
+
/** The player's address — only when granted (the runtime loads it on a click). */
|
|
114
|
+
embedUrl: string | null;
|
|
115
|
+
/** "YouTube" / "Vimeo" ('' for a file). */
|
|
116
|
+
providerLabel: string;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* The ONE decision for a Video node and a visitor's consent (null = none recorded — the server always passes null,
|
|
120
|
+
* so its HTML is always the facade). A poster that is a provider's thumbnail URL is dropped: the poster is the
|
|
121
|
+
* studio's own image (or the thumbnail the server fetched and stored for it), never a request to a third party.
|
|
122
|
+
*/
|
|
123
|
+
declare function planVideo(node: VideoNodeLike | null | undefined, consent: ConsentDecision | null): VideoPlan;
|
|
124
|
+
/** A poster served by the video's provider — a request to a third party before consent, so it never renders. */
|
|
125
|
+
declare function isThirdPartyPoster(url: string): boolean;
|
|
126
|
+
/**
|
|
127
|
+
* The words the renderer draws (board 5; Q7: the consent prompt is NOT an editable part — its words are the
|
|
128
|
+
* consent vocabulary's, one source, translated). English and Hebrew; the page's `lang` picks.
|
|
129
|
+
*/
|
|
130
|
+
interface VideoWords {
|
|
131
|
+
/** The play button's name: the video's title and its provider. */
|
|
132
|
+
play(title: string, provider: string): string;
|
|
133
|
+
/** The play button's name with no title. */
|
|
134
|
+
playUntitled(provider: string): string;
|
|
135
|
+
pauseBackground: string;
|
|
136
|
+
playBackground: string;
|
|
137
|
+
/** The consent prompt (a group): its name, its lead and its body. */
|
|
138
|
+
promptLabel: string;
|
|
139
|
+
promptLead(provider: string): string;
|
|
140
|
+
promptBody(company: string): string;
|
|
141
|
+
/** The same prompt where the player is narrow (board 5's phone): one shorter sentence, its lead in bold. */
|
|
142
|
+
promptCompactLead(provider: string): string;
|
|
143
|
+
promptCompactBody: string;
|
|
144
|
+
allow: string;
|
|
145
|
+
settings: string;
|
|
146
|
+
}
|
|
147
|
+
declare const VIDEO_WORDS: Readonly<Record<'en' | 'he', VideoWords>>;
|
|
148
|
+
/** The words for a page language (`he`, `he-IL` → Hebrew; anything else → English). */
|
|
149
|
+
declare function videoWords(lang?: string | null): VideoWords;
|
|
150
|
+
/** The play button's accessible name. */
|
|
151
|
+
declare function videoPlayLabel(plan: VideoPlan, words?: VideoWords): string;
|
|
152
|
+
declare enum VideoClass {
|
|
153
|
+
Root = "iox-video",
|
|
154
|
+
Poster = "iox-video__poster",
|
|
155
|
+
Play = "iox-video__play",
|
|
156
|
+
Badge = "iox-video__badge",
|
|
157
|
+
Media = "iox-video__media",
|
|
158
|
+
Frame = "iox-video__frame",
|
|
159
|
+
Toggle = "iox-video__toggle",
|
|
160
|
+
Consent = "iox-video__consent",
|
|
161
|
+
ConsentText = "iox-video__consent-text",
|
|
162
|
+
/** The prompt's text on a wide player / on a narrow one (a container query shows one). */
|
|
163
|
+
ConsentWide = "iox-video__consent-wide",
|
|
164
|
+
ConsentCompact = "iox-video__consent-compact",
|
|
165
|
+
ConsentRow = "iox-video__consent-row",
|
|
166
|
+
Allow = "iox-video__allow",
|
|
167
|
+
Settings = "iox-video__settings"
|
|
168
|
+
}
|
|
169
|
+
declare enum VideoAttribute {
|
|
170
|
+
/** On the node element: its kind (`file` · `facade` · `empty`) — what the runtime attaches to. */
|
|
171
|
+
Root = "data-iox-video",
|
|
172
|
+
Source = "data-iox-video-source",
|
|
173
|
+
Id = "data-iox-video-id",
|
|
174
|
+
Hash = "data-iox-video-hash",
|
|
175
|
+
Mode = "data-iox-video-mode",
|
|
176
|
+
Autoplay = "data-iox-video-autoplay",
|
|
177
|
+
Consent = "data-iox-video-consent",
|
|
178
|
+
Title = "data-iox-video-title",
|
|
179
|
+
/** The poster is the token blank (no image): the shared CSS fills it with `surface-2`. */
|
|
180
|
+
PosterEmpty = "data-iox-video-poster-empty",
|
|
181
|
+
/** The player is loaded (after the click and the grant). */
|
|
182
|
+
Playing = "data-iox-video-playing",
|
|
183
|
+
/** The consent prompt is showing. */
|
|
184
|
+
Asking = "data-iox-video-asking"
|
|
185
|
+
}
|
|
186
|
+
declare enum VideoIcon {
|
|
187
|
+
Play = "ph-fill ph-play",
|
|
188
|
+
Pause = "ph-fill ph-pause"
|
|
189
|
+
}
|
|
190
|
+
/** The attributes the node's own element carries (both engine paths, the canvas). */
|
|
191
|
+
declare function videoRootAttributes(plan: VideoPlan): Record<string, string>;
|
|
192
|
+
interface VideoMarkupOptions {
|
|
193
|
+
/** HTML-escape an attribute or text value. */
|
|
194
|
+
esc: (value: string) => string;
|
|
195
|
+
/** The builder canvas: the poster ONLY — no `<video>`, no player, nothing that loads or takes a click. */
|
|
196
|
+
canvas?: boolean;
|
|
197
|
+
words?: VideoWords;
|
|
198
|
+
}
|
|
199
|
+
/** The element's inner markup. The server's HTML for a facade holds no `<iframe>` and requests nothing from
|
|
200
|
+
* the provider: only the studio's poster, the play button and the (hidden) consent prompt. */
|
|
201
|
+
declare function videoInnerHtml(plan: VideoPlan, options: VideoMarkupOptions): string;
|
|
202
|
+
/** Serialise attributes for a string renderer (a present boolean attribute is written bare). */
|
|
203
|
+
declare function serializeVideoAttributes(attrs: Readonly<Record<string, string>>, esc: (value: string) => string): string;
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* The Video's RUNTIME (CC3, board 5) — DOM only, ONE copy for both engine paths (the component path attaches it from
|
|
207
|
+
* its component, the string path from the page's interaction service; `attachVideoAt` is idempotent). It reads the
|
|
208
|
+
* markup `videoInnerHtml` writes. The builder canvas never attaches it (the canvas shows the poster only).
|
|
209
|
+
*
|
|
210
|
+
* - FACADE: play → the visitor's consent grants the category → the player's `<iframe>` replaces the poster, focused,
|
|
211
|
+
* autoplaying (the visitor just pressed play). Not granted → the consent prompt: "Allow and play" grants the
|
|
212
|
+
* category in the site's ONE consent record (the banner and its preferences read the same cookie) and plays;
|
|
213
|
+
* "Privacy settings" opens the site's own preferences dialog.
|
|
214
|
+
* - FILE: the play button starts the `<video>`; muted autoplay only when motion is allowed.
|
|
215
|
+
* - BACKGROUND: muted and looped; it plays only when motion is allowed (never under reduced motion), and its
|
|
216
|
+
* pause button is always there (WCAG 2.2.2).
|
|
217
|
+
*/
|
|
218
|
+
/** Ask the page to record a category the visitor granted (cancelable: the site's consent service handles it). */
|
|
219
|
+
declare const CONSENT_GRANT_EVENT = "iox-consent-grant";
|
|
220
|
+
/** Ask the page to open its consent preferences (cancelable: handled = a dialog opened). */
|
|
221
|
+
declare const CONSENT_PREFERENCES_EVENT = "iox-consent-preferences";
|
|
222
|
+
interface ConsentGrantDetail {
|
|
223
|
+
category: ConsentCategory;
|
|
224
|
+
}
|
|
225
|
+
/** How the runtime reads and writes the visitor's ONE consent record — the page's transport. */
|
|
226
|
+
interface ConsentTransport {
|
|
227
|
+
read(): ConsentDecision | null;
|
|
228
|
+
/** Record the category as granted (synchronously, before the player loads). */
|
|
229
|
+
grant(category: ConsentCategory): void;
|
|
230
|
+
/** Open the site's privacy preferences; false when the page has none. */
|
|
231
|
+
openPreferences(): boolean;
|
|
232
|
+
}
|
|
233
|
+
/** The cookie the consent record lives in, read from a document. */
|
|
234
|
+
declare function readConsentRecord(doc: Document): ConsentDecision | null;
|
|
235
|
+
/**
|
|
236
|
+
* The default transport: the page's own consent service answers the events (the engine records the decision, tells
|
|
237
|
+
* Consent Mode, re-syncs the banner's switches); a page with no such service (the admin's Preview) records the cookie
|
|
238
|
+
* itself, through the same consent-core decision, so the record is the same shape either way.
|
|
239
|
+
*/
|
|
240
|
+
declare function documentConsentTransport(doc: Document): ConsentTransport;
|
|
241
|
+
interface VideoRuntimeOptions {
|
|
242
|
+
consent?: ConsentTransport;
|
|
243
|
+
/** True to stop motion (default: the visitor's reduced-motion setting). */
|
|
244
|
+
reducedMotion?: () => boolean;
|
|
245
|
+
words?: VideoWords;
|
|
246
|
+
}
|
|
247
|
+
/** The embed a facade root names (its provider, id and hash attributes). */
|
|
248
|
+
declare function videoRefOf(root: Element): VideoRef | null;
|
|
249
|
+
declare class VideoRuntime {
|
|
250
|
+
readonly root: HTMLElement;
|
|
251
|
+
private readonly cleanups;
|
|
252
|
+
private readonly consent;
|
|
253
|
+
private readonly reduced;
|
|
254
|
+
private readonly words;
|
|
255
|
+
private readonly doc;
|
|
256
|
+
constructor(root: HTMLElement, options?: VideoRuntimeOptions);
|
|
257
|
+
destroy(): void;
|
|
258
|
+
/** The category a facade waits for (Marketing unless the node named another). */
|
|
259
|
+
private get category();
|
|
260
|
+
private on;
|
|
261
|
+
private attachFacade;
|
|
262
|
+
/** Play pressed: the player when the category is granted, else the consent prompt. */
|
|
263
|
+
requestPlay(): void;
|
|
264
|
+
/** The player — created only now, after the click and the grant. */
|
|
265
|
+
loadPlayer(): void;
|
|
266
|
+
private attachFile;
|
|
267
|
+
private attachBackground;
|
|
268
|
+
}
|
|
269
|
+
/** Run the video at `el` (a `[data-iox-video]` element) once; null when it is not one or already runs. */
|
|
270
|
+
declare function attachVideoAt(el: HTMLElement | null | undefined, options?: VideoRuntimeOptions): VideoRuntime | null;
|
|
271
|
+
/** Every video under `container` that is not running yet (the string path's globals and pages). */
|
|
272
|
+
declare function attachVideos(container: ParentNode, options?: VideoRuntimeOptions): VideoRuntime[];
|
|
273
|
+
/** The runtime already running at `el`, if any. */
|
|
274
|
+
declare function videoRuntimeOf(el: Element | null | undefined): VideoRuntime | null;
|
|
275
|
+
|
|
276
|
+
export { CONSENT_GRANT_EVENT, CONSENT_PREFERENCES_EVENT, VIDEO_EMBED_ORIGINS, VIDEO_NODE_TYPE, VIDEO_PROVIDER_COMPANY, VIDEO_PROVIDER_LABEL, VIDEO_WORDS, VideoAttribute, VideoClass, VideoIcon, VideoKind, VideoMode, VideoRuntime, VideoSource, VideoTrait, attachVideoAt, attachVideos, documentConsentTransport, isThirdPartyPoster, parseVideoUrl, planVideo, readConsentRecord, serializeVideoAttributes, videoConsentCategory, videoEmbedUrl, videoInnerHtml, videoPlayLabel, videoPosterFileName, videoProp, videoRefOf, videoRootAttributes, videoRuntimeOf, videoSourceOf, videoThumbnailRequest, videoWords };
|
|
277
|
+
export type { ConsentGrantDetail, ConsentTransport, VideoMarkupOptions, VideoNodeLike, VideoPlan, VideoRef, VideoRuntimeOptions, VideoThumbnailRequest, VideoWords };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vectoriox/iox-ui",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.38.0",
|
|
4
4
|
"peerDependencies": {
|
|
5
5
|
"@angular/common": ">=20.0.0",
|
|
6
6
|
"@angular/core": ">=20.0.0",
|
|
@@ -43,6 +43,10 @@
|
|
|
43
43
|
"types": "./generation-core/index.d.ts",
|
|
44
44
|
"default": "./fesm2022/vectoriox-iox-ui-generation-core.mjs"
|
|
45
45
|
},
|
|
46
|
+
"./media-core": {
|
|
47
|
+
"types": "./media-core/index.d.ts",
|
|
48
|
+
"default": "./fesm2022/vectoriox-iox-ui-media-core.mjs"
|
|
49
|
+
},
|
|
46
50
|
"./seo-core": {
|
|
47
51
|
"types": "./seo-core/index.d.ts",
|
|
48
52
|
"default": "./fesm2022/vectoriox-iox-ui-seo-core.mjs"
|
|
@@ -11,7 +11,7 @@ export type ActionKind = 'animated' | 'instant' | 'server';
|
|
|
11
11
|
|
|
12
12
|
/** Picker grouping. Flat lists die at ~10 entries; the group is data, never a second list. */
|
|
13
13
|
export type ActionGroup =
|
|
14
|
-
| 'motion' | 'visibility' | 'navigate' | 'dialog' | 'form' | 'data' | 'consent' | 'service';
|
|
14
|
+
| 'motion' | 'visibility' | 'navigate' | 'dialog' | 'item' | 'form' | 'data' | 'consent' | 'service';
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
17
|
* What a step does inside the live builder canvas (the canvas RUNS interactions):
|
|
@@ -29,10 +29,12 @@ export interface ActionParamSpec {
|
|
|
29
29
|
options?: readonly { value: string; labelKey: string }[];
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
/** Does this action point at another node — and may it be any node or only certain types? */
|
|
32
|
+
/** Does this action point at another node — and may it be any node, or only certain types / composite roles? */
|
|
33
33
|
export interface ActionTargetSpec {
|
|
34
34
|
required: boolean;
|
|
35
35
|
types?: string[];
|
|
36
|
+
/** Only nodes playing one of these composite roles (`openItem` targets an accordion item or a disclosure). */
|
|
37
|
+
roles?: string[];
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
/**
|
|
@@ -1,5 +1,10 @@
|
|
|
1
|
+
import { AccordionRole, CompositeItemAction, DisclosureRole, SLIDE_TO_ROW } from '@vectoriox/iox-ui/composite-core';
|
|
2
|
+
|
|
1
3
|
import { ActionDescriptor } from './action.model';
|
|
2
4
|
|
|
5
|
+
/** The items an item action may target: an accordion's item, or a disclosure (its own one item). */
|
|
6
|
+
const ITEM_TARGET_ROLES = [AccordionRole.Item, DisclosureRole.Disclosure];
|
|
7
|
+
|
|
3
8
|
/**
|
|
4
9
|
* The ELEMENT action catalog (actions plan A1) — what a node can DO, as data. One list feeds the
|
|
5
10
|
* grouped picker, the two interaction engines, and the AI composer's validation (§9: the model
|
|
@@ -40,13 +45,25 @@ export const ELEMENT_ACTION_DESCRIPTORS: readonly ActionDescriptor[] = Object.fr
|
|
|
40
45
|
target: { required: false } },
|
|
41
46
|
|
|
42
47
|
// ── Dialog (A1 — needs the Dialog component, A1b) ───────────────────────
|
|
48
|
+
// CC3 (board 6): "Open at the clicked item" — `slideTo: 'row'` opens a lightbox at the clicked thumbnail's slide.
|
|
43
49
|
{ type: 'openDialog', labelKey: 'builder.actions.openDialog', kind: 'instant', group: 'dialog', builderBehaviour: 'run',
|
|
44
|
-
target: { required: true, types: ['Dialog'] }
|
|
50
|
+
target: { required: true, types: ['Dialog'] },
|
|
51
|
+
params: [{ name: 'slideTo', type: 'string', label: 'builder.actions.params.slideTo', options: [{ value: SLIDE_TO_ROW, labelKey: 'builder.actions.params.slideToRow' }] }] },
|
|
45
52
|
{ type: 'closeDialog', labelKey: 'builder.actions.closeDialog', kind: 'instant', group: 'dialog', builderBehaviour: 'run',
|
|
46
53
|
target: { required: true, types: ['Dialog'] } },
|
|
47
54
|
{ type: 'toggleDialog', labelKey: 'builder.actions.toggleDialog', kind: 'instant', group: 'dialog', builderBehaviour: 'run',
|
|
48
55
|
target: { required: true, types: ['Dialog'] } },
|
|
49
56
|
|
|
57
|
+
// ── Item (CC2.1 — composite T14): drive an accordion item or a disclosure from anywhere ("See shipping"
|
|
58
|
+
// opens FAQ item 3). The canvas RUNS them, like the dialog actions: it is the same runtime, and an item you
|
|
59
|
+
// cannot open from the button is a button you cannot design.
|
|
60
|
+
{ type: 'openItem', labelKey: 'builder.actions.openItem', kind: 'instant', group: 'item', builderBehaviour: 'run',
|
|
61
|
+
target: { required: true, roles: ITEM_TARGET_ROLES } },
|
|
62
|
+
{ type: 'closeItem', labelKey: 'builder.actions.closeItem', kind: 'instant', group: 'item', builderBehaviour: 'run',
|
|
63
|
+
target: { required: true, roles: ITEM_TARGET_ROLES } },
|
|
64
|
+
{ type: 'toggleItem', labelKey: 'builder.actions.toggleItem', kind: 'instant', group: 'item', builderBehaviour: 'run',
|
|
65
|
+
target: { required: true, roles: ITEM_TARGET_ROLES } },
|
|
66
|
+
|
|
50
67
|
// ── Form (A1 — Tier A; the submit transport is the forms module's, I7 of its plan) ──
|
|
51
68
|
// simulate in the CANVAS: the builder's Form is a container twin with no real <form>,
|
|
52
69
|
// so there is nothing honest to run — the live engine requestSubmit()s the real one.
|
|
@@ -112,6 +129,24 @@ export const SERVER_ELEMENT_ACTION_TYPES: ReadonlySet<string> =
|
|
|
112
129
|
export const BEHAVIOUR_ACTION_TYPES: ReadonlySet<string> =
|
|
113
130
|
new Set(['navigate', 'scrollTo', 'submitForm', 'copyToClipboard']);
|
|
114
131
|
|
|
132
|
+
/** The item actions' dispatch set (T14) and what each does to its item — the engines branch on it beside
|
|
133
|
+
* DIALOG/BEHAVIOUR, and composite-core `runCompositeItemAction` runs it on the target item's own runtime. */
|
|
134
|
+
export const ITEM_ACTION_TYPES: Readonly<Record<string, CompositeItemAction>> = Object.freeze({
|
|
135
|
+
openItem: CompositeItemAction.Open,
|
|
136
|
+
closeItem: CompositeItemAction.Close,
|
|
137
|
+
toggleItem: CompositeItemAction.Toggle,
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
/** The item action an action type runs, or null when it is not one. */
|
|
141
|
+
/** True when an `openDialog` action opens its dialog at the clicked row's slide (CC3 "Open at the clicked item"). */
|
|
142
|
+
export function opensAtClickedItem(action: { type?: string; params?: Record<string, unknown> | null } | null | undefined): boolean {
|
|
143
|
+
return action?.type === 'openDialog' && action.params?.['slideTo'] === SLIDE_TO_ROW;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function itemActionOf(type: string | null | undefined): CompositeItemAction | null {
|
|
147
|
+
return (type && ITEM_ACTION_TYPES[type]) || null;
|
|
148
|
+
}
|
|
149
|
+
|
|
115
150
|
/** The consent actions' dispatch set (C3) — the engines branch on it beside DIALOG/BEHAVIOUR.
|
|
116
151
|
* `openConsentPreferences` is dialog-opening by nature and included so ONE branch owns consent. */
|
|
117
152
|
export const CONSENT_ACTION_TYPES: ReadonlySet<string> =
|
|
@@ -119,7 +154,7 @@ export const CONSENT_ACTION_TYPES: ReadonlySet<string> =
|
|
|
119
154
|
|
|
120
155
|
/** Picker order — most-used groups first, services last (they are the shop window, not the shop). */
|
|
121
156
|
export const ELEMENT_ACTION_GROUP_ORDER: readonly string[] =
|
|
122
|
-
Object.freeze(['motion', 'visibility', 'navigate', 'dialog', 'form', 'data', 'consent', 'service']);
|
|
157
|
+
Object.freeze(['motion', 'visibility', 'navigate', 'dialog', 'item', 'form', 'data', 'consent', 'service']);
|
|
123
158
|
|
|
124
159
|
/** The catalog, grouped and ordered for the picker — one derivation, no hand-kept second list. */
|
|
125
160
|
export function groupedElementActions(): ReadonlyArray<{ group: string; actions: ActionDescriptor[] }> {
|