@peekling/runtime 0.1.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/AUTHORS +8 -0
- package/LICENSE +202 -0
- package/LICENSING.md +20 -0
- package/NOTICE +6 -0
- package/README.md +294 -0
- package/dist/configuration-validation.d.ts +19 -0
- package/dist/configuration-validation.d.ts.map +1 -0
- package/dist/configuration-validation.js +298 -0
- package/dist/content.d.ts +44 -0
- package/dist/content.d.ts.map +1 -0
- package/dist/content.js +738 -0
- package/dist/contracts.d.ts +10 -0
- package/dist/contracts.d.ts.map +1 -0
- package/dist/contracts.js +106 -0
- package/dist/defaults.d.ts +7 -0
- package/dist/defaults.d.ts.map +1 -0
- package/dist/defaults.js +6 -0
- package/dist/diagnostics.d.ts +25 -0
- package/dist/diagnostics.d.ts.map +1 -0
- package/dist/diagnostics.js +122 -0
- package/dist/direction.d.ts +3 -0
- package/dist/direction.d.ts.map +1 -0
- package/dist/direction.js +6 -0
- package/dist/errors.d.ts +15 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +53 -0
- package/dist/events.d.ts +5 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +60 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/input.d.ts +31 -0
- package/dist/input.d.ts.map +1 -0
- package/dist/input.js +215 -0
- package/dist/json.d.ts +3 -0
- package/dist/json.d.ts.map +1 -0
- package/dist/json.js +109 -0
- package/dist/loader.d.ts +33 -0
- package/dist/loader.d.ts.map +1 -0
- package/dist/loader.js +477 -0
- package/dist/locomotion.d.ts +8 -0
- package/dist/locomotion.d.ts.map +1 -0
- package/dist/locomotion.js +29 -0
- package/dist/normalized.d.ts +5 -0
- package/dist/normalized.d.ts.map +1 -0
- package/dist/normalized.js +266 -0
- package/dist/overrides.d.ts +29 -0
- package/dist/overrides.d.ts.map +1 -0
- package/dist/overrides.js +296 -0
- package/dist/own-data.d.ts +18 -0
- package/dist/own-data.d.ts.map +1 -0
- package/dist/own-data.js +205 -0
- package/dist/pack-api.d.ts +9 -0
- package/dist/pack-api.d.ts.map +1 -0
- package/dist/pack-api.js +6 -0
- package/dist/pack-shared.d.ts +28 -0
- package/dist/pack-shared.d.ts.map +1 -0
- package/dist/pack-shared.js +408 -0
- package/dist/pack.d.ts +6 -0
- package/dist/pack.d.ts.map +1 -0
- package/dist/pack.js +316 -0
- package/dist/peekling.css +1 -0
- package/dist/peekling.css.sri +1 -0
- package/dist/peekling.js +7469 -0
- package/dist/peekling.js.sri +1 -0
- package/dist/peekling.min.js +2 -0
- package/dist/peekling.min.js.sri +1 -0
- package/dist/plan-compiler.d.ts +82 -0
- package/dist/plan-compiler.d.ts.map +1 -0
- package/dist/plan-compiler.js +724 -0
- package/dist/plan.d.ts +21 -0
- package/dist/plan.d.ts.map +1 -0
- package/dist/plan.js +287 -0
- package/dist/preflight-api.d.ts +3 -0
- package/dist/preflight-api.d.ts.map +1 -0
- package/dist/preflight-api.js +1 -0
- package/dist/preflight.d.ts +22 -0
- package/dist/preflight.d.ts.map +1 -0
- package/dist/preflight.js +158 -0
- package/dist/registry.d.ts +9 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +24 -0
- package/dist/renderer.d.ts +16 -0
- package/dist/renderer.d.ts.map +1 -0
- package/dist/renderer.js +176 -0
- package/dist/resolver.d.ts +3 -0
- package/dist/resolver.d.ts.map +1 -0
- package/dist/resolver.js +30 -0
- package/dist/runtime-diagnostics.d.ts +3 -0
- package/dist/runtime-diagnostics.d.ts.map +1 -0
- package/dist/runtime-diagnostics.js +5 -0
- package/dist/runtime-validation.d.ts +6 -0
- package/dist/runtime-validation.d.ts.map +1 -0
- package/dist/runtime-validation.js +104 -0
- package/dist/runtime.d.ts +87 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +1015 -0
- package/dist/sections.d.ts +15 -0
- package/dist/sections.d.ts.map +1 -0
- package/dist/sections.js +221 -0
- package/dist/styles.d.ts +18 -0
- package/dist/styles.d.ts.map +1 -0
- package/dist/styles.js +132 -0
- package/dist/tooling-preflight.d.ts +23 -0
- package/dist/tooling-preflight.d.ts.map +1 -0
- package/dist/tooling-preflight.js +207 -0
- package/dist/tooling-validation.d.ts +8 -0
- package/dist/tooling-validation.d.ts.map +1 -0
- package/dist/tooling-validation.js +94 -0
- package/dist/types.d.ts +403 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +29 -0
- package/dist/visibility.d.ts +12 -0
- package/dist/visibility.d.ts.map +1 -0
- package/dist/visibility.js +333 -0
- package/dist/web-component.d.ts +31 -0
- package/dist/web-component.d.ts.map +1 -0
- package/dist/web-component.js +236 -0
- package/package.json +74 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { SectionSnapshot } from "./types.js";
|
|
2
|
+
export interface SectionVisibilityFact {
|
|
3
|
+
readonly [key: string]: string | number;
|
|
4
|
+
readonly selector: string;
|
|
5
|
+
readonly previousRatio: number;
|
|
6
|
+
readonly ratio: number;
|
|
7
|
+
}
|
|
8
|
+
export declare class SectionTracker {
|
|
9
|
+
#private;
|
|
10
|
+
constructor(document: Document, window: Window, selectors: readonly string[], thresholds: readonly number[], wake: () => void, capture?: (fact: SectionVisibilityFact) => void);
|
|
11
|
+
snapshot(): Readonly<Record<string, SectionSnapshot>>;
|
|
12
|
+
setSuspended(value: boolean): void;
|
|
13
|
+
destroy(): void;
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=sections.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sections.d.ts","sourceRoot":"","sources":["../src/sections.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAalD,MAAM,WAAW,qBAAqB;IACpC,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;IACxC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED,qBAAa,cAAc;;IAezB,YACE,QAAQ,EAAE,QAAQ,EAClB,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,SAAS,MAAM,EAAE,EAC5B,UAAU,EAAE,SAAS,MAAM,EAAE,EAC7B,IAAI,EAAE,MAAM,IAAI,EAChB,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,qBAAqB,KAAK,IAAI,EA4DhD;IAED,QAAQ,IAAI,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC,CAiBpD;IAED,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAmBjC;IAED,OAAO,IAAI,IAAI,CASd;CA+FF"}
|
package/dist/sections.js
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { runtimeMessage } from "./runtime-diagnostics.js";
|
|
2
|
+
const RUNTIME_ROOTS = "[data-peekling-host],[data-peekling-content],[data-peekling-controls]";
|
|
3
|
+
const REFRESH_CADENCE_MS = 250;
|
|
4
|
+
export class SectionTracker {
|
|
5
|
+
#document;
|
|
6
|
+
#window;
|
|
7
|
+
#states = new Map();
|
|
8
|
+
#elements = new Map();
|
|
9
|
+
#observer;
|
|
10
|
+
#mutations;
|
|
11
|
+
#wake;
|
|
12
|
+
#capture;
|
|
13
|
+
#suspended = false;
|
|
14
|
+
#destroyed = false;
|
|
15
|
+
#dirty = false;
|
|
16
|
+
#failure;
|
|
17
|
+
#refreshTimer = 0;
|
|
18
|
+
constructor(document, window, selectors, thresholds, wake, capture) {
|
|
19
|
+
this.#document = document;
|
|
20
|
+
this.#window = window;
|
|
21
|
+
this.#wake = wake;
|
|
22
|
+
this.#capture = capture;
|
|
23
|
+
const initial = selectors.map((selector) => [selector, this.#select(selector)]);
|
|
24
|
+
const BrowserIntersectionObserver = window
|
|
25
|
+
.IntersectionObserver;
|
|
26
|
+
if (BrowserIntersectionObserver) {
|
|
27
|
+
this.#observer = new BrowserIntersectionObserver((entries) => {
|
|
28
|
+
let changed = false;
|
|
29
|
+
for (const entry of entries) {
|
|
30
|
+
for (const selector of this.#elements.get(entry.target) ?? []) {
|
|
31
|
+
const state = this.#states.get(selector);
|
|
32
|
+
if (!state)
|
|
33
|
+
continue;
|
|
34
|
+
const ratio = entry.isIntersecting ? entry.intersectionRatio : 0;
|
|
35
|
+
changed = this.#change(selector, state, ratio) || changed;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (changed)
|
|
39
|
+
wake();
|
|
40
|
+
}, { threshold: [...thresholds] });
|
|
41
|
+
}
|
|
42
|
+
const BrowserMutationObserver = window
|
|
43
|
+
.MutationObserver;
|
|
44
|
+
if (BrowserMutationObserver) {
|
|
45
|
+
this.#mutations = new BrowserMutationObserver((records) => {
|
|
46
|
+
if (this.#suspended ||
|
|
47
|
+
this.#destroyed ||
|
|
48
|
+
this.#refreshTimer ||
|
|
49
|
+
this.#dirty ||
|
|
50
|
+
(records.length > 0 && records.every(runtimeOwnedMutation))) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
let needsRefresh = false;
|
|
54
|
+
for (const state of this.#states.values()) {
|
|
55
|
+
if (!state[0]?.isConnected) {
|
|
56
|
+
needsRefresh = true;
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (!needsRefresh)
|
|
61
|
+
return;
|
|
62
|
+
this.#scheduleRefresh();
|
|
63
|
+
});
|
|
64
|
+
this.#mutations.observe(document.documentElement, {
|
|
65
|
+
childList: true,
|
|
66
|
+
subtree: true,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
for (const [selector, element] of initial) {
|
|
70
|
+
this.#states.set(selector, [undefined, 0, 0]);
|
|
71
|
+
this.#observe(selector, element);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
snapshot() {
|
|
75
|
+
if (this.#failure)
|
|
76
|
+
throw this.#failure;
|
|
77
|
+
if (this.#dirty) {
|
|
78
|
+
this.#dirty = false;
|
|
79
|
+
this.#refreshGuarded();
|
|
80
|
+
if (this.#failure)
|
|
81
|
+
throw this.#failure;
|
|
82
|
+
}
|
|
83
|
+
const result = Object.create(null);
|
|
84
|
+
for (const [selector, state] of this.#states) {
|
|
85
|
+
result[selector] = {
|
|
86
|
+
ratio: state[2],
|
|
87
|
+
previousRatio: state[1],
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
return result;
|
|
91
|
+
}
|
|
92
|
+
setSuspended(value) {
|
|
93
|
+
if (this.#destroyed || value === this.#suspended)
|
|
94
|
+
return;
|
|
95
|
+
this.#suspended = value;
|
|
96
|
+
if (value) {
|
|
97
|
+
this.#cancelRefresh();
|
|
98
|
+
this.#dirty = false;
|
|
99
|
+
this.#observer?.disconnect();
|
|
100
|
+
this.#mutations?.disconnect();
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
this.#mutations?.observe(this.#document.documentElement, {
|
|
104
|
+
childList: true,
|
|
105
|
+
subtree: true,
|
|
106
|
+
});
|
|
107
|
+
this.#dirty = false;
|
|
108
|
+
if (!this.#refreshGuarded())
|
|
109
|
+
return;
|
|
110
|
+
for (const element of this.#elements.keys()) {
|
|
111
|
+
this.#observer?.observe(element);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
destroy() {
|
|
115
|
+
if (this.#destroyed)
|
|
116
|
+
return;
|
|
117
|
+
this.#destroyed = true;
|
|
118
|
+
this.#cancelRefresh();
|
|
119
|
+
this.#dirty = false;
|
|
120
|
+
this.#observer?.disconnect();
|
|
121
|
+
this.#mutations?.disconnect();
|
|
122
|
+
this.#elements.clear();
|
|
123
|
+
this.#states.clear();
|
|
124
|
+
}
|
|
125
|
+
#observe(selector, element = this.#select(selector)) {
|
|
126
|
+
const state = this.#states.get(selector);
|
|
127
|
+
if (!element) {
|
|
128
|
+
this.#detach(selector, state);
|
|
129
|
+
this.#change(selector, state, 0);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
if (element === state[0] && element.isConnected)
|
|
133
|
+
return;
|
|
134
|
+
this.#detach(selector, state);
|
|
135
|
+
state[0] = element;
|
|
136
|
+
const selectors = this.#elements.get(element) ?? new Set();
|
|
137
|
+
if (!selectors.size)
|
|
138
|
+
this.#observer?.observe(element);
|
|
139
|
+
selectors.add(selector);
|
|
140
|
+
this.#elements.set(element, selectors);
|
|
141
|
+
}
|
|
142
|
+
#select(selector) {
|
|
143
|
+
try {
|
|
144
|
+
return this.#document.querySelector(selector);
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
throw new TypeError(runtimeMessage("section.selector", `Invalid section selector: ${selector}`));
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
#detach(selector, state) {
|
|
151
|
+
const previous = state[0];
|
|
152
|
+
state[0] = undefined;
|
|
153
|
+
if (!previous)
|
|
154
|
+
return;
|
|
155
|
+
const selectors = this.#elements.get(previous);
|
|
156
|
+
selectors?.delete(selector);
|
|
157
|
+
if (!selectors?.size) {
|
|
158
|
+
this.#observer?.unobserve(previous);
|
|
159
|
+
this.#elements.delete(previous);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
#change(selector, state, ratio) {
|
|
163
|
+
const previousRatio = state[2];
|
|
164
|
+
if (ratio === previousRatio)
|
|
165
|
+
return false;
|
|
166
|
+
state[1] = previousRatio;
|
|
167
|
+
state[2] = ratio;
|
|
168
|
+
this.#capture?.({ selector, previousRatio, ratio });
|
|
169
|
+
return true;
|
|
170
|
+
}
|
|
171
|
+
#refresh() {
|
|
172
|
+
for (const [selector, state] of this.#states) {
|
|
173
|
+
if (!state[0]?.isConnected)
|
|
174
|
+
this.#observe(selector);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
#scheduleRefresh() {
|
|
178
|
+
if (this.#refreshTimer || this.#suspended || this.#destroyed)
|
|
179
|
+
return;
|
|
180
|
+
this.#refreshTimer = this.#window.setTimeout(() => {
|
|
181
|
+
this.#refreshTimer = 0;
|
|
182
|
+
if (this.#suspended || this.#destroyed || this.#failure || this.#dirty)
|
|
183
|
+
return;
|
|
184
|
+
this.#dirty = true;
|
|
185
|
+
this.#wake();
|
|
186
|
+
}, REFRESH_CADENCE_MS);
|
|
187
|
+
}
|
|
188
|
+
#cancelRefresh() {
|
|
189
|
+
if (this.#refreshTimer)
|
|
190
|
+
this.#window.clearTimeout(this.#refreshTimer);
|
|
191
|
+
this.#refreshTimer = 0;
|
|
192
|
+
}
|
|
193
|
+
#refreshGuarded() {
|
|
194
|
+
if (this.#failure)
|
|
195
|
+
return false;
|
|
196
|
+
try {
|
|
197
|
+
this.#refresh();
|
|
198
|
+
return true;
|
|
199
|
+
}
|
|
200
|
+
catch (cause) {
|
|
201
|
+
this.#failure =
|
|
202
|
+
cause instanceof Error
|
|
203
|
+
? cause
|
|
204
|
+
: new Error(runtimeMessage("section.refresh", "Section tracking failed while the host document changed"));
|
|
205
|
+
this.#observer?.disconnect();
|
|
206
|
+
this.#mutations?.disconnect();
|
|
207
|
+
this.#wake();
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
function runtimeOwnedMutation(record) {
|
|
213
|
+
const target = record.target;
|
|
214
|
+
const element = typeof target.closest === "function" ? target : target.parentElement;
|
|
215
|
+
try {
|
|
216
|
+
return Boolean(element?.closest?.(RUNTIME_ROOTS));
|
|
217
|
+
}
|
|
218
|
+
catch {
|
|
219
|
+
return false;
|
|
220
|
+
}
|
|
221
|
+
}
|
package/dist/styles.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface RuntimeStyleAsset {
|
|
2
|
+
/** Relative or absolute HTTPS candidate when supplied by Configuration. */
|
|
3
|
+
url: string;
|
|
4
|
+
integrity?: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function connectRuntimeHost(host: HTMLElement, document: Document, marker: string): void;
|
|
7
|
+
export declare function setBrowserStyleAsset(asset: RuntimeStyleAsset): void;
|
|
8
|
+
export declare function validStyleIntegrity(value: unknown): value is string | undefined;
|
|
9
|
+
export declare function resolveStyleAsset(configured: RuntimeStyleAsset | undefined, baseUrl: string): RuntimeStyleAsset;
|
|
10
|
+
export declare class ShadowStyleSheet {
|
|
11
|
+
#private;
|
|
12
|
+
readonly ready: Promise<void>;
|
|
13
|
+
constructor(document: Document, root: ShadowRoot, asset: RuntimeStyleAsset);
|
|
14
|
+
addRule(selector: string): CSSStyleDeclaration;
|
|
15
|
+
removeRule(style: CSSStyleDeclaration): void;
|
|
16
|
+
destroy(): void;
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=styles.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"styles.d.ts","sourceRoot":"","sources":["../src/styles.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,iBAAiB;IAChC,2EAA2E;IAC3E,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,WAAW,EACjB,QAAQ,EAAE,QAAQ,EAClB,MAAM,EAAE,MAAM,GACb,IAAI,CAmBN;AAID,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,iBAAiB,GAAG,IAAI,CAEnE;AAED,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,OAAO,GACb,KAAK,IAAI,MAAM,GAAG,SAAS,CAM7B;AAsBD,wBAAgB,iBAAiB,CAC/B,UAAU,EAAE,iBAAiB,GAAG,SAAS,EACzC,OAAO,EAAE,MAAM,GACd,iBAAiB,CAoBnB;AAED,qBAAa,gBAAgB;;IAC3B,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IAM9B,YAAY,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,iBAAiB,EAmDzE;IAED,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,mBAAmB,CAiB7C;IAED,UAAU,CAAC,KAAK,EAAE,mBAAmB,GAAG,IAAI,CAU3C;IAED,OAAO,IAAI,IAAI,CAed;CACF"}
|
package/dist/styles.js
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { runtimeMessage } from "./runtime-diagnostics.js";
|
|
2
|
+
const browserBuild = typeof __PEEKLING_BROWSER_BUILD__ !== "undefined" &&
|
|
3
|
+
__PEEKLING_BROWSER_BUILD__;
|
|
4
|
+
export function connectRuntimeHost(host, document, marker) {
|
|
5
|
+
if (host.ownerDocument !== document) {
|
|
6
|
+
throw new Error(runtimeMessage("root.document", "Peekling root was adopted into another document"));
|
|
7
|
+
}
|
|
8
|
+
const parent = document.documentElement;
|
|
9
|
+
if (!parent)
|
|
10
|
+
throw new Error(runtimeMessage("root.missing", "Peekling document has no document element"));
|
|
11
|
+
if (host.parentNode !== parent)
|
|
12
|
+
parent.append(host);
|
|
13
|
+
if (!host.hasAttribute(marker))
|
|
14
|
+
host.setAttribute(marker, "");
|
|
15
|
+
}
|
|
16
|
+
let browserDefault;
|
|
17
|
+
export function setBrowserStyleAsset(asset) {
|
|
18
|
+
browserDefault = Object.freeze({ ...asset });
|
|
19
|
+
}
|
|
20
|
+
export function validStyleIntegrity(value) {
|
|
21
|
+
return (value === undefined ||
|
|
22
|
+
(typeof value === "string" &&
|
|
23
|
+
/^sha(?:256|384|512)-[A-Za-z0-9+/]+={0,2}$/.test(value)));
|
|
24
|
+
}
|
|
25
|
+
function defaultStyleAsset(baseUrl) {
|
|
26
|
+
if (browserBuild) {
|
|
27
|
+
return { url: new URL("./peekling.css", baseUrl).href };
|
|
28
|
+
}
|
|
29
|
+
try {
|
|
30
|
+
const url = new URL("./peekling.css", import.meta.url);
|
|
31
|
+
if (url.protocol === "https:" || url.protocol === "http:") {
|
|
32
|
+
return { url: url.href };
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
// The explicit Configuration hint below is stable across module loaders.
|
|
37
|
+
}
|
|
38
|
+
throw new TypeError(runtimeMessage("styles.default", "Pass styles.url pointing to @peekling/runtime/peekling.css"));
|
|
39
|
+
}
|
|
40
|
+
export function resolveStyleAsset(configured, baseUrl) {
|
|
41
|
+
const selected = configured ?? browserDefault ?? defaultStyleAsset(baseUrl);
|
|
42
|
+
const url = new URL(selected.url, baseUrl);
|
|
43
|
+
if (url.protocol !== "https:" && url.protocol !== "http:") {
|
|
44
|
+
throw new TypeError(runtimeMessage("styles.url", "styles.url must resolve to HTTP or HTTPS"));
|
|
45
|
+
}
|
|
46
|
+
if (!validStyleIntegrity(selected.integrity)) {
|
|
47
|
+
throw new TypeError(runtimeMessage("styles.integrity", "styles.integrity must be one SRI hash"));
|
|
48
|
+
}
|
|
49
|
+
return Object.freeze({
|
|
50
|
+
url: url.href,
|
|
51
|
+
...(selected.integrity ? { integrity: selected.integrity } : {}),
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
export class ShadowStyleSheet {
|
|
55
|
+
ready;
|
|
56
|
+
#link;
|
|
57
|
+
#sheet;
|
|
58
|
+
#settled = false;
|
|
59
|
+
#rejectReady = () => { };
|
|
60
|
+
constructor(document, root, asset) {
|
|
61
|
+
const link = document.createElement("link");
|
|
62
|
+
this.#link = link;
|
|
63
|
+
link.rel = "stylesheet";
|
|
64
|
+
link.href = asset.url;
|
|
65
|
+
link.crossOrigin = "anonymous";
|
|
66
|
+
link.referrerPolicy = "no-referrer";
|
|
67
|
+
if (asset.integrity)
|
|
68
|
+
link.integrity = asset.integrity;
|
|
69
|
+
this.ready = new Promise((resolve, reject) => {
|
|
70
|
+
this.#rejectReady = reject;
|
|
71
|
+
const fail = (code, detail) => {
|
|
72
|
+
if (this.#settled)
|
|
73
|
+
return;
|
|
74
|
+
this.#settled = true;
|
|
75
|
+
reject(new Error(runtimeMessage(code, detail)));
|
|
76
|
+
};
|
|
77
|
+
link.addEventListener("load", () => {
|
|
78
|
+
if (this.#settled)
|
|
79
|
+
return;
|
|
80
|
+
const sheet = link.sheet;
|
|
81
|
+
if (!sheet || typeof sheet.insertRule !== "function") {
|
|
82
|
+
fail("styles.cssom", "Peekling stylesheet did not expose CSSOM");
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
try {
|
|
86
|
+
void sheet.cssRules.length;
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
fail("styles.cors", "Peekling stylesheet requires same-origin or CORS-enabled delivery");
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
this.#sheet = sheet;
|
|
93
|
+
this.#settled = true;
|
|
94
|
+
resolve();
|
|
95
|
+
}, { once: true });
|
|
96
|
+
link.addEventListener("error", () => fail("styles.load", () => `Peekling stylesheet failed to load: ${asset.url}`), { once: true });
|
|
97
|
+
});
|
|
98
|
+
void this.ready.catch(() => { });
|
|
99
|
+
root.append(link);
|
|
100
|
+
}
|
|
101
|
+
addRule(selector) {
|
|
102
|
+
if (!this.#sheet)
|
|
103
|
+
throw new Error(runtimeMessage("styles.pending", "Peekling stylesheet is not ready"));
|
|
104
|
+
const index = this.#sheet.cssRules.length;
|
|
105
|
+
this.#sheet.insertRule(`${selector}{}`, index);
|
|
106
|
+
const rule = this.#sheet.cssRules[index];
|
|
107
|
+
if (!rule?.style) {
|
|
108
|
+
throw new Error(runtimeMessage("styles.rule", "Peekling stylesheet rule creation failed"));
|
|
109
|
+
}
|
|
110
|
+
return rule.style;
|
|
111
|
+
}
|
|
112
|
+
removeRule(style) {
|
|
113
|
+
const sheet = this.#sheet;
|
|
114
|
+
if (!sheet)
|
|
115
|
+
return;
|
|
116
|
+
for (let index = sheet.cssRules.length - 1; index >= 0; index--) {
|
|
117
|
+
const rule = sheet.cssRules[index];
|
|
118
|
+
if (rule?.style === style) {
|
|
119
|
+
sheet.deleteRule(index);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
destroy() {
|
|
125
|
+
if (!this.#settled) {
|
|
126
|
+
this.#settled = true;
|
|
127
|
+
this.#rejectReady(new DOMException(runtimeMessage("styles.cancelled", "Peekling stylesheet load cancelled"), "AbortError"));
|
|
128
|
+
}
|
|
129
|
+
this.#link.remove();
|
|
130
|
+
this.#sheet = undefined;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export interface PreflightOptions {
|
|
2
|
+
/** Optional Pack data used to validate Plan state and capability references. */
|
|
3
|
+
readonly pack?: unknown;
|
|
4
|
+
/** Base URL used only to resolve and validate relative resource URLs. */
|
|
5
|
+
readonly baseUrl?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface PreflightDiagnostic {
|
|
8
|
+
readonly code: string;
|
|
9
|
+
readonly path: string;
|
|
10
|
+
readonly message: string;
|
|
11
|
+
readonly fix: string;
|
|
12
|
+
}
|
|
13
|
+
export interface PreflightResult {
|
|
14
|
+
readonly valid: boolean;
|
|
15
|
+
readonly errors: readonly PreflightDiagnostic[];
|
|
16
|
+
readonly warnings: readonly PreflightDiagnostic[];
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Inspect configuration and optional Pack data without executing project code,
|
|
20
|
+
* predicates, callbacks, accessors, or network requests.
|
|
21
|
+
*/
|
|
22
|
+
export declare function preflight(configuration: unknown, options?: PreflightOptions): PreflightResult;
|
|
23
|
+
//# sourceMappingURL=tooling-preflight.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tooling-preflight.d.ts","sourceRoot":"","sources":["../src/tooling-preflight.ts"],"names":[],"mappings":"AAeA,MAAM,WAAW,gBAAgB;IAC/B,gFAAgF;IAChF,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IACxB,yEAAyE;IACzE,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,SAAS,mBAAmB,EAAE,CAAC;IAChD,QAAQ,CAAC,QAAQ,EAAE,SAAS,mBAAmB,EAAE,CAAC;CACnD;AAED;;;GAGG;AACH,wBAAgB,SAAS,CACvB,aAAa,EAAE,OAAO,EACtB,OAAO,GAAE,gBAAqB,GAC7B,eAAe,CA2IjB"}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { isNormalizedPack, validateNormalizedPack } from "./normalized.js";
|
|
2
|
+
import { OwnDataError, snapshotConfiguration, snapshotOwnData, } from "./own-data.js";
|
|
3
|
+
import { validateNativePack } from "./pack.js";
|
|
4
|
+
import { preflight as configurationPreflight, } from "./preflight.js";
|
|
5
|
+
import { toolingDiagnostic } from "./tooling-validation.js";
|
|
6
|
+
import { NATIVE_DIRECTIONS } from "./types.js";
|
|
7
|
+
/**
|
|
8
|
+
* Inspect configuration and optional Pack data without executing project code,
|
|
9
|
+
* predicates, callbacks, accessors, or network requests.
|
|
10
|
+
*/
|
|
11
|
+
export function preflight(configuration, options = {}) {
|
|
12
|
+
let checkedOptions;
|
|
13
|
+
try {
|
|
14
|
+
checkedOptions = snapshotOwnData(options);
|
|
15
|
+
}
|
|
16
|
+
catch (cause) {
|
|
17
|
+
return failedOptions(cause instanceof OwnDataError
|
|
18
|
+
? cause.message
|
|
19
|
+
: "Preflight options cannot be inspected safely");
|
|
20
|
+
}
|
|
21
|
+
if (!checkedOptions ||
|
|
22
|
+
typeof checkedOptions !== "object" ||
|
|
23
|
+
Array.isArray(checkedOptions)) {
|
|
24
|
+
return failedOptions("Preflight options must be an object");
|
|
25
|
+
}
|
|
26
|
+
const toolErrors = [];
|
|
27
|
+
for (const field of Object.keys(checkedOptions)) {
|
|
28
|
+
if (field !== "pack" && field !== "baseUrl") {
|
|
29
|
+
toolErrors.push({
|
|
30
|
+
code: "unknown-preflight-option",
|
|
31
|
+
path: `$options.${field}`,
|
|
32
|
+
message: `Unknown preflight option ${field}`,
|
|
33
|
+
fix: "Remove this field",
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
const validBaseUrl = checkedOptions.baseUrl === undefined ||
|
|
38
|
+
(typeof checkedOptions.baseUrl === "string" &&
|
|
39
|
+
absoluteHttpUrl(checkedOptions.baseUrl));
|
|
40
|
+
if (!validBaseUrl) {
|
|
41
|
+
toolErrors.push({
|
|
42
|
+
code: "invalid-base-url",
|
|
43
|
+
path: "$options.baseUrl",
|
|
44
|
+
message: "baseUrl must be an absolute HTTP or HTTPS URL",
|
|
45
|
+
fix: "Pass the intended deployment page URL",
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
let snapshot;
|
|
49
|
+
let snapshotFailure;
|
|
50
|
+
try {
|
|
51
|
+
snapshot = snapshotConfiguration(configuration);
|
|
52
|
+
}
|
|
53
|
+
catch (cause) {
|
|
54
|
+
snapshotFailure = cause;
|
|
55
|
+
}
|
|
56
|
+
const externalPack = checkedOptions.pack;
|
|
57
|
+
if (externalPack !== undefined && snapshot?.pack !== undefined) {
|
|
58
|
+
toolErrors.push({
|
|
59
|
+
code: "pack-context-conflict",
|
|
60
|
+
path: "$options.pack",
|
|
61
|
+
message: "Configuration already contains an inline Pack",
|
|
62
|
+
fix: "Remove the external Pack option or the inline Configuration Pack",
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
const packInput = snapshot?.pack ?? externalPack;
|
|
66
|
+
let pack;
|
|
67
|
+
let packError;
|
|
68
|
+
if (packInput !== undefined) {
|
|
69
|
+
try {
|
|
70
|
+
pack = isNormalizedPack(packInput)
|
|
71
|
+
? validateNormalizedPack(packInput)
|
|
72
|
+
: validateNativePack(packInput);
|
|
73
|
+
}
|
|
74
|
+
catch (cause) {
|
|
75
|
+
if (externalPack !== undefined && snapshot?.pack === undefined) {
|
|
76
|
+
packError = {
|
|
77
|
+
code: "invalid-pack",
|
|
78
|
+
path: "$pack",
|
|
79
|
+
message: diagnosticMessage(cause),
|
|
80
|
+
fix: "Pass a valid data-only Pack",
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
const checked = snapshot
|
|
86
|
+
? configurationPreflight(snapshot, {
|
|
87
|
+
...(validBaseUrl && typeof checkedOptions.baseUrl === "string"
|
|
88
|
+
? { baseUrl: checkedOptions.baseUrl }
|
|
89
|
+
: {}),
|
|
90
|
+
...(externalPack !== undefined ? { externalPackSelection: true } : {}),
|
|
91
|
+
...(pack
|
|
92
|
+
? {
|
|
93
|
+
states: new Set(Object.keys(pack.states)),
|
|
94
|
+
capabilities: new Set(pack.directionalStates &&
|
|
95
|
+
NATIVE_DIRECTIONS.every((direction) => Object.hasOwn(pack.directionalStates ?? {}, direction))
|
|
96
|
+
? ["locomotion"]
|
|
97
|
+
: []),
|
|
98
|
+
}
|
|
99
|
+
: {}),
|
|
100
|
+
})
|
|
101
|
+
: {
|
|
102
|
+
valid: false,
|
|
103
|
+
errors: [
|
|
104
|
+
{
|
|
105
|
+
code: "invalid-options",
|
|
106
|
+
path: "$",
|
|
107
|
+
message: snapshotFailure instanceof OwnDataError
|
|
108
|
+
? snapshotFailure.message
|
|
109
|
+
: "Options cannot be inspected safely",
|
|
110
|
+
fix: "Pass own data in a local ordinary object or null-prototype object",
|
|
111
|
+
},
|
|
112
|
+
],
|
|
113
|
+
warnings: [],
|
|
114
|
+
};
|
|
115
|
+
const errors = [
|
|
116
|
+
...toolErrors,
|
|
117
|
+
...(packError ? [packError] : []),
|
|
118
|
+
...checked.errors,
|
|
119
|
+
...(snapshot &&
|
|
120
|
+
pack &&
|
|
121
|
+
!checked.errors.some((issue) => issue.path === "$.atlasUrl")
|
|
122
|
+
? inspectAtlasDensityPolicy(snapshot, pack)
|
|
123
|
+
: []),
|
|
124
|
+
];
|
|
125
|
+
const warnings = [...checked.warnings];
|
|
126
|
+
if (snapshot?.plan && packInput === undefined) {
|
|
127
|
+
warnings.push({
|
|
128
|
+
code: "pack-context-missing",
|
|
129
|
+
path: "$.plan",
|
|
130
|
+
message: "Plan references cannot be checked without Pack context",
|
|
131
|
+
fix: "Pass the Pack with the preflight options or peekling doctor --pack",
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
return {
|
|
135
|
+
valid: errors.length === 0,
|
|
136
|
+
errors,
|
|
137
|
+
warnings,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
function inspectAtlasDensityPolicy(options, pack) {
|
|
141
|
+
const variants = pack.atlas.variants;
|
|
142
|
+
if (!validDensity(options.maxDensity ?? 4)) {
|
|
143
|
+
return [];
|
|
144
|
+
}
|
|
145
|
+
const maxDensity = options.maxDensity ?? 4;
|
|
146
|
+
if (!variants?.length) {
|
|
147
|
+
const density = pack.atlas.density ?? 1;
|
|
148
|
+
const issues = [];
|
|
149
|
+
if (options.density !== undefined &&
|
|
150
|
+
validDensity(options.density) &&
|
|
151
|
+
options.density !== density) {
|
|
152
|
+
issues.push(toolingDiagnostic("atlas.override-density", "$.density", "single"));
|
|
153
|
+
}
|
|
154
|
+
if (density > maxDensity) {
|
|
155
|
+
issues.push(toolingDiagnostic("atlas.max-density", "$.maxDensity", "single"));
|
|
156
|
+
}
|
|
157
|
+
return issues;
|
|
158
|
+
}
|
|
159
|
+
if (typeof options.atlasUrl !== "string")
|
|
160
|
+
return [];
|
|
161
|
+
const available = variants.filter((item) => item.density <= maxDensity);
|
|
162
|
+
if (!available.length) {
|
|
163
|
+
return [toolingDiagnostic("atlas.max-density", "$.maxDensity")];
|
|
164
|
+
}
|
|
165
|
+
if (options.density === undefined) {
|
|
166
|
+
return [
|
|
167
|
+
toolingDiagnostic("atlas.override-density", "$.density", "missing"),
|
|
168
|
+
];
|
|
169
|
+
}
|
|
170
|
+
if (!validDensity(options.density) ||
|
|
171
|
+
options.density > maxDensity ||
|
|
172
|
+
available.some((item) => item.density === options.density)) {
|
|
173
|
+
return [];
|
|
174
|
+
}
|
|
175
|
+
return [toolingDiagnostic("atlas.override-density", "$.density")];
|
|
176
|
+
}
|
|
177
|
+
function validDensity(value) {
|
|
178
|
+
return value === 1 || value === 2 || value === 4;
|
|
179
|
+
}
|
|
180
|
+
function failedOptions(message) {
|
|
181
|
+
return {
|
|
182
|
+
valid: false,
|
|
183
|
+
errors: [
|
|
184
|
+
{
|
|
185
|
+
code: "invalid-preflight-options",
|
|
186
|
+
path: "$options",
|
|
187
|
+
message,
|
|
188
|
+
fix: "Pass an optional Pack and baseUrl in an object",
|
|
189
|
+
},
|
|
190
|
+
],
|
|
191
|
+
warnings: [],
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
function diagnosticMessage(cause) {
|
|
195
|
+
if (!(cause instanceof Error))
|
|
196
|
+
return "Pack could not be inspected safely";
|
|
197
|
+
return cause.message.replace(/^Pack validation failed:\s*/u, "").trim();
|
|
198
|
+
}
|
|
199
|
+
function absoluteHttpUrl(value) {
|
|
200
|
+
try {
|
|
201
|
+
const parsed = new URL(value);
|
|
202
|
+
return parsed.protocol === "http:" || parsed.protocol === "https:";
|
|
203
|
+
}
|
|
204
|
+
catch {
|
|
205
|
+
return false;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export interface ToolingConfigurationDiagnostic {
|
|
2
|
+
readonly code: string;
|
|
3
|
+
readonly path: string;
|
|
4
|
+
readonly message: string;
|
|
5
|
+
readonly fix: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function toolingDiagnostic(code: string, path: string, detail?: string): ToolingConfigurationDiagnostic;
|
|
8
|
+
//# sourceMappingURL=tooling-validation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tooling-validation.d.ts","sourceRoot":"","sources":["../src/tooling-validation.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,8BAA8B;IAC7C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB;AAED,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,MAAM,GACd,8BAA8B,CAoLhC"}
|