card-gate-frame 1.0.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/LICENSE +21 -0
- package/README.md +152 -0
- package/dist/core/index.d.ts +2 -0
- package/dist/core/index.js +3 -0
- package/dist/core-DzM1ds_q.js +2536 -0
- package/dist/core-DzM1ds_q.js.map +1 -0
- package/dist/index-B0WElJq8.d.ts +142 -0
- package/dist/index-B0WElJq8.d.ts.map +1 -0
- package/dist/index.d.ts +143 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +728 -0
- package/dist/index.js.map +1 -0
- package/package.json +79 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,728 @@
|
|
|
1
|
+
import { i as isGateFamily, n as generateGate, r as isCompatibleCrest, t as renderGateSVG } from "./core-DzM1ds_q.js";
|
|
2
|
+
|
|
3
|
+
//#region src/dom/index.ts
|
|
4
|
+
const freezeContext = (context) => {
|
|
5
|
+
for (const value of Object.values(context)) if (value !== null && typeof value === "object" && !Object.isFrozen(value)) freezeContext(value);
|
|
6
|
+
return Object.freeze(context);
|
|
7
|
+
};
|
|
8
|
+
var GateFrameError = class extends Error {
|
|
9
|
+
constructor(code, message, context = {}, name = "GateFrameError") {
|
|
10
|
+
super(message);
|
|
11
|
+
Object.defineProperties(this, {
|
|
12
|
+
name: {
|
|
13
|
+
value: name,
|
|
14
|
+
enumerable: false
|
|
15
|
+
},
|
|
16
|
+
code: {
|
|
17
|
+
value: code,
|
|
18
|
+
enumerable: true
|
|
19
|
+
},
|
|
20
|
+
context: {
|
|
21
|
+
value: freezeContext(context),
|
|
22
|
+
enumerable: true
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
var GateFrameInvalidSelectorError = class extends GateFrameError {
|
|
28
|
+
constructor(selector) {
|
|
29
|
+
super("INVALID_SELECTOR", "Gate Frame received an invalid selector", { selector }, "GateFrameInvalidSelectorError");
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
var GateFrameTargetNotFoundError = class extends GateFrameError {
|
|
33
|
+
constructor(selector) {
|
|
34
|
+
super("TARGET_NOT_FOUND", "Gate Frame could not find a target for the selector", { selector }, "GateFrameTargetNotFoundError");
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
var GateFrameTargetNotHTMLElementError = class extends GateFrameError {
|
|
38
|
+
constructor(context) {
|
|
39
|
+
super("TARGET_NOT_HTML_ELEMENT", "Gate Frame targets must be HTML elements", { ...context }, "GateFrameTargetNotHTMLElementError");
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
var GateFrameAlreadyMountedError = class extends GateFrameError {
|
|
43
|
+
constructor(target) {
|
|
44
|
+
super("ALREADY_MOUNTED", "Gate Frame is already mounted on this target", { tagName: target.localName }, "GateFrameAlreadyMountedError");
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
var GateFrameUnsupportedTargetError = class extends GateFrameError {
|
|
48
|
+
constructor(target, reason, details = {}) {
|
|
49
|
+
super("UNSUPPORTED_TARGET", "Gate Frame does not support this target", {
|
|
50
|
+
tagName: target.localName,
|
|
51
|
+
reason,
|
|
52
|
+
...details
|
|
53
|
+
}, "GateFrameUnsupportedTargetError");
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
var GateFramePositioningConflictError = class extends GateFrameError {
|
|
57
|
+
constructor(target, descendant) {
|
|
58
|
+
super("POSITIONING_CONFLICT", "Gate Frame will not change the containing block of an absolute descendant", {
|
|
59
|
+
tagName: target.localName,
|
|
60
|
+
descendantTagName: descendant.localName
|
|
61
|
+
}, "GateFramePositioningConflictError");
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
var GateFrameInvalidOptionsError = class extends GateFrameError {
|
|
65
|
+
constructor(context) {
|
|
66
|
+
super("INVALID_OPTIONS", "Gate Frame received invalid options", context.category === void 0 ? {
|
|
67
|
+
optionKeys: [...context.optionKeys],
|
|
68
|
+
reason: context.reason
|
|
69
|
+
} : {
|
|
70
|
+
category: context.category,
|
|
71
|
+
optionKeys: [...context.optionKeys],
|
|
72
|
+
reason: context.reason
|
|
73
|
+
}, "GateFrameInvalidOptionsError");
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
var GateFrameUnsupportedDimensionsError = class extends GateFrameError {
|
|
77
|
+
constructor(dimensions) {
|
|
78
|
+
super("UNSUPPORTED_DIMENSIONS", "Gate Frame cannot generate geometry for the measured dimensions", { dimensions: { ...dimensions } }, "GateFrameUnsupportedDimensionsError");
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
var GateFrameInsufficientSpaceError = class extends GateFrameError {
|
|
82
|
+
constructor(measured, required) {
|
|
83
|
+
super("INSUFFICIENT_CONTENT_SPACE", "Gate Frame requires more existing target padding", {
|
|
84
|
+
measured: { ...measured },
|
|
85
|
+
required: { ...required }
|
|
86
|
+
}, "GateFrameInsufficientSpaceError");
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
var GateFrameNotReadyError = class extends GateFrameError {
|
|
90
|
+
constructor() {
|
|
91
|
+
super("NOT_READY", "Gate Frame has not committed a render", {}, "GateFrameNotReadyError");
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
var GateFrameDestroyedError = class extends GateFrameError {
|
|
95
|
+
constructor() {
|
|
96
|
+
super("DESTROYED", "Gate Frame controller is destroyed", {}, "GateFrameDestroyedError");
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
const PENDING_STATUS = Object.freeze({ state: "pending" });
|
|
100
|
+
const DESTROYED_STATUS = Object.freeze({ state: "destroyed" });
|
|
101
|
+
const mountedTargets = /* @__PURE__ */ new WeakMap();
|
|
102
|
+
const REPLACED_TAGS = /* @__PURE__ */ new Set([
|
|
103
|
+
"AUDIO",
|
|
104
|
+
"CANVAS",
|
|
105
|
+
"EMBED",
|
|
106
|
+
"IFRAME",
|
|
107
|
+
"IMG",
|
|
108
|
+
"OBJECT",
|
|
109
|
+
"VIDEO"
|
|
110
|
+
]);
|
|
111
|
+
const RESTRICTED_FORM_TAGS = /* @__PURE__ */ new Set([
|
|
112
|
+
"BUTTON",
|
|
113
|
+
"INPUT",
|
|
114
|
+
"METER",
|
|
115
|
+
"OPTION",
|
|
116
|
+
"PROGRESS",
|
|
117
|
+
"SELECT",
|
|
118
|
+
"TEXTAREA"
|
|
119
|
+
]);
|
|
120
|
+
const TABLE_TAGS = /* @__PURE__ */ new Set([
|
|
121
|
+
"CAPTION",
|
|
122
|
+
"COL",
|
|
123
|
+
"COLGROUP",
|
|
124
|
+
"TABLE",
|
|
125
|
+
"TBODY",
|
|
126
|
+
"TD",
|
|
127
|
+
"TFOOT",
|
|
128
|
+
"TH",
|
|
129
|
+
"THEAD",
|
|
130
|
+
"TR"
|
|
131
|
+
]);
|
|
132
|
+
const SUPPORTED_TARGET_TAGS = /* @__PURE__ */ new Set([
|
|
133
|
+
"ARTICLE",
|
|
134
|
+
"ASIDE",
|
|
135
|
+
"DIV",
|
|
136
|
+
"SECTION"
|
|
137
|
+
]);
|
|
138
|
+
const SUPPORTED_DISPLAYS = /* @__PURE__ */ new Set([
|
|
139
|
+
"block",
|
|
140
|
+
"flex",
|
|
141
|
+
"grid",
|
|
142
|
+
"inline-block",
|
|
143
|
+
"none"
|
|
144
|
+
]);
|
|
145
|
+
const V1_OPTION_KEYS = /* @__PURE__ */ new Set([
|
|
146
|
+
"seed",
|
|
147
|
+
"family",
|
|
148
|
+
"generationVersion",
|
|
149
|
+
"density",
|
|
150
|
+
"curvature",
|
|
151
|
+
"stroke",
|
|
152
|
+
"surface",
|
|
153
|
+
"responsive"
|
|
154
|
+
]);
|
|
155
|
+
const V2_OPTION_KEYS = /* @__PURE__ */ new Set([
|
|
156
|
+
...V1_OPTION_KEYS,
|
|
157
|
+
"symmetry",
|
|
158
|
+
"peakHeight",
|
|
159
|
+
"sideComplexity",
|
|
160
|
+
"crest"
|
|
161
|
+
]);
|
|
162
|
+
const classifyTarget = (target, style) => {
|
|
163
|
+
const display = style.display || target.style.display || "block";
|
|
164
|
+
const writingMode = style.writingMode || target.style.writingMode || "horizontal-tb";
|
|
165
|
+
const columnCountValue = style.columnCount || target.style.columnCount || "auto";
|
|
166
|
+
const columnWidth = style.columnWidth || target.style.columnWidth || "auto";
|
|
167
|
+
if (target.shadowRoot !== null) throw new GateFrameUnsupportedTargetError(target, "shadow-host");
|
|
168
|
+
if (RESTRICTED_FORM_TAGS.has(target.tagName)) throw new GateFrameUnsupportedTargetError(target, "restricted-form-control");
|
|
169
|
+
if (TABLE_TAGS.has(target.tagName)) throw new GateFrameUnsupportedTargetError(target, "table-internal");
|
|
170
|
+
if (REPLACED_TAGS.has(target.tagName)) throw new GateFrameUnsupportedTargetError(target, "replaced-element");
|
|
171
|
+
if (display === "inline") throw new GateFrameUnsupportedTargetError(target, "display-inline", { display });
|
|
172
|
+
if (display === "contents") throw new GateFrameUnsupportedTargetError(target, "display-contents", { display });
|
|
173
|
+
if (!SUPPORTED_DISPLAYS.has(display)) throw new GateFrameUnsupportedTargetError(target, "unsupported-display", { display });
|
|
174
|
+
if (!SUPPORTED_TARGET_TAGS.has(target.tagName)) throw new GateFrameUnsupportedTargetError(target, "content-model");
|
|
175
|
+
if (!writingMode.startsWith("horizontal")) throw new GateFrameUnsupportedTargetError(target, "writing-mode", { writingMode });
|
|
176
|
+
const columnCount = Number.parseInt(columnCountValue, 10);
|
|
177
|
+
if (columnCountValue !== "auto" && Number.isFinite(columnCount) && columnCount > 1 || columnWidth !== "auto") throw new GateFrameUnsupportedTargetError(target, "fragmented", {
|
|
178
|
+
columnCount: columnCountValue,
|
|
179
|
+
columnWidth
|
|
180
|
+
});
|
|
181
|
+
};
|
|
182
|
+
const createRandomSeed = () => {
|
|
183
|
+
const bytes = /* @__PURE__ */ new Uint8Array(16);
|
|
184
|
+
crypto.getRandomValues(bytes);
|
|
185
|
+
return Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
186
|
+
};
|
|
187
|
+
const invalidOptions = (options, reason) => {
|
|
188
|
+
const optionKeys = options !== null && typeof options === "object" && !Array.isArray(options) ? Object.keys(options).sort() : [];
|
|
189
|
+
return new GateFrameInvalidOptionsError({
|
|
190
|
+
optionKeys,
|
|
191
|
+
reason
|
|
192
|
+
});
|
|
193
|
+
};
|
|
194
|
+
const assertV1OptionKeys = (options) => {
|
|
195
|
+
const unsupportedKeys = Reflect.ownKeys(options).filter((key) => typeof key !== "string" || !V1_OPTION_KEYS.has(key)).map(String).sort();
|
|
196
|
+
if (unsupportedKeys.length > 0) throw new GateFrameInvalidOptionsError({
|
|
197
|
+
optionKeys: unsupportedKeys,
|
|
198
|
+
reason: "unsupported option keys"
|
|
199
|
+
});
|
|
200
|
+
};
|
|
201
|
+
const invalidV2Options = (category, optionKeys, reason) => new GateFrameInvalidOptionsError({
|
|
202
|
+
category,
|
|
203
|
+
optionKeys: [...optionKeys].sort(),
|
|
204
|
+
reason
|
|
205
|
+
});
|
|
206
|
+
const assertV2OptionShape = (options) => {
|
|
207
|
+
if (options === null || typeof options !== "object" || Array.isArray(options)) throw invalidV2Options("shape", [], "options must be a plain object");
|
|
208
|
+
const record = options;
|
|
209
|
+
const unsupportedKeys = Reflect.ownKeys(record).filter((key) => typeof key !== "string" || !V2_OPTION_KEYS.has(key)).map(String).sort();
|
|
210
|
+
if (unsupportedKeys.length > 0) throw invalidV2Options("unknown-key", unsupportedKeys, "unsupported option keys");
|
|
211
|
+
return record;
|
|
212
|
+
};
|
|
213
|
+
const ownValue = (record, key, fallback) => !Object.hasOwn(record, key) || record[key] === void 0 ? fallback : record[key];
|
|
214
|
+
const isGateSeed = (value) => typeof value === "string" && value.length > 0 || typeof value === "number" && Number.isFinite(value);
|
|
215
|
+
const isUnitControl = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0 && value <= 1;
|
|
216
|
+
const EXPECTED_CORE_OPTION_ERROR = /^(?:GF-01 requires family: "courtyard"|GF-01 supports only generationVersion 1|Gate seed numbers must be finite|Gate seeds must be strings or finite numbers|Gate seed strings must not be empty|(?:density|curvature) must be (?:a finite number|between 0 and 1 inclusive)|(?:stroke|surface) paint must be (?:a string|a safe literal CSS color))$/;
|
|
217
|
+
const normalizeV1ControllerOptions = (options, fallbackSeed) => {
|
|
218
|
+
if (options === null || typeof options !== "object" || Array.isArray(options)) throw invalidOptions(options, "options must be a plain object");
|
|
219
|
+
const optionRecord = options;
|
|
220
|
+
assertV1OptionKeys(optionRecord);
|
|
221
|
+
const optionOr = (name, fallback) => optionRecord[name] === void 0 ? fallback : optionRecord[name];
|
|
222
|
+
const responsive = optionOr("responsive", true);
|
|
223
|
+
if (typeof responsive !== "boolean") throw invalidOptions(options, "responsive must be a boolean");
|
|
224
|
+
const generationOptions = {
|
|
225
|
+
seed: optionOr("seed", optionRecord.seed === void 0 && fallbackSeed === void 0 ? createRandomSeed() : fallbackSeed),
|
|
226
|
+
family: optionOr("family", "courtyard"),
|
|
227
|
+
generationVersion: optionOr("generationVersion", 1),
|
|
228
|
+
density: optionOr("density", .55),
|
|
229
|
+
curvature: optionOr("curvature", .65)
|
|
230
|
+
};
|
|
231
|
+
const stroke = optionOr("stroke", "currentColor");
|
|
232
|
+
const surface = optionOr("surface", "transparent");
|
|
233
|
+
let validationGeometry;
|
|
234
|
+
try {
|
|
235
|
+
validationGeometry = generateGate({
|
|
236
|
+
width: 160,
|
|
237
|
+
height: 160
|
|
238
|
+
}, generationOptions);
|
|
239
|
+
renderGateSVG(validationGeometry, {
|
|
240
|
+
stroke,
|
|
241
|
+
surface
|
|
242
|
+
});
|
|
243
|
+
} catch (cause) {
|
|
244
|
+
if ((cause instanceof TypeError || cause instanceof RangeError) && EXPECTED_CORE_OPTION_ERROR.test(cause.message)) throw invalidOptions(options, cause.message);
|
|
245
|
+
throw cause;
|
|
246
|
+
}
|
|
247
|
+
const normalizedStroke = stroke.trim();
|
|
248
|
+
const normalizedSurface = surface.trim();
|
|
249
|
+
if (!CSS.supports("color", normalizedStroke) || !CSS.supports("color", normalizedSurface)) throw invalidOptions(options, "paint must be supported CSS color syntax");
|
|
250
|
+
return Object.freeze({
|
|
251
|
+
seed: validationGeometry.seed,
|
|
252
|
+
family: validationGeometry.options.family,
|
|
253
|
+
generationVersion: validationGeometry.generationVersion,
|
|
254
|
+
density: validationGeometry.options.density,
|
|
255
|
+
curvature: validationGeometry.options.curvature,
|
|
256
|
+
stroke: normalizedStroke,
|
|
257
|
+
surface: normalizedSurface,
|
|
258
|
+
responsive
|
|
259
|
+
});
|
|
260
|
+
};
|
|
261
|
+
const normalizeFamilyControllerOptions = (options, fallbackSeed) => {
|
|
262
|
+
const optionRecord = assertV2OptionShape(options);
|
|
263
|
+
const responsive = ownValue(optionRecord, "responsive", true);
|
|
264
|
+
if (typeof responsive !== "boolean") throw invalidV2Options("responsive", ["responsive"], "responsive must be a boolean");
|
|
265
|
+
const generationVersion = ownValue(optionRecord, "generationVersion", 2);
|
|
266
|
+
if (generationVersion !== 2 && generationVersion !== 3) throw invalidV2Options("version", ["generationVersion"], "generationVersion must be 2 or 3");
|
|
267
|
+
const requestedFamily = ownValue(optionRecord, "family", "auto");
|
|
268
|
+
if (requestedFamily !== "auto" && !isGateFamily(requestedFamily)) throw invalidV2Options("family", ["family"], "family must be auto or a supported family");
|
|
269
|
+
const suppliedSeed = ownValue(optionRecord, "seed", void 0);
|
|
270
|
+
const defaultSeed = suppliedSeed === void 0 && fallbackSeed === void 0 ? createRandomSeed() : fallbackSeed;
|
|
271
|
+
const requestedSeed = suppliedSeed === void 0 ? defaultSeed : suppliedSeed;
|
|
272
|
+
if (!isGateSeed(requestedSeed)) throw invalidV2Options("seed", ["seed"], "seed must be a non-empty string or finite number");
|
|
273
|
+
const controls = [
|
|
274
|
+
["density", .55],
|
|
275
|
+
["curvature", .65],
|
|
276
|
+
["symmetry", 1],
|
|
277
|
+
["peakHeight", .35],
|
|
278
|
+
["sideComplexity", .5]
|
|
279
|
+
];
|
|
280
|
+
const normalizedControls = {
|
|
281
|
+
density: .55,
|
|
282
|
+
curvature: .65,
|
|
283
|
+
symmetry: 1,
|
|
284
|
+
peakHeight: .35,
|
|
285
|
+
sideComplexity: .5
|
|
286
|
+
};
|
|
287
|
+
for (const [key, fallback] of controls) {
|
|
288
|
+
const value = ownValue(optionRecord, key, fallback);
|
|
289
|
+
if (!isUnitControl(value)) throw invalidV2Options("value", [key], `${key} must be between 0 and 1 inclusive`);
|
|
290
|
+
normalizedControls[key] = value;
|
|
291
|
+
}
|
|
292
|
+
const requestedCrest = ownValue(optionRecord, "crest", void 0);
|
|
293
|
+
if (requestedCrest !== void 0 && ![
|
|
294
|
+
"spear",
|
|
295
|
+
"fleur",
|
|
296
|
+
"diamond",
|
|
297
|
+
"none"
|
|
298
|
+
].includes(requestedCrest)) throw invalidV2Options("crest", ["crest"], "crest must be a supported crest");
|
|
299
|
+
const generationOptions = {
|
|
300
|
+
generationVersion,
|
|
301
|
+
seed: requestedSeed,
|
|
302
|
+
family: requestedFamily,
|
|
303
|
+
density: normalizedControls.density,
|
|
304
|
+
curvature: normalizedControls.curvature,
|
|
305
|
+
symmetry: normalizedControls.symmetry,
|
|
306
|
+
peakHeight: normalizedControls.peakHeight,
|
|
307
|
+
sideComplexity: normalizedControls.sideComplexity,
|
|
308
|
+
...requestedCrest === void 0 ? {} : { crest: requestedCrest }
|
|
309
|
+
};
|
|
310
|
+
let validationGeometry;
|
|
311
|
+
try {
|
|
312
|
+
validationGeometry = generateGate({
|
|
313
|
+
width: 160,
|
|
314
|
+
height: 160
|
|
315
|
+
}, generationOptions);
|
|
316
|
+
} catch (cause) {
|
|
317
|
+
if (cause instanceof TypeError || cause instanceof RangeError) throw invalidV2Options("crest", ["crest"], cause.message);
|
|
318
|
+
throw cause;
|
|
319
|
+
}
|
|
320
|
+
if (requestedCrest !== void 0 && !isCompatibleCrest(validationGeometry.options.family, requestedCrest)) throw invalidV2Options("crest", ["crest"], `crest is incompatible with generation-v2 family ${validationGeometry.options.family}`);
|
|
321
|
+
const stroke = ownValue(optionRecord, "stroke", "currentColor");
|
|
322
|
+
const surface = ownValue(optionRecord, "surface", "transparent");
|
|
323
|
+
try {
|
|
324
|
+
renderGateSVG(validationGeometry, {
|
|
325
|
+
stroke,
|
|
326
|
+
surface
|
|
327
|
+
});
|
|
328
|
+
} catch (cause) {
|
|
329
|
+
if (cause instanceof TypeError || cause instanceof RangeError) {
|
|
330
|
+
const key = typeof stroke !== "string" || cause.message.startsWith("stroke") ? "stroke" : "surface";
|
|
331
|
+
throw invalidV2Options("paint", [key], cause.message);
|
|
332
|
+
}
|
|
333
|
+
throw cause;
|
|
334
|
+
}
|
|
335
|
+
const normalizedStroke = stroke.trim();
|
|
336
|
+
const normalizedSurface = surface.trim();
|
|
337
|
+
if (!CSS.supports("color", normalizedStroke)) throw invalidV2Options("paint", ["stroke"], "paint must be supported CSS color syntax");
|
|
338
|
+
if (!CSS.supports("color", normalizedSurface)) throw invalidV2Options("paint", ["surface"], "paint must be supported CSS color syntax");
|
|
339
|
+
return Object.freeze({
|
|
340
|
+
seed: validationGeometry.seed,
|
|
341
|
+
generationVersion,
|
|
342
|
+
family: requestedFamily,
|
|
343
|
+
density: validationGeometry.options.density,
|
|
344
|
+
curvature: validationGeometry.options.curvature,
|
|
345
|
+
symmetry: validationGeometry.options.symmetry,
|
|
346
|
+
peakHeight: validationGeometry.options.peakHeight,
|
|
347
|
+
sideComplexity: validationGeometry.options.sideComplexity,
|
|
348
|
+
crest: requestedCrest,
|
|
349
|
+
stroke: normalizedStroke,
|
|
350
|
+
surface: normalizedSurface,
|
|
351
|
+
responsive
|
|
352
|
+
});
|
|
353
|
+
};
|
|
354
|
+
const normalizeControllerOptions = (options, fallbackSeed, oracleVersion) => {
|
|
355
|
+
const optionRecord = options;
|
|
356
|
+
const version = oracleVersion ?? (options !== null && typeof options === "object" && !Array.isArray(options) && Object.hasOwn(optionRecord, "generationVersion") && (optionRecord.generationVersion === 2 || optionRecord.generationVersion === 3) ? optionRecord.generationVersion : 1);
|
|
357
|
+
return version === 2 || version === 3 ? normalizeFamilyControllerOptions(options, fallbackSeed) : normalizeV1ControllerOptions(options, fallbackSeed);
|
|
358
|
+
};
|
|
359
|
+
const mergeControllerOptions = (current, updates) => {
|
|
360
|
+
const updateRecord = updates;
|
|
361
|
+
const suppliedVersion = updates !== null && typeof updates === "object" && !Array.isArray(updates) && Object.hasOwn(updateRecord, "generationVersion") ? updateRecord.generationVersion : void 0;
|
|
362
|
+
const targetVersion = suppliedVersion === void 0 ? current.generationVersion : suppliedVersion;
|
|
363
|
+
if ((targetVersion === 1 ? 1 : current.generationVersion !== 1 || targetVersion === 2 || targetVersion === 3 ? 2 : 1) === 2) {
|
|
364
|
+
const checkedUpdates = assertV2OptionShape(updates);
|
|
365
|
+
const responsive = ownValue(checkedUpdates, "responsive", void 0);
|
|
366
|
+
if (responsive !== void 0 && typeof responsive !== "boolean") throw invalidV2Options("responsive", ["responsive"], "responsive must be a boolean");
|
|
367
|
+
if (targetVersion !== 2 && targetVersion !== 3) throw invalidV2Options("version", ["generationVersion"], "generationVersion must be 1, 2 or 3");
|
|
368
|
+
} else {
|
|
369
|
+
if (updates === null || typeof updates !== "object" || Array.isArray(updates)) throw invalidOptions(updates, "options must be a plain object");
|
|
370
|
+
assertV1OptionKeys(updateRecord);
|
|
371
|
+
}
|
|
372
|
+
const candidate = targetVersion === current.generationVersion ? { ...current } : Object.fromEntries([
|
|
373
|
+
"seed",
|
|
374
|
+
"family",
|
|
375
|
+
"density",
|
|
376
|
+
"curvature",
|
|
377
|
+
"stroke",
|
|
378
|
+
"surface",
|
|
379
|
+
"responsive",
|
|
380
|
+
...current.generationVersion !== 1 && (targetVersion === 2 || targetVersion === 3) ? [
|
|
381
|
+
"symmetry",
|
|
382
|
+
"peakHeight",
|
|
383
|
+
"sideComplexity",
|
|
384
|
+
"crest"
|
|
385
|
+
] : []
|
|
386
|
+
].map((key) => [key, current[key]]));
|
|
387
|
+
candidate.generationVersion = targetVersion;
|
|
388
|
+
for (const [key, value] of Object.entries(updates)) if (value !== void 0) candidate[key] = value;
|
|
389
|
+
return {
|
|
390
|
+
options: candidate,
|
|
391
|
+
version: targetVersion
|
|
392
|
+
};
|
|
393
|
+
};
|
|
394
|
+
const measuredBorderBox = (entry) => {
|
|
395
|
+
return entry.borderBoxSize[0];
|
|
396
|
+
};
|
|
397
|
+
const physicalBorderWidth = (style, side) => {
|
|
398
|
+
const value = Number.parseFloat(style.getPropertyValue(`border-${side}-width`));
|
|
399
|
+
return Number.isFinite(value) ? value : 0;
|
|
400
|
+
};
|
|
401
|
+
const physicalPadding = (style) => {
|
|
402
|
+
const read = (side) => {
|
|
403
|
+
const value = Number.parseFloat(style.getPropertyValue(`padding-${side}`));
|
|
404
|
+
return Number.isFinite(value) ? value : 0;
|
|
405
|
+
};
|
|
406
|
+
return {
|
|
407
|
+
top: read("top"),
|
|
408
|
+
right: read("right"),
|
|
409
|
+
bottom: read("bottom"),
|
|
410
|
+
left: read("left")
|
|
411
|
+
};
|
|
412
|
+
};
|
|
413
|
+
const hasRequiredPadding = (measured, required) => measured.top >= required.top && measured.right >= required.right && measured.bottom >= required.bottom && measured.left >= required.left;
|
|
414
|
+
const supportsDimensions = (dimensions) => {
|
|
415
|
+
const { width, height } = dimensions;
|
|
416
|
+
const aspectRatio = width / height;
|
|
417
|
+
return Number.isFinite(width) && Number.isFinite(height) && width >= 160 && width <= 1600 && height >= 96 && height <= 1200 && aspectRatio >= .5 && aspectRatio <= 6;
|
|
418
|
+
};
|
|
419
|
+
const parseOverlay = (standaloneSvg) => {
|
|
420
|
+
const parsedRoot = new DOMParser().parseFromString(standaloneSvg, "image/svg+xml").documentElement;
|
|
421
|
+
if (parsedRoot.localName !== "svg" || parsedRoot.namespaceURI !== "http://www.w3.org/2000/svg") throw new Error("Gate Frame core returned an invalid SVG document");
|
|
422
|
+
return document.importNode(parsedRoot, true);
|
|
423
|
+
};
|
|
424
|
+
function gateFrame(targetValue, options = {}) {
|
|
425
|
+
let target;
|
|
426
|
+
if (typeof targetValue === "string") {
|
|
427
|
+
let resolvedTarget;
|
|
428
|
+
try {
|
|
429
|
+
resolvedTarget = document.querySelector(targetValue);
|
|
430
|
+
} catch (cause) {
|
|
431
|
+
if (cause instanceof DOMException && cause.name === "SyntaxError") throw new GateFrameInvalidSelectorError(targetValue);
|
|
432
|
+
throw cause;
|
|
433
|
+
}
|
|
434
|
+
if (resolvedTarget === null) throw new GateFrameTargetNotFoundError(targetValue);
|
|
435
|
+
if (!(resolvedTarget instanceof HTMLElement)) throw new GateFrameTargetNotHTMLElementError({
|
|
436
|
+
source: "selector",
|
|
437
|
+
selector: targetValue,
|
|
438
|
+
tagName: resolvedTarget.localName
|
|
439
|
+
});
|
|
440
|
+
target = resolvedTarget;
|
|
441
|
+
} else {
|
|
442
|
+
const directTarget = targetValue;
|
|
443
|
+
if (!(directTarget instanceof HTMLElement)) throw new GateFrameTargetNotHTMLElementError(directTarget instanceof Element ? {
|
|
444
|
+
source: "direct",
|
|
445
|
+
tagName: directTarget.localName
|
|
446
|
+
} : {
|
|
447
|
+
source: "direct",
|
|
448
|
+
valueType: directTarget === null ? "null" : typeof directTarget
|
|
449
|
+
});
|
|
450
|
+
target = directTarget;
|
|
451
|
+
}
|
|
452
|
+
if (mountedTargets.has(target)) throw new GateFrameAlreadyMountedError(target);
|
|
453
|
+
const initialStyle = getComputedStyle(target);
|
|
454
|
+
classifyTarget(target, initialStyle);
|
|
455
|
+
let currentOptions = normalizeControllerOptions(options);
|
|
456
|
+
const computedPosition = initialStyle.position || target.style.position || "static";
|
|
457
|
+
const previousPosition = target.style.getPropertyValue("position");
|
|
458
|
+
const previousPositionPriority = target.style.getPropertyPriority("position");
|
|
459
|
+
const ownsPosition = computedPosition === "static";
|
|
460
|
+
if (ownsPosition) {
|
|
461
|
+
const absoluteDescendant = Array.from(target.querySelectorAll("*")).find((descendant) => getComputedStyle(descendant).position === "absolute");
|
|
462
|
+
if (absoluteDescendant !== void 0) throw new GateFramePositioningConflictError(target, absoluteDescendant);
|
|
463
|
+
target.style.setProperty("position", "relative");
|
|
464
|
+
}
|
|
465
|
+
let seed = currentOptions.seed;
|
|
466
|
+
let paint = Object.freeze({
|
|
467
|
+
stroke: currentOptions.stroke,
|
|
468
|
+
surface: currentOptions.surface
|
|
469
|
+
});
|
|
470
|
+
let currentStatus = PENDING_STATUS;
|
|
471
|
+
let currentSnapshot = null;
|
|
472
|
+
let standaloneSvg = null;
|
|
473
|
+
let overlay = null;
|
|
474
|
+
let animationFrame = null;
|
|
475
|
+
let generation = 0;
|
|
476
|
+
let destroyed = false;
|
|
477
|
+
let destroyError = null;
|
|
478
|
+
let observing = false;
|
|
479
|
+
let observedBorderBoxDimensions = null;
|
|
480
|
+
let measuredDimensions = null;
|
|
481
|
+
const readyWaiters = [];
|
|
482
|
+
const hasNonzeroLayout = () => target.offsetWidth > 0 && target.offsetHeight > 0;
|
|
483
|
+
const invalidateMeasurement = () => {
|
|
484
|
+
if (destroyed) return;
|
|
485
|
+
measuredDimensions = null;
|
|
486
|
+
currentStatus = PENDING_STATUS;
|
|
487
|
+
generation += 1;
|
|
488
|
+
if (animationFrame !== null) {
|
|
489
|
+
cancelAnimationFrame(animationFrame);
|
|
490
|
+
animationFrame = null;
|
|
491
|
+
}
|
|
492
|
+
};
|
|
493
|
+
const publishRenderError = (error) => {
|
|
494
|
+
generation += 1;
|
|
495
|
+
if (animationFrame !== null) {
|
|
496
|
+
cancelAnimationFrame(animationFrame);
|
|
497
|
+
animationFrame = null;
|
|
498
|
+
}
|
|
499
|
+
overlay?.remove();
|
|
500
|
+
overlay = null;
|
|
501
|
+
currentStatus = Object.freeze({
|
|
502
|
+
state: "error",
|
|
503
|
+
error
|
|
504
|
+
});
|
|
505
|
+
for (const waiter of readyWaiters.splice(0)) waiter.reject(error);
|
|
506
|
+
};
|
|
507
|
+
const scheduleRender = () => {
|
|
508
|
+
if (destroyed || measuredDimensions === null) return;
|
|
509
|
+
currentStatus = PENDING_STATUS;
|
|
510
|
+
const token = ++generation;
|
|
511
|
+
if (animationFrame !== null) {
|
|
512
|
+
cancelAnimationFrame(animationFrame);
|
|
513
|
+
animationFrame = null;
|
|
514
|
+
}
|
|
515
|
+
const frameId = requestAnimationFrame(() => {
|
|
516
|
+
if (animationFrame === frameId) animationFrame = null;
|
|
517
|
+
if (destroyed || token !== generation || measuredDimensions === null) return;
|
|
518
|
+
if (!target.isConnected || !hasNonzeroLayout()) {
|
|
519
|
+
invalidateMeasurement();
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
const style = getComputedStyle(target);
|
|
523
|
+
try {
|
|
524
|
+
classifyTarget(target, style);
|
|
525
|
+
} catch (cause) {
|
|
526
|
+
if (cause instanceof GateFrameUnsupportedTargetError) {
|
|
527
|
+
publishRenderError(cause);
|
|
528
|
+
return;
|
|
529
|
+
}
|
|
530
|
+
throw cause;
|
|
531
|
+
}
|
|
532
|
+
if (!supportsDimensions(measuredDimensions)) {
|
|
533
|
+
publishRenderError(new GateFrameUnsupportedDimensionsError(measuredDimensions));
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
536
|
+
const generationOptions = currentOptions.generationVersion === 1 ? {
|
|
537
|
+
seed,
|
|
538
|
+
family: "courtyard",
|
|
539
|
+
generationVersion: 1,
|
|
540
|
+
density: currentOptions.density,
|
|
541
|
+
curvature: currentOptions.curvature
|
|
542
|
+
} : {
|
|
543
|
+
seed,
|
|
544
|
+
generationVersion: currentOptions.generationVersion,
|
|
545
|
+
family: currentOptions.family,
|
|
546
|
+
density: currentOptions.density,
|
|
547
|
+
curvature: currentOptions.curvature,
|
|
548
|
+
symmetry: currentOptions.symmetry,
|
|
549
|
+
peakHeight: currentOptions.peakHeight,
|
|
550
|
+
sideComplexity: currentOptions.sideComplexity,
|
|
551
|
+
...currentOptions.crest === void 0 ? {} : { crest: currentOptions.crest }
|
|
552
|
+
};
|
|
553
|
+
const geometry = generateGate(measuredDimensions, generationOptions);
|
|
554
|
+
const measuredPadding = physicalPadding(style);
|
|
555
|
+
if (!hasRequiredPadding(measuredPadding, geometry.contentInsets)) {
|
|
556
|
+
publishRenderError(new GateFrameInsufficientSpaceError(measuredPadding, geometry.contentInsets));
|
|
557
|
+
return;
|
|
558
|
+
}
|
|
559
|
+
const nextStandaloneSvg = renderGateSVG(geometry, paint);
|
|
560
|
+
const nextOverlay = parseOverlay(nextStandaloneSvg);
|
|
561
|
+
if (destroyed || token !== generation) return;
|
|
562
|
+
nextOverlay.setAttribute("data-gate-frame-overlay", "");
|
|
563
|
+
nextOverlay.setAttribute("aria-hidden", "true");
|
|
564
|
+
nextOverlay.setAttribute("focusable", "false");
|
|
565
|
+
nextOverlay.style.position = "absolute";
|
|
566
|
+
nextOverlay.style.inset = "0";
|
|
567
|
+
nextOverlay.style.width = "100%";
|
|
568
|
+
nextOverlay.style.height = "100%";
|
|
569
|
+
nextOverlay.style.pointerEvents = "none";
|
|
570
|
+
const nextSnapshot = geometry.generationVersion === 1 ? Object.freeze({
|
|
571
|
+
seed: geometry.seed,
|
|
572
|
+
generationVersion: 1,
|
|
573
|
+
geometry,
|
|
574
|
+
paint
|
|
575
|
+
}) : geometry.generationVersion === 2 ? Object.freeze({
|
|
576
|
+
seed: geometry.seed,
|
|
577
|
+
generationVersion: 2,
|
|
578
|
+
geometry,
|
|
579
|
+
paint
|
|
580
|
+
}) : Object.freeze({
|
|
581
|
+
seed: geometry.seed,
|
|
582
|
+
generationVersion: 3,
|
|
583
|
+
geometry,
|
|
584
|
+
paint
|
|
585
|
+
});
|
|
586
|
+
if (overlay === null) target.append(nextOverlay);
|
|
587
|
+
else overlay.replaceWith(nextOverlay);
|
|
588
|
+
if (currentOptions.responsive === false && observing) {
|
|
589
|
+
observer.disconnect();
|
|
590
|
+
observing = false;
|
|
591
|
+
}
|
|
592
|
+
overlay = nextOverlay;
|
|
593
|
+
standaloneSvg = nextStandaloneSvg;
|
|
594
|
+
currentSnapshot = nextSnapshot;
|
|
595
|
+
currentStatus = Object.freeze({
|
|
596
|
+
state: "ready",
|
|
597
|
+
snapshot: nextSnapshot
|
|
598
|
+
});
|
|
599
|
+
for (const waiter of readyWaiters.splice(0)) waiter.resolve(nextSnapshot);
|
|
600
|
+
});
|
|
601
|
+
animationFrame = frameId;
|
|
602
|
+
};
|
|
603
|
+
const observer = new ResizeObserver((entries) => {
|
|
604
|
+
const entry = entries.find((candidate) => candidate.target === target);
|
|
605
|
+
const borderBox = entry === void 0 ? void 0 : measuredBorderBox(entry);
|
|
606
|
+
if (borderBox === void 0 || !target.isConnected || !hasNonzeroLayout()) {
|
|
607
|
+
invalidateMeasurement();
|
|
608
|
+
return;
|
|
609
|
+
}
|
|
610
|
+
const style = getComputedStyle(target);
|
|
611
|
+
observedBorderBoxDimensions = {
|
|
612
|
+
width: borderBox.inlineSize,
|
|
613
|
+
height: borderBox.blockSize
|
|
614
|
+
};
|
|
615
|
+
const width = observedBorderBoxDimensions.width - physicalBorderWidth(style, "left") - physicalBorderWidth(style, "right");
|
|
616
|
+
const height = observedBorderBoxDimensions.height - physicalBorderWidth(style, "top") - physicalBorderWidth(style, "bottom");
|
|
617
|
+
if (width <= 0 || height <= 0) {
|
|
618
|
+
invalidateMeasurement();
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
const measurementChanged = measuredDimensions?.width !== width || measuredDimensions.height !== height;
|
|
622
|
+
measuredDimensions = {
|
|
623
|
+
width,
|
|
624
|
+
height
|
|
625
|
+
};
|
|
626
|
+
try {
|
|
627
|
+
classifyTarget(target, style);
|
|
628
|
+
} catch (cause) {
|
|
629
|
+
if (cause instanceof GateFrameUnsupportedTargetError) {
|
|
630
|
+
publishRenderError(cause);
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
633
|
+
throw cause;
|
|
634
|
+
}
|
|
635
|
+
if (!measurementChanged) return;
|
|
636
|
+
scheduleRender();
|
|
637
|
+
});
|
|
638
|
+
const controller = {
|
|
639
|
+
status: () => currentStatus,
|
|
640
|
+
whenReady: () => {
|
|
641
|
+
if (destroyed) return Promise.reject(destroyError ?? new GateFrameDestroyedError());
|
|
642
|
+
if (currentStatus.state === "ready") return Promise.resolve(currentStatus.snapshot);
|
|
643
|
+
if (currentStatus.state === "error") return Promise.reject(currentStatus.error);
|
|
644
|
+
return new Promise((resolve, reject) => readyWaiters.push({
|
|
645
|
+
resolve,
|
|
646
|
+
reject
|
|
647
|
+
}));
|
|
648
|
+
},
|
|
649
|
+
update: (nextOptions) => {
|
|
650
|
+
if (destroyed) throw destroyError ?? new GateFrameDestroyedError();
|
|
651
|
+
const merged = mergeControllerOptions(currentOptions, nextOptions);
|
|
652
|
+
const candidateOptions = normalizeControllerOptions(merged.options, currentOptions.seed, merged.version);
|
|
653
|
+
if (observedBorderBoxDimensions !== null) {
|
|
654
|
+
const style = getComputedStyle(target);
|
|
655
|
+
const width = observedBorderBoxDimensions.width - physicalBorderWidth(style, "left") - physicalBorderWidth(style, "right");
|
|
656
|
+
const height = observedBorderBoxDimensions.height - physicalBorderWidth(style, "top") - physicalBorderWidth(style, "bottom");
|
|
657
|
+
if (width <= 0 || height <= 0) invalidateMeasurement();
|
|
658
|
+
else measuredDimensions = {
|
|
659
|
+
width,
|
|
660
|
+
height
|
|
661
|
+
};
|
|
662
|
+
}
|
|
663
|
+
const wasResponsive = currentOptions.responsive !== false;
|
|
664
|
+
currentOptions = candidateOptions;
|
|
665
|
+
seed = candidateOptions.seed;
|
|
666
|
+
paint = Object.freeze({
|
|
667
|
+
stroke: currentOptions.stroke,
|
|
668
|
+
surface: currentOptions.surface
|
|
669
|
+
});
|
|
670
|
+
if (wasResponsive === false && currentOptions.responsive !== false && !observing) {
|
|
671
|
+
observer.observe(target, { box: "border-box" });
|
|
672
|
+
observing = true;
|
|
673
|
+
}
|
|
674
|
+
scheduleRender();
|
|
675
|
+
return controller;
|
|
676
|
+
},
|
|
677
|
+
randomize: (explicitSeed) => {
|
|
678
|
+
if (destroyed) throw destroyError ?? new GateFrameDestroyedError();
|
|
679
|
+
const requestedSeed = explicitSeed === void 0 ? createRandomSeed() : explicitSeed;
|
|
680
|
+
const merged = mergeControllerOptions(currentOptions, { seed: requestedSeed });
|
|
681
|
+
const candidateOptions = normalizeControllerOptions(merged.options, currentOptions.seed, merged.version);
|
|
682
|
+
currentOptions = candidateOptions;
|
|
683
|
+
seed = candidateOptions.seed;
|
|
684
|
+
scheduleRender();
|
|
685
|
+
return seed;
|
|
686
|
+
},
|
|
687
|
+
snapshot: () => {
|
|
688
|
+
if (destroyed) throw destroyError ?? new GateFrameDestroyedError();
|
|
689
|
+
return currentSnapshot;
|
|
690
|
+
},
|
|
691
|
+
toSVG: () => {
|
|
692
|
+
if (destroyed) throw destroyError ?? new GateFrameDestroyedError();
|
|
693
|
+
if (standaloneSvg === null) throw new GateFrameNotReadyError();
|
|
694
|
+
return standaloneSvg;
|
|
695
|
+
},
|
|
696
|
+
destroy: () => {
|
|
697
|
+
if (destroyed) return;
|
|
698
|
+
destroyed = true;
|
|
699
|
+
destroyError = new GateFrameDestroyedError();
|
|
700
|
+
generation += 1;
|
|
701
|
+
if (animationFrame !== null) {
|
|
702
|
+
cancelAnimationFrame(animationFrame);
|
|
703
|
+
animationFrame = null;
|
|
704
|
+
}
|
|
705
|
+
observer.disconnect();
|
|
706
|
+
observing = false;
|
|
707
|
+
overlay?.remove();
|
|
708
|
+
overlay = null;
|
|
709
|
+
for (const waiter of readyWaiters.splice(0)) waiter.reject(destroyError);
|
|
710
|
+
if (ownsPosition && target.style.getPropertyValue("position") === "relative" && target.style.getPropertyPriority("position") === "") {
|
|
711
|
+
if (previousPosition === "") target.style.removeProperty("position");
|
|
712
|
+
else target.style.setProperty("position", previousPosition, previousPositionPriority);
|
|
713
|
+
}
|
|
714
|
+
mountedTargets.delete(target);
|
|
715
|
+
currentSnapshot = null;
|
|
716
|
+
standaloneSvg = null;
|
|
717
|
+
currentStatus = DESTROYED_STATUS;
|
|
718
|
+
}
|
|
719
|
+
};
|
|
720
|
+
mountedTargets.set(target, controller);
|
|
721
|
+
observer.observe(target, { box: "border-box" });
|
|
722
|
+
observing = true;
|
|
723
|
+
return controller;
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
//#endregion
|
|
727
|
+
export { GateFrameAlreadyMountedError, GateFrameDestroyedError, GateFrameError, GateFrameInsufficientSpaceError, GateFrameInvalidOptionsError, GateFrameInvalidSelectorError, GateFrameNotReadyError, GateFramePositioningConflictError, GateFrameTargetNotFoundError, GateFrameTargetNotHTMLElementError, GateFrameUnsupportedDimensionsError, GateFrameUnsupportedTargetError, gateFrame };
|
|
728
|
+
//# sourceMappingURL=index.js.map
|