@financial-times/custom-code-component 1.11.2 → 2.0.1-alpha.10

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.
Files changed (35) hide show
  1. package/dist/index.js +598 -0
  2. package/dist/index.js.map +1 -0
  3. package/dist/webcomponent/src/custom-code-component.d.ts +55 -0
  4. package/dist/webcomponent/src/environment.d.ts +15 -0
  5. package/dist/webcomponent/src/errors.d.ts +27 -0
  6. package/dist/webcomponent/src/events.d.ts +10 -0
  7. package/dist/webcomponent/src/get-trace.d.ts +8 -0
  8. package/dist/webcomponent/src/index.d.ts +1 -0
  9. package/dist/webcomponent/src/logger.d.ts +20 -0
  10. package/dist/webcomponent/src/path.d.ts +23 -0
  11. package/dist/{tracking.d.ts → webcomponent/src/tracking.d.ts} +12 -4
  12. package/dist/webcomponent/src/util.d.ts +33 -0
  13. package/dist/webcomponent/test/environment.test.d.ts +1 -0
  14. package/dist/webcomponent/test/error-handling.test.d.ts +8 -0
  15. package/dist/webcomponent/test/example.d.ts +11 -0
  16. package/dist/webcomponent/test/generate-readable-stream.d.ts +8 -0
  17. package/dist/webcomponent/test/path.test.d.ts +5 -0
  18. package/dist/webcomponent/test/ssr.test.d.ts +4 -0
  19. package/dist/webcomponent/test/utils.test.d.ts +1 -0
  20. package/dist/webcomponent/vite.config.d.ts +2 -0
  21. package/dist/webcomponent/vitest.config.d.ts +2 -0
  22. package/package.json +25 -17
  23. package/src/custom-code-component.ts +247 -167
  24. package/src/environment.ts +77 -0
  25. package/src/errors.ts +73 -0
  26. package/src/events.ts +22 -0
  27. package/src/{get-trace.js → get-trace.ts} +23 -14
  28. package/src/index.ts +8 -0
  29. package/src/logger.ts +71 -0
  30. package/src/path.ts +83 -0
  31. package/src/tracking.ts +20 -11
  32. package/src/util.ts +66 -0
  33. package/dist/custom-code-component.d.ts +0 -21
  34. package/dist/custom-code-component.js +0 -380
  35. package/src/custom-code-component.xsd +0 -67
