@financial-times/custom-code-component 2.0.22 → 2.1.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/dist/custom-element.d.ts +14 -3
- package/dist/custom-element.js +131 -106
- package/dist/custom-element.js.map +1 -1
- package/package.json +1 -1
- package/src/custom-code-component.ts +64 -28
- package/src/logger.ts +2 -2
- package/src/tracking.ts +11 -6
|
@@ -69,7 +69,12 @@ export class FTCustomCodeComponent extends HTMLElement {
|
|
|
69
69
|
* Logger instance. Set log level using `this.logger.setLogLevel(string|number|null)`.
|
|
70
70
|
*/
|
|
71
71
|
logger: Logger;
|
|
72
|
-
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Abort controller used to abort async connectedCallback functionality when disconnectedCallback is called`.
|
|
75
|
+
*/
|
|
76
|
+
ac: AbortController | null = null;
|
|
77
|
+
|
|
73
78
|
/**
|
|
74
79
|
* IntersectionObserver that dispatches CCCViewportEvents
|
|
75
80
|
*/
|
|
@@ -82,7 +87,7 @@ export class FTCustomCodeComponent extends HTMLElement {
|
|
|
82
87
|
source: this.source,
|
|
83
88
|
intersecting: entry.isIntersecting,
|
|
84
89
|
entry,
|
|
85
|
-
})
|
|
90
|
+
}),
|
|
86
91
|
);
|
|
87
92
|
});
|
|
88
93
|
});
|
|
@@ -96,7 +101,7 @@ export class FTCustomCodeComponent extends HTMLElement {
|
|
|
96
101
|
constructor() {
|
|
97
102
|
super();
|
|
98
103
|
this.logger = new Logger();
|
|
99
|
-
|
|
104
|
+
|
|
100
105
|
const supportsDeclarative =
|
|
101
106
|
HTMLElement.prototype.hasOwnProperty("attachInternals");
|
|
102
107
|
|
|
@@ -120,10 +125,11 @@ export class FTCustomCodeComponent extends HTMLElement {
|
|
|
120
125
|
this.logger.component = this.component;
|
|
121
126
|
this.logger.setLogLevel(this.getAttribute("log"));
|
|
122
127
|
this.logger.debug("connectedCallback");
|
|
123
|
-
|
|
124
|
-
this.
|
|
125
|
-
await this.
|
|
126
|
-
|
|
128
|
+
this.ac?.abort();
|
|
129
|
+
this.ac = new AbortController();
|
|
130
|
+
this.app = await this.load(this.ac.signal);
|
|
131
|
+
this.mount();
|
|
132
|
+
this.initTracking();
|
|
127
133
|
} catch (e) {
|
|
128
134
|
if (e instanceof Error) {
|
|
129
135
|
requestAnimationFrame(() => {
|
|
@@ -150,7 +156,7 @@ export class FTCustomCodeComponent extends HTMLElement {
|
|
|
150
156
|
composed: true,
|
|
151
157
|
error,
|
|
152
158
|
message: error.message,
|
|
153
|
-
})
|
|
159
|
+
}),
|
|
154
160
|
);
|
|
155
161
|
} else {
|
|
156
162
|
// Wrap original error with generic CCC error class
|
|
@@ -166,7 +172,7 @@ export class FTCustomCodeComponent extends HTMLElement {
|
|
|
166
172
|
composed: true,
|
|
167
173
|
error: wrappedError,
|
|
168
174
|
message: wrappedError.message,
|
|
169
|
-
})
|
|
175
|
+
}),
|
|
170
176
|
);
|
|
171
177
|
}
|
|
172
178
|
}
|
|
@@ -177,8 +183,10 @@ export class FTCustomCodeComponent extends HTMLElement {
|
|
|
177
183
|
* Called when a <custom-code-component> element is removed from DOM.
|
|
178
184
|
*/
|
|
179
185
|
disconnectedCallback() {
|
|
186
|
+
this.ac?.abort();
|
|
180
187
|
this.logger.debug("disconnectedCallback");
|
|
181
188
|
const path = this.getAttribute("path");
|
|
189
|
+
this.destroyTracking();
|
|
182
190
|
this.logger.info(`<custom-code-component:${path}> disconnected`);
|
|
183
191
|
this.observer.disconnect();
|
|
184
192
|
}
|
|
@@ -233,7 +241,7 @@ export class FTCustomCodeComponent extends HTMLElement {
|
|
|
233
241
|
new CCCReadyEvent({
|
|
234
242
|
component: this.component,
|
|
235
243
|
source: this.source,
|
|
236
|
-
})
|
|
244
|
+
}),
|
|
237
245
|
);
|
|
238
246
|
|
|
239
247
|
this.dataset.cccReady = "true";
|
|
@@ -274,7 +282,7 @@ export class FTCustomCodeComponent extends HTMLElement {
|
|
|
274
282
|
*
|
|
275
283
|
* @param prerendered
|
|
276
284
|
*/
|
|
277
|
-
|
|
285
|
+
mount(prerendered?: ShadowRoot | null) {
|
|
278
286
|
this.logger.debug("mount", prerendered);
|
|
279
287
|
try {
|
|
280
288
|
this.mode =
|
|
@@ -292,7 +300,7 @@ export class FTCustomCodeComponent extends HTMLElement {
|
|
|
292
300
|
new CCCConnectedEvent({
|
|
293
301
|
component: this.component,
|
|
294
302
|
source: this.source,
|
|
295
|
-
})
|
|
303
|
+
}),
|
|
296
304
|
);
|
|
297
305
|
// Add global CCC styles if not already present in shadow DOM.
|
|
298
306
|
if (
|
|
@@ -322,9 +330,9 @@ export class FTCustomCodeComponent extends HTMLElement {
|
|
|
322
330
|
.filter(
|
|
323
331
|
(attribute) =>
|
|
324
332
|
!this.RESERVED_ATTRS.has(attribute.name) &&
|
|
325
|
-
!attribute.name.startsWith("data-")
|
|
333
|
+
!attribute.name.startsWith("data-"),
|
|
326
334
|
)
|
|
327
|
-
.map((attribute) => [attribute.name, attribute.value])
|
|
335
|
+
.map((attribute) => [attribute.name, attribute.value]),
|
|
328
336
|
);
|
|
329
337
|
|
|
330
338
|
// Create tracking instance
|
|
@@ -340,12 +348,12 @@ export class FTCustomCodeComponent extends HTMLElement {
|
|
|
340
348
|
this.logger.warn(
|
|
341
349
|
`CCC ${this.component.toString()}: passing component settings as webcomponent attributes is %cDEPRECATED`,
|
|
342
350
|
"font-weight: bold",
|
|
343
|
-
" and will be removed in v3."
|
|
351
|
+
" and will be removed in v3.",
|
|
344
352
|
);
|
|
345
353
|
this.logger.warn(
|
|
346
354
|
`Please use the %cdata-component-props`,
|
|
347
355
|
"text-decoration: underline;",
|
|
348
|
-
" attribute instead."
|
|
356
|
+
" attribute instead.",
|
|
349
357
|
);
|
|
350
358
|
}
|
|
351
359
|
|
|
@@ -362,7 +370,7 @@ export class FTCustomCodeComponent extends HTMLElement {
|
|
|
362
370
|
children: this.children,
|
|
363
371
|
},
|
|
364
372
|
ssr,
|
|
365
|
-
this.onunmount.bind(this)
|
|
373
|
+
this.onunmount.bind(this),
|
|
366
374
|
) || {};
|
|
367
375
|
|
|
368
376
|
if (onmessage) this.onmessage = onmessage;
|
|
@@ -371,7 +379,7 @@ export class FTCustomCodeComponent extends HTMLElement {
|
|
|
371
379
|
}
|
|
372
380
|
} catch (err) {
|
|
373
381
|
this.logger.info(
|
|
374
|
-
`<custom-code-component> uncaught error during mount from ${this.component?.toString()}
|
|
382
|
+
`<custom-code-component> uncaught error during mount from ${this.component?.toString()}`,
|
|
375
383
|
);
|
|
376
384
|
this.logger.error(err);
|
|
377
385
|
|
|
@@ -391,7 +399,7 @@ export class FTCustomCodeComponent extends HTMLElement {
|
|
|
391
399
|
|
|
392
400
|
const template =
|
|
393
401
|
this.querySelector<HTMLTemplateElement>(
|
|
394
|
-
"template[data-component-fallback]"
|
|
402
|
+
"template[data-component-fallback]",
|
|
395
403
|
) ?? this.querySelector<HTMLTemplateElement>("template");
|
|
396
404
|
|
|
397
405
|
if (template) {
|
|
@@ -412,7 +420,7 @@ export class FTCustomCodeComponent extends HTMLElement {
|
|
|
412
420
|
*
|
|
413
421
|
* @returns Promise resolving to component renderer function
|
|
414
422
|
*/
|
|
415
|
-
async load() {
|
|
423
|
+
async load(signal: AbortSignal) {
|
|
416
424
|
this.logger.debug("load");
|
|
417
425
|
|
|
418
426
|
if (!this.component.isValid) {
|
|
@@ -426,7 +434,7 @@ export class FTCustomCodeComponent extends HTMLElement {
|
|
|
426
434
|
this.testUrl = assignTestURL(testEnv);
|
|
427
435
|
const isTestEnv = await useComponentTestEnv(
|
|
428
436
|
this.component.name,
|
|
429
|
-
this.testUrl
|
|
437
|
+
this.testUrl,
|
|
430
438
|
);
|
|
431
439
|
|
|
432
440
|
// id querystring necessary to multiple allow components with the same source (same name and version number) to appear on the page correctly
|
|
@@ -446,13 +454,14 @@ export class FTCustomCodeComponent extends HTMLElement {
|
|
|
446
454
|
try {
|
|
447
455
|
return await new Promise<typeof BaseRenderer.prototype.render>(
|
|
448
456
|
(resolve, reject) => {
|
|
457
|
+
signal.throwIfAborted();
|
|
449
458
|
const to = setTimeout(() => {
|
|
450
459
|
this.logger.error("CCC import timeout error");
|
|
451
460
|
reject(
|
|
452
461
|
new CCCTimeoutError({
|
|
453
462
|
component: this.component!,
|
|
454
463
|
source: this.source,
|
|
455
|
-
})
|
|
464
|
+
}),
|
|
456
465
|
);
|
|
457
466
|
}, Number(timeout));
|
|
458
467
|
|
|
@@ -468,7 +477,7 @@ export class FTCustomCodeComponent extends HTMLElement {
|
|
|
468
477
|
{
|
|
469
478
|
component: this.component!,
|
|
470
479
|
source: this.source,
|
|
471
|
-
}
|
|
480
|
+
},
|
|
472
481
|
);
|
|
473
482
|
})
|
|
474
483
|
.catch((e) => {
|
|
@@ -479,7 +488,7 @@ export class FTCustomCodeComponent extends HTMLElement {
|
|
|
479
488
|
new CCCImportError(e.message, {
|
|
480
489
|
component: this.component!,
|
|
481
490
|
source: this.source,
|
|
482
|
-
})
|
|
491
|
+
}),
|
|
483
492
|
);
|
|
484
493
|
} else {
|
|
485
494
|
reject(e);
|
|
@@ -492,11 +501,21 @@ export class FTCustomCodeComponent extends HTMLElement {
|
|
|
492
501
|
source: this.source,
|
|
493
502
|
});
|
|
494
503
|
}
|
|
495
|
-
|
|
504
|
+
|
|
505
|
+
signal.addEventListener(
|
|
506
|
+
"abort",
|
|
507
|
+
() => {
|
|
508
|
+
// Stop the main operation
|
|
509
|
+
// Reject the promise with the abort reason.
|
|
510
|
+
reject(signal.reason);
|
|
511
|
+
},
|
|
512
|
+
{ once: true },
|
|
513
|
+
);
|
|
514
|
+
},
|
|
496
515
|
);
|
|
497
516
|
} catch (err) {
|
|
498
517
|
this.logger.error(
|
|
499
|
-
`<custom-code-component> error during import from ${path}@${componentVersionRange}
|
|
518
|
+
`<custom-code-component> error during import from ${path}@${componentVersionRange}`,
|
|
500
519
|
);
|
|
501
520
|
|
|
502
521
|
throw err;
|
|
@@ -506,7 +525,7 @@ export class FTCustomCodeComponent extends HTMLElement {
|
|
|
506
525
|
/**
|
|
507
526
|
* Initialises OTracking
|
|
508
527
|
*/
|
|
509
|
-
initTracking =
|
|
528
|
+
initTracking = () => {
|
|
510
529
|
this.logger.debug("initTracking", { cccId: this.id });
|
|
511
530
|
try {
|
|
512
531
|
this.tracking?.init(this.id);
|
|
@@ -514,7 +533,24 @@ export class FTCustomCodeComponent extends HTMLElement {
|
|
|
514
533
|
const path = this.getAttribute("path");
|
|
515
534
|
const componentVersionRange = this.getAttribute("version");
|
|
516
535
|
this.logger.info(
|
|
517
|
-
`Error initialising tracking on <custom-code-component> ${path}@${componentVersionRange}
|
|
536
|
+
`Error initialising tracking on <custom-code-component> ${path}@${componentVersionRange}`,
|
|
537
|
+
);
|
|
538
|
+
this.logger.error(e);
|
|
539
|
+
}
|
|
540
|
+
};
|
|
541
|
+
|
|
542
|
+
/**
|
|
543
|
+
* Destroys OTracking to avoid duplicate events
|
|
544
|
+
*/
|
|
545
|
+
destroyTracking = () => {
|
|
546
|
+
this.logger.debug("destroyTracking", { cccId: this.id });
|
|
547
|
+
try {
|
|
548
|
+
this.tracking?.destroy();
|
|
549
|
+
} catch (e) {
|
|
550
|
+
const path = this.getAttribute("path");
|
|
551
|
+
const componentVersionRange = this.getAttribute("version");
|
|
552
|
+
this.logger.info(
|
|
553
|
+
`Error destroying tracking on <custom-code-component> ${path}@${componentVersionRange}`,
|
|
518
554
|
);
|
|
519
555
|
this.logger.error(e);
|
|
520
556
|
}
|
package/src/logger.ts
CHANGED
|
@@ -49,7 +49,7 @@ export class Logger {
|
|
|
49
49
|
level: LogLevel.DEFAULT,
|
|
50
50
|
}
|
|
51
51
|
) {
|
|
52
|
-
this.level = localStorage
|
|
52
|
+
this.level = localStorage?.getItem("CCC_LOG_LEVEL")
|
|
53
53
|
? convertStringLogLevel(localStorage.getItem("CCC_LOG_LEVEL"))
|
|
54
54
|
: level;
|
|
55
55
|
this.component = component;
|
|
@@ -82,7 +82,7 @@ export class Logger {
|
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
setLogLevel(level: string | number | null) {
|
|
85
|
-
if (localStorage
|
|
85
|
+
if (localStorage?.getItem("CCC_LOG_LEVEL")) {
|
|
86
86
|
this.level = convertStringLogLevel(localStorage.getItem("CCC_LOG_LEVEL"));
|
|
87
87
|
} else if (typeof level === "number") {
|
|
88
88
|
this.level = level;
|
package/src/tracking.ts
CHANGED
|
@@ -19,6 +19,7 @@ class Tracking {
|
|
|
19
19
|
elements: string | string[];
|
|
20
20
|
isInitialised: boolean;
|
|
21
21
|
log: Logger;
|
|
22
|
+
shadowDelegate: typeof Delegate;
|
|
22
23
|
|
|
23
24
|
constructor({
|
|
24
25
|
id = "00000000-0000-0000-0000-000000000000",
|
|
@@ -68,7 +69,7 @@ class Tracking {
|
|
|
68
69
|
// Controller for handling click events
|
|
69
70
|
handleClickEvent(
|
|
70
71
|
eventData: { action: string; category: string },
|
|
71
|
-
root: Element
|
|
72
|
+
root: Element,
|
|
72
73
|
) {
|
|
73
74
|
return (clickEvent: Event, clickElement: HTMLElement) => {
|
|
74
75
|
const context: any = this.getEventProperties(clickEvent);
|
|
@@ -98,7 +99,7 @@ class Tracking {
|
|
|
98
99
|
detail: eventData,
|
|
99
100
|
bubbles: true,
|
|
100
101
|
composed: true,
|
|
101
|
-
})
|
|
102
|
+
}),
|
|
102
103
|
);
|
|
103
104
|
};
|
|
104
105
|
}
|
|
@@ -125,7 +126,7 @@ class Tracking {
|
|
|
125
126
|
detail: eventData,
|
|
126
127
|
bubbles: true,
|
|
127
128
|
composed: true,
|
|
128
|
-
})
|
|
129
|
+
}),
|
|
129
130
|
);
|
|
130
131
|
}
|
|
131
132
|
|
|
@@ -142,16 +143,20 @@ class Tracking {
|
|
|
142
143
|
const root = this.shadowRoot?.querySelector("[data-component-root]");
|
|
143
144
|
|
|
144
145
|
if (root) {
|
|
145
|
-
|
|
146
|
-
shadowDelegate.on(
|
|
146
|
+
this.shadowDelegate = new Delegate(root);
|
|
147
|
+
this.shadowDelegate.on(
|
|
147
148
|
"click",
|
|
148
149
|
this.elements,
|
|
149
150
|
this.handleClickEvent(eventData, root),
|
|
150
|
-
true
|
|
151
|
+
true,
|
|
151
152
|
);
|
|
152
153
|
}
|
|
153
154
|
}
|
|
154
155
|
}
|
|
156
|
+
|
|
157
|
+
destroy() {
|
|
158
|
+
this.shadowDelegate?.off();
|
|
159
|
+
}
|
|
155
160
|
}
|
|
156
161
|
|
|
157
162
|
export default Tracking;
|