@vectoriox/iox-ui 5.63.0 → 5.65.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 +62 -2
- package/fesm2022/vectoriox-iox-ui-actions-core.mjs +80 -1
- package/fesm2022/vectoriox-iox-ui-actions-core.mjs.map +1 -1
- package/fesm2022/vectoriox-iox-ui-generation-core.mjs +174 -34
- package/fesm2022/vectoriox-iox-ui-generation-core.mjs.map +1 -1
- package/generation-core/index.d.ts +29 -5
- package/package.json +1 -1
- package/src/actions-core/dialog-motion.ts +104 -0
- package/src/actions-core/public-api.ts +1 -0
- package/src/generation-core/compose-site.ts +21 -23
- package/src/generation-core/nav-shape.ts +31 -11
- package/src/generation-core/plan-derived.ts +84 -0
- package/src/generation-core/plan-edits.ts +8 -3
- package/src/generation-core/plan-wire.ts +9 -4
- package/src/generation-core/public-api.ts +2 -0
- package/src/generation-core/recipe-fields.ts +49 -0
- package/src/generation-core/validate-site-plan.ts +15 -2
- package/styles/iox-design.css +67 -8
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
FACT_KEYS, GlobalSlot, PAGE_ARCHETYPES, SITE_NEED_IDS, PlanDataSourceMode, PlanDataSourceType, RelativeOp, REQUIRED_CONTENT_ROLES, SitePlan, SitePlanSourceKind,
|
|
11
11
|
SITE_LOCALES, SITE_PLAN_VERSION,
|
|
12
12
|
} from './site-plan.model';
|
|
13
|
+
import { patternListGap } from './recipe-fields';
|
|
13
14
|
import { SPEC_SITE_PLAN_SHAPE } from './site-spec.generated';
|
|
14
15
|
import {
|
|
15
16
|
isNonEmptyString, isPlainObject as isObject, PathIssue, PathIssueCollector, pathAt as at, PlainObject as Obj, requiredShapeKeys, ShapeEntry, shapeKeys,
|
|
@@ -187,7 +188,12 @@ function validateDesign(c: Collector, design: unknown): void {
|
|
|
187
188
|
if (design['fonts'] !== undefined && !isObject(design['fonts'])) bad('fonts', 'must be an object');
|
|
188
189
|
}
|
|
189
190
|
|
|
190
|
-
interface ContentTypeInfo {
|
|
191
|
+
interface ContentTypeInfo {
|
|
192
|
+
fields: Set<string>;
|
|
193
|
+
/** The fields as a recipe's pickers read them (`recipe-fields`). */
|
|
194
|
+
described: Array<{ name: string; type?: string; controller?: string }>;
|
|
195
|
+
roles: Record<string, string>;
|
|
196
|
+
}
|
|
191
197
|
|
|
192
198
|
function validateContentTypes(c: Collector, list: unknown[]): Map<string, ContentTypeInfo> {
|
|
193
199
|
const types = new Map<string, ContentTypeInfo>();
|
|
@@ -223,11 +229,13 @@ function validateContentTypes(c: Collector, list: unknown[]): Map<string, Conten
|
|
|
223
229
|
}
|
|
224
230
|
}
|
|
225
231
|
if (name && types.has(name)) c.add(SitePlanIssueCode.Duplicate, `${path}.name`, `content type "${name}" is used twice`);
|
|
226
|
-
else if (name) types.set(name, { fields: fieldSet, roles });
|
|
232
|
+
else if (name) types.set(name, { fields: fieldSet, described: fields.filter(isObject).map(describeField), roles });
|
|
227
233
|
});
|
|
228
234
|
return types;
|
|
229
235
|
}
|
|
230
236
|
|
|
237
|
+
const describeField = (f: Obj) => ({ name: String(f['name']), type: typeof f['type'] === 'string' ? f['type'] : undefined, controller: typeof f['controller'] === 'string' ? f['controller'] : undefined });
|
|
238
|
+
|
|
231
239
|
/** A content type's roles (L0-3): known roles only, each played by a field of the type; `title` always; `gallery` a list. */
|
|
232
240
|
function validateRoles(c: Collector, roles: unknown, path: string, typeName: string | null, fields: Set<string>, fieldTypes: Map<unknown, unknown>): Record<string, string> {
|
|
233
241
|
if (!isObject(roles)) { c.add(SitePlanIssueCode.Shape, path, 'must be an object (which field plays each role)'); return {}; }
|
|
@@ -396,6 +404,11 @@ function validatePages(c: Collector, list: unknown[], types: Map<string, Content
|
|
|
396
404
|
const src = sources.get(binding);
|
|
397
405
|
if (!src) c.add(SitePlanIssueCode.UnknownDataSource, `${sp}.binding`, `no data source "${binding}"`);
|
|
398
406
|
else if (src.type !== PlanDataSourceType.Cms || src.mode !== PlanDataSourceMode.Query) c.add(SitePlanIssueCode.Binding, `${sp}.binding`, `"${binding}" is not a list source`);
|
|
407
|
+
else {
|
|
408
|
+
const listed = src.contentType ? types.get(src.contentType) : undefined;
|
|
409
|
+
const gap = listed ? patternListGap(pattern, listed.described) : null;
|
|
410
|
+
if (gap) c.add(SitePlanIssueCode.Binding, `${sp}.binding`, `"${binding}" lists ${src.contentType}, which has no ${gap} — bind "${patternId}" to a list that has${patternNeedsBinding(pattern) ? '' : ', or to none'}`);
|
|
411
|
+
}
|
|
399
412
|
}
|
|
400
413
|
} else if (binding) c.add(SitePlanIssueCode.Binding, `${sp}.binding`, `"${patternId}" repeats nothing — it takes no binding`);
|
|
401
414
|
|
package/styles/iox-design.css
CHANGED
|
@@ -118,15 +118,17 @@ iox-gallery.iox-es-media[data-iox-empty] > .iox-gallery {
|
|
|
118
118
|
|
|
119
119
|
/* The DRAWER (board 1, board-8 Q9): a Dialog wearing an element style, placed at the start or end edge, is
|
|
120
120
|
a full-height sheet on that edge — 100dvh (never 100vh: Safari's toolbar), no gap to the edge, the
|
|
121
|
-
radius kept on its LEADING edge only (a placement rule, not a token).
|
|
122
|
-
|
|
123
|
-
placement margins (0,2,1) and the node rule; a Dialog saved before element styles keeps its look. */
|
|
121
|
+
radius kept on its LEADING edge only (a placement rule, not a token). Beats the engine's placement
|
|
122
|
+
margins (0,2,1) and the node rule; a Dialog saved before element styles keeps its look. */
|
|
124
123
|
dialog[data-iox-dialog].iox-es:is([data-iox-dialog-placement='end'], [data-iox-dialog-placement='start']) {
|
|
125
124
|
margin-block: 0;
|
|
126
125
|
height: 100dvh;
|
|
127
126
|
max-height: 100dvh;
|
|
128
|
-
transition: translate var(--iox-dur-base) var(--iox-ease-standard), overlay var(--iox-dur-base) allow-discrete, display var(--iox-dur-base) allow-discrete;
|
|
129
127
|
}
|
|
128
|
+
/* A NAVIGATION's drawer is padded like the header it opens from, so its Close (the menu button's size,
|
|
129
|
+
generation-core nav-shape) lands where the menu button is — the same button, turned into an X. The token is
|
|
130
|
+
re-declared, not the padding overridden: a padding the author sets on the drawer still wins. */
|
|
131
|
+
.iox-es.iox-role-nav-drawer { --iox-dialog-pad: var(--iox-nav-pad); }
|
|
130
132
|
dialog[data-iox-dialog].iox-es[data-iox-dialog-placement='end'] {
|
|
131
133
|
margin-inline: auto 0;
|
|
132
134
|
border-start-end-radius: 0;
|
|
@@ -137,11 +139,68 @@ dialog[data-iox-dialog].iox-es[data-iox-dialog-placement='start'] {
|
|
|
137
139
|
border-start-start-radius: 0;
|
|
138
140
|
border-end-start-radius: 0;
|
|
139
141
|
}
|
|
142
|
+
/* How a styled dialog COMES and GOES (CC1 follow-up). Three states, one look each:
|
|
143
|
+
· at rest, open — in place, the scrim on;
|
|
144
|
+
· entering (`@starting-style`) and leaving (`data-iox-closing`, set by actions-core `closeDialogAnimated`
|
|
145
|
+
for the whole exit, and `:not([open])` for a browser that closes it natively) — the drawer off its edge,
|
|
146
|
+
any other placement faded 12px toward where it sits, the scrim clear.
|
|
147
|
+
Entering decelerates (ease-enter), leaving accelerates (ease-exit), both over dur-base — the look's motion
|
|
148
|
+
tokens, which the reduced-motion token block collapses to 0ms: under reduced motion it simply appears and
|
|
149
|
+
goes. `overlay`/`display` allow-discrete keep a natively closed dialog in the top layer for its exit. */
|
|
150
|
+
dialog[data-iox-dialog].iox-es {
|
|
151
|
+
--dialog-exit-x: 0px;
|
|
152
|
+
--dialog-exit-y: 12px;
|
|
153
|
+
--dialog-exit-opacity: 0;
|
|
154
|
+
transition:
|
|
155
|
+
translate var(--iox-dur-base) var(--iox-ease-enter),
|
|
156
|
+
opacity var(--iox-dur-base) var(--iox-ease-enter),
|
|
157
|
+
overlay var(--iox-dur-base) allow-discrete,
|
|
158
|
+
display var(--iox-dur-base) allow-discrete;
|
|
159
|
+
}
|
|
160
|
+
dialog[data-iox-dialog].iox-es[data-iox-dialog-placement='top'] { --dialog-exit-y: -12px; }
|
|
161
|
+
dialog[data-iox-dialog].iox-es:is([data-iox-dialog-placement='end'], [data-iox-dialog-placement='start']) {
|
|
162
|
+
--dialog-exit-x: 100%;
|
|
163
|
+
--dialog-exit-y: 0px;
|
|
164
|
+
--dialog-exit-opacity: 1;
|
|
165
|
+
}
|
|
166
|
+
dialog[data-iox-dialog].iox-es[data-iox-dialog-placement='start'],
|
|
167
|
+
[dir='rtl'] dialog[data-iox-dialog].iox-es[data-iox-dialog-placement='end'] { --dialog-exit-x: -100%; }
|
|
168
|
+
[dir='rtl'] dialog[data-iox-dialog].iox-es[data-iox-dialog-placement='start'] { --dialog-exit-x: 100%; }
|
|
169
|
+
dialog[data-iox-dialog].iox-es:is([data-iox-closing], :not([open])) {
|
|
170
|
+
translate: var(--dialog-exit-x) var(--dialog-exit-y);
|
|
171
|
+
opacity: var(--dialog-exit-opacity);
|
|
172
|
+
transition-timing-function: var(--iox-ease-exit);
|
|
173
|
+
}
|
|
174
|
+
@starting-style {
|
|
175
|
+
dialog[data-iox-dialog].iox-es[open] {
|
|
176
|
+
translate: var(--dialog-exit-x) var(--dialog-exit-y);
|
|
177
|
+
opacity: var(--dialog-exit-opacity);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
dialog[data-iox-dialog].iox-es::backdrop {
|
|
181
|
+
transition:
|
|
182
|
+
opacity var(--iox-dur-base) var(--iox-ease-enter),
|
|
183
|
+
overlay var(--iox-dur-base) allow-discrete,
|
|
184
|
+
display var(--iox-dur-base) allow-discrete;
|
|
185
|
+
}
|
|
186
|
+
dialog[data-iox-dialog].iox-es:is([data-iox-closing], :not([open]))::backdrop {
|
|
187
|
+
opacity: 0;
|
|
188
|
+
transition-timing-function: var(--iox-ease-exit);
|
|
189
|
+
}
|
|
140
190
|
@starting-style {
|
|
141
|
-
dialog[data-iox-dialog].iox-es[
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
191
|
+
dialog[data-iox-dialog].iox-es[open]::backdrop { opacity: 0; }
|
|
192
|
+
}
|
|
193
|
+
/* The drawer's pages follow it in, a beat apart (board 1) — a quarter of dur-fast per link, so they collapse
|
|
194
|
+
with the tokens under reduced motion. They leave with the sheet, not one by one. */
|
|
195
|
+
dialog[data-iox-dialog].iox-es[open]:not([data-iox-closing]) .iox-role-nav-drawer-links > * {
|
|
196
|
+
animation: iox-drawer-link-in var(--iox-dur-base) var(--iox-ease-enter) both;
|
|
197
|
+
}
|
|
198
|
+
dialog[data-iox-dialog].iox-es[open] .iox-role-nav-drawer-links > :nth-child(2) { animation-delay: calc(var(--iox-dur-fast) * 0.25); }
|
|
199
|
+
dialog[data-iox-dialog].iox-es[open] .iox-role-nav-drawer-links > :nth-child(3) { animation-delay: calc(var(--iox-dur-fast) * 0.5); }
|
|
200
|
+
dialog[data-iox-dialog].iox-es[open] .iox-role-nav-drawer-links > :nth-child(4) { animation-delay: calc(var(--iox-dur-fast) * 0.75); }
|
|
201
|
+
dialog[data-iox-dialog].iox-es[open] .iox-role-nav-drawer-links > :nth-child(n + 5) { animation-delay: var(--iox-dur-fast); }
|
|
202
|
+
@keyframes iox-drawer-link-in {
|
|
203
|
+
from { opacity: 0; translate: 0 8px; }
|
|
145
204
|
}
|
|
146
205
|
|
|
147
206
|
/* A navigation's link lists (board 1): no underline at rest — the current page's link is the one
|