@financial-times/custom-code-component 0.0.1-THIS-IS-UPDATED-BY-CI

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/README.md ADDED
@@ -0,0 +1,61 @@
1
+ # custom-code-component (`<custom-code-component>`)
2
+ ## Web component custom element for instantiating Visual & Data Journalism team projects
3
+
4
+ ## Usage:
5
+
6
+ Instantiate the web component and pass it a URL to a bundled component you wish to render into the component root.
7
+
8
+ #### example.jsx
9
+
10
+ ```jsx
11
+ import React from 'react';
12
+ import ReactDOM from 'react-dom';
13
+ import css from './Component.css?inline';
14
+
15
+ const App = (props) => <div>{JSON.stringify(props)}</div>;
16
+
17
+ export default (shadowRoot, props, ...children) => {
18
+ const style = document.createElement("style");
19
+ const mountPoint = document.createElement("div");
20
+
21
+ style.innerHTML = css;
22
+ mountPoint.id = "component-root";
23
+
24
+ shadowRoot.appendChild(style);
25
+ shadowRoot.appendChild(mountPoint);
26
+
27
+ ReactDOM.createRoot(mountPoint).render(
28
+ <React.StrictMode>
29
+ <App {...props}>{children}</App>
30
+ </React.StrictMode>
31
+ );
32
+ };
33
+
34
+ ```
35
+
36
+ #### index.html
37
+ ```html
38
+ <script src="custom-code-component.js" type="module"></script>
39
+
40
+ <custom-code-component path="ft-interactive/test-project/test-component" version="^1" data-component-props="{}" data-asset-type="custom-code-component">
41
+ <img alt="test component" src="https://ig.ft.com/components/ft-interactive/test-project/test-component@^1.png">
42
+ </custom-code-component>
43
+ ```
44
+
45
+ ## Attributes
46
+ * `path` (string)
47
+ * Component name in the format `[org]/[repo]/[component]`.
48
+ * If `[org]` is `ccc-sdk` or ommitted, component will be loaded from Vite local dev server (127.0.0.1:5173).
49
+ * Otherwise, it will be loaded from the CCCCDN
50
+ * `version` (string)
51
+ * [Semantic Versioning](https://semver.org) range for the component.
52
+ * `data-component-props` (string)
53
+ * Pass stringified JSON to make it available to children as the `data` prop.
54
+ * `iframe` (boolean)
55
+ * Render inside an iframe using `srcdoc` for extra sandboxing (default: false)
56
+ * `shadow-open` (boolean)
57
+ * Sets the shadow root to either open or closed (default: false)
58
+ * `data-asset-type="custom-code-component"`
59
+ * Part of spec.
60
+ * <any other attributes>
61
+ * All remaining attributes get passed as an object named `props` to render().
@@ -0,0 +1,591 @@
1
+ function m(o) {
2
+ this.listenerMap = [{}, {}], o && this.root(o), this.handle = m.prototype.handle.bind(this), this._removedListeners = [];
3
+ }
4
+ m.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
+ m.prototype.captureForType = function(o) {
23
+ return ["blur", "error", "focus", "load", "resize", "scroll"].indexOf(o) !== -1;
24
+ };
25
+ m.prototype.on = function(o, t, e, n) {
26
+ let r, s, i, c;
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 r = this.rootElement, s = this.listenerMap[n ? 1 : 0], s[o] || (r && r.addEventListener(o, this.handle, n), s[o] = []), t ? /^[a-z]+$/i.test(t) ? (c = t, i = T) : /^#[a-z0-9\-_]+$/i.test(t) ? (c = t.slice(1), i = $) : (c = t, i = Element.prototype.matches) : (c = null, i = S.bind(this)), s[o].push({
32
+ selector: t,
33
+ handler: e,
34
+ matcher: i,
35
+ matcherParam: c
36
+ }), this;
37
+ };
38
+ m.prototype.off = function(o, t, e, n) {
39
+ let r, s, i, c, 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 (i = this.listenerMap[n ? 1 : 0], !o) {
43
+ for (h in i)
44
+ i.hasOwnProperty(h) && this.off(h, t, e);
45
+ return this;
46
+ }
47
+ if (c = i[o], !c || !c.length)
48
+ return this;
49
+ for (r = c.length - 1; r >= 0; r--)
50
+ s = c[r], (!t || t === s.selector) && (!e || e === s.handler) && (this._removedListeners.push(s), c.splice(r, 1));
51
+ return c.length || (delete i[o], this.rootElement && this.rootElement.removeEventListener(o, this.handle, n)), this;
52
+ };
53
+ m.prototype.handle = function(o) {
54
+ let t, e;
55
+ const n = o.type;
56
+ let r, s, i, c, h = [], a;
57
+ const d = "ftLabsDelegateIgnore";
58
+ if (o[d] === !0)
59
+ return;
60
+ switch (a = o.target, a.nodeType === 3 && (a = a.parentNode), a.correspondingUseElement && (a = a.correspondingUseElement), r = this.rootElement, s = o.eventPhase || (o.target !== o.currentTarget ? 3 : 2), s) {
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; a && e; ) {
73
+ for (t = 0; t < e && (i = h[t], !!i); t++)
74
+ a.tagName && ["button", "input", "select", "textarea"].indexOf(a.tagName.toLowerCase()) > -1 && a.hasAttribute("disabled") ? u = [] : i.matcher.call(a, i.matcherParam, a) && u.push([o, a, i]);
75
+ if (a === r || (e = h.length, a = a.parentElement || a.parentNode, a instanceof HTMLDocument))
76
+ break;
77
+ }
78
+ let E;
79
+ for (t = 0; t < u.length; t++)
80
+ if (!(this._removedListeners.indexOf(u[t][2]) > -1) && (c = this.fire.apply(this, u[t]), c === !1)) {
81
+ u[t][0][d] = !0, u[t][0].preventDefault(), E = !1;
82
+ break;
83
+ }
84
+ return E;
85
+ };
86
+ m.prototype.fire = function(o, t, e) {
87
+ return e.handler.call(t, o, t);
88
+ };
89
+ function T(o, t) {
90
+ return o.toLowerCase() === t.tagName.toLowerCase();
91
+ }
92
+ function S(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 $(o, t) {
101
+ return o === t.id;
102
+ }
103
+ m.prototype.destroy = function() {
104
+ this.off(), this.root();
105
+ };
106
+ function y(o) {
107
+ return typeof o == "string" ? o.trim() : o;
108
+ }
109
+ function v(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 p = Object.freeze({
114
+ DEBUG: 0,
115
+ INFO: 1,
116
+ WARN: 2,
117
+ ERROR: 3,
118
+ TEST: 4,
119
+ DEFAULT: 2
120
+ });
121
+ function N(o) {
122
+ const t = o == null ? void 0 : o.toLowerCase();
123
+ return t === "debug" ? p.DEBUG : t === "info" ? p.INFO : t === "warn" ? p.WARN : t === "error" ? p.ERROR : t === "test" ? p.TEST : p.DEFAULT;
124
+ }
125
+ class C {
126
+ constructor({ level: t = p.DEFAULT } = {
127
+ level: p.DEFAULT
128
+ }) {
129
+ this.log = this.debug, this.level = t;
130
+ }
131
+ debug(...t) {
132
+ this.level <= p.DEBUG && console.info(...t);
133
+ }
134
+ info(...t) {
135
+ this.level <= p.INFO && console.info(...t);
136
+ }
137
+ warn(...t) {
138
+ this.level <= p.WARN && console.warn(...t);
139
+ }
140
+ error(...t) {
141
+ this.level <= p.ERROR && console.error(...t);
142
+ }
143
+ }
144
+ const L = (o, t, e) => {
145
+ const n = Array.from((o == null ? void 0 : o.querySelectorAll(e)) ?? []), r = n.findIndex((s) => s === t);
146
+ if (r !== -1)
147
+ return {
148
+ siblings: n.length,
149
+ position: r
150
+ };
151
+ }, k = [
152
+ "nodeName",
153
+ "className",
154
+ "id",
155
+ "href",
156
+ "text",
157
+ "role"
158
+ ], O = (o) => {
159
+ const t = {};
160
+ for (const e of k) {
161
+ const n = o[e] || o.getAttribute(e) || o.hasAttribute(e);
162
+ n !== void 0 && (typeof n == "boolean" ? t[e] = n : t[e] = y(n));
163
+ }
164
+ return t;
165
+ }, P = (o) => {
166
+ try {
167
+ const t = JSON.parse(o), e = Object.prototype.toString.call(t);
168
+ return [e === "[object Object]" || e === "[object Array]", t];
169
+ } catch {
170
+ return [!1, null];
171
+ }
172
+ }, x = (o) => {
173
+ const [t, e] = P(o);
174
+ return t ? e : o;
175
+ }, I = (o, t) => (o.filter(
176
+ (e) => e.name.match(/^data-trackable|^data-o-|^aria-/i)
177
+ ).forEach((e) => {
178
+ t[e.name] = e.value;
179
+ }), t), M = (o, t, e) => {
180
+ const n = {};
181
+ return e && k.forEach((r) => {
182
+ typeof t[r] < "u" && r !== "id" && (n[r] = t[r]);
183
+ }), o.filter((r) => r.name.match(/^data-trackable-context-/i)).forEach((r) => {
184
+ n[r.name.replace("data-trackable-context-", "")] = x(r.value);
185
+ }), n;
186
+ };
187
+ function D(o, t) {
188
+ const e = o, n = e != null && e.getAttribute("data-trackable") ? `[data-trackable="${e.getAttribute("data-trackable")}"]` : e == null ? void 0 : e.nodeName, r = [], s = {};
189
+ for (; o && o !== t; ) {
190
+ const i = O(o), c = Array.from(o.attributes);
191
+ let h = I(c, i);
192
+ h["data-trackable"] && (h = Object.assign(
193
+ h,
194
+ L(o, e, n)
195
+ )), r.push(h);
196
+ const a = M(c, i, o === e);
197
+ v(a, s), o = o.parentNode;
198
+ }
199
+ return { trace: r, customContext: s };
200
+ }
201
+ const U = ["ctrlKey", "altKey", "shiftKey", "metaKey"];
202
+ class F {
203
+ constructor({
204
+ id: t = "00000000-0000-0000-0000-000000000000",
205
+ name: e = "ccc-component",
206
+ subtype: n = "interactive",
207
+ teamName: r = "djd",
208
+ shadowRoot: s = null,
209
+ category: i = "cta",
210
+ elements: c = 'a, button, input, [role="button"]',
211
+ logger: h
212
+ }) {
213
+ this.cccId = t, this.cccName = e, this.subtype = n, this.teamName = r, this.shadowRoot = s, this.category = i, this.elements = c, this.isInitialised = !1, this.log = h ?? new C();
214
+ }
215
+ // Get properties for the event (as opposed to properties of the clicked element)
216
+ getEventProperties(t) {
217
+ const e = {};
218
+ for (const n of U)
219
+ if (t[n])
220
+ try {
221
+ e[n] = y(t[n]);
222
+ } catch (r) {
223
+ this.log.info(r);
224
+ }
225
+ return e;
226
+ }
227
+ // Controller for handling click events
228
+ handleClickEvent(t, e) {
229
+ return (n, r) => {
230
+ const s = this.getEventProperties(n), { trace: i, customContext: c } = D(r, e);
231
+ s.custom = r.dataset && r.dataset.custom ? JSON.parse(r.dataset.custom) : null, s.domPathTokens = i, s.component = {
232
+ id: this.cccId,
233
+ name: this.cccName,
234
+ type: "custom-code-component",
235
+ subtype: this.subtype
236
+ }, s.teamName = this.teamName, s.url = document.URL, v(c, s), s.method = "ftCustomAnalytics", t = { ...t, ...s }, document.body.dispatchEvent(
237
+ new CustomEvent("oTracking.event", {
238
+ detail: t,
239
+ bubbles: !0,
240
+ composed: !0
241
+ })
242
+ );
243
+ };
244
+ }
245
+ sendSpoorEvent(t, e) {
246
+ const n = {
247
+ category: "component",
248
+ action: "act",
249
+ component: {
250
+ id: this.cccId,
251
+ name: this.cccName,
252
+ type: "custom-code-component",
253
+ subtype: this.subtype
254
+ },
255
+ teamName: this.teamName,
256
+ trigger_action: t,
257
+ custom: e,
258
+ method: "ftCustomAnalytics"
259
+ };
260
+ document.body.dispatchEvent(
261
+ new CustomEvent("oTracking.event", {
262
+ detail: n,
263
+ bubbles: !0,
264
+ composed: !0
265
+ })
266
+ );
267
+ }
268
+ init(t) {
269
+ var e;
270
+ if (!this.isInitialised) {
271
+ this.isInitialised = !0, this.cccId = t || this.cccId;
272
+ const n = {
273
+ action: "click",
274
+ category: this.category
275
+ }, r = (e = this.shadowRoot) == null ? void 0 : e.querySelector("[data-component-root]");
276
+ r && new m(r).on(
277
+ "click",
278
+ this.elements,
279
+ this.handleClickEvent(n, r),
280
+ !0
281
+ );
282
+ }
283
+ }
284
+ }
285
+ class l {
286
+ constructor(t) {
287
+ const { org: e, repo: n, component: r, versionRange: s } = w(
288
+ t
289
+ ) ? t : l.fromString(t);
290
+ this.org = e, this.repo = n, this.component = r, this.versionRange = s;
291
+ }
292
+ set path(t) {
293
+ const { org: e, repo: n, component: r, versionRange: s } = w(
294
+ t
295
+ ) ? t : l.fromString(t);
296
+ this.org = e, this.repo = n, this.component = r, this.versionRange = s;
297
+ }
298
+ get path() {
299
+ return `${this.org}/${this.repo}@${this.versionRange}/${this.component}`;
300
+ }
301
+ toString() {
302
+ return this.path;
303
+ }
304
+ static fromString(t, e) {
305
+ var c;
306
+ if (!t)
307
+ throw new f("No path specified");
308
+ const n = e ?? ((c = t.match(/@[^\/]+/)) == null ? void 0 : c.toString().replace("@", "")) ?? "unknown";
309
+ if (!n)
310
+ throw new f("No version specified");
311
+ const [r, s, i] = t.replace(/@[^\/]+/, "").split("/").reverse();
312
+ return new l({ org: i, repo: s, component: r, versionRange: n });
313
+ }
314
+ }
315
+ function w(o) {
316
+ return typeof o == "object" && o !== null ? "org" in o && "repo" in o && "component" in o : !1;
317
+ }
318
+ class f extends Error {
319
+ constructor(t, e) {
320
+ var n = (...r) => {
321
+ super(...r);
322
+ };
323
+ !e && t ? (n(t), this.component = null) : typeof (e == null ? void 0 : e.component) == "string" ? (n(
324
+ t ?? `${e.cause ?? "Unknown error"} in ${e.component} imported from ${e.source ?? "an undefined source"}.`
325
+ ), this.component = l.fromString(e.component)) : w(e == null ? void 0 : e.component) ? (n(
326
+ t ?? `${e.cause ?? "Unknown error"} in ${e.component.org}/${e.component.repo}/${e.component.component}@${e.component.versionRange} imported from ${e.source ?? "an undefined source"}.`
327
+ ), this.component = new l(e.component)) : (n(
328
+ `${(e == null ? void 0 : e.cause) ?? "Unknown error"} in unknown component imported from ${(e == null ? void 0 : e.source) ?? "unknown source"}.`
329
+ ), this.component = null), this.source = e == null ? void 0 : e.source, Error.captureStackTrace && Error.captureStackTrace(this, f), this.name = "CCCError";
330
+ }
331
+ }
332
+ class g extends f {
333
+ constructor(t, e) {
334
+ super(t, { ...e, cause: "Import error" }), this.name = "CCCImportError";
335
+ }
336
+ }
337
+ class W extends f {
338
+ constructor(t) {
339
+ super(null, { ...t, cause: "Timeout error" }), this.name = "CCCTimeoutError";
340
+ }
341
+ }
342
+ const R = class A extends Event {
343
+ constructor(t, e) {
344
+ super(A.eventType, {
345
+ bubbles: !0,
346
+ cancelable: !1,
347
+ composed: !0,
348
+ ...e
349
+ }), this.component = t.component, this.source = t.source;
350
+ }
351
+ };
352
+ R.eventType = "ccc:ConnectedEvent";
353
+ let j = R;
354
+ const _ = (o) => o.replace(
355
+ /[A-Z]+(?![a-z])|[A-Z]/g,
356
+ (t, e) => (e ? "-" : "") + t.toLowerCase()
357
+ );
358
+ function V(o) {
359
+ return b([
360
+ "localhost",
361
+ "local.ft.com",
362
+ /^.*\.apps\.in\.ft\.com$/
363
+ ], o);
364
+ }
365
+ function z() {
366
+ return b([
367
+ "localhost",
368
+ "local.ft.com",
369
+ /^.*\.in\.ft\.com$/
370
+ ], window.location.hostname);
371
+ }
372
+ function J() {
373
+ return b([
374
+ "spark.ft.com",
375
+ "spark-staging.ft.com"
376
+ ], window.location.host);
377
+ }
378
+ function b(o, t) {
379
+ return t ? o.some((e) => typeof e == "string" ? e === t : e.test(t)) : !1;
380
+ }
381
+ function H(o) {
382
+ if (!o)
383
+ return;
384
+ let t;
385
+ const e = new URL("http://localhost:5173");
386
+ try {
387
+ if (typeof o == "string") {
388
+ if (o === "true" && z())
389
+ t = e;
390
+ else if (t = o.startsWith("http://") || o.startsWith("https://") ? new URL(o) : void 0, t && !V(t == null ? void 0 : t.hostname))
391
+ throw new Error("Unsafe testing host override");
392
+ } else
393
+ J() && (t = e);
394
+ } catch {
395
+ return t;
396
+ }
397
+ return t;
398
+ }
399
+ async function K(o) {
400
+ if (!o)
401
+ return !1;
402
+ function t(n) {
403
+ try {
404
+ return new Promise((r) => {
405
+ const s = new WebSocket(`ws://${n}`, "vite-hmr"), i = setTimeout(() => {
406
+ r(s.readyState === WebSocket.OPEN), s.close();
407
+ }, 50);
408
+ s.addEventListener("error", () => {
409
+ clearTimeout(i), s.close(), r(!1);
410
+ }, { once: !0 });
411
+ });
412
+ } catch (r) {
413
+ return console.error("WebSocket creation failed:", r), Promise.resolve(!1);
414
+ }
415
+ }
416
+ return await t(o == null ? void 0 : o.host);
417
+ }
418
+ class q extends HTMLElement {
419
+ constructor() {
420
+ super(), this.mode = "open", this.RESERVED_ATTRS = /* @__PURE__ */ new Set([
421
+ "iframe",
422
+ "path",
423
+ "version",
424
+ "data-component-props",
425
+ "data-asset-type",
426
+ "shadow-open",
427
+ "env",
428
+ "load-timeout"
429
+ ]), this.channel = new MessageChannel(), this.initTracking = async () => {
430
+ var r;
431
+ try {
432
+ (r = this.tracking) == null || r.init(this.id);
433
+ } catch (s) {
434
+ const i = this.getAttribute("path"), c = this.getAttribute("version");
435
+ this.log.info(
436
+ `Error initialising tracking on <custom-code-component> ${i}@${c}`
437
+ ), this.log.error(s);
438
+ }
439
+ }, this.log = new C({
440
+ level: N(this.getAttribute("log"))
441
+ });
442
+ const t = this.getAttribute("path"), e = this.getAttribute("version");
443
+ this.component = l.fromString(t, e), this.lightRoot = Array.from(this.childNodes);
444
+ const n = HTMLElement.prototype.hasOwnProperty("attachInternals");
445
+ try {
446
+ const r = n && this.attachInternals();
447
+ } catch (r) {
448
+ this.log.error(r);
449
+ }
450
+ }
451
+ async connectedCallback() {
452
+ try {
453
+ this.app = await this.load(), await this.mount(), await this.initTracking();
454
+ } catch (t) {
455
+ t instanceof Error && requestAnimationFrame(() => {
456
+ this.emitError(t);
457
+ }), this.unmount(t);
458
+ }
459
+ }
460
+ emitError(t) {
461
+ var n;
462
+ let e;
463
+ if (t instanceof f && ((n = t.name) != null && n.startsWith("CCC")) ? e = t.name.replace(/^CCC/, "ccc:") : e = `ccc:${(t == null ? void 0 : t.name) ?? "UnknownError"}`, !e)
464
+ return this.log.debug(t);
465
+ this.dispatchEvent(
466
+ new ErrorEvent(e, {
467
+ bubbles: !0,
468
+ cancelable: !1,
469
+ composed: !0,
470
+ error: t,
471
+ message: t.message
472
+ })
473
+ );
474
+ }
475
+ disconnectedCallback() {
476
+ const t = this.getAttribute("path");
477
+ this.log.info(`<custom-code-component:${t}> disconnected`), typeof this.onunmount == "function" && this.onunmount();
478
+ }
479
+ onmessage() {
480
+ }
481
+ onunmount(t) {
482
+ }
483
+ async onready(t) {
484
+ try {
485
+ await t;
486
+ } catch (e) {
487
+ e instanceof Error && this.emitError(e);
488
+ }
489
+ }
490
+ postMessage(t) {
491
+ this.channel.port1.postMessage(t);
492
+ }
493
+ async mount(t) {
494
+ try {
495
+ if (this.mode = this.getAttribute("shadow-open") == "false" ? "closed" : "open", this.dispatchEvent(
496
+ new j({
497
+ component: this.component,
498
+ source: this.source
499
+ })
500
+ ), this.dataset.cccReady = "true", delete this.dataset.cccError, !this.app)
501
+ throw new Error("CCC mounted without App");
502
+ const e = this.shadowRoot !== null, n = this.shadowRoot ?? this.attachShadow({ mode: this.mode }), r = JSON.parse(this.getAttribute("data-component-props")), s = Object.fromEntries(
503
+ [...this.attributes].filter((a) => !this.RESERVED_ATTRS.has(a.name)).map((a) => [a.name, a.value])
504
+ );
505
+ this.tracking = new F({
506
+ name: this.component.toString(),
507
+ subtype: "interactive",
508
+ teamName: "djd",
509
+ shadowRoot: this.shadowRoot,
510
+ logger: this.log
511
+ });
512
+ const { unmount: i, onmessage: c, ready: h } = this.app(
513
+ n,
514
+ {
515
+ ...s,
516
+ data: r,
517
+ port: this.channel.port2,
518
+ tracking: this.tracking,
519
+ prerendered: !!t,
520
+ children: this.children
521
+ },
522
+ e
523
+ ) || {};
524
+ i && (this.onunmount = i), c && (this.onmessage = c), h && this.onready(h);
525
+ } catch (e) {
526
+ throw this.log.info(
527
+ `<custom-code-component> uncaught error during mount from ${this.component.toString()}`
528
+ ), this.log.error(e), e;
529
+ }
530
+ }
531
+ // Called in top-level error handler
532
+ // Replace shadow root with light DOM children on error
533
+ unmount(t) {
534
+ var e;
535
+ this.onunmount(), (e = this.shadowRoot) == null || e.replaceChildren(...this.lightRoot), this.dataset.cccError || (this.dataset.cccError = _(t.name.replace("CCC", ""))), delete this.dataset.cccReady;
536
+ }
537
+ async load() {
538
+ const t = this.getAttribute("path"), e = this.getAttribute("version"), n = Number(this.getAttribute("load-timeout") || 1e4), r = this.getAttribute("testEnv"), s = H(r), i = await K(s), c = this.getAttribute("id");
539
+ this.source = i ? `${s == null ? void 0 : s.origin}/src/${this.component.component}/index.jsx?id=${c}` : `https://www.ft.com/__component/${this.component.org}/${this.component.repo}${e ? `@${e}` : "@latest"}/${this.component.component}/${this.component.component}.js?id=${c}`;
540
+ try {
541
+ return await new Promise(
542
+ (h, a) => {
543
+ const d = setTimeout(() => {
544
+ this.log.error("CCC import timeout error"), a(
545
+ new W({
546
+ component: this.component,
547
+ source: this.source
548
+ })
549
+ );
550
+ }, Number(n));
551
+ if (this.source)
552
+ import(
553
+ /* webpackIgnore: true */
554
+ this.source
555
+ /* @vite-ignore */
556
+ ).then(({ default: u }) => {
557
+ if (u)
558
+ clearTimeout(d), h(u);
559
+ else
560
+ throw new g(
561
+ "No component renderer default export found",
562
+ {
563
+ component: this.component,
564
+ source: this.source
565
+ }
566
+ );
567
+ }).catch((u) => {
568
+ clearTimeout(d), this.log.error(u), u instanceof Error && !(u instanceof g) ? a(
569
+ new g(u.message, {
570
+ component: this.component,
571
+ source: this.source
572
+ })
573
+ ) : a(u);
574
+ });
575
+ else
576
+ throw clearTimeout(d), new g(`Unable to mount ${t}`, {
577
+ component: this.component,
578
+ source: this.source
579
+ });
580
+ }
581
+ );
582
+ } catch (h) {
583
+ throw this.log.error(
584
+ `<custom-code-component> error during import from ${t}@${e}`
585
+ ), h;
586
+ }
587
+ }
588
+ }
589
+ export {
590
+ q as FTCustomCodeComponent
591
+ };
@@ -0,0 +1,55 @@
1
+ /**
2
+ * @file
3
+ * Main component definition for custom-code-component
4
+ */
5
+ import type { ContentTree } from "@financial-times/content-tree";
6
+ import { BaseRenderer } from "../../ccc-sdk/src/renderers/BaseRenderer";
7
+ import Tracking from "./tracking";
8
+ import { Logger } from "./logger";
9
+ import { ComponentPath } from "./path";
10
+ export declare class FTCustomCodeComponent extends HTMLElement {
11
+ app?: typeof BaseRenderer.prototype.render;
12
+ mode: "closed" | "open";
13
+ RESERVED_ATTRS: Set<string>;
14
+ source?: string;
15
+ tracking?: Tracking;
16
+ lightRoot: ChildNode[];
17
+ component: ComponentPath;
18
+ log: Logger;
19
+ constructor();
20
+ connectedCallback(): Promise<void>;
21
+ emitError(error: Error): void;
22
+ disconnectedCallback(): void;
23
+ channel: MessageChannel;
24
+ onmessage(): void;
25
+ onunmount(root?: any): void;
26
+ onready(app: Promise<void>): Promise<void>;
27
+ postMessage(event: any): void;
28
+ mount(prerendered?: ShadowRoot | null): Promise<void>;
29
+ unmount(e: Error): void;
30
+ load(): Promise<(shadowRoot: ShadowRoot, attrs: any, ssr?: boolean) => {
31
+ unmount: (root: any) => void;
32
+ onmessage: {
33
+ (...data: any[]): void;
34
+ (message?: any, ...optionalParams: any[]): void;
35
+ };
36
+ ready: Promise<void>;
37
+ }>;
38
+ initTracking: () => Promise<void>;
39
+ }
40
+ export interface CustomCodeComponent extends ContentTree.Node {
41
+ type: "CustomCodeComponent";
42
+ path: string;
43
+ versionRange: string;
44
+ altText: string;
45
+ lastModified: string;
46
+ fallbackImage?: ContentTree.Image;
47
+ displayFallbackText: boolean;
48
+ layout: "in-line" | "mid-grid" | "full-grid" | "full-bleed";
49
+ attributes: {
50
+ [key: string]: string | boolean | undefined;
51
+ } | {
52
+ children?: CustomCodeComponent | Array<CustomCodeComponent>;
53
+ };
54
+ }
55
+ export type { FTCustomCodeComponent as CCCHTMLElement };