@oscarpalmer/toretto 0.9.0 → 0.11.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/event.js ADDED
@@ -0,0 +1,75 @@
1
+ // node_modules/@oscarpalmer/atoms/dist/js/is.mjs
2
+ function isPlainObject(value2) {
3
+ if (typeof value2 !== "object" || value2 === null) {
4
+ return false;
5
+ }
6
+ const prototype = Object.getPrototypeOf(value2);
7
+ return (prototype === null || prototype === Object.prototype || Object.getPrototypeOf(prototype) === null) && !(Symbol.toStringTag in value2) && !(Symbol.iterator in value2);
8
+ }
9
+
10
+ // src/event.ts
11
+ function createDispatchOptions(options) {
12
+ return {
13
+ bubbles: getBoolean(options?.bubbles),
14
+ cancelable: getBoolean(options?.cancelable),
15
+ composed: getBoolean(options?.composed)
16
+ };
17
+ }
18
+ function createEvent(type, options) {
19
+ const hasOptions = isPlainObject(options);
20
+ if (hasOptions && "detail" in options) {
21
+ return new CustomEvent(type, {
22
+ ...createDispatchOptions(options),
23
+ detail: options?.detail
24
+ });
25
+ }
26
+ return new Event(type, createDispatchOptions(hasOptions ? options : {}));
27
+ }
28
+ function createEventOptions(options) {
29
+ if (isPlainObject(options)) {
30
+ return {
31
+ capture: getBoolean(options.capture),
32
+ once: getBoolean(options.once),
33
+ passive: getBoolean(options.passive, true)
34
+ };
35
+ }
36
+ return {
37
+ capture: getBoolean(options),
38
+ once: false,
39
+ passive: true
40
+ };
41
+ }
42
+ function dispatch(target, type, options) {
43
+ target.dispatchEvent(createEvent(type, options));
44
+ }
45
+ function getBoolean(value2, defaultValue) {
46
+ return typeof value2 === "boolean" ? value2 : defaultValue ?? false;
47
+ }
48
+ function getPosition(event) {
49
+ let x;
50
+ let y;
51
+ if (event instanceof MouseEvent) {
52
+ x = event.clientX;
53
+ y = event.clientY;
54
+ } else if (event instanceof TouchEvent) {
55
+ x = event.touches[0]?.clientX;
56
+ y = event.touches[0]?.clientY;
57
+ }
58
+ return typeof x === "number" && typeof y === "number" ? { x, y } : undefined;
59
+ }
60
+ function off(target, type, listener, options) {
61
+ target.removeEventListener(type, listener, createEventOptions(options));
62
+ }
63
+ function on(target, type, listener, options) {
64
+ const extended = createEventOptions(options);
65
+ target.addEventListener(type, listener, extended);
66
+ return () => {
67
+ target.removeEventListener(type, listener, extended);
68
+ };
69
+ }
70
+ export {
71
+ on,
72
+ off,
73
+ getPosition,
74
+ dispatch
75
+ };
package/dist/event.mjs ADDED
@@ -0,0 +1,67 @@
1
+ // src/event.ts
2
+ import {isPlainObject} from "@oscarpalmer/atoms/is";
3
+ function createDispatchOptions(options) {
4
+ return {
5
+ bubbles: getBoolean(options?.bubbles),
6
+ cancelable: getBoolean(options?.cancelable),
7
+ composed: getBoolean(options?.composed)
8
+ };
9
+ }
10
+ function createEvent(type, options) {
11
+ const hasOptions = isPlainObject(options);
12
+ if (hasOptions && "detail" in options) {
13
+ return new CustomEvent(type, {
14
+ ...createDispatchOptions(options),
15
+ detail: options?.detail
16
+ });
17
+ }
18
+ return new Event(type, createDispatchOptions(hasOptions ? options : {}));
19
+ }
20
+ function createEventOptions(options) {
21
+ if (isPlainObject(options)) {
22
+ return {
23
+ capture: getBoolean(options.capture),
24
+ once: getBoolean(options.once),
25
+ passive: getBoolean(options.passive, true)
26
+ };
27
+ }
28
+ return {
29
+ capture: getBoolean(options),
30
+ once: false,
31
+ passive: true
32
+ };
33
+ }
34
+ function dispatch(target, type, options) {
35
+ target.dispatchEvent(createEvent(type, options));
36
+ }
37
+ function getBoolean(value, defaultValue) {
38
+ return typeof value === "boolean" ? value : defaultValue ?? false;
39
+ }
40
+ function getPosition(event) {
41
+ let x;
42
+ let y;
43
+ if (event instanceof MouseEvent) {
44
+ x = event.clientX;
45
+ y = event.clientY;
46
+ } else if (event instanceof TouchEvent) {
47
+ x = event.touches[0]?.clientX;
48
+ y = event.touches[0]?.clientY;
49
+ }
50
+ return typeof x === "number" && typeof y === "number" ? { x, y } : undefined;
51
+ }
52
+ function off(target, type, listener, options) {
53
+ target.removeEventListener(type, listener, createEventOptions(options));
54
+ }
55
+ function on(target, type, listener, options) {
56
+ const extended = createEventOptions(options);
57
+ target.addEventListener(type, listener, extended);
58
+ return () => {
59
+ target.removeEventListener(type, listener, extended);
60
+ };
61
+ }
62
+ export {
63
+ on,
64
+ off,
65
+ getPosition,
66
+ dispatch
67
+ };
package/dist/html.js ADDED
@@ -0,0 +1,121 @@
1
+ // node_modules/@oscarpalmer/atoms/dist/js/is.mjs
2
+ function isPlainObject(value2) {
3
+ if (typeof value2 !== "object" || value2 === null) {
4
+ return false;
5
+ }
6
+ const prototype = Object.getPrototypeOf(value2);
7
+ return (prototype === null || prototype === Object.prototype || Object.getPrototypeOf(prototype) === null) && !(Symbol.toStringTag in value2) && !(Symbol.iterator in value2);
8
+ }
9
+
10
+ // src/attribute.ts
11
+ function isBadAttribute(attribute) {
12
+ return onPrefix.test(attribute.name) || sourcePrefix.test(attribute.name) && valuePrefix.test(attribute.value);
13
+ }
14
+ function isEmptyNonBooleanAttribute(attribute) {
15
+ return !booleanAttributes.includes(attribute.name) && attribute.value.trim().length === 0;
16
+ }
17
+ function isInvalidBooleanAttribute(attribute) {
18
+ if (!booleanAttributes.includes(attribute.name)) {
19
+ return true;
20
+ }
21
+ const normalised = attribute.value.toLowerCase().trim();
22
+ return !(normalised.length === 0 || normalised === attribute.name || attribute.name === "hidden" && normalised === "until-found");
23
+ }
24
+ var booleanAttributes = Object.freeze([
25
+ "async",
26
+ "autofocus",
27
+ "autoplay",
28
+ "checked",
29
+ "controls",
30
+ "default",
31
+ "defer",
32
+ "disabled",
33
+ "formnovalidate",
34
+ "hidden",
35
+ "inert",
36
+ "ismap",
37
+ "itemscope",
38
+ "loop",
39
+ "multiple",
40
+ "muted",
41
+ "nomodule",
42
+ "novalidate",
43
+ "open",
44
+ "playsinline",
45
+ "readonly",
46
+ "required",
47
+ "reversed",
48
+ "selected"
49
+ ]);
50
+ var onPrefix = /^on/i;
51
+ var sourcePrefix = /^(href|src|xlink:href)$/i;
52
+ var valuePrefix = /(data:text\/html|javascript:)/i;
53
+
54
+ // src/sanitise.ts
55
+ function sanitise(value2, options) {
56
+ return sanitiseNodes(Array.isArray(value2) ? value2 : [value2], {
57
+ sanitiseBooleanAttributes: options?.sanitiseBooleanAttributes ?? true
58
+ });
59
+ }
60
+ function sanitiseAttributes(element, attributes, options) {
61
+ const { length } = attributes;
62
+ for (let index = 0;index < length; index += 1) {
63
+ const attribute2 = attributes[index];
64
+ if (isBadAttribute(attribute2) || isEmptyNonBooleanAttribute(attribute2)) {
65
+ element.removeAttribute(attribute2.name);
66
+ } else if (options.sanitiseBooleanAttributes && isInvalidBooleanAttribute(attribute2)) {
67
+ element.setAttribute(attribute2.name, "");
68
+ }
69
+ }
70
+ }
71
+ function sanitiseNodes(nodes, options) {
72
+ const { length } = nodes;
73
+ for (let index = 0;index < length; index += 1) {
74
+ const node = nodes[index];
75
+ if (node instanceof Element) {
76
+ sanitiseAttributes(node, [...node.attributes], options);
77
+ }
78
+ sanitiseNodes([...node.childNodes], options);
79
+ }
80
+ return nodes;
81
+ }
82
+
83
+ // src/html.ts
84
+ function createTemplate(html) {
85
+ const template2 = document.createElement("template");
86
+ template2.innerHTML = html;
87
+ templates[html] = template2;
88
+ return template2;
89
+ }
90
+ function getTemplate(value2) {
91
+ if (value2.trim().length === 0) {
92
+ return;
93
+ }
94
+ let template2;
95
+ if (/^[\w-]+$/.test(value2)) {
96
+ template2 = document.querySelector(`#${value2}`);
97
+ }
98
+ if (template2 instanceof HTMLTemplateElement) {
99
+ return template2;
100
+ }
101
+ return templates[value2] ?? createTemplate(value2);
102
+ }
103
+ function html(value2, sanitisation) {
104
+ const options = sanitisation == null || sanitisation === true ? {} : isPlainObject(sanitisation) ? { ...sanitisation } : null;
105
+ const template2 = value2 instanceof HTMLTemplateElement ? value2 : typeof value2 === "string" ? getTemplate(value2) : null;
106
+ if (template2 == null) {
107
+ return [];
108
+ }
109
+ const cloned = template2.content.cloneNode(true);
110
+ const scripts = cloned.querySelectorAll("script");
111
+ const { length } = scripts;
112
+ for (let index = 0;index < length; index += 1) {
113
+ scripts[index].remove();
114
+ }
115
+ cloned.normalize();
116
+ return options != null ? sanitise([...cloned.childNodes], options) : [...cloned.childNodes];
117
+ }
118
+ var templates = {};
119
+ export {
120
+ html
121
+ };
package/dist/html.mjs ADDED
@@ -0,0 +1,41 @@
1
+ // src/html.ts
2
+ import {isPlainObject} from "@oscarpalmer/atoms/is";
3
+ import {sanitise as sanitise2} from "./sanitise";
4
+ function createTemplate(html) {
5
+ const template = document.createElement("template");
6
+ template.innerHTML = html;
7
+ templates[html] = template;
8
+ return template;
9
+ }
10
+ function getTemplate(value) {
11
+ if (value.trim().length === 0) {
12
+ return;
13
+ }
14
+ let template;
15
+ if (/^[\w-]+$/.test(value)) {
16
+ template = document.querySelector(`#${value}`);
17
+ }
18
+ if (template instanceof HTMLTemplateElement) {
19
+ return template;
20
+ }
21
+ return templates[value] ?? createTemplate(value);
22
+ }
23
+ function html(value, sanitisation) {
24
+ const options = sanitisation == null || sanitisation === true ? {} : isPlainObject(sanitisation) ? { ...sanitisation } : null;
25
+ const template = value instanceof HTMLTemplateElement ? value : typeof value === "string" ? getTemplate(value) : null;
26
+ if (template == null) {
27
+ return [];
28
+ }
29
+ const cloned = template.content.cloneNode(true);
30
+ const scripts = cloned.querySelectorAll("script");
31
+ const { length } = scripts;
32
+ for (let index = 0;index < length; index += 1) {
33
+ scripts[index].remove();
34
+ }
35
+ cloned.normalize();
36
+ return options != null ? sanitise2([...cloned.childNodes], options) : [...cloned.childNodes];
37
+ }
38
+ var templates = {};
39
+ export {
40
+ html
41
+ };
package/dist/index.js CHANGED
@@ -147,6 +147,66 @@ function setData(element, first, second) {
147
147
  function updateDataAttribute(element, key, value2) {
148
148
  updateElementValue(element, `data-${key}`, value2, element.setAttribute, element.removeAttribute, true);
149
149
  }
150
+ // src/event.ts
151
+ function createDispatchOptions(options) {
152
+ return {
153
+ bubbles: getBoolean(options?.bubbles),
154
+ cancelable: getBoolean(options?.cancelable),
155
+ composed: getBoolean(options?.composed)
156
+ };
157
+ }
158
+ function createEvent(type, options) {
159
+ const hasOptions = isPlainObject(options);
160
+ if (hasOptions && "detail" in options) {
161
+ return new CustomEvent(type, {
162
+ ...createDispatchOptions(options),
163
+ detail: options?.detail
164
+ });
165
+ }
166
+ return new Event(type, createDispatchOptions(hasOptions ? options : {}));
167
+ }
168
+ function createEventOptions(options) {
169
+ if (isPlainObject(options)) {
170
+ return {
171
+ capture: getBoolean(options.capture),
172
+ once: getBoolean(options.once),
173
+ passive: getBoolean(options.passive, true)
174
+ };
175
+ }
176
+ return {
177
+ capture: getBoolean(options),
178
+ once: false,
179
+ passive: true
180
+ };
181
+ }
182
+ function dispatch(target, type, options) {
183
+ target.dispatchEvent(createEvent(type, options));
184
+ }
185
+ function getBoolean(value2, defaultValue) {
186
+ return typeof value2 === "boolean" ? value2 : defaultValue ?? false;
187
+ }
188
+ function getPosition(event) {
189
+ let x;
190
+ let y;
191
+ if (event instanceof MouseEvent) {
192
+ x = event.clientX;
193
+ y = event.clientY;
194
+ } else if (event instanceof TouchEvent) {
195
+ x = event.touches[0]?.clientX;
196
+ y = event.touches[0]?.clientY;
197
+ }
198
+ return typeof x === "number" && typeof y === "number" ? { x, y } : undefined;
199
+ }
200
+ function off(target, type, listener, options) {
201
+ target.removeEventListener(type, listener, createEventOptions(options));
202
+ }
203
+ function on(target, type, listener, options) {
204
+ const extended = createEventOptions(options);
205
+ target.addEventListener(type, listener, extended);
206
+ return () => {
207
+ target.removeEventListener(type, listener, extended);
208
+ };
209
+ }
150
210
  // src/find.ts
151
211
  function calculateDistance(origin, target) {
152
212
  if (origin === target || origin.parentElement === target) {
@@ -465,6 +525,43 @@ function sanitiseNodes(nodes, options) {
465
525
  }
466
526
  return nodes;
467
527
  }
528
+
529
+ // src/html.ts
530
+ function createTemplate(html) {
531
+ const template2 = document.createElement("template");
532
+ template2.innerHTML = html;
533
+ templates[html] = template2;
534
+ return template2;
535
+ }
536
+ function getTemplate(value2) {
537
+ if (value2.trim().length === 0) {
538
+ return;
539
+ }
540
+ let template2;
541
+ if (/^[\w-]+$/.test(value2)) {
542
+ template2 = document.querySelector(`#${value2}`);
543
+ }
544
+ if (template2 instanceof HTMLTemplateElement) {
545
+ return template2;
546
+ }
547
+ return templates[value2] ?? createTemplate(value2);
548
+ }
549
+ function html(value2, sanitisation) {
550
+ const options = sanitisation == null || sanitisation === true ? {} : isPlainObject(sanitisation) ? { ...sanitisation } : null;
551
+ const template2 = value2 instanceof HTMLTemplateElement ? value2 : typeof value2 === "string" ? getTemplate(value2) : null;
552
+ if (template2 == null) {
553
+ return [];
554
+ }
555
+ const cloned = template2.content.cloneNode(true);
556
+ const scripts = cloned.querySelectorAll("script");
557
+ const { length } = scripts;
558
+ for (let index = 0;index < length; index += 1) {
559
+ scripts[index].remove();
560
+ }
561
+ cloned.normalize();
562
+ return options != null ? sanitise([...cloned.childNodes], options) : [...cloned.childNodes];
563
+ }
564
+ var templates = {};
468
565
  // src/style.ts
469
566
  function getStyle(element, property) {
470
567
  return element.style[property];
@@ -505,16 +602,20 @@ export {
505
602
  setAttributes,
506
603
  setAttribute,
507
604
  sanitise,
605
+ on,
606
+ off,
508
607
  isTabbable,
509
608
  isInvalidBooleanAttribute,
510
609
  isFocusable,
511
610
  isEmptyNonBooleanAttribute,
512
611
  isBooleanAttribute,
513
612
  isBadAttribute,
613
+ html,
514
614
  getTextDirection,
515
615
  getTabbable,
516
616
  getStyles,
517
617
  getStyle,
618
+ getPosition,
518
619
  getFocusable,
519
620
  getElementUnderPointer,
520
621
  getData,
@@ -522,6 +623,7 @@ export {
522
623
  findElements,
523
624
  findElement,
524
625
  findAncestor,
626
+ dispatch,
525
627
  booleanAttributes,
526
628
  findElements as $$,
527
629
  findElement as $
package/dist/index.mjs CHANGED
@@ -1,8 +1,10 @@
1
1
  // src/index.ts
2
2
  export * from "./attribute";
3
3
  export * from "./data";
4
+ export * from "./event";
4
5
  export * from "./find";
5
6
  export * from "./focusable";
7
+ export * from "./html";
6
8
  export * from "./models";
7
9
  export * from "./sanitise";
8
10
  export * from "./style";
package/package.json CHANGED
@@ -11,7 +11,7 @@
11
11
  "@biomejs/biome": "^1.8.3",
12
12
  "@happy-dom/global-registrator": "^14.12.3",
13
13
  "@types/bun": "^1.1.6",
14
- "bun": "^1.1.21",
14
+ "bun": "^1.1.22",
15
15
  "dts-bundle-generator": "^9.5.1",
16
16
  "typescript": "^5.5.4"
17
17
  },
@@ -39,6 +39,12 @@
39
39
  "import": "./dist/data.mjs",
40
40
  "require": "./dist/data.js"
41
41
  },
42
+ "./event": {
43
+ "types": "./types/event.d.ts",
44
+ "bun": "./src/event.ts",
45
+ "import": "./dist/event.mjs",
46
+ "require": "./dist/event.js"
47
+ },
42
48
  "./find": {
43
49
  "types": "./types/find.d.ts",
44
50
  "bun": "./src/find.ts",
@@ -51,6 +57,12 @@
51
57
  "import": "./dist/focusable.mjs",
52
58
  "require": "./dist/focusable.js"
53
59
  },
60
+ "./html": {
61
+ "types": "./types/html.d.ts",
62
+ "bun": "./src/html.ts",
63
+ "import": "./dist/html.mjs",
64
+ "require": "./dist/html.js"
65
+ },
54
66
  "./models": {
55
67
  "types": "./types/models.d.ts",
56
68
  "bun": "./src/models.ts"
@@ -97,5 +109,5 @@
97
109
  },
98
110
  "type": "module",
99
111
  "types": "types/index.d.cts",
100
- "version": "0.9.0"
112
+ "version": "0.11.0"
101
113
  }
package/src/event.ts ADDED
@@ -0,0 +1,165 @@
1
+ import {isPlainObject} from '@oscarpalmer/atoms/is';
2
+ import type {
3
+ CustomDispatchOptions,
4
+ DispatchOptions,
5
+ EventPosition,
6
+ } from './models';
7
+
8
+ /**
9
+ * Remove the current event listener
10
+ */
11
+ type RemoveEventListener = () => void;
12
+
13
+ function createDispatchOptions(options: DispatchOptions): DispatchOptions {
14
+ return {
15
+ bubbles: getBoolean(options?.bubbles),
16
+ cancelable: getBoolean(options?.cancelable),
17
+ composed: getBoolean(options?.composed),
18
+ };
19
+ }
20
+
21
+ function createEvent(type: string, options?: DispatchOptions): Event {
22
+ const hasOptions = isPlainObject(options);
23
+
24
+ if (hasOptions && 'detail' in options) {
25
+ return new CustomEvent(type, {
26
+ ...createDispatchOptions(options),
27
+ detail: options?.detail,
28
+ });
29
+ }
30
+
31
+ return new Event(type, createDispatchOptions(hasOptions ? options : {}));
32
+ }
33
+
34
+ function createEventOptions(
35
+ options?: boolean | AddEventListenerOptions,
36
+ ): AddEventListenerOptions {
37
+ if (isPlainObject(options)) {
38
+ return {
39
+ capture: getBoolean(options.capture),
40
+ once: getBoolean(options.once),
41
+ passive: getBoolean(options.passive, true),
42
+ };
43
+ }
44
+
45
+ return {
46
+ capture: getBoolean(options),
47
+ once: false,
48
+ passive: true,
49
+ };
50
+ }
51
+
52
+ /**
53
+ * Dispatch an event for a target
54
+ */
55
+ export function dispatch<Type extends keyof HTMLElementEventMap>(
56
+ target: EventTarget,
57
+ type: Type,
58
+ options?: DispatchOptions,
59
+ ): void;
60
+
61
+ /**
62
+ * Dispatch an event for a target
63
+ */
64
+ export function dispatch<Type extends keyof HTMLElementEventMap>(
65
+ target: EventTarget,
66
+ type: Type,
67
+ options?: CustomDispatchOptions,
68
+ ): void;
69
+
70
+ /**
71
+ * Dispatch an event for a target
72
+ */
73
+ export function dispatch(
74
+ target: EventTarget,
75
+ type: string,
76
+ options?: DispatchOptions,
77
+ ): void;
78
+
79
+ /**
80
+ * Dispatch an event for a target
81
+ */
82
+ export function dispatch(
83
+ target: EventTarget,
84
+ type: string,
85
+ options?: CustomDispatchOptions,
86
+ ): void;
87
+
88
+ export function dispatch(
89
+ target: EventTarget,
90
+ type: string,
91
+ options?: DispatchOptions,
92
+ ): void {
93
+ target.dispatchEvent(createEvent(type, options));
94
+ }
95
+
96
+ function getBoolean(value: unknown, defaultValue?: boolean): boolean {
97
+ return typeof value === 'boolean' ? value : defaultValue ?? false;
98
+ }
99
+
100
+ /**
101
+ * Get the X- and Y-coordinates from a pointer event
102
+ */
103
+ export function getPosition(
104
+ event: MouseEvent | TouchEvent,
105
+ ): EventPosition | undefined {
106
+ let x: number | undefined;
107
+ let y: number | undefined;
108
+
109
+ if (event instanceof MouseEvent) {
110
+ x = event.clientX;
111
+ y = event.clientY;
112
+ } else if (event instanceof TouchEvent) {
113
+ x = event.touches[0]?.clientX;
114
+ y = event.touches[0]?.clientY;
115
+ }
116
+
117
+ return typeof x === 'number' && typeof y === 'number' ? {x, y} : undefined;
118
+ }
119
+
120
+ /**
121
+ * Remove an event listener
122
+ */
123
+ export function off(
124
+ target: EventTarget,
125
+ type: string,
126
+ listener: EventListener,
127
+ options?: boolean | EventListenerOptions,
128
+ ): void {
129
+ target.removeEventListener(type, listener, createEventOptions(options));
130
+ }
131
+
132
+ /**
133
+ * Add an event listener
134
+ */
135
+ export function on<Type extends keyof HTMLElementEventMap>(
136
+ target: EventTarget,
137
+ type: Type,
138
+ listener: (event: HTMLElementEventMap[Type]) => void,
139
+ options?: boolean | AddEventListenerOptions,
140
+ ): RemoveEventListener;
141
+
142
+ /**
143
+ * Add an event listener
144
+ */
145
+ export function on(
146
+ target: EventTarget,
147
+ type: string,
148
+ listener: EventListener,
149
+ options?: boolean | AddEventListenerOptions,
150
+ ): RemoveEventListener;
151
+
152
+ export function on(
153
+ target: EventTarget,
154
+ type: string,
155
+ listener: EventListener,
156
+ options?: boolean | AddEventListenerOptions,
157
+ ): RemoveEventListener {
158
+ const extended = createEventOptions(options);
159
+
160
+ target.addEventListener(type, listener, extended);
161
+
162
+ return () => {
163
+ target.removeEventListener(type, listener, extended);
164
+ };
165
+ }
package/src/find.ts CHANGED
@@ -286,3 +286,4 @@ function traverse(from: Element, to: Element): number {
286
286
  }
287
287
 
288
288
  export {findElement as $, findElements as $$};
289
+
package/src/html.ts ADDED
@@ -0,0 +1,90 @@
1
+ import {isPlainObject} from '@oscarpalmer/atoms/is';
2
+ import {type SanitiseOptions, sanitise} from './sanitise';
3
+
4
+ const templates: Record<string, HTMLTemplateElement> = {};
5
+
6
+ function createTemplate(html: string): HTMLTemplateElement {
7
+ const template = document.createElement('template');
8
+
9
+ template.innerHTML = html;
10
+
11
+ templates[html] = template;
12
+
13
+ return template;
14
+ }
15
+
16
+ function getTemplate(value: string): HTMLTemplateElement | undefined {
17
+ if (value.trim().length === 0) {
18
+ return;
19
+ }
20
+
21
+ let template: unknown;
22
+
23
+ if (/^[\w-]+$/.test(value)) {
24
+ template = document.querySelector(`#${value}`);
25
+ }
26
+
27
+ if (template instanceof HTMLTemplateElement) {
28
+ return template;
29
+ }
30
+
31
+ return templates[value] ?? createTemplate(value);
32
+ }
33
+
34
+ /**
35
+ * - Create nodes from a string of _HTML_ or a template element
36
+ * - If `value` doesn't contain any whitespace, it will be treated as an _ID_ before falling back to being treated as _HTML_
37
+ * - If `sanitisation` is not provided, `true`, or an options object, bad markup will be sanitised or removed
38
+ * - Regardless of the value of `sanitisation`, scripts will always be removed
39
+ */
40
+ export function html(
41
+ value: string,
42
+ sanitisation?: boolean | SanitiseOptions,
43
+ ): Node[];
44
+
45
+ /**
46
+ * - Create nodes from a template element
47
+ * - If `sanitisation` is not provided, `true`, or an options object, bad markup will be sanitised or removed
48
+ * - Regardless of the value of `sanitisation`, scripts will always be removed
49
+ */
50
+ export function html(
51
+ value: HTMLTemplateElement,
52
+ sanitisation?: boolean | SanitiseOptions,
53
+ ): Node[];
54
+
55
+ export function html(
56
+ value: string | HTMLTemplateElement,
57
+ sanitisation?: boolean | SanitiseOptions,
58
+ ): Node[] {
59
+ const options =
60
+ sanitisation == null || sanitisation === true
61
+ ? {}
62
+ : isPlainObject(sanitisation)
63
+ ? {...sanitisation}
64
+ : null;
65
+
66
+ const template =
67
+ value instanceof HTMLTemplateElement
68
+ ? value
69
+ : typeof value === 'string'
70
+ ? getTemplate(value)
71
+ : null;
72
+
73
+ if (template == null) {
74
+ return [];
75
+ }
76
+
77
+ const cloned = template.content.cloneNode(true) as DocumentFragment;
78
+ const scripts = cloned.querySelectorAll('script');
79
+ const {length} = scripts;
80
+
81
+ for (let index = 0; index < length; index += 1) {
82
+ scripts[index].remove();
83
+ }
84
+
85
+ cloned.normalize();
86
+
87
+ return options != null
88
+ ? sanitise([...cloned.childNodes], options)
89
+ : [...cloned.childNodes];
90
+ }
package/src/index.ts CHANGED
@@ -1,7 +1,10 @@
1
1
  export * from './attribute';
2
2
  export * from './data';
3
+ export * from './event';
3
4
  export * from './find';
4
5
  export * from './focusable';
6
+ export * from './html';
5
7
  export * from './models';
6
8
  export * from './sanitise';
7
9
  export * from './style';
10
+
package/src/models.ts CHANGED
@@ -3,6 +3,21 @@ export type Attribute<Value = unknown> = {
3
3
  value: Value;
4
4
  };
5
5
 
6
+ export type CustomDispatchOptions = {
7
+ detail?: unknown;
8
+ } & DispatchOptions;
9
+
10
+ export type DispatchOptions = {
11
+ bubbles?: boolean;
12
+ cancelable?: boolean;
13
+ composed?: boolean;
14
+ };
15
+
16
+ export type EventPosition = {
17
+ x: number;
18
+ y: number;
19
+ };
20
+
6
21
  export type Selector = string | Document | Element | Element[] | NodeList;
7
22
 
8
23
  export type TextDirection = 'ltr' | 'rtl';
package/src/sanitise.ts CHANGED
@@ -60,4 +60,4 @@ function sanitiseNodes(nodes: Node[], options: SanitiseOptions): Node[] {
60
60
  }
61
61
 
62
62
  return nodes;
63
- }
63
+ }
@@ -0,0 +1,38 @@
1
+ import type { CustomDispatchOptions, DispatchOptions, EventPosition } from './models';
2
+ /**
3
+ * Remove the current event listener
4
+ */
5
+ type RemoveEventListener = () => void;
6
+ /**
7
+ * Dispatch an event for a target
8
+ */
9
+ export declare function dispatch<Type extends keyof HTMLElementEventMap>(target: EventTarget, type: Type, options?: DispatchOptions): void;
10
+ /**
11
+ * Dispatch an event for a target
12
+ */
13
+ export declare function dispatch<Type extends keyof HTMLElementEventMap>(target: EventTarget, type: Type, options?: CustomDispatchOptions): void;
14
+ /**
15
+ * Dispatch an event for a target
16
+ */
17
+ export declare function dispatch(target: EventTarget, type: string, options?: DispatchOptions): void;
18
+ /**
19
+ * Dispatch an event for a target
20
+ */
21
+ export declare function dispatch(target: EventTarget, type: string, options?: CustomDispatchOptions): void;
22
+ /**
23
+ * Get the X- and Y-coordinates from a pointer event
24
+ */
25
+ export declare function getPosition(event: MouseEvent | TouchEvent): EventPosition | undefined;
26
+ /**
27
+ * Remove an event listener
28
+ */
29
+ export declare function off(target: EventTarget, type: string, listener: EventListener, options?: boolean | EventListenerOptions): void;
30
+ /**
31
+ * Add an event listener
32
+ */
33
+ export declare function on<Type extends keyof HTMLElementEventMap>(target: EventTarget, type: Type, listener: (event: HTMLElementEventMap[Type]) => void, options?: boolean | AddEventListenerOptions): RemoveEventListener;
34
+ /**
35
+ * Add an event listener
36
+ */
37
+ export declare function on(target: EventTarget, type: string, listener: EventListener, options?: boolean | AddEventListenerOptions): RemoveEventListener;
38
+ export {};
@@ -0,0 +1,14 @@
1
+ import { type SanitiseOptions } from './sanitise';
2
+ /**
3
+ * - Create nodes from a string of _HTML_ or a template element
4
+ * - If `value` doesn't contain any whitespace, it will be treated as an _ID_ before falling back to being treated as _HTML_
5
+ * - If `sanitisation` is not provided, `true`, or an options object, bad markup will be sanitised or removed
6
+ * - Regardless of the value of `sanitisation`, scripts will always be removed
7
+ */
8
+ export declare function html(value: string, sanitisation?: boolean | SanitiseOptions): Node[];
9
+ /**
10
+ * - Create nodes from a template element
11
+ * - If `sanitisation` is not provided, `true`, or an options object, bad markup will be sanitised or removed
12
+ * - Regardless of the value of `sanitisation`, scripts will always be removed
13
+ */
14
+ export declare function html(value: HTMLTemplateElement, sanitisation?: boolean | SanitiseOptions): Node[];
package/types/index.d.cts CHANGED
@@ -4,6 +4,18 @@ export type Attribute<Value = unknown> = {
4
4
  name: string;
5
5
  value: Value;
6
6
  };
