@oscarpalmer/toretto 0.41.0 → 0.43.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.
Files changed (55) hide show
  1. package/dist/attribute/{get.d.mts → get.attribute.d.mts} +3 -2
  2. package/dist/attribute/{get.mjs → get.attribute.mjs} +4 -3
  3. package/dist/attribute/index.d.mts +3 -16
  4. package/dist/attribute/index.mjs +4 -7
  5. package/dist/attribute/{set.d.mts → set.attribute.d.mts} +4 -4
  6. package/dist/attribute/{set.mjs → set.attribute.mjs} +2 -2
  7. package/dist/create.d.mts +25 -0
  8. package/dist/create.mjs +17 -0
  9. package/dist/data.mjs +7 -7
  10. package/dist/event/delegation.mjs +8 -1
  11. package/dist/html/index.d.mts +23 -26
  12. package/dist/html/index.mjs +85 -18
  13. package/dist/html/sanitize.mjs +6 -5
  14. package/dist/index.d.mts +117 -54
  15. package/dist/index.mjs +541 -380
  16. package/dist/internal/attribute.d.mts +4 -3
  17. package/dist/internal/attribute.mjs +13 -23
  18. package/dist/internal/element-value.d.mts +2 -2
  19. package/dist/internal/element-value.mjs +12 -6
  20. package/dist/internal/get-value.mjs +3 -1
  21. package/dist/internal/property.d.mts +4 -0
  22. package/dist/internal/property.mjs +24 -0
  23. package/dist/property/get.property.d.mts +20 -0
  24. package/dist/property/get.property.mjs +35 -0
  25. package/dist/property/index.d.mts +3 -0
  26. package/dist/property/index.mjs +3 -0
  27. package/dist/property/set.property.d.mts +32 -0
  28. package/dist/property/set.property.mjs +32 -0
  29. package/dist/style.d.mts +16 -9
  30. package/dist/style.mjs +22 -21
  31. package/package.json +14 -6
  32. package/src/attribute/{get.ts → get.attribute.ts} +14 -3
  33. package/src/attribute/index.ts +10 -22
  34. package/src/attribute/{set.ts → set.attribute.ts} +9 -5
  35. package/src/create.ts +81 -0
  36. package/src/data.ts +16 -8
  37. package/src/event/delegation.ts +24 -3
  38. package/src/event/index.ts +9 -3
  39. package/src/find/index.ts +11 -3
  40. package/src/find/relative.ts +4 -0
  41. package/src/focusable.ts +10 -2
  42. package/src/html/index.ts +166 -58
  43. package/src/html/sanitize.ts +14 -11
  44. package/src/index.ts +2 -1
  45. package/src/internal/attribute.ts +23 -42
  46. package/src/internal/element-value.ts +25 -6
  47. package/src/internal/get-value.ts +14 -0
  48. package/src/internal/is.ts +4 -0
  49. package/src/internal/property.ts +42 -0
  50. package/src/is.ts +10 -2
  51. package/src/property/get.property.ts +73 -0
  52. package/src/property/index.ts +2 -0
  53. package/src/property/set.property.ts +103 -0
  54. package/src/style.ts +81 -36
  55. package/src/touch.ts +14 -2
@@ -1,16 +1,15 @@
1
- import {setAttribute} from '../attribute/set';
2
- import {
3
- _isBadAttribute,
4
- _isEmptyNonBooleanAttribute,
5
- _isInvalidBooleanAttribute,
6
- } from '../internal/attribute';
1
+ import {setAttribute} from '../attribute/set.attribute';
2
+ import {_isBadAttribute, _isInvalidBooleanAttribute} from '../internal/attribute';
3
+
4
+ // #region Functions
7
5
 
