@vertexvis/doc-viewer 1.0.0-testing.8 → 1.0.1-canary.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/cjs/doc-viewer.cjs.js +1 -1
- package/dist/cjs/loader.cjs.js +1 -1
- package/dist/cjs/vertex-document-viewer.cjs.entry.js +60 -6
- package/dist/collection/components/document-viewer/document-viewer.js +88 -4
- package/dist/collection/lib/dom.js +8 -0
- package/dist/collection/lib/interactions/pan-interaction-handler.js +4 -3
- package/dist/components/vertex-document-viewer.js +2 -2
- package/dist/doc-viewer/doc-viewer.esm.js +1 -1
- package/dist/doc-viewer/p-00895e2d.entry.js +25 -0
- package/dist/esm/doc-viewer.js +1 -1
- package/dist/esm/loader.js +1 -1
- package/dist/esm/loader.mjs +1 -1
- package/dist/esm/vertex-document-viewer.entry.js +60 -6
- package/dist/types/components/document-viewer/document-viewer.d.ts +19 -2
- package/dist/types/components.d.ts +8 -0
- package/dist/types/lib/dom.d.ts +1 -0
- package/dist/types/lib/interactions/pan-interaction-handler.d.ts +2 -1
- package/package.json +5 -5
- package/readme.md +1 -1
- package/dist/doc-viewer/p-e5411da0.entry.js +0 -25
package/dist/esm/doc-viewer.js
CHANGED
|
@@ -19,5 +19,5 @@ var patchBrowser = () => {
|
|
|
19
19
|
|
|
20
20
|
patchBrowser().then(async (options) => {
|
|
21
21
|
await globalScripts();
|
|
22
|
-
return bootstrapLazy([["vertex-document-viewer",[[257,"vertex-document-viewer",{"src":[1],"documentId":[513,"document-id"],"provider":[1040],"interactionMode":[1,"interaction-mode"],"documentState":[1040],"layers":[1040],"config":[16],"resizeDebounce":[2,"resize-debounce"],"panByDelta":[64],"zoomTo":[64],"loadPage":[64]},null,{"src":[{"handleSrcChange":0}],"config":[{"handleConfigChange":0}],"interactionMode":[{"handleInteractionModeChange":0}]}]]]], options);
|
|
22
|
+
return bootstrapLazy([["vertex-document-viewer",[[257,"vertex-document-viewer",{"src":[1],"documentId":[513,"document-id"],"provider":[1040],"interactionMode":[1,"interaction-mode"],"documentState":[1040],"layers":[1040],"config":[16],"resizeDebounce":[2,"resize-debounce"],"registerBasicInteractionHandler":[64],"panByDelta":[64],"zoomTo":[64],"loadPage":[64]},null,{"src":[{"handleSrcChange":0}],"config":[{"handleConfigChange":0}],"interactionMode":[{"handleInteractionModeChange":0}]}]]]], options);
|
|
23
23
|
});
|
package/dist/esm/loader.js
CHANGED
|
@@ -7,7 +7,7 @@ export { s as setNonce } from './index-CZJPiyR_.js';
|
|
|
7
7
|
const defineCustomElements = async (win, options) => {
|
|
8
8
|
if (typeof window === 'undefined') return undefined;
|
|
9
9
|
await globalScripts();
|
|
10
|
-
return bootstrapLazy([["vertex-document-viewer",[[257,"vertex-document-viewer",{"src":[1],"documentId":[513,"document-id"],"provider":[1040],"interactionMode":[1,"interaction-mode"],"documentState":[1040],"layers":[1040],"config":[16],"resizeDebounce":[2,"resize-debounce"],"panByDelta":[64],"zoomTo":[64],"loadPage":[64]},null,{"src":[{"handleSrcChange":0}],"config":[{"handleConfigChange":0}],"interactionMode":[{"handleInteractionModeChange":0}]}]]]], options);
|
|
10
|
+
return bootstrapLazy([["vertex-document-viewer",[[257,"vertex-document-viewer",{"src":[1],"documentId":[513,"document-id"],"provider":[1040],"interactionMode":[1,"interaction-mode"],"documentState":[1040],"layers":[1040],"config":[16],"resizeDebounce":[2,"resize-debounce"],"registerBasicInteractionHandler":[64],"panByDelta":[64],"zoomTo":[64],"loadPage":[64]},null,{"src":[{"handleSrcChange":0}],"config":[{"handleConfigChange":0}],"interactionMode":[{"handleInteractionModeChange":0}]}]]]], options);
|
|
11
11
|
};
|
|
12
12
|
|
|
13
13
|
export { defineCustomElements };
|
package/dist/esm/loader.mjs
CHANGED
|
@@ -7,7 +7,7 @@ export { s as setNonce } from './index-CZJPiyR_.js';
|
|
|
7
7
|
const defineCustomElements = async (win, options) => {
|
|
8
8
|
if (typeof window === 'undefined') return undefined;
|
|
9
9
|
await globalScripts();
|
|
10
|
-
return bootstrapLazy([["vertex-document-viewer",[[257,"vertex-document-viewer",{"src":[1],"documentId":[513,"document-id"],"provider":[1040],"interactionMode":[1,"interaction-mode"],"documentState":[1040],"layers":[1040],"config":[16],"resizeDebounce":[2,"resize-debounce"],"panByDelta":[64],"zoomTo":[64],"loadPage":[64]},null,{"src":[{"handleSrcChange":0}],"config":[{"handleConfigChange":0}],"interactionMode":[{"handleInteractionModeChange":0}]}]]]], options);
|
|
10
|
+
return bootstrapLazy([["vertex-document-viewer",[[257,"vertex-document-viewer",{"src":[1],"documentId":[513,"document-id"],"provider":[1040],"interactionMode":[1,"interaction-mode"],"documentState":[1040],"layers":[1040],"config":[16],"resizeDebounce":[2,"resize-debounce"],"registerBasicInteractionHandler":[64],"panByDelta":[64],"zoomTo":[64],"loadPage":[64]},null,{"src":[{"handleSrcChange":0}],"config":[{"handleConfigChange":0}],"interactionMode":[{"handleInteractionModeChange":0}]}]]]], options);
|
|
11
11
|
};
|
|
12
12
|
|
|
13
13
|
export { defineCustomElements };
|
|
@@ -642,11 +642,20 @@ class DocumentLayersController {
|
|
|
642
642
|
function getElementBoundingClientRect(element) {
|
|
643
643
|
return element.getBoundingClientRect();
|
|
644
644
|
}
|
|
645
|
+
function getAllVertexElementChildren(element) {
|
|
646
|
+
return getAllChildren(element)
|
|
647
|
+
.filter(node => node.nodeName.startsWith('VERTEX-'))
|
|
648
|
+
.reduce((elements, element) => [...elements, element, ...getAllChildren(element)], []);
|
|
649
|
+
}
|
|
650
|
+
function getAllChildren(element) {
|
|
651
|
+
return Array.from(element.querySelectorAll('*'));
|
|
652
|
+
}
|
|
645
653
|
|
|
646
654
|
const DRAG_PIXEL_THRESHOLD = 2;
|
|
647
655
|
class PanInteractionHandler {
|
|
648
|
-
constructor(element, api) {
|
|
656
|
+
constructor(element, hostElement, api) {
|
|
649
657
|
this.element = element;
|
|
658
|
+
this.hostElement = hostElement;
|
|
650
659
|
this.api = api;
|
|
651
660
|
this.isDragging = false;
|
|
652
661
|
this.handlePointerDown = this.handlePointerDown.bind(this);
|
|
@@ -655,11 +664,12 @@ class PanInteractionHandler {
|
|
|
655
664
|
this.handleWheel = this.handleWheel.bind(this);
|
|
656
665
|
this.element.addEventListener('pointerdown', this.handlePointerDown);
|
|
657
666
|
this.element.addEventListener('wheel', this.handleWheel);
|
|
658
|
-
|
|
667
|
+
this.hostElement.addEventListener('wheel', this.handleWheel);
|
|
659
668
|
}
|
|
660
669
|
dispose() {
|
|
661
670
|
this.element.removeEventListener('pointerdown', this.handlePointerDown);
|
|
662
671
|
this.element.removeEventListener('wheel', this.handleWheel);
|
|
672
|
+
this.hostElement.removeEventListener('wheel', this.handleWheel);
|
|
663
673
|
this.removeWindowListeners();
|
|
664
674
|
}
|
|
665
675
|
handleWheel(event) {
|
|
@@ -703,7 +713,6 @@ class PanInteractionHandler {
|
|
|
703
713
|
removeWindowListeners() {
|
|
704
714
|
window.removeEventListener('pointermove', this.handlePointerMove);
|
|
705
715
|
window.removeEventListener('pointerup', this.handlePointerUp);
|
|
706
|
-
window.removeEventListener('wheel', this.handleWheel);
|
|
707
716
|
}
|
|
708
717
|
}
|
|
709
718
|
|
|
@@ -34615,6 +34624,7 @@ const VertexDocumentViewer = class {
|
|
|
34615
34624
|
*/
|
|
34616
34625
|
this.resizeDebounce = 100;
|
|
34617
34626
|
this.dimensions = dimensions.create(0, 0);
|
|
34627
|
+
this.interactionHandlers = [];
|
|
34618
34628
|
}
|
|
34619
34629
|
componentWillLoad() {
|
|
34620
34630
|
this.handleElementResize = this.handleElementResize.bind(this);
|
|
@@ -34622,6 +34632,7 @@ const VertexDocumentViewer = class {
|
|
|
34622
34632
|
this.handlePageLoaded = this.handlePageLoaded.bind(this);
|
|
34623
34633
|
this.handlePageDrawn = this.handlePageDrawn.bind(this);
|
|
34624
34634
|
this.resizeObserver = new ResizeObserver(this.handleElementResize);
|
|
34635
|
+
this.registerSlotChangeListeners();
|
|
34625
34636
|
}
|
|
34626
34637
|
componentShouldUpdate(newValue, oldValue, propName) {
|
|
34627
34638
|
// Ignore updates to the documentState property, as it is only intended to reflect the current state
|
|
@@ -34633,12 +34644,36 @@ const VertexDocumentViewer = class {
|
|
|
34633
34644
|
(_a = this.resizeObserver) === null || _a === void 0 ? void 0 : _a.observe(this.hostEl);
|
|
34634
34645
|
this.updateComponentDimensions();
|
|
34635
34646
|
this.handleSrcChange();
|
|
34647
|
+
this.injectViewerApi();
|
|
34636
34648
|
}
|
|
34637
34649
|
disconnectedCallback() {
|
|
34638
34650
|
var _a;
|
|
34639
34651
|
(_a = this.resizeObserver) === null || _a === void 0 ? void 0 : _a.disconnect();
|
|
34640
34652
|
this.clearCurrentDocument();
|
|
34641
34653
|
}
|
|
34654
|
+
/**
|
|
34655
|
+
* Registers and initializes an interaction handler with the document viewer. Returns a
|
|
34656
|
+
* `Disposable` that should be used to deregister the interaction handler.
|
|
34657
|
+
*
|
|
34658
|
+
* `InteractionHandler`s are used to build custom mouse and touch interactions.
|
|
34659
|
+
*
|
|
34660
|
+
* @param interactionHandler The interaction handler to register.
|
|
34661
|
+
* @returns {Promise<void>} A promise containing the disposable to use to
|
|
34662
|
+
* deregister the handler.
|
|
34663
|
+
*/
|
|
34664
|
+
async registerBasicInteractionHandler(interactionHandler) {
|
|
34665
|
+
this.interactionHandlers.push(interactionHandler);
|
|
34666
|
+
this.initializeInteractionHandler(interactionHandler);
|
|
34667
|
+
return {
|
|
34668
|
+
dispose: () => {
|
|
34669
|
+
const index = this.interactionHandlers.indexOf(interactionHandler);
|
|
34670
|
+
if (index !== -1) {
|
|
34671
|
+
this.interactionHandlers[index].dispose();
|
|
34672
|
+
this.interactionHandlers.splice(index, 1);
|
|
34673
|
+
}
|
|
34674
|
+
},
|
|
34675
|
+
};
|
|
34676
|
+
}
|
|
34642
34677
|
/**
|
|
34643
34678
|
* Pans the currently loaded document by the specified delta.
|
|
34644
34679
|
*
|
|
@@ -34697,9 +34732,9 @@ const VertexDocumentViewer = class {
|
|
|
34697
34732
|
this.updateInteractionHandler();
|
|
34698
34733
|
}
|
|
34699
34734
|
render() {
|
|
34700
|
-
return (h(Host, { key: '
|
|
34735
|
+
return (h(Host, { key: 'f1ddf0b4e321bc244a5d757e48f6d71c23caca3c' }, h("div", { key: '174c64c537f4b0f1c45fc6543e5b7f54515e4063', ref: ref => (this.viewerContainerElement = ref), class: "viewer-container", onContextMenu: event => event.preventDefault() }, h("div", { key: '4c47dadba03b32bde017252a9a8ea7f6e6d323f3', ref: ref => (this.canvasContainerElement = ref), class: classNames('canvas-container', {
|
|
34701
34736
|
'enable-pointer-events ': window.PointerEvent != null,
|
|
34702
|
-
}) }, h("canvas", { key: '
|
|
34737
|
+
}) }, h("canvas", { key: 'b8697a0a5be45d212b16b3bd4b7b50a70c0a1143', role: "presentation", ref: el => (this.canvasEl = el) })), h("slot", { key: 'c9ec02fd4bfaa251d8fc742fc868b0d91ee70618' }))));
|
|
34703
34738
|
}
|
|
34704
34739
|
getDocumentApi() {
|
|
34705
34740
|
if (this.documentApi == null) {
|
|
@@ -34737,7 +34772,7 @@ const VertexDocumentViewer = class {
|
|
|
34737
34772
|
var _a;
|
|
34738
34773
|
(_a = this.panInteractionHandler) === null || _a === void 0 ? void 0 : _a.dispose();
|
|
34739
34774
|
if (this.interactionMode === 'pan' && this.canvasEl != null && this.documentApi != null) {
|
|
34740
|
-
this.panInteractionHandler = new PanInteractionHandler(this.canvasEl, this.documentApi);
|
|
34775
|
+
this.panInteractionHandler = new PanInteractionHandler(this.canvasEl, this.hostEl, this.documentApi);
|
|
34741
34776
|
}
|
|
34742
34777
|
}
|
|
34743
34778
|
updateComponentDimensions(dimensions) {
|
|
@@ -34765,6 +34800,25 @@ const VertexDocumentViewer = class {
|
|
|
34765
34800
|
await ((_a = this.documentApi) === null || _a === void 0 ? void 0 : _a.updateViewport(this.dimensions));
|
|
34766
34801
|
}, this.resizeDebounce);
|
|
34767
34802
|
}
|
|
34803
|
+
initializeInteractionHandler(handler) {
|
|
34804
|
+
if (this.canvasEl == null) {
|
|
34805
|
+
throw new Error('Cannot initialize interaction handler');
|
|
34806
|
+
}
|
|
34807
|
+
handler.initialize(this.canvasEl);
|
|
34808
|
+
}
|
|
34809
|
+
registerSlotChangeListeners() {
|
|
34810
|
+
this.mutationObserver = new MutationObserver(_ => this.injectViewerApi());
|
|
34811
|
+
this.mutationObserver.observe(this.hostEl, {
|
|
34812
|
+
childList: true,
|
|
34813
|
+
subtree: true,
|
|
34814
|
+
});
|
|
34815
|
+
}
|
|
34816
|
+
injectViewerApi() {
|
|
34817
|
+
getAllVertexElementChildren(this.hostEl).forEach(node => {
|
|
34818
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
34819
|
+
node.viewer = this.hostEl;
|
|
34820
|
+
});
|
|
34821
|
+
}
|
|
34768
34822
|
get hostEl() { return getElement(this); }
|
|
34769
34823
|
static get watchers() { return {
|
|
34770
34824
|
"src": [{
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { EventEmitter } from '../../stencil-public-runtime';
|
|
2
2
|
import { Point } from '@vertexvis/geometry';
|
|
3
|
+
import { BasicInteractionHandler, BasicViewer, Disposable } from '@vertexvis/utils';
|
|
3
4
|
import { PartialConfig } from '../../lib/config';
|
|
4
5
|
import { DocumentApiState } from '../../lib/document/api';
|
|
5
6
|
import { DocumentLayersController } from '../../lib/document/layers/controller';
|
|
6
7
|
import { DocumentProvider } from '../../lib/document/provider';
|
|
7
8
|
export type InteractionMode = 'none' | 'pan';
|
|
8
|
-
export declare class VertexDocumentViewer {
|
|
9
|
+
export declare class VertexDocumentViewer implements BasicViewer {
|
|
9
10
|
/**
|
|
10
11
|
* A URI of the document to load when the component is mounted in the DOM tree.
|
|
11
12
|
* Currently only supports URLs for client-side rendering.
|
|
@@ -76,17 +77,30 @@ export declare class VertexDocumentViewer {
|
|
|
76
77
|
private canvasEl?;
|
|
77
78
|
private dimensions;
|
|
78
79
|
private resizeObserver?;
|
|
80
|
+
private mutationObserver?;
|
|
79
81
|
private resizeTimer?;
|
|
80
82
|
private documentRenderer?;
|
|
81
83
|
private documentApi?;
|
|
82
|
-
private panInteractionHandler?;
|
|
83
84
|
private documentApiStateChangedDisposable?;
|
|
84
85
|
private pageLoadedDisposable?;
|
|
85
86
|
private pageDrawnDisposable?;
|
|
87
|
+
private interactionHandlers;
|
|
88
|
+
private panInteractionHandler?;
|
|
86
89
|
protected componentWillLoad(): void;
|
|
87
90
|
protected componentShouldUpdate(newValue: unknown, oldValue: unknown, propName: string): boolean;
|
|
88
91
|
protected componentDidLoad(): void;
|
|
89
92
|
protected disconnectedCallback(): void;
|
|
93
|
+
/**
|
|
94
|
+
* Registers and initializes an interaction handler with the document viewer. Returns a
|
|
95
|
+
* `Disposable` that should be used to deregister the interaction handler.
|
|
96
|
+
*
|
|
97
|
+
* `InteractionHandler`s are used to build custom mouse and touch interactions.
|
|
98
|
+
*
|
|
99
|
+
* @param interactionHandler The interaction handler to register.
|
|
100
|
+
* @returns {Promise<void>} A promise containing the disposable to use to
|
|
101
|
+
* deregister the handler.
|
|
102
|
+
*/
|
|
103
|
+
registerBasicInteractionHandler(interactionHandler: BasicInteractionHandler): Promise<Disposable>;
|
|
90
104
|
/**
|
|
91
105
|
* Pans the currently loaded document by the specified delta.
|
|
92
106
|
*
|
|
@@ -129,4 +143,7 @@ export declare class VertexDocumentViewer {
|
|
|
129
143
|
private updateComponentDimensions;
|
|
130
144
|
private handleElementResize;
|
|
131
145
|
private restartResizeTimer;
|
|
146
|
+
private initializeInteractionHandler;
|
|
147
|
+
private registerSlotChangeListeners;
|
|
148
|
+
private injectViewerApi;
|
|
132
149
|
}
|
|
@@ -10,12 +10,14 @@ import { InteractionMode } from "./components/document-viewer/document-viewer";
|
|
|
10
10
|
import { DocumentApiState } from "./lib/document/api";
|
|
11
11
|
import { DocumentLayersController } from "./lib/document/layers/controller";
|
|
12
12
|
import { PartialConfig } from "./lib/config";
|
|
13
|
+
import { BasicInteractionHandler, Disposable } from "@vertexvis/utils";
|
|
13
14
|
import { Point } from "@vertexvis/geometry";
|
|
14
15
|
export { DocumentProvider } from "./lib/document/provider";
|
|
15
16
|
export { InteractionMode } from "./components/document-viewer/document-viewer";
|
|
16
17
|
export { DocumentApiState } from "./lib/document/api";
|
|
17
18
|
export { DocumentLayersController } from "./lib/document/layers/controller";
|
|
18
19
|
export { PartialConfig } from "./lib/config";
|
|
20
|
+
export { BasicInteractionHandler, Disposable } from "@vertexvis/utils";
|
|
19
21
|
export { Point } from "@vertexvis/geometry";
|
|
20
22
|
export namespace Components {
|
|
21
23
|
interface VertexDocumentViewer {
|
|
@@ -55,6 +57,12 @@ export namespace Components {
|
|
|
55
57
|
* @default new PdfJsProvider()
|
|
56
58
|
*/
|
|
57
59
|
"provider": DocumentProvider;
|
|
60
|
+
/**
|
|
61
|
+
* Registers and initializes an interaction handler with the document viewer. Returns a `Disposable` that should be used to deregister the interaction handler. `InteractionHandler`s are used to build custom mouse and touch interactions.
|
|
62
|
+
* @param interactionHandler The interaction handler to register.
|
|
63
|
+
* @returns A promise containing the disposable to use to deregister the handler.
|
|
64
|
+
*/
|
|
65
|
+
"registerBasicInteractionHandler": (interactionHandler: BasicInteractionHandler) => Promise<Disposable>;
|
|
58
66
|
/**
|
|
59
67
|
* An optional value that will debounce image updates when resizing this viewer element.
|
|
60
68
|
* @default 100
|
package/dist/types/lib/dom.d.ts
CHANGED
|
@@ -2,11 +2,12 @@ import { Disposable } from '@vertexvis/utils';
|
|
|
2
2
|
import { DocumentApi } from '../document/api';
|
|
3
3
|
export declare class PanInteractionHandler implements Disposable {
|
|
4
4
|
private readonly element;
|
|
5
|
+
private readonly hostElement;
|
|
5
6
|
private readonly api;
|
|
6
7
|
private lastPosition?;
|
|
7
8
|
private isDragging;
|
|
8
9
|
private downPosition?;
|
|
9
|
-
constructor(element: HTMLElement, api: DocumentApi);
|
|
10
|
+
constructor(element: HTMLElement, hostElement: HTMLElement, api: DocumentApi);
|
|
10
11
|
dispose(): void;
|
|
11
12
|
private handleWheel;
|
|
12
13
|
private handlePointerDown;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vertexvis/doc-viewer",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1-canary.0",
|
|
4
4
|
"description": "The Vertex SDK for viewing documents.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"collection:main": "dist/collection/index.js",
|
|
12
12
|
"unpkg": "dist/doc-viewer/doc-viewer.esm.js",
|
|
13
13
|
"license": "MIT",
|
|
14
|
-
"author": "Vertex Developers <support@
|
|
14
|
+
"author": "Vertex Developers <support@vertex3d.com> (https://developer.vertex3d.com)",
|
|
15
15
|
"homepage": "https://github.com/Vertexvis/vertex-web-sdk#readme",
|
|
16
16
|
"repository": {
|
|
17
17
|
"type": "git",
|
|
@@ -95,11 +95,11 @@
|
|
|
95
95
|
"typescript": "~5.8.3"
|
|
96
96
|
},
|
|
97
97
|
"dependencies": {
|
|
98
|
-
"@vertexvis/geometry": "1.0.
|
|
99
|
-
"@vertexvis/utils": "1.0.
|
|
98
|
+
"@vertexvis/geometry": "1.0.1-canary.0",
|
|
99
|
+
"@vertexvis/utils": "1.0.1-canary.0",
|
|
100
100
|
"classnames": "^2.5.1",
|
|
101
101
|
"pdfjs-dist": "^5.5.207",
|
|
102
102
|
"resize-observer": "^1.0.4"
|
|
103
103
|
},
|
|
104
|
-
"gitHead": "
|
|
104
|
+
"gitHead": "7daa5cb17dea388bb10ec14fd35d6c525f1846e3"
|
|
105
105
|
}
|
package/readme.md
CHANGED
|
@@ -13,5 +13,5 @@ can run in any browser supporting the Custom Elements v1 specification. For
|
|
|
13
13
|
browsers that do not support the Custom Elements v1 spec, a polyfill will
|
|
14
14
|
automatically be used.
|
|
15
15
|
|
|
16
|
-
[vertex]: https://www.
|
|
16
|
+
[vertex]: https://www.vertex3d.com
|
|
17
17
|
[web components]: https://developer.mozilla.org/en-US/docs/Web/Web_Components
|