7
+ export type CustomDispatchOptions = {
8
+ detail?: unknown;
9
+ } & DispatchOptions;
10
+ export type DispatchOptions = {
11
+ bubbles?: boolean;
12
+ cancelable?: boolean;
13
+ composed?: boolean;
14
+ };
15
+ export type EventPosition = {
16
+ x: number;
17
+ y: number;
18
+ };
7
19
  export type Selector = string | Document | Element | Element[] | NodeList;
8
20
  export type TextDirection = "ltr" | "rtl";
9
21
  /**
@@ -91,6 +103,42 @@ export declare function setData(element: HTMLElement, data: PlainObject): void;
91
103
  * Set a data value on an element
92
104
  */
93
105
  export declare function setData(element: HTMLElement, key: string, value: unknown): void;
106
+ /**
107
+ * Remove the current event listener
108
+ */
109
+ export type RemoveEventListener = () => void;
110
+ /**
111
+ * Dispatch an event for a target
112
+ */
113
+ export declare function dispatch<Type extends keyof HTMLElementEventMap>(target: EventTarget, type: Type, options?: DispatchOptions): void;
114
+ /**
115
+ * Dispatch an event for a target
116
+ */
117
+ export declare function dispatch<Type extends keyof HTMLElementEventMap>(target: EventTarget, type: Type, options?: CustomDispatchOptions): void;
118
+ /**
119
+ * Dispatch an event for a target
120
+ */
121
+ export declare function dispatch(target: EventTarget, type: string, options?: DispatchOptions): void;
122
+ /**
123
+ * Dispatch an event for a target
124
+ */
125
+ export declare function dispatch(target: EventTarget, type: string, options?: CustomDispatchOptions): void;
126
+ /**
127
+ * Get the X- and Y-coordinates from a pointer event
128
+ */
129
+ export declare function getPosition(event: MouseEvent | TouchEvent): EventPosition | undefined;
130
+ /**
131
+ * Remove an event listener
132
+ */
133
+ export declare function off(target: EventTarget, type: string, listener: EventListener, options?: boolean | EventListenerOptions): void;
134
+ /**
135
+ * Add an event listener
136
+ */
137
+ export declare function on<Type extends keyof HTMLElementEventMap>(target: EventTarget, type: Type, listener: (event: HTMLElementEventMap[Type]) => void, options?: boolean | AddEventListenerOptions): RemoveEventListener;
138
+ /**
139
+ * Add an event listener
140
+ */
141
+ export declare function on(target: EventTarget, type: string, listener: EventListener, options?: boolean | AddEventListenerOptions): RemoveEventListener;
94
142
  /**
95
143
  * - Find the closest ancestor element that matches the selector
96
144
  * - Matches may be found by a query string or a callback
@@ -149,6 +197,19 @@ export type SanitiseOptions = {
149
197
  * - Removes or sanitises bad attributes
150
198
  */