8
6
  function handleElement(element: Element, depth: number): void {
9
7
  if (depth === 0) {
10
- const removable = element.querySelectorAll(REMOVE_SELECTOR);
8
+ const removable = [...element.querySelectorAll(REMOVE_SELECTOR)];
9
+ const {length} = removable;
11
10
 
12
- for (const item of removable) {
13
- item.remove();
11
+ for (let index = 0; index < length; index += 1) {
12
+ removable[index].remove();
14
13
  }
15
14
  }
16
15
 
@@ -49,7 +48,7 @@ export function sanitizeAttributes(element: Element, attributes: Attr[]): void {
49
48
  for (let index = 0; index < length; index += 1) {
50
49
  const {name, value} = attributes[index];
51
50
 
52
- if (_isBadAttribute(name, value, false) || _isEmptyNonBooleanAttribute(name, value, false)) {
51
+ if (_isBadAttribute(name, value, false)) {
53
52
  element.removeAttribute(name);
54
53
  } else if (_isInvalidBooleanAttribute(name, value, false)) {
55
54
  setAttribute(element, name, true);
@@ -103,8 +102,12 @@ export function sanitizeNodes(nodes: Node[], depth: number): Node[] {
103
102
  return nodes;
104
103
  }
105
104
 
106
- //
105
+ // #endregion
106
+
107
+ // #region Variables
107
108
 
108
109
  const COMMENT_HARMFUL = /<[/\w]/g;
109
110
 
110
111
  const REMOVE_SELECTOR = 'script, toretto-temporary';
112
+
113
+ // #endregion
package/src/index.ts CHANGED
@@ -6,11 +6,11 @@ export {
6
6
  getAttributes,
7
7
  isBadAttribute,
8
8
  isBooleanAttribute,
9
- isEmptyNonBooleanAttribute,
10
9
  isInvalidBooleanAttribute,
11
10
  setAttribute,
12
11
  setAttributes,
13
12
  } from './attribute/index';
13
+ export * from './create';
14
14
  export * from './data';
15
15
  export * from './event/index';
16
16
  export * from './find/index';
@@ -18,6 +18,7 @@ export * from './focusable';
18
18
  export * from './html/index';
19
19
  export * from './is';
20
20
  export * from './models';
21
+ export * from './property/index';
21
22
  export * from './style';
22
23
 
23
24
  export {supportsTouch};
@@ -1,7 +1,11 @@
1
- import type {PlainObject} from '@oscarpalmer/atoms/models';
2
1
  import {isPlainObject} from '@oscarpalmer/atoms/is';
2
+ import type {PlainObject} from '@oscarpalmer/atoms/models';
3
+ import {kebabCase} from '@oscarpalmer/atoms/string/case';
3
4
  import type {Attribute} from '../models';
4
5
  import {updateElementValue} from './element-value';
6
+ import {updateProperty} from './property';
7
+
8
+ // #region Functions
5
9
 
6
10
  function badAttributeHandler(name?: string, value?: string): boolean {
7
11
  if (typeof name !== 'string' || name.trim().length === 0 || typeof value !== 'string') {
@@ -67,6 +71,10 @@ function handleAttribute(
67
71
  value = second;
68
72
  }
69
73
 
74
+ if (name != null) {
75
+ name = kebabCase(name);
76
+ }
77
+
70
78
  if (decode && value != null) {
71
79
  value = decodeAttribute(value);
72
80
  }
@@ -96,20 +104,6 @@ export function _isBooleanAttribute(first: unknown, decode: boolean): boolean {
96
104
  );
97
105
  }
98
106
 
99
- export function _isEmptyNonBooleanAttribute(
100
- first: unknown,
101
- second: unknown,
102
- decode: boolean,
103
- ): boolean {
104
- return handleAttribute(
105
- (name, value) =>
106
- name != null && value != null && !booleanAttributesSet.has(name) && value.trim().length === 0,
107
- decode,
108
- first,
109
- second,
110
- );
111
- }
112
-
113
107
  export function _isInvalidBooleanAttribute(
114
108
  first: unknown,
115
109
  second: unknown,
@@ -126,49 +120,39 @@ export function updateAttribute(
126
120
  element: Element,
127
121
  name: string,
128
122
  value: unknown,
129
- dispatch?: unknown,
123
+ dispatch: boolean,
130
124
  ): void {
131
- const normalizedName = name.toLowerCase();
125
+ const lowerCaseName = name.toLowerCase();
132
126
 
133
- const isBoolean = booleanAttributesSet.has(normalizedName);
127
+ const isBoolean = booleanAttributesSet.has(lowerCaseName);
134
128
 
135
129
  const next = isBoolean
136
130
  ? value === true ||
137
- (typeof value === 'string' && (value === '' || value.toLowerCase() === normalizedName))
131
+ (typeof value === 'string' && (value === '' || value.toLowerCase() === lowerCaseName))
138
132
  : value == null
139
133
  ? ''
140
134
  : value;
141
135
 
142
- if (name in element) {
143
- updateProperty(element, normalizedName, next, dispatch);
136
+ if (isBoolean || dispatchedAttributes.has(name)) {
137
+ updateProperty(element, name, next, dispatch);
144
138
  }
145
139
 
146
140
  updateElementValue(
147
141
  element,
148
142
  name,
149
143
  isBoolean ? (next ? '' : null) : value,
144
+ // oxlint-disable-next-line typescript/unbound-method: using `.call` in `updateElementValue`
150
145
  element.setAttribute,
146
+ // oxlint-disable-next-line typescript/unbound-method: using `.call` in `updateElementValue`
151
147
  element.removeAttribute,
152
148
  isBoolean,
153
149
  false,
154
150
  );
155
151
  }
156
152
 
157
- function updateProperty(element: Element, name: string, value: unknown, dispatch?: unknown): void {
158
- if (Object.is((element as unknown as PlainObject)[name], value)) {
159
- return;
160
- }
161
-
162
- (element as unknown as PlainObject)[name] = value;
153
+ // #endregion
163
154
 
164
- const event = dispatch !== false && elementEvents[element.tagName]?.[name];
165
-
166
- if (typeof event === 'string') {
167
- element.dispatchEvent(new Event(event, {bubbles: true}));
168
- }
169
- }
170
-
171
- //
155
+ // #region Variables
172
156
 
173
157
  const EXPRESSION_CLOBBERED_NAME = /^(id|name)$/i;
174
158
 
@@ -220,15 +204,12 @@ export const booleanAttributes: readonly string[] = Object.freeze([
220
204
  'selected',
221
205
  ]);
222
206
 
223
- const booleanAttributesSet = new Set(booleanAttributes);
207
+ export const booleanAttributesSet = new Set(booleanAttributes);
224
208
 
225
- const elementEvents: Record<string, Record<string, string>> = {
226
- DETAILS: {open: 'toggle'},
227
- INPUT: {checked: 'change', value: 'input'},
228
- SELECT: {value: 'change'},
229
- TEXTAREA: {value: 'input'},
230
- };
209
+ export const dispatchedAttributes = new Set(['checked', 'open', 'value']);
231
210
 
232
211
  const formElement = document.createElement('form');
233
212
 
234
213
  let textArea: HTMLTextAreaElement;
214
+
215
+ // #endregion
@@ -1,22 +1,30 @@
1
1
  import {isNullableOrWhitespace} from '@oscarpalmer/atoms/is';
2
+ import {kebabCase} from '@oscarpalmer/atoms/string/case';
2
3
  import {isHTMLOrSVGElement} from '../is';
3
4
  import {isAttribute} from './attribute';
4
5
 
6
+ // #region Functions
7
+
8
+ function normalizeKey(key: string, style?: boolean): string {
9
+ return style && key.startsWith(CSS_VARIABLE_PREFIX) ? key : kebabCase(key);
10
+ }
11
+
5
12
  export function setElementValue(
6
13
  element: Element,
7
14
  first: unknown,
8
15
  second: unknown,
9
16
  third: unknown,
10
- callback: (element: Element, key: string, value: unknown) => void,
17
+ callback: (element: Element, key: string, value: unknown, dispatch: boolean) => void,
18
+ style?: boolean,
11
19
  ): void {
12
20
  if (!isHTMLOrSVGElement(element)) {
13
21
  return;
14
22
  }
15
23
 
16
24
  if (typeof first === 'string') {
17
- setElementValues(element, first, second, third, callback);
25
+ setElementValues(element, first, second, third, callback, style);
18
26
  } else if (isAttribute(first)) {
19
- setElementValues(element, first.name, first.value, third, callback);
27
+ setElementValues(element, first.name, first.value, third, callback, style);
20
28
  }
21
29
  }
22
30
 
@@ -25,14 +33,17 @@ export function setElementValues(
25
33
  first: unknown,
26
34
  second: unknown,
27
35
  third: unknown,
28
- callback: (element: Element, key: string, value: unknown, dispatch?: unknown) => void,
36
+ callback: (element: Element, key: string, value: unknown, dispatch: boolean) => void,
37
+ style?: boolean,
29
38
  ): void {
30
39
  if (!isHTMLOrSVGElement(element)) {
31
40
  return;
32
41
  }
33
42
 
43
+ const dispatch = third !== false;
44
+
34
45
  if (typeof first === 'string') {
35
- callback(element, first, second, third);
46
+ callback(element, normalizeKey(first, style), second, dispatch);
36
47
 
37
48
  return;
38
49
  }
@@ -50,7 +61,7 @@ export function setElementValues(
50
61
  const entry = entries[index];
51
62
 
52
63
  if (typeof entry === 'object' && typeof entry?.name === 'string') {
53
- callback(element, entry.name, entry.value, third);
64
+ callback(element, normalizeKey(entry.name, style), entry.value, dispatch);
54
65
  }
55
66
  }
56
67
  }
@@ -70,3 +81,11 @@ export function updateElementValue(
70
81
  set.call(element, key, json ? JSON.stringify(value) : String(value));
71
82
  }
72
83
  }
84
+
85
+ // #endregion Functions
86
+
87
+ // #region Variables
88
+
89
+ const CSS_VARIABLE_PREFIX = '--';
90
+
91
+ // #endregion
@@ -1,6 +1,8 @@
1
1
  import {parse} from '@oscarpalmer/atoms/string';
2
2
  import {camelCase, kebabCase} from '@oscarpalmer/atoms/string/case';
3
3
 
4
+ // #region Functions
5
+
4
6
  export function getBoolean(value: unknown, defaultValue?: boolean): boolean {
5
7
  return typeof value === 'boolean' ? value : (defaultValue ?? false);
6
8
  }
@@ -20,6 +22,10 @@ export function getStyleValue(
20
22
  property: string,
21
23
  computed: boolean,
22
24
  ): string | undefined {
25
+ if (property.startsWith(CSS_VARIABLE_PREFIX)) {
26
+ return (element as HTMLElement).style.getPropertyValue(property);
27
+ }
28
+
23
29
  const name = camelCase(property);
24
30
 
25
31
  return computed
@@ -27,4 +33,12 @@ export function getStyleValue(
27
33
  : (element as HTMLElement).style[name as never];
28
34
  }
29
35
 
36
+ // #endregion
37
+
38
+ // #region Variables
39
+
30
40
  export const EXPRESSION_DATA_PREFIX = /^data-/i;
41
+
42
+ const CSS_VARIABLE_PREFIX = '--';
43
+
44
+ // #endregion
@@ -1,3 +1,5 @@
1
+ // #region Functions
2
+
1
3
  /**
2
4
  * Is the value an event target?
3
5
  * @param value Value to check
@@ -21,3 +23,5 @@ export function isEventTarget(value: unknown): value is EventTarget {
21
23
  export function isHTMLOrSVGElement(value: unknown): value is HTMLElement | SVGElement {
22
24
  return value instanceof HTMLElement || value instanceof SVGElement;
23
25
  }
26
+
27
+ // #endregion
@@ -0,0 +1,42 @@
1
+ import type {PlainObject} from '@oscarpalmer/atoms/models';
2
+ import {camelCase} from '@oscarpalmer/atoms/string/case';
3
+
4
+ // #region Functions
5
+
6
+ export function updateProperty(
7
+ element: Element,
8
+ name: string,
9
+ value: unknown,
10
+ dispatch: boolean,
11
+ ): void {
12
+ let property = name;
13
+
14
+ if (!(property in element)) {
15
+ property = camelCase(name);
16
+ }
17
+
18
+ if (!(property in element) || Object.is((element as unknown as PlainObject)[property], value)) {
19
+ return;
20
+ }
21
+
22
+ (element as unknown as PlainObject)[property] = value;
23
+
24
+ const event = dispatch && elementEvents[element.tagName]?.[property];
25
+
26
+ if (typeof event === 'string') {
27
+ element.dispatchEvent(new Event(event, {bubbles: true, cancelable: true}));
28
+ }
29
+ }
30
+
31
+ // #endregion
32
+
33
+ // #region Variables
34
+
35
+ const elementEvents: Record<string, Record<string, string>> = {
36
+ DETAILS: {open: 'toggle'},
37
+ INPUT: {checked: 'change', value: 'input'},
38
+ SELECT: {value: 'change'},
39
+ TEXTAREA: {value: 'input'},
40
+ };
41
+
42
+ // #endregion
package/src/is.ts CHANGED
@@ -1,3 +1,5 @@
1
+ // #region Functions
2
+
1
3
  /**
2
4
  * Is the value a child node?
3
5
  * @param value Value to check
@@ -36,7 +38,9 @@ export function isInDocument(node: Node, doc?: Document): boolean {
36
38
  : node.ownerDocument === doc && doc.contains(node);
37
39
  }
38
40
 
39
- //
41
+ // #endregion
42
+
43
+ // #region Variables
40
44
 
41
45
  const CHILD_NODE_TYPES: Set<number> = new Set([
42
46
  Node.ELEMENT_NODE,
@@ -46,6 +50,10 @@ const CHILD_NODE_TYPES: Set<number> = new Set([
46
50
  Node.DOCUMENT_TYPE_NODE,
47
51
  ]);
48
52
 
49
- //
53
+ // #endregion
54
+
55
+ // #region Exports
50
56
 
51
57
  export {isEventTarget, isHTMLOrSVGElement} from './internal/is';
58
+
59
+ // #endregion
@@ -0,0 +1,73 @@
1
+ import type {Primitive} from '@oscarpalmer/atoms/models';
2
+ import {camelCase} from '@oscarpalmer/atoms/string/case';
3
+ import {isHTMLOrSVGElement} from '../internal/is';
4
+
5
+ // #region Types
6
+
7
+ type GetProperties<Target extends Element> = {
8
+ [Property in keyof Target as Target[Property] extends Primitive
9
+ ? Property
10
+ : never]?: Target[Property];
11
+ };
12
+
13
+ // #endregion
14
+
15
+ // #region Functions
16
+
17
+ /**
18
+ * Get the values of one or more properties on an element
19
+ * @param target Target element
20
+ * @param properties Properties to get
21
+ * @returns Property values
22
+ */
23
+ export function getProperties<Target extends Element, Property extends keyof GetProperties<Target>>(
24
+ target: Target,
25
+ properties: Property[],
26
+ ): Pick<GetProperties<Target>, Property> {
27
+ const values: Partial<GetProperties<Target>> = {};
28
+
29
+ if (!isHTMLOrSVGElement(target) || !Array.isArray(properties)) {
30
+ return values as Pick<GetProperties<Target>, Property>;
31
+ }
32
+
33
+ const {length} = properties;
34
+
35
+ for (let index = 0; index < length; index += 1) {
36
+ const property = properties[index];
37
+
38
+ if (typeof property === 'string') {
39
+ values[property] = getPropertyValue(target, property) as GetProperties<Target>[Property];
40
+ }
41
+ }
42
+
43
+ return values as Pick<GetProperties<Target>, Property>;
44
+ }
45
+
46
+ /**
47
+ * Get the value of a property on an element
48
+ * @param target Target element
49
+ * @param property Property to get
50
+ * @returns Property value
51
+ */
52
+ export function getProperty<Target extends Element, Property extends keyof GetProperties<Target>>(
53
+ target: Target,
54
+ property: Property,
55
+ ): GetProperties<Target>[Property] {
56
+ if (isHTMLOrSVGElement(target) && typeof property === 'string') {
57
+ return getPropertyValue(target, property) as GetProperties<Target>[Property];
58
+ }
59
+ }
60
+
61
+ function getPropertyValue(element: HTMLElement, property: string): unknown {
62
+ let actual = property;
63
+
64
+ if (!(actual in element)) {
65
+ actual = camelCase(actual);
66
+ }
67
+
68
+ if (actual in element) {
69
+ return element[actual as keyof HTMLElement];
70
+ }
71
+ }
72
+
73
+ // #endregion
@@ -0,0 +1,2 @@
1
+ export * from './get.property';
2
+ export * from './set.property';
@@ -0,0 +1,103 @@
1
+ import {isPlainObject} from '@oscarpalmer/atoms/is';
2
+ import type {PlainObject, Primitive} from '@oscarpalmer/atoms/models';
3
+ import {setAttribute} from '../attribute';
4
+ import type {DispatchedAttributeName} from '../attribute/set.attribute';
5
+ import {booleanAttributesSet, dispatchedAttributes} from '../internal/attribute';
6
+ import {updateProperty} from '../internal/property';
7
+ import {isHTMLOrSVGElement} from '../is';
8
+
9
+ // #region Types
10
+
11
+ type DispatchedPropertyValue<
12
+ Target extends Element,
13
+ Property extends DispatchedAttributeName,
14
+ > = Property extends keyof SetProperties<Target> ? SetProperties<Target>[Property] : never;
15
+
16
+ type SetProperties<Target extends Element> = {
17
+ [Property in keyof Target as Target[Property] extends Primitive
18
+ ? Property
19
+ : never]?: Target[Property];
20
+ };
21
+
22
+ // #endregion
23
+
24
+ // #region Functions
25
+
26
+ /**
27
+ * Set the values of one or more properties on an element
28
+ *
29
+ * Also updates attributes for boolean/dispatchable properties, and if `dispatch` is `true`, will dispatch events for dispatchable properties
30
+ * @param target Target element
31
+ * @param properties Properties to set
32
+ * @param dispatch Dispatch events for properties? _(defaults to `true`)_
33
+ */
34
+ export function setProperties<Target extends Element>(
35
+ target: Target,
36
+ properties: SetProperties<Target>,
37
+ dispatch?: boolean,
38
+ ): void {
39
+ if (!isHTMLOrSVGElement(target) || !isPlainObject(properties)) {
40
+ return;
41
+ }
42
+
43
+ const shouldDispatch = dispatch !== false;
44
+
45
+ const keys = Object.keys(properties);
46
+ const {length} = keys;
47
+
48
+ for (let index = 0; index < length; index += 1) {
49
+ setPropertyValue(target, keys[index], (properties as PlainObject)[keys[index]], shouldDispatch);
50
+ }
51
+ }
52
+
53
+ /**
54
+ * Set the value for a dispatchable property on an element
55
+ * @param target Target element
56
+ * @param property Property to set
57
+ * @param value Value to set
58
+ * @param dispatch Dispatch event for property? _(defaults to `true`)_
59
+ */
60
+ export function setProperty<Target extends Element, Property extends DispatchedAttributeName>(
61
+ target: Target,
62
+ property: Property,
63
+ value: DispatchedPropertyValue<Target, Property>,
64
+ dispatch?: boolean,
65
+ ): void;
66
+
67
+ /**
68
+ * Set the value for a property on an element
69
+ * @param target Target element
70
+ * @param property Property to set
71
+ * @param value Value to set
72
+ */
73
+ export function setProperty<Target extends Element, Property extends keyof SetProperties<Target>>(
74
+ target: Target,
75
+ property: Property,
76
+ value: SetProperties<Target>[Property],
77
+ ): void;
78
+
79
+ export function setProperty<Target extends Element, Property extends keyof SetProperties<Target>>(
80
+ target: Target,
81
+ property: Property,
82
+ value: SetProperties<Target>[Property],
83
+ dispatch?: boolean,
84
+ ): void {
85
+ if (isHTMLOrSVGElement(target) && typeof property === 'string') {
86
+ setPropertyValue(target, property, value, dispatch !== false);
87
+ }
88
+ }
89
+
90
+ function setPropertyValue(
91
+ element: Element,
92
+ property: string,
93
+ value: unknown,
94
+ dispatch: boolean,
95
+ ): void {
96
+ if (booleanAttributesSet.has(property.toLowerCase()) || dispatchedAttributes.has(property)) {
97
+ setAttribute(element, property as never, value, dispatch);
98
+ } else {
99
+ updateProperty(element, property, value, dispatch);
100
+ }
101
+ }
102
+
103
+ // #endregion