@ds-gn/ui 0.9.0 → 0.10.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/custom-elements.json
CHANGED
|
@@ -1219,6 +1219,22 @@
|
|
|
1219
1219
|
"description": "Space below the content.",
|
|
1220
1220
|
"attribute": "padding-bottom",
|
|
1221
1221
|
"reflects": true
|
|
1222
|
+
},
|
|
1223
|
+
{
|
|
1224
|
+
"kind": "field",
|
|
1225
|
+
"name": "motion",
|
|
1226
|
+
"type": {
|
|
1227
|
+
"text": "MotionType"
|
|
1228
|
+
},
|
|
1229
|
+
"description": "Animate the section in when it scrolls into view. Each heading block\ninside fades in its eyebrow, writes its heading letter by letter, then\nfades in its description and actions; each form block then fades in its\ninput and button together, then its note. A section already in view when\nit loads, and reduced motion, stay static.",
|
|
1230
|
+
"attribute": "motion",
|
|
1231
|
+
"reflects": true
|
|
1232
|
+
},
|
|
1233
|
+
{
|
|
1234
|
+
"kind": "field",
|
|
1235
|
+
"name": "_entrance",
|
|
1236
|
+
"privacy": "private",
|
|
1237
|
+
"default": "new SectionEntranceController( this, () => this.motion === motionValues.ON, )"
|
|
1222
1238
|
}
|
|
1223
1239
|
],
|
|
1224
1240
|
"superclass": {
|
|
@@ -1268,6 +1284,14 @@
|
|
|
1268
1284
|
"text": "SectionSpaceType | undefined"
|
|
1269
1285
|
},
|
|
1270
1286
|
"description": "Space below the content."
|
|
1287
|
+
},
|
|
1288
|
+
{
|
|
1289
|
+
"name": "motion",
|
|
1290
|
+
"fieldName": "motion",
|
|
1291
|
+
"type": {
|
|
1292
|
+
"text": "MotionType"
|
|
1293
|
+
},
|
|
1294
|
+
"description": "Animate the section in when it scrolls into view. Each heading block\ninside fades in its eyebrow, writes its heading letter by letter, then\nfades in its description and actions; each form block then fades in its\ninput and button together, then its note. A section already in view when\nit loads, and reduced motion, stay static."
|
|
1271
1295
|
}
|
|
1272
1296
|
]
|
|
1273
1297
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { ReactiveController, ReactiveControllerHost } from 'lit';
|
|
2
|
+
type EntrancePart = {
|
|
3
|
+
kind: 'fade';
|
|
4
|
+
els: HTMLElement[];
|
|
5
|
+
} | {
|
|
6
|
+
kind: 'letters';
|
|
7
|
+
el: HTMLElement;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* The entrance parts inside `root`, in document order. Each heading block
|
|
11
|
+
* contributes its eyebrow (fade), heading (letter by letter), description,
|
|
12
|
+
* then actions; each form block its input and button together, then its note.
|
|
13
|
+
*/
|
|
14
|
+
export declare function sectionEntranceParts(root: Element): EntrancePart[];
|
|
15
|
+
/**
|
|
16
|
+
* Plays a section's entrance when it scrolls into view: the heading blocks
|
|
17
|
+
* and form blocks it contains animate in one timeline (see
|
|
18
|
+
* `sectionEntranceParts`). Decided from the first visibility report: a
|
|
19
|
+
* section already on screen, and reduced motion, stay static; one off screen
|
|
20
|
+
* holds its parts hidden (from script, so nothing is hidden when scripts do
|
|
21
|
+
* not run) and plays once when it comes into view.
|
|
22
|
+
*/
|
|
23
|
+
export declare class SectionEntranceController implements ReactiveController {
|
|
24
|
+
private readonly host;
|
|
25
|
+
private readonly enabled;
|
|
26
|
+
private readonly motion;
|
|
27
|
+
private observer?;
|
|
28
|
+
private state?;
|
|
29
|
+
private parts;
|
|
30
|
+
constructor(host: ReactiveControllerHost & HTMLElement, enabled: () => boolean);
|
|
31
|
+
hostUpdated(): void;
|
|
32
|
+
hostDisconnected(): void;
|
|
33
|
+
private report;
|
|
34
|
+
private play;
|
|
35
|
+
private stopObserving;
|
|
36
|
+
}
|
|
37
|
+
export {};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import '../../../types/global.d.ts';
|
|
2
2
|
import { type CSSResultGroup } from 'lit';
|
|
3
3
|
import { Base } from '../../../common/Base/Base.js';
|
|
4
|
+
import { type MotionType } from '../../../common/props/motion/motion.props.js';
|
|
4
5
|
import { type ContainerPaddingType, type ContainerSizeType, type SectionSpaceType } from './Container.props.js';
|
|
5
6
|
/** @slot - Constrained page content. */
|
|
6
7
|
export declare class Container extends Base {
|
|
@@ -22,6 +23,15 @@ export declare class Container extends Base {
|
|
|
22
23
|
paddingTop?: SectionSpaceType;
|
|
23
24
|
/** Space below the content. */
|
|
24
25
|
paddingBottom?: SectionSpaceType;
|
|
26
|
+
/**
|
|
27
|
+
* Animate the section in when it scrolls into view. Each heading block
|
|
28
|
+
* inside fades in its eyebrow, writes its heading letter by letter, then
|
|
29
|
+
* fades in its description and actions; each form block then fades in its
|
|
30
|
+
* input and button together, then its note. A section already in view when
|
|
31
|
+
* it loads, and reduced motion, stay static.
|
|
32
|
+
*/
|
|
33
|
+
motion: MotionType;
|
|
34
|
+
private _entrance;
|
|
25
35
|
render(): import("lit").TemplateResult<1>;
|
|
26
36
|
}
|
|
27
37
|
declare global {
|
|
@@ -91,6 +91,146 @@ class Base extends LitElement {
|
|
|
91
91
|
}
|
|
92
92
|
defineElement("dsgn-base", Base);
|
|
93
93
|
|
|
94
|
+
// common/controllers/SectionEntranceController.ts
|
|
95
|
+
import {
|
|
96
|
+
prefersReducedMotion,
|
|
97
|
+
splitText
|
|
98
|
+
} from "@ds-gn/motion";
|
|
99
|
+
import { MotionController } from "@ds-gn/motion/lit";
|
|
100
|
+
var HEADING_STAGGER = 20;
|
|
101
|
+
var HEADING_MAX_SPREAD = 500;
|
|
102
|
+
var HEADING_RISE = 4;
|
|
103
|
+
var MOTION_OVERLAP = 160;
|
|
104
|
+
var holdHidden = (el) => el.style.setProperty("opacity", "0");
|
|
105
|
+
var releaseHidden = (el) => el.style.removeProperty("opacity");
|
|
106
|
+
var ENTRANCE_BLOCKS = new Set(["dsgn-heading-block", "dsgn-form-block"]);
|
|
107
|
+
var hasContent = (el) => !["div", "span"].includes(el.localName) || el.children.length > 0 || (el.textContent ?? "").trim() !== "";
|
|
108
|
+
var childrenIn = (parent, ...slots) => Array.from(parent.children).filter((child) => child instanceof HTMLElement && slots.includes(child.slot) && hasContent(child));
|
|
109
|
+
function sectionEntranceParts(root) {
|
|
110
|
+
const parts = [];
|
|
111
|
+
const fade = (els) => {
|
|
112
|
+
if (els.length > 0)
|
|
113
|
+
parts.push({ kind: "fade", els });
|
|
114
|
+
};
|
|
115
|
+
Array.from(root.querySelectorAll("*")).filter((el) => ENTRANCE_BLOCKS.has(el.localName)).forEach((block) => {
|
|
116
|
+
if (block.localName === "dsgn-form-block") {
|
|
117
|
+
fade(childrenIn(block, "input", "button"));
|
|
118
|
+
fade(childrenIn(block, "note"));
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
fade(childrenIn(block, "eyebrow"));
|
|
122
|
+
const [heading] = childrenIn(block, "heading");
|
|
123
|
+
if (heading)
|
|
124
|
+
parts.push({ kind: "letters", el: heading });
|
|
125
|
+
fade(childrenIn(block, "description"));
|
|
126
|
+
fade(childrenIn(block, "action-primary", "action-secondary"));
|
|
127
|
+
});
|
|
128
|
+
return parts;
|
|
129
|
+
}
|
|
130
|
+
var elementsOf = (part) => part.kind === "fade" ? part.els : [part.el];
|
|
131
|
+
|
|
132
|
+
class SectionEntranceController {
|
|
133
|
+
constructor(host, enabled) {
|
|
134
|
+
this.parts = [];
|
|
135
|
+
this.host = host;
|
|
136
|
+
this.enabled = enabled;
|
|
137
|
+
this.motion = new MotionController(host);
|
|
138
|
+
host.addController(this);
|
|
139
|
+
}
|
|
140
|
+
hostUpdated() {
|
|
141
|
+
if (this.observer || this.state !== undefined || !this.enabled())
|
|
142
|
+
return;
|
|
143
|
+
if (typeof IntersectionObserver === "undefined") {
|
|
144
|
+
this.state = "static";
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
this.observer = new IntersectionObserver((entries) => {
|
|
148
|
+
const entry = entries[entries.length - 1];
|
|
149
|
+
if (entry)
|
|
150
|
+
this.report(entry.isIntersecting);
|
|
151
|
+
});
|
|
152
|
+
this.observer.observe(this.host);
|
|
153
|
+
}
|
|
154
|
+
hostDisconnected() {
|
|
155
|
+
this.stopObserving();
|
|
156
|
+
if (this.state === "held") {
|
|
157
|
+
this.parts.flatMap(elementsOf).forEach(releaseHidden);
|
|
158
|
+
this.state = undefined;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
report(inView) {
|
|
162
|
+
if (this.state === undefined) {
|
|
163
|
+
const play = !inView && !prefersReducedMotion();
|
|
164
|
+
this.state = play ? "held" : "static";
|
|
165
|
+
if (!play)
|
|
166
|
+
return this.stopObserving();
|
|
167
|
+
this.parts = sectionEntranceParts(this.host);
|
|
168
|
+
this.parts.flatMap(elementsOf).forEach(holdHidden);
|
|
169
|
+
}
|
|
170
|
+
if (this.state === "held" && inView) {
|
|
171
|
+
this.state = "played";
|
|
172
|
+
this.stopObserving();
|
|
173
|
+
this.play();
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
play() {
|
|
177
|
+
this.parts.flatMap(elementsOf).forEach(releaseHidden);
|
|
178
|
+
const splits = [];
|
|
179
|
+
const steps = this.parts.map((part, index) => {
|
|
180
|
+
const overlap = index === 0 ? undefined : MOTION_OVERLAP;
|
|
181
|
+
if (part.kind === "fade") {
|
|
182
|
+
return { targets: part.els, preset: "fade", stagger: 0, overlap };
|
|
183
|
+
}
|
|
184
|
+
const split = splitText(part.el);
|
|
185
|
+
splits.push(split);
|
|
186
|
+
return {
|
|
187
|
+
targets: split.chars,
|
|
188
|
+
preset: "fade",
|
|
189
|
+
y: HEADING_RISE,
|
|
190
|
+
stagger: HEADING_STAGGER,
|
|
191
|
+
maxDuration: HEADING_MAX_SPREAD
|
|
192
|
+
};
|
|
193
|
+
});
|
|
194
|
+
if (steps.length === 0)
|
|
195
|
+
return;
|
|
196
|
+
this.motion.timeline(steps).finished.then(() => splits.forEach((split) => split.revert()));
|
|
197
|
+
}
|
|
198
|
+
stopObserving() {
|
|
199
|
+
this.observer?.disconnect();
|
|
200
|
+
this.observer = undefined;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// common/helpers/enum-validator.ts
|
|
205
|
+
var enumValidator = (propName, enumSet, candidateValue) => {
|
|
206
|
+
if (!enumSet.has(candidateValue)) {
|
|
207
|
+
throw new Error(`Invalid prop ${propName} of value ${candidateValue}. Expected one of: ${Array.from(enumSet).join(", ")}`);
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
// common/helpers/enum-creator.ts
|
|
212
|
+
var enumCreator = (acceptableValues, propName, type, other = {}) => {
|
|
213
|
+
const acceptableValuesSet = new Set(acceptableValues);
|
|
214
|
+
return {
|
|
215
|
+
type,
|
|
216
|
+
hasChanged(newValue, oldValue) {
|
|
217
|
+
if (newValue !== oldValue) {
|
|
218
|
+
enumValidator(propName, acceptableValuesSet, newValue);
|
|
219
|
+
return true;
|
|
220
|
+
}
|
|
221
|
+
return false;
|
|
222
|
+
},
|
|
223
|
+
...other
|
|
224
|
+
};
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
// common/props/motion/motion.props.ts
|
|
228
|
+
var motionValues = {
|
|
229
|
+
ON: "on",
|
|
230
|
+
OFF: "off"
|
|
231
|
+
};
|
|
232
|
+
var motionProps = enumCreator(Object.values(motionValues), "motion", String, { reflect: true });
|
|
233
|
+
|
|
94
234
|
// components/Container/src/Container.css
|
|
95
235
|
import { css as css2 } from "lit";
|
|
96
236
|
var styles2 = css2`/*! tailwindcss v4.3.0 | MIT License | https://tailwindcss.com */
|
|
@@ -138,6 +278,9 @@ var styles2 = css2`/*! tailwindcss v4.3.0 | MIT License | https://tailwindcss.co
|
|
|
138
278
|
.mx-auto {
|
|
139
279
|
margin-inline: auto;
|
|
140
280
|
}
|
|
281
|
+
.block {
|
|
282
|
+
display: block;
|
|
283
|
+
}
|
|
141
284
|
.w-full {
|
|
142
285
|
width: 100%;
|
|
143
286
|
}
|
|
@@ -229,29 +372,6 @@ var styles2 = css2`/*! tailwindcss v4.3.0 | MIT License | https://tailwindcss.co
|
|
|
229
372
|
`;
|
|
230
373
|
var Container_default = styles2;
|
|
231
374
|
|
|
232
|
-
// common/helpers/enum-validator.ts
|
|
233
|
-
var enumValidator = (propName, enumSet, candidateValue) => {
|
|
234
|
-
if (!enumSet.has(candidateValue)) {
|
|
235
|
-
throw new Error(`Invalid prop ${propName} of value ${candidateValue}. Expected one of: ${Array.from(enumSet).join(", ")}`);
|
|
236
|
-
}
|
|
237
|
-
};
|
|
238
|
-
|
|
239
|
-
// common/helpers/enum-creator.ts
|
|
240
|
-
var enumCreator = (acceptableValues, propName, type, other = {}) => {
|
|
241
|
-
const acceptableValuesSet = new Set(acceptableValues);
|
|
242
|
-
return {
|
|
243
|
-
type,
|
|
244
|
-
hasChanged(newValue, oldValue) {
|
|
245
|
-
if (newValue !== oldValue) {
|
|
246
|
-
enumValidator(propName, acceptableValuesSet, newValue);
|
|
247
|
-
return true;
|
|
248
|
-
}
|
|
249
|
-
return false;
|
|
250
|
-
},
|
|
251
|
-
...other
|
|
252
|
-
};
|
|
253
|
-
};
|
|
254
|
-
|
|
255
375
|
// components/Container/src/Container.props.ts
|
|
256
376
|
var containerSizeValues = {
|
|
257
377
|
SM: "sm",
|
|
@@ -341,6 +461,8 @@ class Container extends Base {
|
|
|
341
461
|
super(...arguments);
|
|
342
462
|
this.size = containerSizeValues.LG;
|
|
343
463
|
this.section = false;
|
|
464
|
+
this.motion = motionValues.OFF;
|
|
465
|
+
this._entrance = new SectionEntranceController(this, () => this.motion === motionValues.ON);
|
|
344
466
|
}
|
|
345
467
|
static styles = [Base.styles, Container_default];
|
|
346
468
|
render() {
|
|
@@ -366,6 +488,9 @@ __legacyDecorateClassTS([
|
|
|
366
488
|
__legacyDecorateClassTS([
|
|
367
489
|
property({ ...paddingBottomProps })
|
|
368
490
|
], Container.prototype, "paddingBottom", undefined);
|
|
491
|
+
__legacyDecorateClassTS([
|
|
492
|
+
property({ ...motionProps })
|
|
493
|
+
], Container.prototype, "motion", undefined);
|
|
369
494
|
defineElement("dsgn-container", Container);
|
|
370
495
|
export {
|
|
371
496
|
Container
|