@webiny/website-builder-sdk 0.0.0-unstable.eb196ccd2f → 0.0.0-unstable.eb81deee7e
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/BindingsApi.js +29 -36
- package/BindingsApi.js.map +1 -1
- package/BindingsProcessor.js +34 -43
- package/BindingsProcessor.js.map +1 -1
- package/BindingsProcessor.test.js +82 -77
- package/BindingsProcessor.test.js.map +1 -1
- package/BindingsResolver.d.ts +5 -2
- package/BindingsResolver.js +99 -122
- package/BindingsResolver.js.map +1 -1
- package/BindingsResolver.test.js +399 -362
- package/BindingsResolver.test.js.map +1 -1
- package/ComponentInputTraverser.js +32 -49
- package/ComponentInputTraverser.js.map +1 -1
- package/ComponentManifestToAstConverter.js +20 -21
- package/ComponentManifestToAstConverter.js.map +1 -1
- package/ComponentRegistry.d.ts +9 -0
- package/ComponentRegistry.js +38 -22
- package/ComponentRegistry.js.map +1 -1
- package/ComponentResolver.d.ts +3 -2
- package/ComponentResolver.js +39 -29
- package/ComponentResolver.js.map +1 -1
- package/ComponentResolver.test.d.ts +1 -0
- package/ComponentResolver.test.js +81 -0
- package/ComponentResolver.test.js.map +1 -0
- package/ConstraintEvaluator.d.ts +36 -0
- package/ConstraintEvaluator.js +288 -0
- package/ConstraintEvaluator.js.map +1 -0
- package/ConstraintEvaluator.test.d.ts +1 -0
- package/ConstraintEvaluator.test.js +1634 -0
- package/ConstraintEvaluator.test.js.map +1 -0
- package/ContentSdk.d.ts +17 -3
- package/ContentSdk.js +113 -90
- package/ContentSdk.js.map +1 -1
- package/DocumentStore.d.ts +1 -1
- package/DocumentStore.js +47 -59
- package/DocumentStore.js.map +1 -1
- package/DocumentStoreManager.js +17 -16
- package/DocumentStoreManager.js.map +1 -1
- package/EditingSdk.d.ts +2 -2
- package/EditingSdk.js +92 -124
- package/EditingSdk.js.map +1 -1
- package/ElementFactory.d.ts +2 -3
- package/ElementFactory.js +164 -163
- package/ElementFactory.js.map +1 -1
- package/ElementFactory.objectListDefaults.test.d.ts +1 -0
- package/ElementFactory.objectListDefaults.test.js +423 -0
- package/ElementFactory.objectListDefaults.test.js.map +1 -0
- package/ElementFactory.test.d.ts +1 -0
- package/ElementFactory.test.js +353 -0
- package/ElementFactory.test.js.map +1 -0
- package/Environment.js +18 -19
- package/Environment.js.map +1 -1
- package/FunctionConverter.js +8 -7
- package/FunctionConverter.js.map +1 -1
- package/HashObject.js +11 -12
- package/HashObject.js.map +1 -1
- package/HotkeyManager.js +41 -48
- package/HotkeyManager.js.map +1 -1
- package/IBindingsUpdater.js +0 -3
- package/IRedirects.js +0 -3
- package/InheritanceProcessor.d.ts +2 -2
- package/InheritanceProcessor.js +99 -139
- package/InheritanceProcessor.js.map +1 -1
- package/InheritanceProcessor.test.js +178 -178
- package/InheritanceProcessor.test.js.map +1 -1
- package/InheritedValueResolver.d.ts +18 -1
- package/InheritedValueResolver.js +18 -20
- package/InheritedValueResolver.js.map +1 -1
- package/InputBindingsProcessor.d.ts +23 -2
- package/InputBindingsProcessor.js +200 -289
- package/InputBindingsProcessor.js.map +1 -1
- package/InputBindingsProcessorTokens.test.d.ts +1 -0
- package/InputBindingsProcessorTokens.test.js +400 -0
- package/InputBindingsProcessorTokens.test.js.map +1 -0
- package/InputsBindingsProcessor.test.js +334 -313
- package/InputsBindingsProcessor.test.js.map +1 -1
- package/InputsUpdater.d.ts +1 -1
- package/InputsUpdater.js +23 -26
- package/InputsUpdater.js.map +1 -1
- package/LiveSdk.d.ts +2 -2
- package/LiveSdk.js +12 -13
- package/LiveSdk.js.map +1 -1
- package/Logger.d.ts +2 -1
- package/Logger.js +9 -8
- package/Logger.js.map +1 -1
- package/MouseTracker.js +77 -83
- package/MouseTracker.js.map +1 -1
- package/NullSdk.d.ts +4 -3
- package/NullSdk.js +22 -14
- package/NullSdk.js.map +1 -1
- package/PreviewDocument.d.ts +1 -0
- package/PreviewDocument.js +30 -30
- package/PreviewDocument.js.map +1 -1
- package/PreviewSdk.d.ts +4 -3
- package/PreviewSdk.js +19 -17
- package/PreviewSdk.js.map +1 -1
- package/PreviewViewport.d.ts +0 -1
- package/PreviewViewport.js +50 -82
- package/PreviewViewport.js.map +1 -1
- package/README.md +10 -2
- package/ResizeObserver.js +24 -31
- package/ResizeObserver.js.map +1 -1
- package/StylesBindingsProcessor.d.ts +12 -2
- package/StylesBindingsProcessor.js +62 -79
- package/StylesBindingsProcessor.js.map +1 -1
- package/StylesUpdater.d.ts +1 -1
- package/StylesUpdater.js +20 -25
- package/StylesUpdater.js.map +1 -1
- package/Theme.js +30 -32
- package/Theme.js.map +1 -1
- package/ViewportManager.d.ts +2 -0
- package/ViewportManager.js +89 -97
- package/ViewportManager.js.map +1 -1
- package/assertBindingShapes.d.ts +31 -0
- package/assertBindingShapes.js +64 -0
- package/assertBindingShapes.js.map +1 -0
- package/assertBindingShapes.test.d.ts +1 -0
- package/assertBindingShapes.test.js +131 -0
- package/assertBindingShapes.test.js.map +1 -0
- package/asset/deliveryUrl.d.ts +41 -0
- package/asset/deliveryUrl.js +34 -0
- package/asset/deliveryUrl.js.map +1 -0
- package/asset/deliveryUrl.test.d.ts +1 -0
- package/asset/deliveryUrl.test.js +127 -0
- package/asset/deliveryUrl.test.js.map +1 -0
- package/asset/geometry.d.ts +7 -0
- package/asset/geometry.js +10 -0
- package/asset/geometry.js.map +1 -0
- package/asset/imageSrcSet.d.ts +63 -0
- package/asset/imageSrcSet.js +98 -0
- package/asset/imageSrcSet.js.map +1 -0
- package/asset/imageSrcSet.test.d.ts +1 -0
- package/asset/imageSrcSet.test.js +103 -0
- package/asset/imageSrcSet.test.js.map +1 -0
- package/asset/index.d.ts +5 -0
- package/asset/index.js +5 -0
- package/asset/normalize.d.ts +24 -0
- package/asset/normalize.js +106 -0
- package/asset/normalize.js.map +1 -0
- package/asset/normalize.test.d.ts +1 -0
- package/asset/normalize.test.js +263 -0
- package/asset/normalize.test.js.map +1 -0
- package/asset/types.d.ts +2 -0
- package/asset/types.js +0 -0
- package/constants.d.ts +1 -0
- package/constants.js +7 -5
- package/constants.js.map +1 -1
- package/contentEntry/ContentEntryBindingsResolver.d.ts +30 -0
- package/contentEntry/ContentEntryBindingsResolver.js +45 -0
- package/contentEntry/ContentEntryBindingsResolver.js.map +1 -0
- package/contentEntry/ContentEntryCache.d.ts +46 -0
- package/contentEntry/ContentEntryCache.js +50 -0
- package/contentEntry/ContentEntryCache.js.map +1 -0
- package/contentEntry/resolveContentEntry.d.ts +74 -0
- package/contentEntry/resolveContentEntry.js +53 -0
- package/contentEntry/resolveContentEntry.js.map +1 -0
- package/contentEntry/resolveContentEntry.test.d.ts +1 -0
- package/contentEntry/resolveContentEntry.test.js +213 -0
- package/contentEntry/resolveContentEntry.test.js.map +1 -0
- package/createComponent.d.ts +5 -0
- package/createComponent.js +32 -0
- package/createComponent.js.map +1 -0
- package/createElement.js +5 -6
- package/createElement.js.map +1 -1
- package/createInput.d.ts +132 -16
- package/createInput.js +92 -111
- package/createInput.js.map +1 -1
- package/createTheme.js +2 -3
- package/createTheme.js.map +1 -1
- package/dataProviders/ApiClient.d.ts +10 -5
- package/dataProviders/ApiClient.js +40 -49
- package/dataProviders/ApiClient.js.map +1 -1
- package/dataProviders/DefaultDataProvider.d.ts +7 -3
- package/dataProviders/DefaultDataProvider.js +91 -45
- package/dataProviders/DefaultDataProvider.js.map +1 -1
- package/dataProviders/GET_EXPERIMENT_PAUSED.d.ts +1 -0
- package/dataProviders/GET_EXPERIMENT_PAUSED.js +17 -0
- package/dataProviders/GET_EXPERIMENT_PAUSED.js.map +1 -0
- package/dataProviders/GET_PAGE_BY_ID.d.ts +1 -1
- package/dataProviders/GET_PAGE_BY_ID.js +4 -1
- package/dataProviders/GET_PAGE_BY_ID.js.map +1 -1
- package/dataProviders/GET_PAGE_BY_PATH.d.ts +1 -1
- package/dataProviders/GET_PAGE_BY_PATH.js +4 -1
- package/dataProviders/GET_PAGE_BY_PATH.js.map +1 -1
- package/dataProviders/GET_PAGE_EXPERIMENT.d.ts +1 -0
- package/dataProviders/GET_PAGE_EXPERIMENT.js +32 -0
- package/dataProviders/GET_PAGE_EXPERIMENT.js.map +1 -0
- package/dataProviders/GET_VARIANT_CONTENT.d.ts +1 -0
- package/dataProviders/GET_VARIANT_CONTENT.js +24 -0
- package/dataProviders/GET_VARIANT_CONTENT.js.map +1 -0
- package/dataProviders/LIST_PUBLISHED_PAGES.d.ts +1 -1
- package/dataProviders/LIST_PUBLISHED_PAGES.js +17 -5
- package/dataProviders/LIST_PUBLISHED_PAGES.js.map +1 -1
- package/dataProviders/NullDataProvider.d.ts +16 -4
- package/dataProviders/NullDataProvider.js +30 -13
- package/dataProviders/NullDataProvider.js.map +1 -1
- package/dataProviders/RedirectsProvider.js +24 -27
- package/dataProviders/RedirectsProvider.js.map +1 -1
- package/defaultBreakpoints.js +43 -22
- package/defaultBreakpoints.js.map +1 -1
- package/documentOperations/$addElementReferenceToParent.d.ts +1 -1
- package/documentOperations/$addElementReferenceToParent.js +30 -26
- package/documentOperations/$addElementReferenceToParent.js.map +1 -1
- package/documentOperations/AddElement.js +8 -7
- package/documentOperations/AddElement.js.map +1 -1
- package/documentOperations/AddToParent.d.ts +2 -2
- package/documentOperations/AddToParent.js +14 -13
- package/documentOperations/AddToParent.js.map +1 -1
- package/documentOperations/IDocumentOperation.js +0 -3
- package/documentOperations/RemoveElement.js +10 -7
- package/documentOperations/RemoveElement.js.map +1 -1
- package/documentOperations/SetGlobalInputBinding.js +23 -22
- package/documentOperations/SetGlobalInputBinding.js.map +1 -1
- package/documentOperations/SetGlobalStyleBinding.js +23 -23
- package/documentOperations/SetGlobalStyleBinding.js.map +1 -1
- package/documentOperations/SetInputBindingOverride.js +30 -29
- package/documentOperations/SetInputBindingOverride.js.map +1 -1
- package/documentOperations/SetStyleBindingOverride.js +30 -31
- package/documentOperations/SetStyleBindingOverride.js.map +1 -1
- package/documentOperations/index.js +9 -8
- package/documentOperations/index.js.map +1 -1
- package/experiments/PostHogAnalyticsProvider.d.ts +31 -0
- package/experiments/PostHogAnalyticsProvider.js +28 -0
- package/experiments/PostHogAnalyticsProvider.js.map +1 -0
- package/experiments/analyticsProvider.d.ts +4 -0
- package/experiments/analyticsProvider.js +11 -0
- package/experiments/analyticsProvider.js.map +1 -0
- package/experiments/bucketing.d.ts +27 -0
- package/experiments/bucketing.js +91 -0
- package/experiments/bucketing.js.map +1 -0
- package/experiments/bucketing.test.d.ts +1 -0
- package/experiments/bucketing.test.js +183 -0
- package/experiments/bucketing.test.js.map +1 -0
- package/experiments/index.d.ts +5 -0
- package/experiments/index.js +5 -0
- package/experiments/render.d.ts +79 -0
- package/experiments/render.js +149 -0
- package/experiments/render.js.map +1 -0
- package/experiments/types.d.ts +108 -0
- package/experiments/types.js +4 -0
- package/experiments/types.js.map +1 -0
- package/findMatchingAstNode.js +11 -13
- package/findMatchingAstNode.js.map +1 -1
- package/generateElementId.d.ts +1 -1
- package/generateElementId.js +2 -2
- package/generateElementId.js.map +1 -1
- package/headersProvider.js +4 -3
- package/headersProvider.js.map +1 -1
- package/image/geometry.d.ts +54 -0
- package/image/geometry.js +112 -0
- package/image/geometry.js.map +1 -0
- package/image/geometry.test.d.ts +1 -0
- package/image/geometry.test.js +190 -0
- package/image/geometry.test.js.map +1 -0
- package/image/index.d.ts +3 -0
- package/image/index.js +3 -0
- package/image/presets.d.ts +6 -0
- package/image/presets.js +20 -0
- package/image/presets.js.map +1 -0
- package/image/types.d.ts +10 -0
- package/image/types.js +0 -0
- package/index.d.ts +12 -1
- package/index.js +11 -3
- package/jsonPatch.d.ts +1 -1
- package/jsonPatch.js +5 -9
- package/jsonPatch.js.map +1 -1
- package/messages.js +12 -11
- package/messages.js.map +1 -1
- package/messenger/MessageOrigin.js +12 -11
- package/messenger/MessageOrigin.js.map +1 -1
- package/messenger/Messenger.js +59 -70
- package/messenger/Messenger.js.map +1 -1
- package/messenger/index.js +0 -2
- package/package.json +19 -22
- package/registerComponentGroup.js +5 -6
- package/registerComponentGroup.js.map +1 -1
- package/tokenBinding.d.ts +26 -0
- package/tokenBinding.js +15 -0
- package/tokenBinding.js.map +1 -0
- package/tokenBinding.test.d.ts +1 -0
- package/tokenBinding.test.js +165 -0
- package/tokenBinding.test.js.map +1 -0
- package/types/ShorthandCssProperties.js +0 -3
- package/types/WebsiteBuilderTheme.d.ts +54 -45
- package/types/WebsiteBuilderTheme.js +0 -3
- package/types.d.ts +294 -13
- package/types.js +0 -3
- package/IBindingsUpdater.js.map +0 -1
- package/IRedirects.js.map +0 -1
- package/documentOperations/IDocumentOperation.js.map +0 -1
- package/index.js.map +0 -1
- package/lexical/createDefaultLexicalTheme.d.ts +0 -2
- package/lexical/createDefaultLexicalTheme.js +0 -84
- package/lexical/createDefaultLexicalTheme.js.map +0 -1
- package/lexical/createLexicalTheme.d.ts +0 -2
- package/lexical/createLexicalTheme.js +0 -7
- package/lexical/createLexicalTheme.js.map +0 -1
- package/lexical/deepMerge.d.ts +0 -1
- package/lexical/deepMerge.js +0 -23
- package/lexical/deepMerge.js.map +0 -1
- package/messenger/index.js.map +0 -1
- package/types/LexicalEditorTheme.d.ts +0 -2
- package/types/LexicalEditorTheme.js +0 -3
- package/types/LexicalEditorTheme.js.map +0 -1
- package/types/ShorthandCssProperties.js.map +0 -1
- package/types/WebsiteBuilderTheme.js.map +0 -1
- package/types.js.map +0 -1
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { nanoid } from "nanoid";
|
|
2
|
+
import { getHeadersProvider } from "../headersProvider.js";
|
|
3
|
+
import { assignVariant, forcedAssignment } from "./bucketing.js";
|
|
4
|
+
import { getAnalyticsProvider } from "./analyticsProvider.js";
|
|
5
|
+
const DEFAULT_VISITOR_COOKIE = "wb_ab_vid";
|
|
6
|
+
const FORCED_VARIANT_PARAM = "wb-variant";
|
|
7
|
+
const DEFAULT_GEO_HEADERS = [
|
|
8
|
+
"x-vercel-ip-country",
|
|
9
|
+
"cloudfront-viewer-country",
|
|
10
|
+
"cf-ipcountry",
|
|
11
|
+
"x-country-code"
|
|
12
|
+
];
|
|
13
|
+
const readCookie = (cookieHeader, name)=>{
|
|
14
|
+
if (!cookieHeader) return;
|
|
15
|
+
for (const part of cookieHeader.split(";")){
|
|
16
|
+
const index = part.indexOf("=");
|
|
17
|
+
if (-1 === index) continue;
|
|
18
|
+
const key = part.slice(0, index).trim();
|
|
19
|
+
if (key === name) return decodeURIComponent(part.slice(index + 1).trim());
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
const detectDevice = (userAgent)=>{
|
|
23
|
+
if (!userAgent) return;
|
|
24
|
+
if (/iPad|Tablet|PlayBook|Silk/i.test(userAgent)) return "tablet";
|
|
25
|
+
if (/Mobi|Android.+Mobile|iPhone|iPod|Windows Phone/i.test(userAgent)) return "mobile";
|
|
26
|
+
return "desktop";
|
|
27
|
+
};
|
|
28
|
+
const resolveVisitorContext = async (options = {})=>{
|
|
29
|
+
const cookieName = options.cookieName ?? DEFAULT_VISITOR_COOKIE;
|
|
30
|
+
const generate = options.generateVisitorId ?? (()=>nanoid());
|
|
31
|
+
let headers;
|
|
32
|
+
const provider = getHeadersProvider();
|
|
33
|
+
if (provider) try {
|
|
34
|
+
headers = await provider();
|
|
35
|
+
} catch {
|
|
36
|
+
headers = void 0;
|
|
37
|
+
}
|
|
38
|
+
let visitorId = options.visitorId;
|
|
39
|
+
let generatedVisitorId = false;
|
|
40
|
+
if (!visitorId) visitorId = readCookie(headers?.get("cookie"), cookieName);
|
|
41
|
+
if (!visitorId) {
|
|
42
|
+
visitorId = generate();
|
|
43
|
+
generatedVisitorId = true;
|
|
44
|
+
}
|
|
45
|
+
let country = options.country;
|
|
46
|
+
if (!country && headers) for (const header of options.geoHeaders ?? DEFAULT_GEO_HEADERS){
|
|
47
|
+
const value = headers.get(header);
|
|
48
|
+
if (value) {
|
|
49
|
+
country = value.toUpperCase();
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
const device = options.device ?? detectDevice(headers?.get("user-agent"));
|
|
54
|
+
return {
|
|
55
|
+
context: {
|
|
56
|
+
visitorId,
|
|
57
|
+
country,
|
|
58
|
+
device
|
|
59
|
+
},
|
|
60
|
+
generatedVisitorId
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
const resolveForcedVariantId = (options)=>{
|
|
64
|
+
if (options.forcedVariantId) return options.forcedVariantId;
|
|
65
|
+
const raw = options.searchParams?.[FORCED_VARIANT_PARAM];
|
|
66
|
+
return Array.isArray(raw) ? raw[0] : raw;
|
|
67
|
+
};
|
|
68
|
+
const overlayVariant = (baseline, variant)=>({
|
|
69
|
+
...baseline,
|
|
70
|
+
properties: variant.properties,
|
|
71
|
+
bindings: variant.bindings,
|
|
72
|
+
elements: variant.elements,
|
|
73
|
+
extensions: variant.extensions,
|
|
74
|
+
metadata: variant.metadata
|
|
75
|
+
});
|
|
76
|
+
const getPageWithExperiment = async (sdk, path, options = {})=>{
|
|
77
|
+
const [controlPage, experiment] = await Promise.all([
|
|
78
|
+
sdk.getPage(path),
|
|
79
|
+
sdk.getPageExperiment(path)
|
|
80
|
+
]);
|
|
81
|
+
if (!experiment || !controlPage) return {
|
|
82
|
+
page: controlPage,
|
|
83
|
+
experiment: experiment ?? null,
|
|
84
|
+
assignment: null
|
|
85
|
+
};
|
|
86
|
+
if ("running" !== experiment.status) return {
|
|
87
|
+
page: controlPage,
|
|
88
|
+
experiment,
|
|
89
|
+
assignment: null
|
|
90
|
+
};
|
|
91
|
+
if (sdk.getExperimentPaused) {
|
|
92
|
+
const paused = await sdk.getExperimentPaused(experiment.experimentId).catch(()=>false);
|
|
93
|
+
if (paused) return {
|
|
94
|
+
page: controlPage,
|
|
95
|
+
experiment,
|
|
96
|
+
assignment: null
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
const forcedVariantId = resolveForcedVariantId(options);
|
|
100
|
+
const { context, generatedVisitorId } = await resolveVisitorContext(options);
|
|
101
|
+
let assignment = null;
|
|
102
|
+
if (forcedVariantId) assignment = forcedAssignment(experiment, forcedVariantId);
|
|
103
|
+
if (!assignment) assignment = assignVariant(experiment, context);
|
|
104
|
+
let page = controlPage;
|
|
105
|
+
if (!assignment.isControl) {
|
|
106
|
+
const variant = await sdk.getVariantContent(assignment.variantId);
|
|
107
|
+
if (variant) page = overlayVariant(controlPage, variant);
|
|
108
|
+
else assignment = {
|
|
109
|
+
variantId: experiment.controlVariantId,
|
|
110
|
+
isControl: true,
|
|
111
|
+
excluded: assignment.excluded,
|
|
112
|
+
forced: assignment.forced
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
const shouldEmit = (options.emitExposure ?? true) && !assignment.excluded && !assignment.forced;
|
|
116
|
+
if (shouldEmit) {
|
|
117
|
+
const provider = getAnalyticsProvider(experiment.analytics.provider);
|
|
118
|
+
if (provider) {
|
|
119
|
+
const experimentKey = "string" == typeof experiment.analytics.experimentKey ? experiment.analytics.experimentKey : void 0;
|
|
120
|
+
try {
|
|
121
|
+
await provider.emitExposure({
|
|
122
|
+
experimentId: experiment.experimentId,
|
|
123
|
+
experimentKey,
|
|
124
|
+
variantId: assignment.variantId,
|
|
125
|
+
revisionId: experiment.revisionId,
|
|
126
|
+
tenantId: experiment.tenantId,
|
|
127
|
+
visitorId: context.visitorId,
|
|
128
|
+
pageId: experiment.pageEntryId,
|
|
129
|
+
path: experiment.path,
|
|
130
|
+
timestamp: new Date().toISOString()
|
|
131
|
+
});
|
|
132
|
+
} catch {}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
const result = {
|
|
136
|
+
page,
|
|
137
|
+
experiment,
|
|
138
|
+
assignment
|
|
139
|
+
};
|
|
140
|
+
if (generatedVisitorId) result.cookie = {
|
|
141
|
+
name: options.cookieName ?? DEFAULT_VISITOR_COOKIE,
|
|
142
|
+
value: context.visitorId,
|
|
143
|
+
maxAge: options.cookieMaxAge ?? 31536000
|
|
144
|
+
};
|
|
145
|
+
return result;
|
|
146
|
+
};
|
|
147
|
+
export { DEFAULT_VISITOR_COOKIE, FORCED_VARIANT_PARAM, getPageWithExperiment, resolveVisitorContext };
|
|
148
|
+
|
|
149
|
+
//# sourceMappingURL=render.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"experiments/render.js","sources":["../../src/experiments/render.ts"],"sourcesContent":["import { nanoid } from \"nanoid\";\nimport type { PublicPage } from \"~/types.js\";\nimport { getHeadersProvider } from \"~/headersProvider.js\";\nimport { assignVariant, forcedAssignment } from \"./bucketing.js\";\nimport { getAnalyticsProvider } from \"./analyticsProvider.js\";\nimport type {\n ActiveExperiment,\n DeviceType,\n VariantAssignment,\n VariantContent,\n VisitorContext\n} from \"./types.js\";\n\n/** The cookie that persists the stable visitor id used for deterministic bucketing. */\nexport const DEFAULT_VISITOR_COOKIE = \"wb_ab_vid\";\n\n/** Default query parameter used to force a variant for QA/review. */\nexport const FORCED_VARIANT_PARAM = \"wb-variant\";\n\nconst DEFAULT_GEO_HEADERS = [\n \"x-vercel-ip-country\",\n \"cloudfront-viewer-country\",\n \"cf-ipcountry\",\n \"x-country-code\"\n];\n\n/** The minimal data surface the render helper needs. ContentSdk satisfies this. */\nexport interface ExperimentSdk {\n getPage(path: string): Promise<PublicPage | null>;\n getPageExperiment(path: string): Promise<ActiveExperiment | null>;\n getVariantContent(variantId: string): Promise<VariantContent | null>;\n /** Runtime kill-switch check. When omitted, the pause state is not consulted. */\n getExperimentPaused?(experimentId: string): Promise<boolean>;\n}\n\nexport interface ExperimentCookie {\n name: string;\n value: string;\n maxAge: number;\n}\n\nexport interface GetPageWithExperimentOptions {\n /** Forced variant id (from the `?wb-variant=` query parameter). Overrides bucketing, never counted. */\n forcedVariantId?: string;\n /**\n * Request query parameters. When provided, the forced variant is read from\n * `searchParams[FORCED_VARIANT_PARAM]` unless `forcedVariantId` is set explicitly. Convenient\n * for framework handlers that already have the parsed query (e.g. Next.js `searchParams`).\n */\n searchParams?: Record<string, string | string[] | undefined>;\n /** Explicit visitor id. When omitted, it is read from the cookie or generated. */\n visitorId?: string;\n /** Explicit ISO country code. When omitted, it is read from common CDN geo headers. */\n country?: string;\n /** Explicit device type. When omitted, it is derived from the user agent. */\n device?: DeviceType;\n /** Header names to read the visitor country from, in priority order. */\n geoHeaders?: string[];\n /** Bucketing cookie name. Defaults to `wb_ab_vid`. */\n cookieName?: string;\n /** Bucketing cookie max age in seconds. Defaults to one year. */\n cookieMaxAge?: number;\n /** Set false to skip exposure emission (e.g. when consent has not been granted). */\n emitExposure?: boolean;\n /** Visitor id generator. Defaults to nanoid. */\n generateVisitorId?: () => string;\n}\n\nexport interface ExperimentRenderResult {\n /** The content to render server-side: the baseline (control) or a variant overlay. */\n page: PublicPage | null;\n /** The active experiment, or null when there is none for the path. */\n experiment: ActiveExperiment | null;\n /** The visitor's assignment, or null when no experiment applies. */\n assignment: VariantAssignment | null;\n /**\n * Set when the caller should persist the bucketing cookie (i.e. a visitor id was generated\n * this request). Persisting it in a Route Handler / middleware keeps bucketing stable.\n */\n cookie?: ExperimentCookie;\n}\n\nconst readCookie = (cookieHeader: string | null | undefined, name: string): string | undefined => {\n if (!cookieHeader) {\n return undefined;\n }\n for (const part of cookieHeader.split(\";\")) {\n const index = part.indexOf(\"=\");\n if (index === -1) {\n continue;\n }\n const key = part.slice(0, index).trim();\n if (key === name) {\n return decodeURIComponent(part.slice(index + 1).trim());\n }\n }\n return undefined;\n};\n\nconst detectDevice = (userAgent: string | null | undefined): DeviceType | undefined => {\n if (!userAgent) {\n return undefined;\n }\n if (/iPad|Tablet|PlayBook|Silk/i.test(userAgent)) {\n return \"tablet\";\n }\n if (/Mobi|Android.+Mobile|iPhone|iPod|Windows Phone/i.test(userAgent)) {\n return \"mobile\";\n }\n return \"desktop\";\n};\n\n/**\n * Resolve request-time visitor context from request headers/cookies. Never uses a logged-in\n * identity. Returns whether a visitor id was generated, so the caller can persist the cookie.\n */\nexport const resolveVisitorContext = async (\n options: GetPageWithExperimentOptions = {}\n): Promise<{ context: VisitorContext; generatedVisitorId: boolean }> => {\n const cookieName = options.cookieName ?? DEFAULT_VISITOR_COOKIE;\n const generate = options.generateVisitorId ?? (() => nanoid());\n\n let headers: Headers | undefined;\n const provider = getHeadersProvider();\n if (provider) {\n try {\n headers = await provider();\n } catch {\n headers = undefined;\n }\n }\n\n let visitorId = options.visitorId;\n let generatedVisitorId = false;\n if (!visitorId) {\n visitorId = readCookie(headers?.get(\"cookie\"), cookieName);\n }\n if (!visitorId) {\n visitorId = generate();\n generatedVisitorId = true;\n }\n\n let country = options.country;\n if (!country && headers) {\n for (const header of options.geoHeaders ?? DEFAULT_GEO_HEADERS) {\n const value = headers.get(header);\n if (value) {\n country = value.toUpperCase();\n break;\n }\n }\n }\n\n const device = options.device ?? detectDevice(headers?.get(\"user-agent\"));\n\n return { context: { visitorId, country, device }, generatedVisitorId };\n};\n\n/** Forced variant id from an explicit option, falling back to the query parameter. */\nconst resolveForcedVariantId = (options: GetPageWithExperimentOptions): string | undefined => {\n if (options.forcedVariantId) {\n return options.forcedVariantId;\n }\n const raw = options.searchParams?.[FORCED_VARIANT_PARAM];\n return Array.isArray(raw) ? raw[0] : raw;\n};\n\nconst overlayVariant = (baseline: PublicPage, variant: VariantContent): PublicPage => {\n return {\n ...baseline,\n properties: variant.properties as PublicPage[\"properties\"],\n bindings: variant.bindings as PublicPage[\"bindings\"],\n elements: variant.elements as PublicPage[\"elements\"],\n extensions: variant.extensions,\n metadata: variant.metadata\n };\n};\n\n/**\n * Resolve and render the right page for a visitor, server-side, with no client-side content\n * swap.\n *\n * Caching contract (important — handled here so projects do not reimplement it):\n * - The baseline (control) is fetched via `getPage(path)` and caches per path.\n * - The active experiment metadata is fetched via `getPageExperiment(path)` and caches per path.\n * - A variant's content is fetched via `getVariantContent(variantId)` and caches per variant id.\n * Every request therefore resolves to one of a small, fixed set of cacheable objects keyed by\n * (path) or (variant id) — NEVER by the raw visitor id. Each active variant adds exactly one\n * cacheable object per URL.\n */\nexport const getPageWithExperiment = async (\n sdk: ExperimentSdk,\n path: string,\n options: GetPageWithExperimentOptions = {}\n): Promise<ExperimentRenderResult> => {\n // Baseline content + active experiment. Both cache per path.\n const [controlPage, experiment] = await Promise.all([\n sdk.getPage(path),\n sdk.getPageExperiment(path)\n ]);\n\n if (!experiment || !controlPage) {\n return { page: controlPage, experiment: experiment ?? null, assignment: null };\n }\n\n // Only a running experiment serves variants. `getPageExperiment` already returns the active\n // one, but guard defensively.\n if (experiment.status !== \"running\") {\n return { page: controlPage, experiment, assignment: null };\n }\n\n // Kill-switch: a paused experiment instantly reverts to the control. Consulted uncached by the\n // data provider so it takes effect immediately while page/variant content stays cached.\n if (sdk.getExperimentPaused) {\n const paused = await sdk.getExperimentPaused(experiment.experimentId).catch(() => false);\n if (paused) {\n return { page: controlPage, experiment, assignment: null };\n }\n }\n\n const forcedVariantId = resolveForcedVariantId(options);\n\n const { context, generatedVisitorId } = await resolveVisitorContext(options);\n\n let assignment: VariantAssignment | null = null;\n if (forcedVariantId) {\n assignment = forcedAssignment(experiment, forcedVariantId);\n }\n if (!assignment) {\n assignment = assignVariant(experiment, context);\n }\n\n let page = controlPage;\n if (!assignment.isControl) {\n const variant = await sdk.getVariantContent(assignment.variantId);\n if (variant) {\n page = overlayVariant(controlPage, variant);\n } else {\n // The variant disappeared (e.g. deleted) — fall back to the control safely.\n assignment = {\n variantId: experiment.controlVariantId,\n isControl: true,\n excluded: assignment.excluded,\n forced: assignment.forced\n };\n }\n }\n\n // Emit a single canonical exposure event through the provider seam. Forced and excluded\n // assignments are never reported. Analytics failures must never break rendering.\n const shouldEmit = (options.emitExposure ?? true) && !assignment.excluded && !assignment.forced;\n if (shouldEmit) {\n const provider = getAnalyticsProvider(experiment.analytics.provider);\n if (provider) {\n const experimentKey =\n typeof experiment.analytics.experimentKey === \"string\"\n ? experiment.analytics.experimentKey\n : undefined;\n try {\n await provider.emitExposure({\n experimentId: experiment.experimentId,\n experimentKey,\n variantId: assignment.variantId,\n revisionId: experiment.revisionId,\n tenantId: experiment.tenantId,\n visitorId: context.visitorId,\n pageId: experiment.pageEntryId,\n path: experiment.path,\n timestamp: new Date().toISOString()\n });\n } catch {\n // Swallow — exposure measurement must never affect the rendered response.\n }\n }\n }\n\n const result: ExperimentRenderResult = { page, experiment, assignment };\n\n if (generatedVisitorId) {\n result.cookie = {\n name: options.cookieName ?? DEFAULT_VISITOR_COOKIE,\n value: context.visitorId,\n maxAge: options.cookieMaxAge ?? 60 * 60 * 24 * 365\n };\n }\n\n return result;\n};\n"],"names":["DEFAULT_VISITOR_COOKIE","FORCED_VARIANT_PARAM","DEFAULT_GEO_HEADERS","readCookie","cookieHeader","name","part","index","key","decodeURIComponent","detectDevice","userAgent","resolveVisitorContext","options","cookieName","generate","nanoid","headers","provider","getHeadersProvider","undefined","visitorId","generatedVisitorId","country","header","value","device","resolveForcedVariantId","raw","Array","overlayVariant","baseline","variant","getPageWithExperiment","sdk","path","controlPage","experiment","Promise","paused","forcedVariantId","context","assignment","forcedAssignment","assignVariant","page","shouldEmit","getAnalyticsProvider","experimentKey","Date","result"],"mappings":"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"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The reserved variant id for the control bucket. The control bucket renders the baseline
|
|
3
|
+
* revision content; it has no Variant object of its own.
|
|
4
|
+
*/
|
|
5
|
+
export declare const CONTROL_VARIANT_ID = "control";
|
|
6
|
+
export type DeviceType = "desktop" | "mobile" | "tablet";
|
|
7
|
+
export interface ExperimentTrafficSplit {
|
|
8
|
+
control: number;
|
|
9
|
+
variants: Record<string, number>;
|
|
10
|
+
}
|
|
11
|
+
export interface ExperimentTargeting {
|
|
12
|
+
trafficPercentage: number;
|
|
13
|
+
geo?: string[];
|
|
14
|
+
device?: DeviceType[];
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Provider-agnostic analytics configuration carried by an experiment. Adapters read this and
|
|
18
|
+
* map it to their own shape. No provider-specific field name appears here.
|
|
19
|
+
*/
|
|
20
|
+
export interface ExperimentAnalyticsConfig {
|
|
21
|
+
provider: string;
|
|
22
|
+
[key: string]: unknown;
|
|
23
|
+
}
|
|
24
|
+
export interface ActiveExperimentVariant {
|
|
25
|
+
variantId: string;
|
|
26
|
+
name: string;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* A variant's full content snapshot, shaped for rendering. Overlaid onto the baseline page so
|
|
30
|
+
* the result is a complete, renderable page.
|
|
31
|
+
*/
|
|
32
|
+
export interface VariantContent {
|
|
33
|
+
id: string;
|
|
34
|
+
properties: Record<string, any>;
|
|
35
|
+
bindings: Record<string, any>;
|
|
36
|
+
elements: Record<string, any>;
|
|
37
|
+
extensions: Record<string, any>;
|
|
38
|
+
metadata: Record<string, any>;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The active experiment for a path as returned by the Website Builder API. This is the only
|
|
42
|
+
* experiment shape the render path deals with — it is provider-agnostic.
|
|
43
|
+
*/
|
|
44
|
+
export interface ActiveExperiment {
|
|
45
|
+
experimentId: string;
|
|
46
|
+
revisionId: string;
|
|
47
|
+
pageEntryId: string;
|
|
48
|
+
path: string;
|
|
49
|
+
status: string;
|
|
50
|
+
tenantId: string;
|
|
51
|
+
controlVariantId: string;
|
|
52
|
+
trafficSplit: ExperimentTrafficSplit;
|
|
53
|
+
targeting: ExperimentTargeting;
|
|
54
|
+
analytics: ExperimentAnalyticsConfig;
|
|
55
|
+
variants: ActiveExperimentVariant[];
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Request-time context used to bucket a visitor. Derived server-side from request headers /
|
|
59
|
+
* cookies — never from a logged-in identity.
|
|
60
|
+
*/
|
|
61
|
+
export interface VisitorContext {
|
|
62
|
+
/** A stable visitor id (from the bucketing cookie, or a generated id). */
|
|
63
|
+
visitorId: string;
|
|
64
|
+
/** ISO country code, if the CDN/edge provided one. */
|
|
65
|
+
country?: string;
|
|
66
|
+
/** Device type, derived from the user agent. */
|
|
67
|
+
device?: DeviceType;
|
|
68
|
+
}
|
|
69
|
+
export interface VariantAssignment {
|
|
70
|
+
/** The assigned variant id, or the control variant id. */
|
|
71
|
+
variantId: string;
|
|
72
|
+
/** True when the control bucket (baseline revision) was assigned. */
|
|
73
|
+
isControl: boolean;
|
|
74
|
+
/**
|
|
75
|
+
* True when the visitor was not entered into the experiment (targeting or traffic
|
|
76
|
+
* percentage). The control is served and no exposure is emitted.
|
|
77
|
+
*/
|
|
78
|
+
excluded: boolean;
|
|
79
|
+
/** True when a forced-variant query parameter overrode bucketing (QA). Never reported. */
|
|
80
|
+
forced: boolean;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* The single canonical exposure event. Each analytics provider adapter maps from this into its
|
|
84
|
+
* own event shape. No PostHog (or other provider) field names may appear here.
|
|
85
|
+
*/
|
|
86
|
+
export interface ExposureEvent {
|
|
87
|
+
experimentId: string;
|
|
88
|
+
/** Optional human-readable experiment key for provider mapping (from analytics config). */
|
|
89
|
+
experimentKey?: string;
|
|
90
|
+
/** The assigned variant id (the control variant id for the control bucket). */
|
|
91
|
+
variantId: string;
|
|
92
|
+
revisionId: string;
|
|
93
|
+
tenantId: string;
|
|
94
|
+
/** Stable distinct visitor id. */
|
|
95
|
+
visitorId: string;
|
|
96
|
+
pageId: string;
|
|
97
|
+
path: string;
|
|
98
|
+
timestamp: string;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* The provider seam. A v2 GA4 adapter is a drop-in implementation of this interface — the data
|
|
102
|
+
* model, assignment logic, and render path never change.
|
|
103
|
+
*/
|
|
104
|
+
export interface AnalyticsProvider {
|
|
105
|
+
/** Provider id, matched against an experiment's analytics.provider. */
|
|
106
|
+
readonly name: string;
|
|
107
|
+
emitExposure(event: ExposureEvent): Promise<void>;
|
|
108
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"experiments/types.js","sources":["../../src/experiments/types.ts"],"sourcesContent":["/**\n * The reserved variant id for the control bucket. The control bucket renders the baseline\n * revision content; it has no Variant object of its own.\n */\nexport const CONTROL_VARIANT_ID = \"control\";\n\nexport type DeviceType = \"desktop\" | \"mobile\" | \"tablet\";\n\nexport interface ExperimentTrafficSplit {\n control: number;\n variants: Record<string, number>;\n}\n\nexport interface ExperimentTargeting {\n trafficPercentage: number;\n geo?: string[];\n device?: DeviceType[];\n}\n\n/**\n * Provider-agnostic analytics configuration carried by an experiment. Adapters read this and\n * map it to their own shape. No provider-specific field name appears here.\n */\nexport interface ExperimentAnalyticsConfig {\n provider: string;\n [key: string]: unknown;\n}\n\nexport interface ActiveExperimentVariant {\n variantId: string;\n name: string;\n}\n\n/**\n * A variant's full content snapshot, shaped for rendering. Overlaid onto the baseline page so\n * the result is a complete, renderable page.\n */\nexport interface VariantContent {\n id: string;\n properties: Record<string, any>;\n bindings: Record<string, any>;\n elements: Record<string, any>;\n extensions: Record<string, any>;\n metadata: Record<string, any>;\n}\n\n/**\n * The active experiment for a path as returned by the Website Builder API. This is the only\n * experiment shape the render path deals with — it is provider-agnostic.\n */\nexport interface ActiveExperiment {\n experimentId: string;\n revisionId: string;\n pageEntryId: string;\n path: string;\n status: string;\n tenantId: string;\n controlVariantId: string;\n trafficSplit: ExperimentTrafficSplit;\n targeting: ExperimentTargeting;\n analytics: ExperimentAnalyticsConfig;\n variants: ActiveExperimentVariant[];\n}\n\n/**\n * Request-time context used to bucket a visitor. Derived server-side from request headers /\n * cookies — never from a logged-in identity.\n */\nexport interface VisitorContext {\n /** A stable visitor id (from the bucketing cookie, or a generated id). */\n visitorId: string;\n /** ISO country code, if the CDN/edge provided one. */\n country?: string;\n /** Device type, derived from the user agent. */\n device?: DeviceType;\n}\n\nexport interface VariantAssignment {\n /** The assigned variant id, or the control variant id. */\n variantId: string;\n /** True when the control bucket (baseline revision) was assigned. */\n isControl: boolean;\n /**\n * True when the visitor was not entered into the experiment (targeting or traffic\n * percentage). The control is served and no exposure is emitted.\n */\n excluded: boolean;\n /** True when a forced-variant query parameter overrode bucketing (QA). Never reported. */\n forced: boolean;\n}\n\n/**\n * The single canonical exposure event. Each analytics provider adapter maps from this into its\n * own event shape. No PostHog (or other provider) field names may appear here.\n */\nexport interface ExposureEvent {\n experimentId: string;\n /** Optional human-readable experiment key for provider mapping (from analytics config). */\n experimentKey?: string;\n /** The assigned variant id (the control variant id for the control bucket). */\n variantId: string;\n revisionId: string;\n tenantId: string;\n /** Stable distinct visitor id. */\n visitorId: string;\n pageId: string;\n path: string;\n timestamp: string;\n}\n\n/**\n * The provider seam. A v2 GA4 adapter is a drop-in implementation of this interface — the data\n * model, assignment logic, and render path never change.\n */\nexport interface AnalyticsProvider {\n /** Provider id, matched against an experiment's analytics.provider. */\n readonly name: string;\n emitExposure(event: ExposureEvent): Promise<void>;\n}\n"],"names":["CONTROL_VARIANT_ID"],"mappings":"AAIO,MAAMA,qBAAqB"}
|
package/findMatchingAstNode.js
CHANGED
|
@@ -1,17 +1,15 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
return undefined;
|
|
1
|
+
function findMatchingAstNode(path, ast) {
|
|
2
|
+
const cleanedPath = path.replace(/\[\d+\]/g, "");
|
|
3
|
+
const pathParts = cleanedPath.split(".");
|
|
4
|
+
let nodes = ast;
|
|
5
|
+
let currentNode;
|
|
6
|
+
for (const part of pathParts){
|
|
7
|
+
currentNode = nodes.find((n)=>n.name === part);
|
|
8
|
+
if (!currentNode) return;
|
|
9
|
+
nodes = currentNode.children;
|
|
11
10
|
}
|
|
12
|
-
|
|
13
|
-
}
|
|
14
|
-
return currentNode;
|
|
11
|
+
return currentNode;
|
|
15
12
|
}
|
|
13
|
+
export { findMatchingAstNode };
|
|
16
14
|
|
|
17
15
|
//# sourceMappingURL=findMatchingAstNode.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"
|
|
1
|
+
{"version":3,"file":"findMatchingAstNode.js","sources":["../src/findMatchingAstNode.ts"],"sourcesContent":["import type { InputAstNode } from \"~/ComponentManifestToAstConverter.js\";\n\nexport function findMatchingAstNode(path: string, ast: InputAstNode[]): InputAstNode | undefined {\n // Remove all array indices, e.g. columns[0].children → columns.children\n const cleanedPath = path.replace(/\\[\\d+\\]/g, \"\");\n const pathParts = cleanedPath.split(\".\");\n\n let nodes = ast;\n let currentNode: InputAstNode | undefined;\n\n for (const part of pathParts) {\n currentNode = nodes.find(n => n.name === part);\n if (!currentNode) {\n return undefined;\n }\n\n nodes = currentNode.children;\n }\n\n return currentNode;\n}\n"],"names":["findMatchingAstNode","path","ast","cleanedPath","pathParts","nodes","currentNode","part","n"],"mappings":"AAEO,SAASA,oBAAoBC,IAAY,EAAEC,GAAmB;IAEjE,MAAMC,cAAcF,KAAK,OAAO,CAAC,YAAY;IAC7C,MAAMG,YAAYD,YAAY,KAAK,CAAC;IAEpC,IAAIE,QAAQH;IACZ,IAAII;IAEJ,KAAK,MAAMC,QAAQH,UAAW;QAC1BE,cAAcD,MAAM,IAAI,CAACG,CAAAA,IAAKA,EAAE,IAAI,KAAKD;QACzC,IAAI,CAACD,aACD;QAGJD,QAAQC,YAAY,QAAQ;IAChC;IAEA,OAAOA;AACX"}
|
package/generateElementId.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const generateElementId: (size?: number
|
|
1
|
+
export declare const generateElementId: (size?: number) => string;
|
package/generateElementId.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { customAlphabet } from "nanoid";
|
|
2
|
-
// @ts-expect-error
|
|
3
2
|
import { lowercase, numbers } from "nanoid-dictionary";
|
|
4
3
|
const DEFAULT_SIZE = 21;
|
|
5
|
-
|
|
4
|
+
const generateElementId = customAlphabet(`${lowercase}${numbers}`, DEFAULT_SIZE);
|
|
5
|
+
export { generateElementId };
|
|
6
6
|
|
|
7
7
|
//# sourceMappingURL=generateElementId.js.map
|
package/generateElementId.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"
|
|
1
|
+
{"version":3,"file":"generateElementId.js","sources":["../src/generateElementId.ts"],"sourcesContent":["import { customAlphabet } from \"nanoid\";\nimport { lowercase, numbers } from \"nanoid-dictionary\";\n\nconst DEFAULT_SIZE = 21;\n\nexport const generateElementId = customAlphabet(`${lowercase}${numbers}`, DEFAULT_SIZE);\n"],"names":["DEFAULT_SIZE","generateElementId","customAlphabet","lowercase","numbers"],"mappings":";;AAGA,MAAMA,eAAe;AAEd,MAAMC,oBAAoBC,eAAe,GAAGC,YAAYC,SAAS,EAAEJ"}
|
package/headersProvider.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
let headersProvider;
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
const setHeadersProvider = (provider)=>{
|
|
3
|
+
headersProvider = provider;
|
|
4
4
|
};
|
|
5
|
-
|
|
5
|
+
const getHeadersProvider = ()=>headersProvider;
|
|
6
|
+
export { getHeadersProvider, setHeadersProvider };
|
|
6
7
|
|
|
7
8
|
//# sourceMappingURL=headersProvider.js.map
|
package/headersProvider.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"
|
|
1
|
+
{"version":3,"file":"headersProvider.js","sources":["../src/headersProvider.ts"],"sourcesContent":["type HeadersProvider = () => Promise<Headers>;\n\nlet headersProvider: HeadersProvider;\n\nexport const setHeadersProvider = (provider: HeadersProvider) => {\n headersProvider = provider;\n};\n\nexport const getHeadersProvider = () => headersProvider;\n"],"names":["headersProvider","setHeadersProvider","provider","getHeadersProvider"],"mappings":"AAEA,IAAIA;AAEG,MAAMC,qBAAqB,CAACC;IAC/BF,kBAAkBE;AACtB;AAEO,MAAMC,qBAAqB,IAAMH"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure geometry core for crop + focal point rendering.
|
|
3
|
+
*
|
|
4
|
+
* This module is the single source of truth shared by:
|
|
5
|
+
* - the Admin editor previews (square / 4:3 / 16:9),
|
|
6
|
+
* - the Next.js render helper (`getImageProps` / `<Image>`),
|
|
7
|
+
* - and any future URL-transform loader (`?rect=…`).
|
|
8
|
+
*
|
|
9
|
+
* Keeping the math here guarantees that what an editor previews is exactly what
|
|
10
|
+
* a visitor sees. Everything is framework-agnostic and side-effect free.
|
|
11
|
+
*/
|
|
12
|
+
import type { AssetCrop, AssetImage } from "@webiny/sdk";
|
|
13
|
+
import type { AspectRatioInput } from "./types.js";
|
|
14
|
+
/** A rectangle in normalized (0..1) coordinates of the original image. */
|
|
15
|
+
export interface NormalizedRect {
|
|
16
|
+
x: number;
|
|
17
|
+
y: number;
|
|
18
|
+
width: number;
|
|
19
|
+
height: number;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* CSS needed to render an image cropped to a target aspect ratio while honoring
|
|
23
|
+
* both the crop rectangle and the focal point — without any server-side processing.
|
|
24
|
+
*
|
|
25
|
+
* `objectPosition` is the simpler, focal-point-only path (apply to an `object-fit:
|
|
26
|
+
* cover` image). `container` + `image` is the accurate path that also honors the
|
|
27
|
+
* crop rectangle via an overflow-clipped wrapper.
|
|
28
|
+
*/
|
|
29
|
+
export interface ImageRenderData {
|
|
30
|
+
rect: NormalizedRect;
|
|
31
|
+
objectPosition: string;
|
|
32
|
+
container: {
|
|
33
|
+
position: "relative";
|
|
34
|
+
width: "100%";
|
|
35
|
+
aspectRatio: string;
|
|
36
|
+
overflow: "hidden";
|
|
37
|
+
};
|
|
38
|
+
image: {
|
|
39
|
+
position: "absolute";
|
|
40
|
+
width: string;
|
|
41
|
+
height: string;
|
|
42
|
+
left: string;
|
|
43
|
+
top: string;
|
|
44
|
+
maxWidth: "none";
|
|
45
|
+
objectFit: "cover";
|
|
46
|
+
objectPosition: string;
|
|
47
|
+
};
|
|
48
|
+
intrinsicWidth: number;
|
|
49
|
+
intrinsicHeight: number;
|
|
50
|
+
}
|
|
51
|
+
export declare function resolveAssetImage(override?: AssetImage | null, assetDefault?: AssetImage | null): AssetImage;
|
|
52
|
+
export declare function getCropRect(crop?: AssetCrop | null): NormalizedRect;
|
|
53
|
+
export declare function getVisibleRect(image: AssetImage, aspectRatio?: AspectRatioInput): NormalizedRect;
|
|
54
|
+
export declare function getImageRenderData(image: AssetImage, aspectRatio?: AspectRatioInput): ImageRenderData;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
const clamp = (value, min = 0, max = 1)=>{
|
|
2
|
+
if (Number.isNaN(value)) return min;
|
|
3
|
+
return Math.min(max, Math.max(min, value));
|
|
4
|
+
};
|
|
5
|
+
const round = (n)=>Math.round(1000 * n) / 1000;
|
|
6
|
+
const DEFAULT_CROP = {
|
|
7
|
+
top: 0,
|
|
8
|
+
left: 0,
|
|
9
|
+
bottom: 0,
|
|
10
|
+
right: 0
|
|
11
|
+
};
|
|
12
|
+
const DEFAULT_FOCAL_POINT = {
|
|
13
|
+
x: 0.5,
|
|
14
|
+
y: 0.5
|
|
15
|
+
};
|
|
16
|
+
function resolveAssetImage(override, assetDefault) {
|
|
17
|
+
return {
|
|
18
|
+
crop: override?.crop ?? assetDefault?.crop,
|
|
19
|
+
focalPoint: override?.focalPoint ?? assetDefault?.focalPoint,
|
|
20
|
+
alt: override?.alt ?? assetDefault?.alt,
|
|
21
|
+
caption: override?.caption ?? assetDefault?.caption
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
function getCropRect(crop) {
|
|
25
|
+
const top = clamp(crop?.top ?? DEFAULT_CROP.top);
|
|
26
|
+
const left = clamp(crop?.left ?? DEFAULT_CROP.left);
|
|
27
|
+
const bottom = clamp(crop?.bottom ?? DEFAULT_CROP.bottom);
|
|
28
|
+
const right = clamp(crop?.right ?? DEFAULT_CROP.right);
|
|
29
|
+
const width = Math.max(0, 1 - left - right);
|
|
30
|
+
const height = Math.max(0, 1 - top - bottom);
|
|
31
|
+
return {
|
|
32
|
+
x: left,
|
|
33
|
+
y: top,
|
|
34
|
+
width,
|
|
35
|
+
height
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
const resolveAspectRatio = (aspectRatio)=>{
|
|
39
|
+
if ("number" == typeof aspectRatio) return aspectRatio > 0 ? aspectRatio : 1;
|
|
40
|
+
const { width, height } = aspectRatio;
|
|
41
|
+
return width > 0 && height > 0 ? width / height : 1;
|
|
42
|
+
};
|
|
43
|
+
const getFocalCenter = (fp)=>({
|
|
44
|
+
x: clamp(fp?.x ?? DEFAULT_FOCAL_POINT.x),
|
|
45
|
+
y: clamp(fp?.y ?? DEFAULT_FOCAL_POINT.y)
|
|
46
|
+
});
|
|
47
|
+
function getVisibleRect(image, aspectRatio) {
|
|
48
|
+
const cropRect = getCropRect(image.crop);
|
|
49
|
+
if (void 0 === aspectRatio || cropRect.width <= 0 || cropRect.height <= 0) return cropRect;
|
|
50
|
+
const iw = (image.width ?? 0) > 0 ? image.width : 1;
|
|
51
|
+
const ih = (image.height ?? 0) > 0 ? image.height : 1;
|
|
52
|
+
const targetAR = resolveAspectRatio(aspectRatio);
|
|
53
|
+
const cropWpx = cropRect.width * iw;
|
|
54
|
+
const cropHpx = cropRect.height * ih;
|
|
55
|
+
const cropAR = cropWpx / cropHpx;
|
|
56
|
+
let finalWpx;
|
|
57
|
+
let finalHpx;
|
|
58
|
+
if (targetAR > cropAR) {
|
|
59
|
+
finalWpx = cropWpx;
|
|
60
|
+
finalHpx = cropWpx / targetAR;
|
|
61
|
+
} else {
|
|
62
|
+
finalHpx = cropHpx;
|
|
63
|
+
finalWpx = cropHpx * targetAR;
|
|
64
|
+
}
|
|
65
|
+
const finalW = finalWpx / iw;
|
|
66
|
+
const finalH = finalHpx / ih;
|
|
67
|
+
const center = getFocalCenter(image.focalPoint);
|
|
68
|
+
const maxX = cropRect.x + cropRect.width - finalW;
|
|
69
|
+
const maxY = cropRect.y + cropRect.height - finalH;
|
|
70
|
+
const x = clamp(center.x - finalW / 2, cropRect.x, Math.max(cropRect.x, maxX));
|
|
71
|
+
const y = clamp(center.y - finalH / 2, cropRect.y, Math.max(cropRect.y, maxY));
|
|
72
|
+
return {
|
|
73
|
+
x,
|
|
74
|
+
y,
|
|
75
|
+
width: finalW,
|
|
76
|
+
height: finalH
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
function getImageRenderData(image, aspectRatio) {
|
|
80
|
+
const rect = getVisibleRect(image, aspectRatio);
|
|
81
|
+
const center = getFocalCenter(image.focalPoint);
|
|
82
|
+
const safeW = rect.width > 0 ? rect.width : 1;
|
|
83
|
+
const safeH = rect.height > 0 ? rect.height : 1;
|
|
84
|
+
const iw = image.width || 1;
|
|
85
|
+
const ih = image.height || 1;
|
|
86
|
+
const boxAspectRatio = void 0 !== aspectRatio ? resolveAspectRatio(aspectRatio) : rect.width * iw / (rect.height * ih);
|
|
87
|
+
return {
|
|
88
|
+
rect,
|
|
89
|
+
objectPosition: `${round(100 * center.x)}% ${round(100 * center.y)}%`,
|
|
90
|
+
container: {
|
|
91
|
+
position: "relative",
|
|
92
|
+
width: "100%",
|
|
93
|
+
aspectRatio: `${round(boxAspectRatio)}`,
|
|
94
|
+
overflow: "hidden"
|
|
95
|
+
},
|
|
96
|
+
image: {
|
|
97
|
+
position: "absolute",
|
|
98
|
+
width: `${round(100 / safeW)}%`,
|
|
99
|
+
height: `${round(100 / safeH)}%`,
|
|
100
|
+
left: `${round(100 * -(rect.x / safeW))}%`,
|
|
101
|
+
top: `${round(100 * -(rect.y / safeH))}%`,
|
|
102
|
+
maxWidth: "none",
|
|
103
|
+
objectFit: "cover",
|
|
104
|
+
objectPosition: `${round(100 * center.x)}% ${round(100 * center.y)}%`
|
|
105
|
+
},
|
|
106
|
+
intrinsicWidth: iw,
|
|
107
|
+
intrinsicHeight: ih
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
export { getCropRect, getImageRenderData, getVisibleRect, resolveAssetImage };
|
|
111
|
+
|
|
112
|
+
//# sourceMappingURL=geometry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"image/geometry.js","sources":["../../src/image/geometry.ts"],"sourcesContent":["/**\n * Pure geometry core for crop + focal point rendering.\n *\n * This module is the single source of truth shared by:\n * - the Admin editor previews (square / 4:3 / 16:9),\n * - the Next.js render helper (`getImageProps` / `<Image>`),\n * - and any future URL-transform loader (`?rect=…`).\n *\n * Keeping the math here guarantees that what an editor previews is exactly what\n * a visitor sees. Everything is framework-agnostic and side-effect free.\n */\nimport type { AssetCrop, AssetFocalPoint, AssetImage } from \"@webiny/sdk\";\nimport type { AspectRatioInput } from \"./types.js\";\n\n/** A rectangle in normalized (0..1) coordinates of the original image. */\nexport interface NormalizedRect {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\n/**\n * CSS needed to render an image cropped to a target aspect ratio while honoring\n * both the crop rectangle and the focal point — without any server-side processing.\n *\n * `objectPosition` is the simpler, focal-point-only path (apply to an `object-fit:\n * cover` image). `container` + `image` is the accurate path that also honors the\n * crop rectangle via an overflow-clipped wrapper.\n */\nexport interface ImageRenderData {\n rect: NormalizedRect;\n objectPosition: string;\n container: {\n position: \"relative\";\n width: \"100%\";\n aspectRatio: string;\n overflow: \"hidden\";\n };\n image: {\n position: \"absolute\";\n width: string;\n height: string;\n left: string;\n top: string;\n maxWidth: \"none\";\n objectFit: \"cover\";\n objectPosition: string;\n };\n intrinsicWidth: number;\n intrinsicHeight: number;\n}\n\nconst clamp = (value: number, min = 0, max = 1): number => {\n if (Number.isNaN(value)) {\n return min;\n }\n return Math.min(max, Math.max(min, value));\n};\n\nconst round = (n: number): number => Math.round(n * 1000) / 1000;\n\nconst DEFAULT_CROP: AssetCrop = { top: 0, left: 0, bottom: 0, right: 0 };\nconst DEFAULT_FOCAL_POINT: AssetFocalPoint = { x: 0.5, y: 0.5 };\n\nexport function resolveAssetImage(\n override?: AssetImage | null,\n assetDefault?: AssetImage | null\n): AssetImage {\n return {\n crop: override?.crop ?? assetDefault?.crop,\n focalPoint: override?.focalPoint ?? assetDefault?.focalPoint,\n alt: override?.alt ?? assetDefault?.alt,\n caption: override?.caption ?? assetDefault?.caption\n };\n}\n\nexport function getCropRect(crop?: AssetCrop | null): NormalizedRect {\n const top = clamp(crop?.top ?? DEFAULT_CROP.top);\n const left = clamp(crop?.left ?? DEFAULT_CROP.left);\n const bottom = clamp(crop?.bottom ?? DEFAULT_CROP.bottom);\n const right = clamp(crop?.right ?? DEFAULT_CROP.right);\n\n const width = Math.max(0, 1 - left - right);\n const height = Math.max(0, 1 - top - bottom);\n\n return { x: left, y: top, width, height };\n}\n\nconst resolveAspectRatio = (aspectRatio: AspectRatioInput): number => {\n if (typeof aspectRatio === \"number\") {\n return aspectRatio > 0 ? aspectRatio : 1;\n }\n const { width, height } = aspectRatio;\n return width > 0 && height > 0 ? width / height : 1;\n};\n\nconst getFocalCenter = (fp?: AssetFocalPoint | null): { x: number; y: number } => ({\n x: clamp(fp?.x ?? DEFAULT_FOCAL_POINT.x),\n y: clamp(fp?.y ?? DEFAULT_FOCAL_POINT.y)\n});\n\nexport function getVisibleRect(image: AssetImage, aspectRatio?: AspectRatioInput): NormalizedRect {\n const cropRect = getCropRect(image.crop);\n\n if (aspectRatio === undefined || cropRect.width <= 0 || cropRect.height <= 0) {\n return cropRect;\n }\n\n const iw = (image.width ?? 0) > 0 ? image.width! : 1;\n const ih = (image.height ?? 0) > 0 ? image.height! : 1;\n const targetAR = resolveAspectRatio(aspectRatio);\n\n const cropWpx = cropRect.width * iw;\n const cropHpx = cropRect.height * ih;\n const cropAR = cropWpx / cropHpx;\n\n let finalWpx: number;\n let finalHpx: number;\n if (targetAR > cropAR) {\n finalWpx = cropWpx;\n finalHpx = cropWpx / targetAR;\n } else {\n finalHpx = cropHpx;\n finalWpx = cropHpx * targetAR;\n }\n\n const finalW = finalWpx / iw;\n const finalH = finalHpx / ih;\n\n const center = getFocalCenter(image.focalPoint);\n const maxX = cropRect.x + cropRect.width - finalW;\n const maxY = cropRect.y + cropRect.height - finalH;\n const x = clamp(center.x - finalW / 2, cropRect.x, Math.max(cropRect.x, maxX));\n const y = clamp(center.y - finalH / 2, cropRect.y, Math.max(cropRect.y, maxY));\n\n return { x, y, width: finalW, height: finalH };\n}\n\nexport function getImageRenderData(\n image: AssetImage,\n aspectRatio?: AspectRatioInput\n): ImageRenderData {\n const rect = getVisibleRect(image, aspectRatio);\n const center = getFocalCenter(image.focalPoint);\n\n const safeW = rect.width > 0 ? rect.width : 1;\n const safeH = rect.height > 0 ? rect.height : 1;\n\n const iw = image.width || 1;\n const ih = image.height || 1;\n\n const boxAspectRatio =\n aspectRatio !== undefined\n ? resolveAspectRatio(aspectRatio)\n : (rect.width * iw) / (rect.height * ih);\n\n return {\n rect,\n objectPosition: `${round(center.x * 100)}% ${round(center.y * 100)}%`,\n container: {\n position: \"relative\",\n width: \"100%\",\n aspectRatio: `${round(boxAspectRatio)}`,\n overflow: \"hidden\"\n },\n image: {\n position: \"absolute\",\n width: `${round(100 / safeW)}%`,\n height: `${round(100 / safeH)}%`,\n left: `${round(-(rect.x / safeW) * 100)}%`,\n top: `${round(-(rect.y / safeH) * 100)}%`,\n maxWidth: \"none\",\n objectFit: \"cover\",\n objectPosition: `${round(center.x * 100)}% ${round(center.y * 100)}%`\n },\n intrinsicWidth: iw,\n intrinsicHeight: ih\n };\n}\n"],"names":["clamp","value","min","max","Number","Math","round","n","DEFAULT_CROP","DEFAULT_FOCAL_POINT","resolveAssetImage","override","assetDefault","getCropRect","crop","top","left","bottom","right","width","height","resolveAspectRatio","aspectRatio","getFocalCenter","fp","getVisibleRect","image","cropRect","undefined","iw","ih","targetAR","cropWpx","cropHpx","cropAR","finalWpx","finalHpx","finalW","finalH","center","maxX","maxY","x","y","getImageRenderData","rect","safeW","safeH","boxAspectRatio"],"mappings":"AAqDA,MAAMA,QAAQ,CAACC,OAAeC,MAAM,CAAC,EAAEC,MAAM,CAAC;IAC1C,IAAIC,OAAO,KAAK,CAACH,QACb,OAAOC;IAEX,OAAOG,KAAK,GAAG,CAACF,KAAKE,KAAK,GAAG,CAACH,KAAKD;AACvC;AAEA,MAAMK,QAAQ,CAACC,IAAsBF,KAAK,KAAK,CAACE,AAAI,OAAJA,KAAY;AAE5D,MAAMC,eAA0B;IAAE,KAAK;IAAG,MAAM;IAAG,QAAQ;IAAG,OAAO;AAAE;AACvE,MAAMC,sBAAuC;IAAE,GAAG;IAAK,GAAG;AAAI;AAEvD,SAASC,kBACZC,QAA4B,EAC5BC,YAAgC;IAEhC,OAAO;QACH,MAAMD,UAAU,QAAQC,cAAc;QACtC,YAAYD,UAAU,cAAcC,cAAc;QAClD,KAAKD,UAAU,OAAOC,cAAc;QACpC,SAASD,UAAU,WAAWC,cAAc;IAChD;AACJ;AAEO,SAASC,YAAYC,IAAuB;IAC/C,MAAMC,MAAMf,MAAMc,MAAM,OAAON,aAAa,GAAG;IAC/C,MAAMQ,OAAOhB,MAAMc,MAAM,QAAQN,aAAa,IAAI;IAClD,MAAMS,SAASjB,MAAMc,MAAM,UAAUN,aAAa,MAAM;IACxD,MAAMU,QAAQlB,MAAMc,MAAM,SAASN,aAAa,KAAK;IAErD,MAAMW,QAAQd,KAAK,GAAG,CAAC,GAAG,IAAIW,OAAOE;IACrC,MAAME,SAASf,KAAK,GAAG,CAAC,GAAG,IAAIU,MAAME;IAErC,OAAO;QAAE,GAAGD;QAAM,GAAGD;QAAKI;QAAOC;IAAO;AAC5C;AAEA,MAAMC,qBAAqB,CAACC;IACxB,IAAI,AAAuB,YAAvB,OAAOA,aACP,OAAOA,cAAc,IAAIA,cAAc;IAE3C,MAAM,EAAEH,KAAK,EAAEC,MAAM,EAAE,GAAGE;IAC1B,OAAOH,QAAQ,KAAKC,SAAS,IAAID,QAAQC,SAAS;AACtD;AAEA,MAAMG,iBAAiB,CAACC,KAA2D;QAC/E,GAAGxB,MAAMwB,IAAI,KAAKf,oBAAoB,CAAC;QACvC,GAAGT,MAAMwB,IAAI,KAAKf,oBAAoB,CAAC;IAC3C;AAEO,SAASgB,eAAeC,KAAiB,EAAEJ,WAA8B;IAC5E,MAAMK,WAAWd,YAAYa,MAAM,IAAI;IAEvC,IAAIJ,AAAgBM,WAAhBN,eAA6BK,SAAS,KAAK,IAAI,KAAKA,SAAS,MAAM,IAAI,GACvE,OAAOA;IAGX,MAAME,KAAMH,AAAAA,CAAAA,MAAM,KAAK,IAAI,KAAK,IAAIA,MAAM,KAAK,GAAI;IACnD,MAAMI,KAAMJ,AAAAA,CAAAA,MAAM,MAAM,IAAI,KAAK,IAAIA,MAAM,MAAM,GAAI;IACrD,MAAMK,WAAWV,mBAAmBC;IAEpC,MAAMU,UAAUL,SAAS,KAAK,GAAGE;IACjC,MAAMI,UAAUN,SAAS,MAAM,GAAGG;IAClC,MAAMI,SAASF,UAAUC;IAEzB,IAAIE;IACJ,IAAIC;IACJ,IAAIL,WAAWG,QAAQ;QACnBC,WAAWH;QACXI,WAAWJ,UAAUD;IACzB,OAAO;QACHK,WAAWH;QACXE,WAAWF,UAAUF;IACzB;IAEA,MAAMM,SAASF,WAAWN;IAC1B,MAAMS,SAASF,WAAWN;IAE1B,MAAMS,SAAShB,eAAeG,MAAM,UAAU;IAC9C,MAAMc,OAAOb,SAAS,CAAC,GAAGA,SAAS,KAAK,GAAGU;IAC3C,MAAMI,OAAOd,SAAS,CAAC,GAAGA,SAAS,MAAM,GAAGW;IAC5C,MAAMI,IAAI1C,MAAMuC,OAAO,CAAC,GAAGF,SAAS,GAAGV,SAAS,CAAC,EAAEtB,KAAK,GAAG,CAACsB,SAAS,CAAC,EAAEa;IACxE,MAAMG,IAAI3C,MAAMuC,OAAO,CAAC,GAAGD,SAAS,GAAGX,SAAS,CAAC,EAAEtB,KAAK,GAAG,CAACsB,SAAS,CAAC,EAAEc;IAExE,OAAO;QAAEC;QAAGC;QAAG,OAAON;QAAQ,QAAQC;IAAO;AACjD;AAEO,SAASM,mBACZlB,KAAiB,EACjBJ,WAA8B;IAE9B,MAAMuB,OAAOpB,eAAeC,OAAOJ;IACnC,MAAMiB,SAAShB,eAAeG,MAAM,UAAU;IAE9C,MAAMoB,QAAQD,KAAK,KAAK,GAAG,IAAIA,KAAK,KAAK,GAAG;IAC5C,MAAME,QAAQF,KAAK,MAAM,GAAG,IAAIA,KAAK,MAAM,GAAG;IAE9C,MAAMhB,KAAKH,MAAM,KAAK,IAAI;IAC1B,MAAMI,KAAKJ,MAAM,MAAM,IAAI;IAE3B,MAAMsB,iBACF1B,AAAgBM,WAAhBN,cACMD,mBAAmBC,eAClBuB,KAAK,KAAK,GAAGhB,KAAOgB,CAAAA,KAAK,MAAM,GAAGf,EAAC;IAE9C,OAAO;QACHe;QACA,gBAAgB,GAAGvC,MAAMiC,AAAW,MAAXA,OAAO,CAAC,EAAQ,EAAE,EAAEjC,MAAMiC,AAAW,MAAXA,OAAO,CAAC,EAAQ,CAAC,CAAC;QACrE,WAAW;YACP,UAAU;YACV,OAAO;YACP,aAAa,GAAGjC,MAAM0C,iBAAiB;YACvC,UAAU;QACd;QACA,OAAO;YACH,UAAU;YACV,OAAO,GAAG1C,MAAM,MAAMwC,OAAO,CAAC,CAAC;YAC/B,QAAQ,GAAGxC,MAAM,MAAMyC,OAAO,CAAC,CAAC;YAChC,MAAM,GAAGzC,MAAM,AAAoB,MAApB,CAAEuC,CAAAA,KAAK,CAAC,GAAGC,KAAI,GAAU,CAAC,CAAC;YAC1C,KAAK,GAAGxC,MAAM,AAAoB,MAApB,CAAEuC,CAAAA,KAAK,CAAC,GAAGE,KAAI,GAAU,CAAC,CAAC;YACzC,UAAU;YACV,WAAW;YACX,gBAAgB,GAAGzC,MAAMiC,AAAW,MAAXA,OAAO,CAAC,EAAQ,EAAE,EAAEjC,MAAMiC,AAAW,MAAXA,OAAO,CAAC,EAAQ,CAAC,CAAC;QACzE;QACA,gBAAgBV;QAChB,iBAAiBC;IACrB;AACJ"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|