@vectoriox/iox-ui 5.4.0 → 5.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/actions-core/index.d.ts +21 -1
- package/consent-core/index.d.ts +7 -1
- package/fesm2022/vectoriox-iox-ui-actions-core.mjs +67 -1
- package/fesm2022/vectoriox-iox-ui-actions-core.mjs.map +1 -1
- package/fesm2022/vectoriox-iox-ui-consent-core.mjs +17 -1
- package/fesm2022/vectoriox-iox-ui-consent-core.mjs.map +1 -1
- package/package.json +1 -1
- package/src/actions-core/element-actions.ts +76 -0
- package/src/actions-core/public-api.ts +1 -0
- package/src/consent-core/consent-mode.ts +15 -0
package/actions-core/index.d.ts
CHANGED
|
@@ -82,5 +82,25 @@ interface ActionAvailabilityContext {
|
|
|
82
82
|
}
|
|
83
83
|
declare function resolveActionAvailability(descriptor: ActionDescriptor, ctx?: ActionAvailabilityContext): ActionAvailability;
|
|
84
84
|
|
|
85
|
-
|
|
85
|
+
/**
|
|
86
|
+
* The ELEMENT action catalog (actions plan A1) — what a node can DO, as data. One list feeds the
|
|
87
|
+
* grouped picker, the two interaction engines, and the AI composer's validation (§9: the model
|
|
88
|
+
* picks actions BY NAME from this catalog and can never invent behaviour).
|
|
89
|
+
*
|
|
90
|
+
* Shipped rows (motion/visibility/context/swiper) are listed so the catalog is the WHOLE truth;
|
|
91
|
+
* their `kind`/`builderBehaviour` make the long-standing implicit rules explicit — the canvas is
|
|
92
|
+
* a live runtime (feedback_builder_live_runtime), navigate must SIMULATE, services are INERT.
|
|
93
|
+
* `signIn`/`startCheckout` ship greyed with the honest reason (D-A5): no visitor identity, no
|
|
94
|
+
* payments — a subsystem and a product, not dropdown entries.
|
|
95
|
+
*/
|
|
96
|
+
declare const ELEMENT_ACTION_DESCRIPTORS: readonly ActionDescriptor[];
|
|
97
|
+
/** Picker order — most-used groups first, services last (they are the shop window, not the shop). */
|
|
98
|
+
declare const ELEMENT_ACTION_GROUP_ORDER: readonly string[];
|
|
99
|
+
/** The catalog, grouped and ordered for the picker — one derivation, no hand-kept second list. */
|
|
100
|
+
declare function groupedElementActions(): ReadonlyArray<{
|
|
101
|
+
group: string;
|
|
102
|
+
actions: ActionDescriptor[];
|
|
103
|
+
}>;
|
|
104
|
+
|
|
105
|
+
export { ELEMENT_ACTION_DESCRIPTORS, ELEMENT_ACTION_GROUP_ORDER, FORM_SUBMIT_ACTION_DESCRIPTORS, addableActions, groupedElementActions, resolveActionAvailability };
|
|
86
106
|
export type { ActionAvailability, ActionAvailabilityContext, ActionAvailabilityState, ActionDescriptor, ActionGroup, ActionKind, ActionParamSpec, ActionTargetSpec, BuilderBehaviour };
|
package/consent-core/index.d.ts
CHANGED
|
@@ -55,6 +55,12 @@ declare const CONSENT_DEFAULTS_DENIED: Readonly<Record<(typeof CONSENT_MODE_PARA
|
|
|
55
55
|
declare function toConsentModeSignals(decision: ConsentDecision | null | undefined): Record<string, ConsentModeSignal>;
|
|
56
56
|
/** The inline `<head>` bootstrap (basic mode, D-C2): defaults denied before anything loads. */
|
|
57
57
|
declare function consentModeBootstrapJs(): string;
|
|
58
|
+
/**
|
|
59
|
+
* The gated injection, as HTML (C2). Emitted by the SSR server ONLY when the request's cookie
|
|
60
|
+
* grants analytics AND the org has a measurementId — the bootstrap above is unconditional, this
|
|
61
|
+
* is not. Order inside: consent UPDATE before the loader, config after it.
|
|
62
|
+
*/
|
|
63
|
+
declare function analyticsInjectionHtml(measurementId: string, decision: ConsentDecision): string;
|
|
58
64
|
|
|
59
|
-
export { CONSENT_COOKIE_NAME, CONSENT_DEFAULTS_DENIED, CONSENT_MODE_PARAMS, ConsentCategory, consentModeBootstrapJs, cookieAttributes, decode, emptyDecision, encode, hasConsent, isExpired, needsPrompt, toConsentModeSignals };
|
|
65
|
+
export { CONSENT_COOKIE_NAME, CONSENT_DEFAULTS_DENIED, CONSENT_MODE_PARAMS, ConsentCategory, analyticsInjectionHtml, consentModeBootstrapJs, cookieAttributes, decode, emptyDecision, encode, hasConsent, isExpired, needsPrompt, toConsentModeSignals };
|
|
60
66
|
export type { ConsentDecision, ConsentModeSignal };
|
|
@@ -38,6 +38,72 @@ function resolveActionAvailability(descriptor, ctx = {}) {
|
|
|
38
38
|
return { state: 'available' };
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
/**
|
|
42
|
+
* The ELEMENT action catalog (actions plan A1) — what a node can DO, as data. One list feeds the
|
|
43
|
+
* grouped picker, the two interaction engines, and the AI composer's validation (§9: the model
|
|
44
|
+
* picks actions BY NAME from this catalog and can never invent behaviour).
|
|
45
|
+
*
|
|
46
|
+
* Shipped rows (motion/visibility/context/swiper) are listed so the catalog is the WHOLE truth;
|
|
47
|
+
* their `kind`/`builderBehaviour` make the long-standing implicit rules explicit — the canvas is
|
|
48
|
+
* a live runtime (feedback_builder_live_runtime), navigate must SIMULATE, services are INERT.
|
|
49
|
+
* `signIn`/`startCheckout` ship greyed with the honest reason (D-A5): no visitor identity, no
|
|
50
|
+
* payments — a subsystem and a product, not dropdown entries.
|
|
51
|
+
*/
|
|
52
|
+
const ELEMENT_ACTION_DESCRIPTORS = Object.freeze([
|
|
53
|
+
// ── Motion (shipped WAAPI actions — timing row applies) ──────────────────
|
|
54
|
+
{ type: 'fadeIn', labelKey: 'builder.actions.fadeIn', kind: 'animated', group: 'motion', builderBehaviour: 'run' },
|
|
55
|
+
{ type: 'fadeOut', labelKey: 'builder.actions.fadeOut', kind: 'animated', group: 'motion', builderBehaviour: 'run' },
|
|
56
|
+
{ type: 'moveUp', labelKey: 'builder.actions.moveUp', kind: 'animated', group: 'motion', builderBehaviour: 'run' },
|
|
57
|
+
{ type: 'moveDown', labelKey: 'builder.actions.moveDown', kind: 'animated', group: 'motion', builderBehaviour: 'run' },
|
|
58
|
+
{ type: 'moveLeft', labelKey: 'builder.actions.moveLeft', kind: 'animated', group: 'motion', builderBehaviour: 'run' },
|
|
59
|
+
{ type: 'moveRight', labelKey: 'builder.actions.moveRight', kind: 'animated', group: 'motion', builderBehaviour: 'run' },
|
|
60
|
+
{ type: 'scaleIn', labelKey: 'builder.actions.scaleIn', kind: 'animated', group: 'motion', builderBehaviour: 'run' },
|
|
61
|
+
{ type: 'scaleOut', labelKey: 'builder.actions.scaleOut', kind: 'animated', group: 'motion', builderBehaviour: 'run' },
|
|
62
|
+
{ type: 'rotate', labelKey: 'builder.actions.rotate', kind: 'animated', group: 'motion', builderBehaviour: 'run',
|
|
63
|
+
params: [{ name: 'degrees', type: 'number', label: 'Degrees' }] },
|
|
64
|
+
// ── Visibility (shipped) ─────────────────────────────────────────────────
|
|
65
|
+
{ type: 'show', labelKey: 'builder.actions.show', kind: 'instant', group: 'visibility', builderBehaviour: 'run' },
|
|
66
|
+
{ type: 'hide', labelKey: 'builder.actions.hide', kind: 'instant', group: 'visibility', builderBehaviour: 'run' },
|
|
67
|
+
{ type: 'toggleVisibility', labelKey: 'builder.actions.toggleVisibility', kind: 'instant', group: 'visibility', builderBehaviour: 'run' },
|
|
68
|
+
// ── Navigate (A1 — Tier A) ───────────────────────────────────────────────
|
|
69
|
+
{ type: 'navigate', labelKey: 'builder.actions.navigate', kind: 'instant', group: 'navigate', builderBehaviour: 'simulate',
|
|
70
|
+
params: [
|
|
71
|
+
{ name: 'navigateTo', type: 'string', label: 'Destination' }, // 'page' | 'url' | 'back'
|
|
72
|
+
{ name: 'page', type: 'page', label: 'Page' },
|
|
73
|
+
{ name: 'url', type: 'string', label: 'URL' },
|
|
74
|
+
] },
|
|
75
|
+
{ type: 'scrollTo', labelKey: 'builder.actions.scrollTo', kind: 'instant', group: 'navigate', builderBehaviour: 'run',
|
|
76
|
+
target: { required: false } },
|
|
77
|
+
// ── Dialog (A1 — needs the Dialog component, A1b) ───────────────────────
|
|
78
|
+
{ type: 'openDialog', labelKey: 'builder.actions.openDialog', kind: 'instant', group: 'dialog', builderBehaviour: 'run',
|
|
79
|
+
target: { required: true, types: ['Dialog'] } },
|
|
80
|
+
{ type: 'closeDialog', labelKey: 'builder.actions.closeDialog', kind: 'instant', group: 'dialog', builderBehaviour: 'run',
|
|
81
|
+
target: { required: true, types: ['Dialog'] } },
|
|
82
|
+
{ type: 'toggleDialog', labelKey: 'builder.actions.toggleDialog', kind: 'instant', group: 'dialog', builderBehaviour: 'run',
|
|
83
|
+
target: { required: true, types: ['Dialog'] } },
|
|
84
|
+
// ── Form (A1 — Tier A; the submit transport is the forms module's, I7 of its plan) ──
|
|
85
|
+
{ type: 'submitForm', labelKey: 'builder.actions.submitForm', kind: 'instant', group: 'form', builderBehaviour: 'run',
|
|
86
|
+
target: { required: true, types: ['Form'] } },
|
|
87
|
+
// ── Data (shipped context actions + the one trivial newcomer) ───────────
|
|
88
|
+
{ type: 'setContext', labelKey: 'builder.actions.setContext', kind: 'instant', group: 'data', builderBehaviour: 'run' },
|
|
89
|
+
{ type: 'clearContext', labelKey: 'builder.actions.clearContext', kind: 'instant', group: 'data', builderBehaviour: 'run' },
|
|
90
|
+
{ type: 'copyToClipboard', labelKey: 'builder.actions.copyToClipboard', kind: 'instant', group: 'data', builderBehaviour: 'run',
|
|
91
|
+
params: [{ name: 'text', type: 'string', label: 'Text' }] },
|
|
92
|
+
// ── Services (D-A5: visible, greyed, with the reason — never hidden, never live) ──
|
|
93
|
+
{ type: 'signIn', labelKey: 'builder.actions.signIn', kind: 'server', group: 'service', builderBehaviour: 'inert',
|
|
94
|
+
planned: true, entitlement: 'visitorIdentity' },
|
|
95
|
+
{ type: 'startCheckout', labelKey: 'builder.actions.startCheckout', kind: 'server', group: 'service', builderBehaviour: 'inert',
|
|
96
|
+
planned: true, connector: 'payment', entitlement: 'commerce' },
|
|
97
|
+
]);
|
|
98
|
+
/** Picker order — most-used groups first, services last (they are the shop window, not the shop). */
|
|
99
|
+
const ELEMENT_ACTION_GROUP_ORDER = Object.freeze(['motion', 'visibility', 'navigate', 'dialog', 'form', 'data', 'service']);
|
|
100
|
+
/** The catalog, grouped and ordered for the picker — one derivation, no hand-kept second list. */
|
|
101
|
+
function groupedElementActions() {
|
|
102
|
+
return ELEMENT_ACTION_GROUP_ORDER
|
|
103
|
+
.map(group => ({ group, actions: ELEMENT_ACTION_DESCRIPTORS.filter(d => d.group === group) }))
|
|
104
|
+
.filter(g => g.actions.length > 0);
|
|
105
|
+
}
|
|
106
|
+
|
|
41
107
|
/**
|
|
42
108
|
* @vectoriox/iox-ui/actions-core — the shared, FRAMEWORK-FREE action vocabulary.
|
|
43
109
|
*
|
|
@@ -53,5 +119,5 @@ function resolveActionAvailability(descriptor, ctx = {}) {
|
|
|
53
119
|
* Generated bundle index. Do not edit.
|
|
54
120
|
*/
|
|
55
121
|
|
|
56
|
-
export { FORM_SUBMIT_ACTION_DESCRIPTORS, addableActions, resolveActionAvailability };
|
|
122
|
+
export { ELEMENT_ACTION_DESCRIPTORS, ELEMENT_ACTION_GROUP_ORDER, FORM_SUBMIT_ACTION_DESCRIPTORS, addableActions, groupedElementActions, resolveActionAvailability };
|
|
57
123
|
//# sourceMappingURL=vectoriox-iox-ui-actions-core.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vectoriox-iox-ui-actions-core.mjs","sources":["../../../projects/iox-ui/src/actions-core/action.model.ts","../../../projects/iox-ui/src/actions-core/availability.ts","../../../projects/iox-ui/src/actions-core/public-api.ts","../../../projects/iox-ui/src/actions-core/vectoriox-iox-ui-actions-core.ts"],"sourcesContent":["/**\n * The ONE descriptor shape for everything an author can put in an ordered action list —\n * a form's after-submit steps today, element behaviour actions (A1) next. Promoted out of\n * `forms-core` because a button action importing from the forms module would be a lie; the\n * forms catalog lives here now and `forms-core` re-exports it unchanged.\n * See iox-ai-guidance `architecture/builder/actions-framework-plan.md` §4 (A0).\n */\n\n/** How a step executes — decides which editor fields appear (timing only for `animated`). */\nexport type ActionKind = 'animated' | 'instant' | 'server';\n\n/** Picker grouping. Flat lists die at ~10 entries; the group is data, never a second list. */\nexport type ActionGroup =\n | 'motion' | 'visibility' | 'navigate' | 'dialog' | 'form' | 'data' | 'service';\n\n/**\n * What a step does inside the live builder canvas (the canvas RUNS interactions):\n * `run` for real (open the dialog), `simulate` announce-only (navigate), `inert` with a\n * \"runs on the live site\" note (anything server-side or costing money).\n */\nexport type BuilderBehaviour = 'run' | 'simulate' | 'inert';\n\n/** A configurable parameter, same shape ComponentMethodDef.params already uses. */\nexport interface ActionParamSpec {\n name: string;\n type: 'number' | 'string' | 'boolean' | 'page' | 'node' | 'form';\n label: string;\n}\n\n/** Does this action point at another node — and may it be any node or only certain types? */\nexport interface ActionTargetSpec {\n required: boolean;\n types?: string[];\n}\n\n/**\n * One row of an action catalog. Every field beyond `type`/`labelKey` is optional so the\n * pre-A0 forms descriptors remain valid verbatim.\n */\nexport interface ActionDescriptor {\n type: string;\n labelKey: string;\n kind?: ActionKind;\n group?: ActionGroup;\n builderBehaviour?: BuilderBehaviour;\n target?: ActionTargetSpec;\n params?: ActionParamSpec[];\n /** The connector that must be configured & healthy before this action is offerable. */\n connector?: string;\n /** Plan-entitlement key (AI plan D5). Absent = never gated by plan. */\n entitlement?: string;\n /** Always present in the list and not removable (forms: `store`). */\n locked?: boolean;\n /** Not offered yet — shown greyed with a \"coming soon\" note, so the roadmap is visible. */\n planned?: boolean;\n}\n\n/**\n * The FORM after-submit catalog (moved verbatim from forms-core; `forms-core` re-exports it\n * under its old name `ACTION_DESCRIPTORS`). A1's element catalog is a SEPARATE constant —\n * the two lists share this shape, never one bucket.\n */\nexport const FORM_SUBMIT_ACTION_DESCRIPTORS: readonly ActionDescriptor[] = Object.freeze([\n { type: 'store', labelKey: 'forms.actions.store', locked: true, kind: 'server', group: 'data', builderBehaviour: 'inert' },\n { type: 'email', labelKey: 'forms.actions.email', kind: 'server', group: 'service', builderBehaviour: 'inert', connector: 'mailer' },\n { type: 'redirect', labelKey: 'forms.actions.redirect', kind: 'instant', group: 'navigate', builderBehaviour: 'simulate' },\n { type: 'createItem', labelKey: 'forms.actions.createItem', planned: true, kind: 'server', group: 'data', builderBehaviour: 'inert' },\n { type: 'webhook', labelKey: 'forms.actions.webhook', planned: true, kind: 'server', group: 'service', builderBehaviour: 'inert' },\n { type: 'autoReply', labelKey: 'forms.actions.autoReply', planned: true, kind: 'server', group: 'service', builderBehaviour: 'inert', connector: 'mailer' },\n]);\n\n/** Steps that can still be added to a list — each at most once, and never a planned one. */\nexport function addableActions(\n existing: readonly { type: string }[] = [],\n catalog: readonly ActionDescriptor[] = FORM_SUBMIT_ACTION_DESCRIPTORS,\n): ActionDescriptor[] {\n const used = new Set(existing.map(action => action.type));\n return catalog.filter(action => !action.locked && !action.planned && !used.has(action.type));\n}\n","import { ActionDescriptor } from './action.model';\n\n/**\n * K4 of the actions plan: four different reasons a catalog row is unavailable, four different\n * sentences — *coming soon* · *always on* · *connect X first* · *upgrade*. One resolver feeds\n * the picker AND the AI composer's validation, so they can never disagree.\n */\nexport type ActionAvailabilityState =\n | 'available'\n | 'planned' // not built yet — greyed, \"coming soon\"\n | 'locked' // always present, not addable/removable\n | 'connector-missing' // the named connector is not configured/healthy\n | 'entitlement-missing'; // the plan does not include it\n\nexport interface ActionAvailability {\n state: ActionAvailabilityState;\n /** Set when state is connector-missing / entitlement-missing — what to name in the message. */\n connector?: string;\n entitlement?: string;\n}\n\nexport interface ActionAvailabilityContext {\n /** Connector health by name. An absent map, or an absent key, means \"not known\" → allowed. */\n connectors?: Partial<Record<string, boolean>>;\n /**\n * Plan entitlements by key. `Plan.entitlements` does not exist yet anywhere (the known gap),\n * so an ABSENT map means \"no gating\" and every entitlement passes — the hook is live the day\n * the control plane starts sending them.\n */\n entitlements?: Partial<Record<string, boolean>>;\n}\n\nexport function resolveActionAvailability(\n descriptor: ActionDescriptor,\n ctx: ActionAvailabilityContext = {},\n): ActionAvailability {\n if (descriptor.planned) return { state: 'planned' };\n if (descriptor.locked) return { state: 'locked' };\n if (descriptor.connector && ctx.connectors && ctx.connectors[descriptor.connector] === false) {\n return { state: 'connector-missing', connector: descriptor.connector };\n }\n if (descriptor.entitlement && ctx.entitlements && ctx.entitlements[descriptor.entitlement] === false) {\n return { state: 'entitlement-missing', entitlement: descriptor.entitlement };\n }\n return { state: 'available' };\n}\n","/**\n * @vectoriox/iox-ui/actions-core — the shared, FRAMEWORK-FREE action vocabulary.\n *\n * One `ActionDescriptor` shape for every ordered action list in the product: the forms\n * after-submit steps (whose catalog moved here; `forms-core` re-exports it unchanged) and\n * the element behaviour actions of A1. Framework-free for the same reason as `forms-core`\n * and `seo-core`: `iox-cms` consumes it under plain Node.\n * ⚠️ Consumers wire the usual trio — ng-package raw-ts assets, jest moduleNameMapper, and\n * tsconfig paths — or `nest start` dies with ERR_REQUIRE_ESM while jest and build pass.\n */\nexport * from './action.model';\nexport * from './availability';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"AAAA;;;;;;AAMG;AAmDH;;;;AAIG;AACI,MAAM,8BAA8B,GAAgC,MAAM,CAAC,MAAM,CAAC;IACrF,EAAE,IAAI,EAAE,OAAO,EAAO,QAAQ,EAAE,qBAAqB,EAAO,MAAM,EAAE,IAAI,EAAG,IAAI,EAAE,QAAQ,EAAG,KAAK,EAAE,MAAM,EAAM,gBAAgB,EAAE,OAAO,EAAE;IAC1I,EAAE,IAAI,EAAE,OAAO,EAAO,QAAQ,EAAE,qBAAqB,EAAsB,IAAI,EAAE,QAAQ,EAAG,KAAK,EAAE,SAAS,EAAG,gBAAgB,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE;AAC/J,IAAA,EAAE,IAAI,EAAE,UAAU,EAAI,QAAQ,EAAE,wBAAwB,EAAmB,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,UAAU,EAAE,gBAAgB,EAAE,UAAU,EAAE;IAC7I,EAAE,IAAI,EAAE,YAAY,EAAE,QAAQ,EAAE,0BAA0B,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAG,KAAK,EAAE,MAAM,EAAM,gBAAgB,EAAE,OAAO,EAAE;IAC1I,EAAE,IAAI,EAAE,SAAS,EAAK,QAAQ,EAAE,uBAAuB,EAAK,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAG,KAAK,EAAE,SAAS,EAAG,gBAAgB,EAAE,OAAO,EAAE;IAC1I,EAAE,IAAI,EAAE,WAAW,EAAG,QAAQ,EAAE,yBAAyB,EAAG,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAG,KAAK,EAAE,SAAS,EAAG,gBAAgB,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE;AAClK,CAAA;AAED;SACgB,cAAc,CAC1B,WAAwC,EAAE,EAC1C,UAAuC,8BAA8B,EAAA;AAErE,IAAA,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC;AACzD,IAAA,OAAO,OAAO,CAAC,MAAM,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;AAChG;;SC9CgB,yBAAyB,CACrC,UAA4B,EAC5B,MAAiC,EAAE,EAAA;IAEnC,IAAI,UAAU,CAAC,OAAO;AAAE,QAAA,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE;IACnD,IAAI,UAAU,CAAC,MAAM;AAAE,QAAA,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE;AACjD,IAAA,IAAI,UAAU,CAAC,SAAS,IAAI,GAAG,CAAC,UAAU,IAAI,GAAG,CAAC,UAAU,CAAC,UAAU,CAAC,SAAS,CAAC,KAAK,KAAK,EAAE;QAC1F,OAAO,EAAE,KAAK,EAAE,mBAAmB,EAAE,SAAS,EAAE,UAAU,CAAC,SAAS,EAAE;IAC1E;AACA,IAAA,IAAI,UAAU,CAAC,WAAW,IAAI,GAAG,CAAC,YAAY,IAAI,GAAG,CAAC,YAAY,CAAC,UAAU,CAAC,WAAW,CAAC,KAAK,KAAK,EAAE;QAClG,OAAO,EAAE,KAAK,EAAE,qBAAqB,EAAE,WAAW,EAAE,UAAU,CAAC,WAAW,EAAE;IAChF;AACA,IAAA,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE;AACjC;;AC7CA;;;;;;;;;AASG;;ACTH;;AAEG;;;;"}
|
|
1
|
+
{"version":3,"file":"vectoriox-iox-ui-actions-core.mjs","sources":["../../../projects/iox-ui/src/actions-core/action.model.ts","../../../projects/iox-ui/src/actions-core/availability.ts","../../../projects/iox-ui/src/actions-core/element-actions.ts","../../../projects/iox-ui/src/actions-core/public-api.ts","../../../projects/iox-ui/src/actions-core/vectoriox-iox-ui-actions-core.ts"],"sourcesContent":["/**\n * The ONE descriptor shape for everything an author can put in an ordered action list —\n * a form's after-submit steps today, element behaviour actions (A1) next. Promoted out of\n * `forms-core` because a button action importing from the forms module would be a lie; the\n * forms catalog lives here now and `forms-core` re-exports it unchanged.\n * See iox-ai-guidance `architecture/builder/actions-framework-plan.md` §4 (A0).\n */\n\n/** How a step executes — decides which editor fields appear (timing only for `animated`). */\nexport type ActionKind = 'animated' | 'instant' | 'server';\n\n/** Picker grouping. Flat lists die at ~10 entries; the group is data, never a second list. */\nexport type ActionGroup =\n | 'motion' | 'visibility' | 'navigate' | 'dialog' | 'form' | 'data' | 'service';\n\n/**\n * What a step does inside the live builder canvas (the canvas RUNS interactions):\n * `run` for real (open the dialog), `simulate` announce-only (navigate), `inert` with a\n * \"runs on the live site\" note (anything server-side or costing money).\n */\nexport type BuilderBehaviour = 'run' | 'simulate' | 'inert';\n\n/** A configurable parameter, same shape ComponentMethodDef.params already uses. */\nexport interface ActionParamSpec {\n name: string;\n type: 'number' | 'string' | 'boolean' | 'page' | 'node' | 'form';\n label: string;\n}\n\n/** Does this action point at another node — and may it be any node or only certain types? */\nexport interface ActionTargetSpec {\n required: boolean;\n types?: string[];\n}\n\n/**\n * One row of an action catalog. Every field beyond `type`/`labelKey` is optional so the\n * pre-A0 forms descriptors remain valid verbatim.\n */\nexport interface ActionDescriptor {\n type: string;\n labelKey: string;\n kind?: ActionKind;\n group?: ActionGroup;\n builderBehaviour?: BuilderBehaviour;\n target?: ActionTargetSpec;\n params?: ActionParamSpec[];\n /** The connector that must be configured & healthy before this action is offerable. */\n connector?: string;\n /** Plan-entitlement key (AI plan D5). Absent = never gated by plan. */\n entitlement?: string;\n /** Always present in the list and not removable (forms: `store`). */\n locked?: boolean;\n /** Not offered yet — shown greyed with a \"coming soon\" note, so the roadmap is visible. */\n planned?: boolean;\n}\n\n/**\n * The FORM after-submit catalog (moved verbatim from forms-core; `forms-core` re-exports it\n * under its old name `ACTION_DESCRIPTORS`). A1's element catalog is a SEPARATE constant —\n * the two lists share this shape, never one bucket.\n */\nexport const FORM_SUBMIT_ACTION_DESCRIPTORS: readonly ActionDescriptor[] = Object.freeze([\n { type: 'store', labelKey: 'forms.actions.store', locked: true, kind: 'server', group: 'data', builderBehaviour: 'inert' },\n { type: 'email', labelKey: 'forms.actions.email', kind: 'server', group: 'service', builderBehaviour: 'inert', connector: 'mailer' },\n { type: 'redirect', labelKey: 'forms.actions.redirect', kind: 'instant', group: 'navigate', builderBehaviour: 'simulate' },\n { type: 'createItem', labelKey: 'forms.actions.createItem', planned: true, kind: 'server', group: 'data', builderBehaviour: 'inert' },\n { type: 'webhook', labelKey: 'forms.actions.webhook', planned: true, kind: 'server', group: 'service', builderBehaviour: 'inert' },\n { type: 'autoReply', labelKey: 'forms.actions.autoReply', planned: true, kind: 'server', group: 'service', builderBehaviour: 'inert', connector: 'mailer' },\n]);\n\n/** Steps that can still be added to a list — each at most once, and never a planned one. */\nexport function addableActions(\n existing: readonly { type: string }[] = [],\n catalog: readonly ActionDescriptor[] = FORM_SUBMIT_ACTION_DESCRIPTORS,\n): ActionDescriptor[] {\n const used = new Set(existing.map(action => action.type));\n return catalog.filter(action => !action.locked && !action.planned && !used.has(action.type));\n}\n","import { ActionDescriptor } from './action.model';\n\n/**\n * K4 of the actions plan: four different reasons a catalog row is unavailable, four different\n * sentences — *coming soon* · *always on* · *connect X first* · *upgrade*. One resolver feeds\n * the picker AND the AI composer's validation, so they can never disagree.\n */\nexport type ActionAvailabilityState =\n | 'available'\n | 'planned' // not built yet — greyed, \"coming soon\"\n | 'locked' // always present, not addable/removable\n | 'connector-missing' // the named connector is not configured/healthy\n | 'entitlement-missing'; // the plan does not include it\n\nexport interface ActionAvailability {\n state: ActionAvailabilityState;\n /** Set when state is connector-missing / entitlement-missing — what to name in the message. */\n connector?: string;\n entitlement?: string;\n}\n\nexport interface ActionAvailabilityContext {\n /** Connector health by name. An absent map, or an absent key, means \"not known\" → allowed. */\n connectors?: Partial<Record<string, boolean>>;\n /**\n * Plan entitlements by key. `Plan.entitlements` does not exist yet anywhere (the known gap),\n * so an ABSENT map means \"no gating\" and every entitlement passes — the hook is live the day\n * the control plane starts sending them.\n */\n entitlements?: Partial<Record<string, boolean>>;\n}\n\nexport function resolveActionAvailability(\n descriptor: ActionDescriptor,\n ctx: ActionAvailabilityContext = {},\n): ActionAvailability {\n if (descriptor.planned) return { state: 'planned' };\n if (descriptor.locked) return { state: 'locked' };\n if (descriptor.connector && ctx.connectors && ctx.connectors[descriptor.connector] === false) {\n return { state: 'connector-missing', connector: descriptor.connector };\n }\n if (descriptor.entitlement && ctx.entitlements && ctx.entitlements[descriptor.entitlement] === false) {\n return { state: 'entitlement-missing', entitlement: descriptor.entitlement };\n }\n return { state: 'available' };\n}\n","import { ActionDescriptor } from './action.model';\n\n/**\n * The ELEMENT action catalog (actions plan A1) — what a node can DO, as data. One list feeds the\n * grouped picker, the two interaction engines, and the AI composer's validation (§9: the model\n * picks actions BY NAME from this catalog and can never invent behaviour).\n *\n * Shipped rows (motion/visibility/context/swiper) are listed so the catalog is the WHOLE truth;\n * their `kind`/`builderBehaviour` make the long-standing implicit rules explicit — the canvas is\n * a live runtime (feedback_builder_live_runtime), navigate must SIMULATE, services are INERT.\n * `signIn`/`startCheckout` ship greyed with the honest reason (D-A5): no visitor identity, no\n * payments — a subsystem and a product, not dropdown entries.\n */\nexport const ELEMENT_ACTION_DESCRIPTORS: readonly ActionDescriptor[] = Object.freeze([\n // ── Motion (shipped WAAPI actions — timing row applies) ──────────────────\n { type: 'fadeIn', labelKey: 'builder.actions.fadeIn', kind: 'animated', group: 'motion', builderBehaviour: 'run' },\n { type: 'fadeOut', labelKey: 'builder.actions.fadeOut', kind: 'animated', group: 'motion', builderBehaviour: 'run' },\n { type: 'moveUp', labelKey: 'builder.actions.moveUp', kind: 'animated', group: 'motion', builderBehaviour: 'run' },\n { type: 'moveDown', labelKey: 'builder.actions.moveDown', kind: 'animated', group: 'motion', builderBehaviour: 'run' },\n { type: 'moveLeft', labelKey: 'builder.actions.moveLeft', kind: 'animated', group: 'motion', builderBehaviour: 'run' },\n { type: 'moveRight',labelKey: 'builder.actions.moveRight',kind: 'animated', group: 'motion', builderBehaviour: 'run' },\n { type: 'scaleIn', labelKey: 'builder.actions.scaleIn', kind: 'animated', group: 'motion', builderBehaviour: 'run' },\n { type: 'scaleOut', labelKey: 'builder.actions.scaleOut', kind: 'animated', group: 'motion', builderBehaviour: 'run' },\n { type: 'rotate', labelKey: 'builder.actions.rotate', kind: 'animated', group: 'motion', builderBehaviour: 'run',\n params: [{ name: 'degrees', type: 'number', label: 'Degrees' }] },\n\n // ── Visibility (shipped) ─────────────────────────────────────────────────\n { type: 'show', labelKey: 'builder.actions.show', kind: 'instant', group: 'visibility', builderBehaviour: 'run' },\n { type: 'hide', labelKey: 'builder.actions.hide', kind: 'instant', group: 'visibility', builderBehaviour: 'run' },\n { type: 'toggleVisibility', labelKey: 'builder.actions.toggleVisibility', kind: 'instant', group: 'visibility', builderBehaviour: 'run' },\n\n // ── Navigate (A1 — Tier A) ───────────────────────────────────────────────\n { type: 'navigate', labelKey: 'builder.actions.navigate', kind: 'instant', group: 'navigate', builderBehaviour: 'simulate',\n params: [\n { name: 'navigateTo', type: 'string', label: 'Destination' }, // 'page' | 'url' | 'back'\n { name: 'page', type: 'page', label: 'Page' },\n { name: 'url', type: 'string', label: 'URL' },\n ] },\n { type: 'scrollTo', labelKey: 'builder.actions.scrollTo', kind: 'instant', group: 'navigate', builderBehaviour: 'run',\n target: { required: false } },\n\n // ── Dialog (A1 — needs the Dialog component, A1b) ───────────────────────\n { type: 'openDialog', labelKey: 'builder.actions.openDialog', kind: 'instant', group: 'dialog', builderBehaviour: 'run',\n target: { required: true, types: ['Dialog'] } },\n { type: 'closeDialog', labelKey: 'builder.actions.closeDialog', kind: 'instant', group: 'dialog', builderBehaviour: 'run',\n target: { required: true, types: ['Dialog'] } },\n { type: 'toggleDialog', labelKey: 'builder.actions.toggleDialog', kind: 'instant', group: 'dialog', builderBehaviour: 'run',\n target: { required: true, types: ['Dialog'] } },\n\n // ── Form (A1 — Tier A; the submit transport is the forms module's, I7 of its plan) ──\n { type: 'submitForm', labelKey: 'builder.actions.submitForm', kind: 'instant', group: 'form', builderBehaviour: 'run',\n target: { required: true, types: ['Form'] } },\n\n // ── Data (shipped context actions + the one trivial newcomer) ───────────\n { type: 'setContext', labelKey: 'builder.actions.setContext', kind: 'instant', group: 'data', builderBehaviour: 'run' },\n { type: 'clearContext', labelKey: 'builder.actions.clearContext', kind: 'instant', group: 'data', builderBehaviour: 'run' },\n { type: 'copyToClipboard', labelKey: 'builder.actions.copyToClipboard', kind: 'instant', group: 'data', builderBehaviour: 'run',\n params: [{ name: 'text', type: 'string', label: 'Text' }] },\n\n // ── Services (D-A5: visible, greyed, with the reason — never hidden, never live) ──\n { type: 'signIn', labelKey: 'builder.actions.signIn', kind: 'server', group: 'service', builderBehaviour: 'inert',\n planned: true, entitlement: 'visitorIdentity' },\n { type: 'startCheckout', labelKey: 'builder.actions.startCheckout', kind: 'server', group: 'service', builderBehaviour: 'inert',\n planned: true, connector: 'payment', entitlement: 'commerce' },\n]);\n\n/** Picker order — most-used groups first, services last (they are the shop window, not the shop). */\nexport const ELEMENT_ACTION_GROUP_ORDER: readonly string[] =\n Object.freeze(['motion', 'visibility', 'navigate', 'dialog', 'form', 'data', 'service']);\n\n/** The catalog, grouped and ordered for the picker — one derivation, no hand-kept second list. */\nexport function groupedElementActions(): ReadonlyArray<{ group: string; actions: ActionDescriptor[] }> {\n return ELEMENT_ACTION_GROUP_ORDER\n .map(group => ({ group, actions: ELEMENT_ACTION_DESCRIPTORS.filter(d => d.group === group) }))\n .filter(g => g.actions.length > 0);\n}\n","/**\n * @vectoriox/iox-ui/actions-core — the shared, FRAMEWORK-FREE action vocabulary.\n *\n * One `ActionDescriptor` shape for every ordered action list in the product: the forms\n * after-submit steps (whose catalog moved here; `forms-core` re-exports it unchanged) and\n * the element behaviour actions of A1. Framework-free for the same reason as `forms-core`\n * and `seo-core`: `iox-cms` consumes it under plain Node.\n * ⚠️ Consumers wire the usual trio — ng-package raw-ts assets, jest moduleNameMapper, and\n * tsconfig paths — or `nest start` dies with ERR_REQUIRE_ESM while jest and build pass.\n */\nexport * from './action.model';\nexport * from './availability';\nexport * from './element-actions';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
|
@@ -120,6 +120,22 @@ function consentModeBootstrapJs() {
|
|
|
120
120
|
return "window.dataLayer=window.dataLayer||[];function gtag(){dataLayer.push(arguments);}" +
|
|
121
121
|
`gtag('consent','default',${JSON.stringify(CONSENT_DEFAULTS_DENIED)});`;
|
|
122
122
|
}
|
|
123
|
+
/**
|
|
124
|
+
* The gated injection, as HTML (C2). Emitted by the SSR server ONLY when the request's cookie
|
|
125
|
+
* grants analytics AND the org has a measurementId — the bootstrap above is unconditional, this
|
|
126
|
+
* is not. Order inside: consent UPDATE before the loader, config after it.
|
|
127
|
+
*/
|
|
128
|
+
function analyticsInjectionHtml(measurementId, decision) {
|
|
129
|
+
const id = (measurementId ?? '').trim();
|
|
130
|
+
if (!id || !/^[A-Za-z0-9-]+$/.test(id))
|
|
131
|
+
return '';
|
|
132
|
+
if (!hasConsent(decision, ConsentCategory.Analytics))
|
|
133
|
+
return '';
|
|
134
|
+
const signals = JSON.stringify(toConsentModeSignals(decision));
|
|
135
|
+
return `<script>gtag('consent','update',${signals});</script>` +
|
|
136
|
+
`<script async src="https://www.googletagmanager.com/gtag/js?id=${id}"></script>` +
|
|
137
|
+
`<script>gtag('js',new Date());gtag('config','${id}');</script>`;
|
|
138
|
+
}
|
|
123
139
|
|
|
124
140
|
/**
|
|
125
141
|
* @vectoriox/iox-ui/consent-core — the shared, FRAMEWORK-FREE core of visitor consent.
|
|
@@ -137,5 +153,5 @@ function consentModeBootstrapJs() {
|
|
|
137
153
|
* Generated bundle index. Do not edit.
|
|
138
154
|
*/
|
|
139
155
|
|
|
140
|
-
export { CONSENT_COOKIE_NAME, CONSENT_DEFAULTS_DENIED, CONSENT_MODE_PARAMS, ConsentCategory, consentModeBootstrapJs, cookieAttributes, decode, emptyDecision, encode, hasConsent, isExpired, needsPrompt, toConsentModeSignals };
|
|
156
|
+
export { CONSENT_COOKIE_NAME, CONSENT_DEFAULTS_DENIED, CONSENT_MODE_PARAMS, ConsentCategory, analyticsInjectionHtml, consentModeBootstrapJs, cookieAttributes, decode, emptyDecision, encode, hasConsent, isExpired, needsPrompt, toConsentModeSignals };
|
|
141
157
|
//# sourceMappingURL=vectoriox-iox-ui-consent-core.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vectoriox-iox-ui-consent-core.mjs","sources":["../../../projects/iox-ui/src/consent-core/consent.model.ts","../../../projects/iox-ui/src/consent-core/storage.ts","../../../projects/iox-ui/src/consent-core/consent-mode.ts","../../../projects/iox-ui/src/consent-core/public-api.ts","../../../projects/iox-ui/src/consent-core/vectoriox-iox-ui-consent-core.ts"],"sourcesContent":["/**\n * The visitor-consent vocabulary (consent plan C1). Framework-free for the same forced reason as\n * forms-core/seo-core/actions-core: iox-cms consumes it under plain Node, the engine at SSR time,\n * the builder for design-time preview — ONE implementation of what a decision is.\n */\n\nexport enum ConsentCategory {\n /** Always on, never user-controllable — the site working at all. */\n Necessary = 'necessary',\n Analytics = 'analytics',\n Marketing = 'marketing',\n}\n\n/** A visitor's recorded decision. `necessary` is implied true and never stored as false. */\nexport interface ConsentDecision {\n categories: Partial<Record<ConsentCategory, boolean>>;\n /** Bumping the org's policy version invalidates every prior decision. */\n policyVersion: string;\n /** ISO timestamp of the decision. */\n decidedAt: string;\n anonymousId: string;\n}\n\n/** A fresh \"nothing granted\" decision skeleton — used when a visitor must be asked. */\nexport function emptyDecision(anonymousId: string, policyVersion: string, now: Date = new Date()): ConsentDecision {\n return {\n categories: { [ConsentCategory.Necessary]: true },\n policyVersion,\n decidedAt: now.toISOString(),\n anonymousId,\n };\n}\n\n/** `null` (no decision yet) grants nothing; `necessary` is always granted. */\nexport function hasConsent(decision: ConsentDecision | null | undefined, category: ConsentCategory): boolean {\n if (category === ConsentCategory.Necessary) return true;\n if (!decision) return false;\n return decision.categories?.[category] === true;\n}\n\n/** A decision older than `maxMonths` no longer counts (default re-ask: 12 months, D-C6). */\nexport function isExpired(decision: ConsentDecision, now: Date, maxMonths: number): boolean {\n const decided = new Date(decision.decidedAt);\n if (Number.isNaN(decided.getTime())) return true;\n const expiry = new Date(decided);\n expiry.setMonth(expiry.getMonth() + maxMonths);\n return now >= expiry;\n}\n\n/**\n * The function the dead fork got wrong: **a recorded \"no\" is a decision.** The banner shows only\n * when there is no decision, the decision expired, or the policy version moved on — never because\n * the visitor declined.\n */\nexport function needsPrompt(\n decision: ConsentDecision | null | undefined,\n policyVersion: string,\n now: Date = new Date(),\n maxMonths = 12,\n): boolean {\n if (!decision) return true;\n if (decision.policyVersion !== policyVersion) return true;\n return isExpired(decision, now, maxMonths);\n}\n","import { ConsentCategory, ConsentDecision } from './consent.model';\n\n/** First-party cookie name — the visitor's browser carries the proof (D-C1). */\nexport const CONSENT_COOKIE_NAME = 'iox-consent';\n\n/** Cookie payload. Versioned so a future shape change can read old cookies tolerantly. */\nexport function encode(decision: ConsentDecision): string {\n return JSON.stringify({ v: 1, ...decision });\n}\n\n/**\n * Tolerant by contract: bad JSON, a foreign shape, a tampered cookie — all read as `null`\n * (\"no decision\"), never a throw and never a guess. The old fork threw its way to a re-prompt.\n */\nexport function decode(raw: string | null | undefined): ConsentDecision | null {\n if (!raw) return null;\n try {\n const parsed = JSON.parse(raw);\n if (!parsed || typeof parsed !== 'object') return null;\n if (typeof parsed.anonymousId !== 'string' || !parsed.anonymousId) return null;\n if (typeof parsed.policyVersion !== 'string') return null;\n if (!parsed.categories || typeof parsed.categories !== 'object' || Array.isArray(parsed.categories)) return null;\n const categories: Partial<Record<ConsentCategory, boolean>> = {};\n for (const [k, v] of Object.entries(parsed.categories)) {\n categories[k as ConsentCategory] = v === true;\n }\n categories[ConsentCategory.Necessary] = true;\n return {\n categories,\n policyVersion: parsed.policyVersion,\n decidedAt: typeof parsed.decidedAt === 'string' ? parsed.decidedAt : '',\n anonymousId: parsed.anonymousId,\n };\n } catch {\n return null;\n }\n}\n\n/** `Set-Cookie` attributes for the decision: a NECESSARY cookie, 12 months, first-party. */\nexport function cookieAttributes(maxMonths = 12): string {\n const maxAge = Math.floor(maxMonths * 30.44 * 24 * 60 * 60);\n return `Path=/; Max-Age=${maxAge}; SameSite=Lax; Secure`;\n}\n","import { ConsentCategory, ConsentDecision, hasConsent } from './consent.model';\n\n/**\n * Google Consent Mode v2 (consent plan §4): four parameters, mandatory for EEA/UK measurement\n * since March 2024. Our categories map onto them; `necessary` maps to none.\n */\nexport type ConsentModeSignal = 'granted' | 'denied';\n\nexport const CONSENT_MODE_PARAMS = [\n 'analytics_storage', 'ad_storage', 'ad_user_data', 'ad_personalization',\n] as const;\n\n/** What the SSR head declares BEFORE any tag can load (invariant I1): everything denied. */\nexport const CONSENT_DEFAULTS_DENIED: Readonly<Record<(typeof CONSENT_MODE_PARAMS)[number], ConsentModeSignal>> =\n Object.freeze({\n analytics_storage: 'denied',\n ad_storage: 'denied',\n ad_user_data: 'denied',\n ad_personalization: 'denied',\n });\n\n/** A decision → the four v2 signals. `null` (undecided) is indistinguishable from denied. */\nexport function toConsentModeSignals(decision: ConsentDecision | null | undefined): Record<string, ConsentModeSignal> {\n const analytics: ConsentModeSignal = hasConsent(decision ?? null, ConsentCategory.Analytics) ? 'granted' : 'denied';\n const marketing: ConsentModeSignal = hasConsent(decision ?? null, ConsentCategory.Marketing) ? 'granted' : 'denied';\n return {\n analytics_storage: analytics,\n ad_storage: marketing,\n ad_user_data: marketing,\n ad_personalization: marketing,\n };\n}\n\n/** The inline `<head>` bootstrap (basic mode, D-C2): defaults denied before anything loads. */\nexport function consentModeBootstrapJs(): string {\n return \"window.dataLayer=window.dataLayer||[];function gtag(){dataLayer.push(arguments);}\" +\n `gtag('consent','default',${JSON.stringify(CONSENT_DEFAULTS_DENIED)});`;\n}\n","/**\n * @vectoriox/iox-ui/consent-core — the shared, FRAMEWORK-FREE core of visitor consent.\n *\n * What a decision IS (`needsPrompt`: a recorded \"no\" is a decision), how it travels (a tolerant\n * first-party cookie), and what it tells Google (Consent Mode v2 signals, denied-by-default).\n * Consumed by the SSR engine (the gate, C2), the builder (banner preview, C3) and iox-cms (the\n * optional per-org log, C4) — the fourth entry point on the forms-core pattern, replacing the\n * dead byte-identical fork C0 deleted.\n * ⚠️ Consumers wire the usual trio — ng-package raw-ts assets, jest moduleNameMapper, tsconfig\n * paths — or `nest start` dies with ERR_REQUIRE_ESM while jest and build both pass.\n */\nexport * from './consent.model';\nexport * from './storage';\nexport * from './consent-mode';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"vectoriox-iox-ui-consent-core.mjs","sources":["../../../projects/iox-ui/src/consent-core/consent.model.ts","../../../projects/iox-ui/src/consent-core/storage.ts","../../../projects/iox-ui/src/consent-core/consent-mode.ts","../../../projects/iox-ui/src/consent-core/public-api.ts","../../../projects/iox-ui/src/consent-core/vectoriox-iox-ui-consent-core.ts"],"sourcesContent":["/**\n * The visitor-consent vocabulary (consent plan C1). Framework-free for the same forced reason as\n * forms-core/seo-core/actions-core: iox-cms consumes it under plain Node, the engine at SSR time,\n * the builder for design-time preview — ONE implementation of what a decision is.\n */\n\nexport enum ConsentCategory {\n /** Always on, never user-controllable — the site working at all. */\n Necessary = 'necessary',\n Analytics = 'analytics',\n Marketing = 'marketing',\n}\n\n/** A visitor's recorded decision. `necessary` is implied true and never stored as false. */\nexport interface ConsentDecision {\n categories: Partial<Record<ConsentCategory, boolean>>;\n /** Bumping the org's policy version invalidates every prior decision. */\n policyVersion: string;\n /** ISO timestamp of the decision. */\n decidedAt: string;\n anonymousId: string;\n}\n\n/** A fresh \"nothing granted\" decision skeleton — used when a visitor must be asked. */\nexport function emptyDecision(anonymousId: string, policyVersion: string, now: Date = new Date()): ConsentDecision {\n return {\n categories: { [ConsentCategory.Necessary]: true },\n policyVersion,\n decidedAt: now.toISOString(),\n anonymousId,\n };\n}\n\n/** `null` (no decision yet) grants nothing; `necessary` is always granted. */\nexport function hasConsent(decision: ConsentDecision | null | undefined, category: ConsentCategory): boolean {\n if (category === ConsentCategory.Necessary) return true;\n if (!decision) return false;\n return decision.categories?.[category] === true;\n}\n\n/** A decision older than `maxMonths` no longer counts (default re-ask: 12 months, D-C6). */\nexport function isExpired(decision: ConsentDecision, now: Date, maxMonths: number): boolean {\n const decided = new Date(decision.decidedAt);\n if (Number.isNaN(decided.getTime())) return true;\n const expiry = new Date(decided);\n expiry.setMonth(expiry.getMonth() + maxMonths);\n return now >= expiry;\n}\n\n/**\n * The function the dead fork got wrong: **a recorded \"no\" is a decision.** The banner shows only\n * when there is no decision, the decision expired, or the policy version moved on — never because\n * the visitor declined.\n */\nexport function needsPrompt(\n decision: ConsentDecision | null | undefined,\n policyVersion: string,\n now: Date = new Date(),\n maxMonths = 12,\n): boolean {\n if (!decision) return true;\n if (decision.policyVersion !== policyVersion) return true;\n return isExpired(decision, now, maxMonths);\n}\n","import { ConsentCategory, ConsentDecision } from './consent.model';\n\n/** First-party cookie name — the visitor's browser carries the proof (D-C1). */\nexport const CONSENT_COOKIE_NAME = 'iox-consent';\n\n/** Cookie payload. Versioned so a future shape change can read old cookies tolerantly. */\nexport function encode(decision: ConsentDecision): string {\n return JSON.stringify({ v: 1, ...decision });\n}\n\n/**\n * Tolerant by contract: bad JSON, a foreign shape, a tampered cookie — all read as `null`\n * (\"no decision\"), never a throw and never a guess. The old fork threw its way to a re-prompt.\n */\nexport function decode(raw: string | null | undefined): ConsentDecision | null {\n if (!raw) return null;\n try {\n const parsed = JSON.parse(raw);\n if (!parsed || typeof parsed !== 'object') return null;\n if (typeof parsed.anonymousId !== 'string' || !parsed.anonymousId) return null;\n if (typeof parsed.policyVersion !== 'string') return null;\n if (!parsed.categories || typeof parsed.categories !== 'object' || Array.isArray(parsed.categories)) return null;\n const categories: Partial<Record<ConsentCategory, boolean>> = {};\n for (const [k, v] of Object.entries(parsed.categories)) {\n categories[k as ConsentCategory] = v === true;\n }\n categories[ConsentCategory.Necessary] = true;\n return {\n categories,\n policyVersion: parsed.policyVersion,\n decidedAt: typeof parsed.decidedAt === 'string' ? parsed.decidedAt : '',\n anonymousId: parsed.anonymousId,\n };\n } catch {\n return null;\n }\n}\n\n/** `Set-Cookie` attributes for the decision: a NECESSARY cookie, 12 months, first-party. */\nexport function cookieAttributes(maxMonths = 12): string {\n const maxAge = Math.floor(maxMonths * 30.44 * 24 * 60 * 60);\n return `Path=/; Max-Age=${maxAge}; SameSite=Lax; Secure`;\n}\n","import { ConsentCategory, ConsentDecision, hasConsent } from './consent.model';\n\n/**\n * Google Consent Mode v2 (consent plan §4): four parameters, mandatory for EEA/UK measurement\n * since March 2024. Our categories map onto them; `necessary` maps to none.\n */\nexport type ConsentModeSignal = 'granted' | 'denied';\n\nexport const CONSENT_MODE_PARAMS = [\n 'analytics_storage', 'ad_storage', 'ad_user_data', 'ad_personalization',\n] as const;\n\n/** What the SSR head declares BEFORE any tag can load (invariant I1): everything denied. */\nexport const CONSENT_DEFAULTS_DENIED: Readonly<Record<(typeof CONSENT_MODE_PARAMS)[number], ConsentModeSignal>> =\n Object.freeze({\n analytics_storage: 'denied',\n ad_storage: 'denied',\n ad_user_data: 'denied',\n ad_personalization: 'denied',\n });\n\n/** A decision → the four v2 signals. `null` (undecided) is indistinguishable from denied. */\nexport function toConsentModeSignals(decision: ConsentDecision | null | undefined): Record<string, ConsentModeSignal> {\n const analytics: ConsentModeSignal = hasConsent(decision ?? null, ConsentCategory.Analytics) ? 'granted' : 'denied';\n const marketing: ConsentModeSignal = hasConsent(decision ?? null, ConsentCategory.Marketing) ? 'granted' : 'denied';\n return {\n analytics_storage: analytics,\n ad_storage: marketing,\n ad_user_data: marketing,\n ad_personalization: marketing,\n };\n}\n\n/** The inline `<head>` bootstrap (basic mode, D-C2): defaults denied before anything loads. */\nexport function consentModeBootstrapJs(): string {\n return \"window.dataLayer=window.dataLayer||[];function gtag(){dataLayer.push(arguments);}\" +\n `gtag('consent','default',${JSON.stringify(CONSENT_DEFAULTS_DENIED)});`;\n}\n\n/**\n * The gated injection, as HTML (C2). Emitted by the SSR server ONLY when the request's cookie\n * grants analytics AND the org has a measurementId — the bootstrap above is unconditional, this\n * is not. Order inside: consent UPDATE before the loader, config after it.\n */\nexport function analyticsInjectionHtml(measurementId: string, decision: ConsentDecision): string {\n const id = (measurementId ?? '').trim();\n if (!id || !/^[A-Za-z0-9-]+$/.test(id)) return '';\n if (!hasConsent(decision, ConsentCategory.Analytics)) return '';\n const signals = JSON.stringify(toConsentModeSignals(decision));\n return `<script>gtag('consent','update',${signals});</script>` +\n `<script async src=\"https://www.googletagmanager.com/gtag/js?id=${id}\"></script>` +\n `<script>gtag('js',new Date());gtag('config','${id}');</script>`;\n}\n","/**\n * @vectoriox/iox-ui/consent-core — the shared, FRAMEWORK-FREE core of visitor consent.\n *\n * What a decision IS (`needsPrompt`: a recorded \"no\" is a decision), how it travels (a tolerant\n * first-party cookie), and what it tells Google (Consent Mode v2 signals, denied-by-default).\n * Consumed by the SSR engine (the gate, C2), the builder (banner preview, C3) and iox-cms (the\n * optional per-org log, C4) — the fourth entry point on the forms-core pattern, replacing the\n * dead byte-identical fork C0 deleted.\n * ⚠️ Consumers wire the usual trio — ng-package raw-ts assets, jest moduleNameMapper, tsconfig\n * paths — or `nest start` dies with ERR_REQUIRE_ESM while jest and build both pass.\n */\nexport * from './consent.model';\nexport * from './storage';\nexport * from './consent-mode';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { ActionDescriptor } from './action.model';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The ELEMENT action catalog (actions plan A1) — what a node can DO, as data. One list feeds the
|
|
5
|
+
* grouped picker, the two interaction engines, and the AI composer's validation (§9: the model
|
|
6
|
+
* picks actions BY NAME from this catalog and can never invent behaviour).
|
|
7
|
+
*
|
|
8
|
+
* Shipped rows (motion/visibility/context/swiper) are listed so the catalog is the WHOLE truth;
|
|
9
|
+
* their `kind`/`builderBehaviour` make the long-standing implicit rules explicit — the canvas is
|
|
10
|
+
* a live runtime (feedback_builder_live_runtime), navigate must SIMULATE, services are INERT.
|
|
11
|
+
* `signIn`/`startCheckout` ship greyed with the honest reason (D-A5): no visitor identity, no
|
|
12
|
+
* payments — a subsystem and a product, not dropdown entries.
|
|
13
|
+
*/
|
|
14
|
+
export const ELEMENT_ACTION_DESCRIPTORS: readonly ActionDescriptor[] = Object.freeze([
|
|
15
|
+
// ── Motion (shipped WAAPI actions — timing row applies) ──────────────────
|
|
16
|
+
{ type: 'fadeIn', labelKey: 'builder.actions.fadeIn', kind: 'animated', group: 'motion', builderBehaviour: 'run' },
|
|
17
|
+
{ type: 'fadeOut', labelKey: 'builder.actions.fadeOut', kind: 'animated', group: 'motion', builderBehaviour: 'run' },
|
|
18
|
+
{ type: 'moveUp', labelKey: 'builder.actions.moveUp', kind: 'animated', group: 'motion', builderBehaviour: 'run' },
|
|
19
|
+
{ type: 'moveDown', labelKey: 'builder.actions.moveDown', kind: 'animated', group: 'motion', builderBehaviour: 'run' },
|
|
20
|
+
{ type: 'moveLeft', labelKey: 'builder.actions.moveLeft', kind: 'animated', group: 'motion', builderBehaviour: 'run' },
|
|
21
|
+
{ type: 'moveRight',labelKey: 'builder.actions.moveRight',kind: 'animated', group: 'motion', builderBehaviour: 'run' },
|
|
22
|
+
{ type: 'scaleIn', labelKey: 'builder.actions.scaleIn', kind: 'animated', group: 'motion', builderBehaviour: 'run' },
|
|
23
|
+
{ type: 'scaleOut', labelKey: 'builder.actions.scaleOut', kind: 'animated', group: 'motion', builderBehaviour: 'run' },
|
|
24
|
+
{ type: 'rotate', labelKey: 'builder.actions.rotate', kind: 'animated', group: 'motion', builderBehaviour: 'run',
|
|
25
|
+
params: [{ name: 'degrees', type: 'number', label: 'Degrees' }] },
|
|
26
|
+
|
|
27
|
+
// ── Visibility (shipped) ─────────────────────────────────────────────────
|
|
28
|
+
{ type: 'show', labelKey: 'builder.actions.show', kind: 'instant', group: 'visibility', builderBehaviour: 'run' },
|
|
29
|
+
{ type: 'hide', labelKey: 'builder.actions.hide', kind: 'instant', group: 'visibility', builderBehaviour: 'run' },
|
|
30
|
+
{ type: 'toggleVisibility', labelKey: 'builder.actions.toggleVisibility', kind: 'instant', group: 'visibility', builderBehaviour: 'run' },
|
|
31
|
+
|
|
32
|
+
// ── Navigate (A1 — Tier A) ───────────────────────────────────────────────
|
|
33
|
+
{ type: 'navigate', labelKey: 'builder.actions.navigate', kind: 'instant', group: 'navigate', builderBehaviour: 'simulate',
|
|
34
|
+
params: [
|
|
35
|
+
{ name: 'navigateTo', type: 'string', label: 'Destination' }, // 'page' | 'url' | 'back'
|
|
36
|
+
{ name: 'page', type: 'page', label: 'Page' },
|
|
37
|
+
{ name: 'url', type: 'string', label: 'URL' },
|
|
38
|
+
] },
|
|
39
|
+
{ type: 'scrollTo', labelKey: 'builder.actions.scrollTo', kind: 'instant', group: 'navigate', builderBehaviour: 'run',
|
|
40
|
+
target: { required: false } },
|
|
41
|
+
|
|
42
|
+
// ── Dialog (A1 — needs the Dialog component, A1b) ───────────────────────
|
|
43
|
+
{ type: 'openDialog', labelKey: 'builder.actions.openDialog', kind: 'instant', group: 'dialog', builderBehaviour: 'run',
|
|
44
|
+
target: { required: true, types: ['Dialog'] } },
|
|
45
|
+
{ type: 'closeDialog', labelKey: 'builder.actions.closeDialog', kind: 'instant', group: 'dialog', builderBehaviour: 'run',
|
|
46
|
+
target: { required: true, types: ['Dialog'] } },
|
|
47
|
+
{ type: 'toggleDialog', labelKey: 'builder.actions.toggleDialog', kind: 'instant', group: 'dialog', builderBehaviour: 'run',
|
|
48
|
+
target: { required: true, types: ['Dialog'] } },
|
|
49
|
+
|
|
50
|
+
// ── Form (A1 — Tier A; the submit transport is the forms module's, I7 of its plan) ──
|
|
51
|
+
{ type: 'submitForm', labelKey: 'builder.actions.submitForm', kind: 'instant', group: 'form', builderBehaviour: 'run',
|
|
52
|
+
target: { required: true, types: ['Form'] } },
|
|
53
|
+
|
|
54
|
+
// ── Data (shipped context actions + the one trivial newcomer) ───────────
|
|
55
|
+
{ type: 'setContext', labelKey: 'builder.actions.setContext', kind: 'instant', group: 'data', builderBehaviour: 'run' },
|
|
56
|
+
{ type: 'clearContext', labelKey: 'builder.actions.clearContext', kind: 'instant', group: 'data', builderBehaviour: 'run' },
|
|
57
|
+
{ type: 'copyToClipboard', labelKey: 'builder.actions.copyToClipboard', kind: 'instant', group: 'data', builderBehaviour: 'run',
|
|
58
|
+
params: [{ name: 'text', type: 'string', label: 'Text' }] },
|
|
59
|
+
|
|
60
|
+
// ── Services (D-A5: visible, greyed, with the reason — never hidden, never live) ──
|
|
61
|
+
{ type: 'signIn', labelKey: 'builder.actions.signIn', kind: 'server', group: 'service', builderBehaviour: 'inert',
|
|
62
|
+
planned: true, entitlement: 'visitorIdentity' },
|
|
63
|
+
{ type: 'startCheckout', labelKey: 'builder.actions.startCheckout', kind: 'server', group: 'service', builderBehaviour: 'inert',
|
|
64
|
+
planned: true, connector: 'payment', entitlement: 'commerce' },
|
|
65
|
+
]);
|
|
66
|
+
|
|
67
|
+
/** Picker order — most-used groups first, services last (they are the shop window, not the shop). */
|
|
68
|
+
export const ELEMENT_ACTION_GROUP_ORDER: readonly string[] =
|
|
69
|
+
Object.freeze(['motion', 'visibility', 'navigate', 'dialog', 'form', 'data', 'service']);
|
|
70
|
+
|
|
71
|
+
/** The catalog, grouped and ordered for the picker — one derivation, no hand-kept second list. */
|
|
72
|
+
export function groupedElementActions(): ReadonlyArray<{ group: string; actions: ActionDescriptor[] }> {
|
|
73
|
+
return ELEMENT_ACTION_GROUP_ORDER
|
|
74
|
+
.map(group => ({ group, actions: ELEMENT_ACTION_DESCRIPTORS.filter(d => d.group === group) }))
|
|
75
|
+
.filter(g => g.actions.length > 0);
|
|
76
|
+
}
|
|
@@ -36,3 +36,18 @@ export function consentModeBootstrapJs(): string {
|
|
|
36
36
|
return "window.dataLayer=window.dataLayer||[];function gtag(){dataLayer.push(arguments);}" +
|
|
37
37
|
`gtag('consent','default',${JSON.stringify(CONSENT_DEFAULTS_DENIED)});`;
|
|
38
38
|
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* The gated injection, as HTML (C2). Emitted by the SSR server ONLY when the request's cookie
|
|
42
|
+
* grants analytics AND the org has a measurementId — the bootstrap above is unconditional, this
|
|
43
|
+
* is not. Order inside: consent UPDATE before the loader, config after it.
|
|
44
|
+
*/
|
|
45
|
+
export function analyticsInjectionHtml(measurementId: string, decision: ConsentDecision): string {
|
|
46
|
+
const id = (measurementId ?? '').trim();
|
|
47
|
+
if (!id || !/^[A-Za-z0-9-]+$/.test(id)) return '';
|
|
48
|
+
if (!hasConsent(decision, ConsentCategory.Analytics)) return '';
|
|
49
|
+
const signals = JSON.stringify(toConsentModeSignals(decision));
|
|
50
|
+
return `<script>gtag('consent','update',${signals});</script>` +
|
|
51
|
+
`<script async src="https://www.googletagmanager.com/gtag/js?id=${id}"></script>` +
|
|
52
|
+
`<script>gtag('js',new Date());gtag('config','${id}');</script>`;
|
|
53
|
+
}
|