151
199
  export declare function sanitise(value: Node | Node[], options?: Partial<SanitiseOptions>): Node[];
200
+ /**
201
+ * - Create nodes from a string of _HTML_ or a template element
202
+ * - If `value` doesn't contain any whitespace, it will be treated as an _ID_ before falling back to being treated as _HTML_
203
+ * - If `sanitisation` is not provided, `true`, or an options object, bad markup will be sanitised or removed
204
+ * - Regardless of the value of `sanitisation`, scripts will always be removed
205
+ */
206
+ export declare function html(value: string, sanitisation?: boolean | SanitiseOptions): Node[];
207
+ /**
208
+ * - Create nodes from a template element
209
+ * - If `sanitisation` is not provided, `true`, or an options object, bad markup will be sanitised or removed
210
+ * - Regardless of the value of `sanitisation`, scripts will always be removed
211
+ */
212
+ export declare function html(value: HTMLTemplateElement, sanitisation?: boolean | SanitiseOptions): Node[];
152
213
  /**
153
214
  * Get a style from an element
154
215
  */
package/types/index.d.ts CHANGED
@@ -1,7 +1,9 @@
1
1
  export * from './attribute';
2
2
  export * from './data';
3
+ export * from './event';
3
4
  export * from './find';
4
5
  export * from './focusable';
6
+ export * from './html';
5
7
  export * from './models';
6
8
  export * from './sanitise';
7
9
  export * from './style';
package/types/models.d.ts CHANGED
@@ -2,5 +2,17 @@ export type Attribute<Value = unknown> = {
2
2
  name: string;
3
3
  value: Value;
4
4
  };
5
+ export type CustomDispatchOptions = {
6
+ detail?: unknown;
7
+ } & DispatchOptions;
8
+ export type DispatchOptions = {
9
+ bubbles?: boolean;
10
+ cancelable?: boolean;
11
+ composed?: boolean;
12
+ };
13
+ export type EventPosition = {
14
+ x: number;
15
+ y: number;
16
+ };
5
17
  export type Selector = string | Document | Element | Element[] | NodeList;
6
18
  export type TextDirection = 'ltr' | 'rtl';