@@ -1,380 +0,0 @@
1
- function d(o) {
2
- this.listenerMap = [{}, {}], o && this.root(o), this.handle = d.prototype.handle.bind(this), this._removedListeners = [];
3
- }
4
- d.prototype.root = function(o) {
5
- const t = this.listenerMap;
6
- let e;
7
- if (this.rootElement) {
8
- for (e in t[1])
9
- t[1].hasOwnProperty(e) && this.rootElement.removeEventListener(e, this.handle, !0);
10
- for (e in t[0])
11
- t[0].hasOwnProperty(e) && this.rootElement.removeEventListener(e, this.handle, !1);
12
- }
13
- if (!o || !o.addEventListener)
14
- return this.rootElement && delete this.rootElement, this;
15
- this.rootElement = o;
16
- for (e in t[1])
17
- t[1].hasOwnProperty(e) && this.rootElement.addEventListener(e, this.handle, !0);
18
- for (e in t[0])
19
- t[0].hasOwnProperty(e) && this.rootElement.addEventListener(e, this.handle, !1);
20
- return this;
21
- };
22
- d.prototype.captureForType = function(o) {
23
- return ["blur", "error", "focus", "load", "resize", "scroll"].indexOf(o) !== -1;
24
- };
25
- d.prototype.on = function(o, t, e, n) {
26
- let s, i, a, r;
27
- if (!o)
28
- throw new TypeError("Invalid event type: " + o);
29
- if (typeof t == "function" && (n = e, e = t, t = null), n === void 0 && (n = this.captureForType(o)), typeof e != "function")
30
- throw new TypeError("Handler must be a type of Function");
31
- return s = this.rootElement, i = this.listenerMap[n ? 1 : 0], i[o] || (s && s.addEventListener(o, this.handle, n), i[o] = []), t ? /^[a-z]+$/i.test(t) ? (r = t, a = y) : /^#[a-z0-9\-_]+$/i.test(t) ? (r = t.slice(1), a = v) : (r = t, a = Element.prototype.matches) : (r = null, a = w.bind(this)), i[o].push({
32
- selector: t,
33
- handler: e,
34
- matcher: a,
35
- matcherParam: r
36
- }), this;
37
- };
38
- d.prototype.off = function(o, t, e, n) {
39
- let s, i, a, r, h;
40
- if (typeof t == "function" && (n = e, e = t, t = null), n === void 0)
41
- return this.off(o, t, e, !0), this.off(o, t, e, !1), this;
42
- if (a = this.listenerMap[n ? 1 : 0], !o) {
43
- for (h in a)
44
- a.hasOwnProperty(h) && this.off(h, t, e);
45
- return this;
46
- }
47
- if (r = a[o], !r || !r.length)
48
- return this;
49
- for (s = r.length - 1; s >= 0; s--)
50
- i = r[s], (!t || t === i.selector) && (!e || e === i.handler) && (this._removedListeners.push(i), r.splice(s, 1));
51
- return r.length || (delete a[o], this.rootElement && this.rootElement.removeEventListener(o, this.handle, n)), this;
52
- };
53
- d.prototype.handle = function(o) {
54
- let t, e;
55
- const n = o.type;
56
- let s, i, a, r, h = [], c;
57
- const l = "ftLabsDelegateIgnore";
58
- if (o[l] === !0)
59
- return;
60
- switch (c = o.target, c.nodeType === 3 && (c = c.parentNode), c.correspondingUseElement && (c = c.correspondingUseElement), s = this.rootElement, i = o.eventPhase || (o.target !== o.currentTarget ? 3 : 2), i) {
61
- case 1:
62
- h = this.listenerMap[1][n];
63
- break;
64
- case 2:
65
- this.listenerMap[0] && this.listenerMap[0][n] && (h = h.concat(this.listenerMap[0][n])), this.listenerMap[1] && this.listenerMap[1][n] && (h = h.concat(this.listenerMap[1][n]));
66
- break;
67
- case 3:
68
- h = this.listenerMap[0][n];
69
- break;
70
- }
71
- let u = [];
72
- for (e = h.length; c && e; ) {
73
- for (t = 0; t < e && (a = h[t], !!a); t++)
74
- c.tagName && ["button", "input", "select", "textarea"].indexOf(c.tagName.toLowerCase()) > -1 && c.hasAttribute("disabled") ? u = [] : a.matcher.call(c, a.matcherParam, c) && u.push([o, c, a]);
75
- if (c === s || (e = h.length, c = c.parentElement || c.parentNode, c instanceof HTMLDocument))
76
- break;
77
- }
78
- let m;
79
- for (t = 0; t < u.length; t++)
80
- if (!(this._removedListeners.indexOf(u[t][2]) > -1) && (r = this.fire.apply(this, u[t]), r === !1)) {
81
- u[t][0][l] = !0, u[t][0].preventDefault(), m = !1;
82
- break;
83
- }
84
- return m;
85
- };
86
- d.prototype.fire = function(o, t, e) {
87
- return e.handler.call(t, o, t);
88
- };
89
- function y(o, t) {
90
- return o.toLowerCase() === t.tagName.toLowerCase();
91
- }
92
- function w(o, t) {
93
- return this.rootElement === window ? (
94
- // Match the outer document (dispatched from document)
95
- t === document || // The <html> element (dispatched from document.body or document.documentElement)
96
- t === document.documentElement || // Or the window itself (dispatched from window)
97
- t === window
98
- ) : this.rootElement === t;
99
- }
100
- function v(o, t) {
101
- return o === t.id;
102
- }
103
- d.prototype.destroy = function() {
104
- this.off(), this.root();
105
- };
106
- function g(o) {
107
- return typeof o == "string" ? o.trim() : o;
108
- }
109
- function b(o, t) {
110
- for (const e in o)
111
- t[e] ? console.warn(`You can't set a custom property called ${e}`) : t[e] = o[e];
112
- }
113
- const k = (o, t, e) => {
114
- const n = Array.from(o.querySelectorAll(e)), s = n.findIndex((i) => i === t);
115
- if (s !== -1)
116
- return {
117
- siblings: n.length,
118
- position: s
119
- };
120
- }, E = [
121
- "nodeName",
122
- "className",
123
- "id",
124
- "href",
125
- "text",
126
- "role"
127
- ], A = (o) => {
128
- const t = {};
129
- for (const e of E) {
130
- const n = o[e] || o.getAttribute(e) || o.hasAttribute(e);
131
- n !== void 0 && (typeof n == "boolean" ? t[e] = n : t[e] = g(n));
132
- }
133
- return t;
134
- }, N = (o) => {
135
- try {
136
- const t = JSON.parse(o), e = Object.prototype.toString.call(t);
137
- return [e === "[object Object]" || e === "[object Array]", t];
138
- } catch {
139
- return [!1, null];
140
- }
141
- }, P = (o) => {
142
- const [t, e] = N(o);
143
- return t ? e : o;
144
- }, $ = (o, t) => (o.filter(
145
- (e) => e.name.match(/^data-trackable|^data-o-|^aria-/i)
146
- ).forEach((e) => {
147
- t[e.name] = e.value;
148
- }), t), C = (o, t, e) => {
149
- const n = {};
150
- return e && E.forEach((s) => {
151
- typeof t[s] < "u" && s !== "id" && (n[s] = t[s]);
152
- }), o.filter((s) => s.name.match(/^data-trackable-context-/i)).forEach((s) => {
153
- n[s.name.replace("data-trackable-context-", "")] = P(s.value);
154
- }), n;
155
- };
156
- function L(o, t) {
157
- const e = o, n = e.getAttribute("data-trackable") ? `[data-trackable="${e.getAttribute("data-trackable")}"]` : e.nodeName, s = [], i = {};
158
- for (; o && o !== t; ) {
159
- const a = A(o), r = Array.from(o.attributes);
160
- let h = $(r, a);
161
- h["data-trackable"] && (h = Object.assign(
162
- h,
163
- k(o, e, n)
164
- )), s.push(h);
165
- const c = C(r, a, o === e);
166
- b(c, i), o = o.parentNode;
167
- }
168
- return { trace: s, customContext: i };
169
- }
170
- const x = ["ctrlKey", "altKey", "shiftKey", "metaKey"];
171
- class M {
172
- constructor({
173
- id: t = "00000000-0000-0000-0000-000000000000",
174
- name: e = "ccc-component",
175
- subtype: n = "interactive",
176
- teamName: s = "djd",
177
- shadowRoot: i = null,
178
- category: a = "cta",
179
- elements: r = 'a, button, input, [role="button"]'
180
- }) {
181
- this.cccId = t, this.cccName = e, this.subtype = n, this.teamName = s, this.shadowRoot = i, this.category = a, this.elements = r, this.isInitialised = !1;
182
- }
183
- // Get properties for the event (as opposed to properties of the clicked element)
184
- getEventProperties(t) {
185
- const e = {};
186
- for (const n of x)
187
- if (t[n])
188
- try {
189
- e[n] = g(t[n]);
190
- } catch (s) {
191
- console.log(s);
192
- }
193
- return e;
194
- }
195
- // Controller for handling click events
196
- handleClickEvent(t, e) {
197
- return (n, s) => {
198
- const i = this.getEventProperties(n), { trace: a, customContext: r } = L(s, e);
199
- i.custom = s.dataset && s.dataset.custom ? JSON.parse(s.dataset.custom) : null, i.domPathTokens = a, i.component = {
200
- id: this.cccId,
201
- name: this.cccName,
202
- type: "custom-code-component",
203
- subtype: this.subtype
204
- }, i.teamName = this.teamName, i.url = document.URL, b(r, i), i.method = "ftCustomAnalytics", t = { ...t, ...i }, document.body.dispatchEvent(
205
- new CustomEvent("oTracking.event", {
206
- detail: t,
207
- bubbles: !0,
208
- composed: !0
209
- })
210
- );
211
- };
212
- }
213
- sendSpoorEvent(t, e) {
214
- const n = {
215
- category: "component",
216
- action: "act",
217
- component: {
218
- id: this.cccId,
219
- name: this.cccName,
220
- type: "custom-code-component",
221
- subtype: this.subtype
222
- },
223
- teamName: this.teamName,
224
- trigger_action: t,
225
- custom: e,
226
- method: "ftCustomAnalytics"
227
- };
228
- document.body.dispatchEvent(
229
- new CustomEvent("oTracking.event", {
230
- detail: n,
231
- bubbles: !0,
232
- composed: !0
233
- })
234
- );
235
- }
236
- init(t) {
237
- var e;
238
- if (!this.isInitialised) {
239
- this.isInitialised = !0, this.cccId = t || this.cccId;
240
- const n = {
241
- action: "click",
242
- category: this.category
243
- }, s = (e = this.shadowRoot) == null ? void 0 : e.querySelector("[data-component-root]");
244
- this.shadowRoot && new d(s).on(
245
- "click",
246
- this.elements,
247
- this.handleClickEvent(n, s),
248
- !0
249
- );
250
- }
251
- }
252
- }
253
- class R extends HTMLElement {
254
- constructor() {
255
- super(...arguments), this.RESERVED_ATTRS = /* @__PURE__ */ new Set([
256
- "iframe",
257
- "path",
258
- "version",
259
- "data-component-props",
260
- "data-asset-type",
261
- "shadow-open",
262
- "env",
263
- "load-timeout"
264
- ]), this.unmount = (t) => {
265
- }, this.channel = new MessageChannel();
266
- }
267
- async mount() {
268
- if (!this.app)
269
- throw new Error("CCC mounted without App");
270
- const t = this.shadowRoot ?? this.attachShadow({ mode: this.mode }), e = this.app, n = JSON.parse(this.getAttribute("data-component-props")), s = Object.fromEntries(
271
- [...this.attributes].filter((r) => !this.RESERVED_ATTRS.has(r.name)).map((r) => [r.name, r.value])
272
- );
273
- this.tracking = new M({
274
- name: `${this.getAttribute("path")}@${this.getAttribute("version")}`,
275
- subtype: "interactive",
276
- teamName: "djd",
277
- shadowRoot: this.shadowRoot
278
- });
279
- const { unmount: i, onmessage: a } = e(
280
- t,
281
- {
282
- ...s,
283
- data: n,
284
- port: this.channel.port2,
285
- tracking: this.tracking
286
- },
287
- ...this.children
288
- ) || {};
289
- i && (this.unmount = i), a && (this.onmessage = a);
290
- }
291
- async connectedCallback() {
292
- var c;
293
- const t = this.getAttribute("path"), e = this.getAttribute("version"), n = this.getAttribute("load-timeout") ?? 2e3, s = (c = this.getAttribute("env")) == null ? void 0 : c.toLowerCase().startsWith("d");
294
- if (!t)
295
- throw new Error(
296
- "path attribute not specified in <custom-code-component>"
297
- );
298
- const [i, a, r] = t.split("/").reverse();
299
- if (!i || !a || !r)
300
- return;
301
- const h = this.getAttribute("id");
302
- this.source = s ? `http://localhost:5173/src/${i}/index.jsx?id=${h}` : `https://www.ft.com/__component/${r}/${a}${e ? `@${e}` : "@latest"}/${i}/${i}.js?id=${h}`;
303
- try {
304
- this.app = await new Promise((l, u) => {
305
- const m = setTimeout(() => {
306
- this.dispatchEvent(
307
- new CustomEvent("ccc-timeout", {
308
- bubbles: !0,
309
- cancelable: !0,
310
- detail: {
311
- component: `${t}@${e}`,
312
- source: this.source
313
- }
314
- })
315
- ), this.dataset.cccError = "import-timeout", delete this.dataset.cccReady;
316
- }, Number(n));
317
- import(
318
- /* webpackIgnore: true */
319
- this.source
320
- /* @vite-ignore */
321
- ).then(({ default: p }) => {
322
- if (p)
323
- clearTimeout(m), l(p);
324
- else
325
- throw new f(
326
- "No component renderer default export found"
327
- );
328
- }).catch((p) => u(new f(p)));
329
- });
330
- } catch (l) {
331
- console.error(
332
- `<custom-code-component> error during import from ${t}@${e}`
333
- ), delete this.dataset.cccReady, this.dataset.cccError = "import-failure", this.dispatchEvent(new ErrorEvent("error", l)), console.error(l);
334
- return;
335
- }
336
- try {
337
- this.mode = this.getAttribute("shadow-open") == "false" ? "closed" : "open", await this.mount(), this.dispatchEvent(
338
- new CustomEvent("ccc-connected", {
339
- bubbles: !0,
340
- cancelable: !0,
341
- detail: {
342
- component: `${t}@${e}`,
343
- source: this.source
344
- }
345
- })
346
- ), this.dataset.cccReady = "true", delete this.dataset.cccError;
347
- } catch (l) {
348
- console.info(
349
- `<custom-code-component> uncaught error during mount from ${t}@${e}`
350
- ), console.error(l), this.dispatchEvent(new ErrorEvent("error", l)), this.dataset.cccError = "mount-error", delete this.dataset.cccReady;
351
- }
352
- try {
353
- this.tracking.init(this.id);
354
- } catch (l) {
355
- console.info(
356
- `Error initialising tracking on <custom-code-component> ${t}@${e}`
357
- ), console.error(l);
358
- }
359
- }
360
- disconnectedCallback() {
361
- const t = this.getAttribute("path");
362
- console.info(`<custom-code-component:${t}> disconnected`), typeof this.unmount == "function" && this.unmount();
363
- }
364
- onmessage() {
365
- }
366
- // I'm honestly not sure what to do with this
367
- postMessage(t) {
368
- this.channel.port1.postMessage(t);
369
- }
370
- }
371
- const O = () => customElements.define("custom-code-component", R);
372
- customElements && !customElements.get("custom-code-component") && O();
373
- class f extends Error {
374
- constructor(...t) {
375
- super(...t), Error.captureStackTrace && Error.captureStackTrace(this, f), this.name = "CCCImportError";
376
- }
377
- }
378
- export {
379
- O as init
380
- };
@@ -1,67 +0,0 @@
1
- <?xml version="1.0"?>
2
-
3
- <xs:schema
4
- xmlns:xs="http://www.w3.org/2001/XMLSchema"
5
- xmlns:sch="http://purl.oclc.org/dsdl/schematron"
6
- >
7
- <xs:complexType name="CustomCodeComponent">
8
- <xs:sequence minOccurs="0">
9
- <xs:element
10
- name="custom-code-component"
11
- type="CustomCodeComponent"
12
- maxOccurs="unbounded"
13
- />
14
- </xs:sequence>
15
- <xs:attribute
16
- name="path"
17
- type="xs:anyURI"
18
- use="required"
19
- />
20
- <xs:attribute
21
- name="versionRange"
22
- type="xs:string"
23
- use="required"
24
- />
25
- <xs:attribute
26
- name="altText"
27
- type="xs:string"
28
- use="required"
29
- />
30
- <xs:attribute
31
- name="lastModifiedTimestamp"
32
- type="xs:string"
33
- use="required"
34
- />
35
- <xs:attribute
36
- name="fallbackImageURL"
37
- type="xs:string"
38
- />
39
- <xs:attribute
40
- name="displayFallbackText"
41
- type="xs:boolean"
42
- default="false"
43
- />
44
- <xs:attribute
45
- name="layout"
46
- default="in-line"
47
- >
48
- <xs:simpleType>
49
- <xs:restriction base="xs:string">
50
- <xs:enumeration value="in-line" />
51
- <xs:enumeration value="mid-grid" />
52
- <xs:enumeration value="full-grid" />
53
- <xs:enumeration value="full-bleed" />
54
- </xs:restriction>
55
- </xs:simpleType>
56
- </xs:attribute>
57
- <xs:attribute
58
- name="attributes"
59
- type="xs:string"
60
- default="{}"
61
- />
62
- </xs:complexType>
63
- <xs:element
64
- name="custom-code-component"
65
- type="CustomCodeComponent"
66
- />
67
- </xs:schema>