@shgysk8zer0/polyfills 0.3.0 → 0.3.2

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.
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @copyright 2023 Chris Zuber <admin@kernvalley.us>
2
+ * @copyright 2023-2024 Chris Zuber <admin@kernvalley.us>
3
3
  */
4
4
  import { SanitizerConfig as defaultConfig } from './SanitizerConfigW3C.js';
5
5
  import { createPolicy } from './trust.js';
@@ -9,16 +9,21 @@ import { urls } from './attributes.js';
9
9
  export const supported = () => 'Sanitizer' in globalThis;
10
10
  export const nativeSupport = supported();
11
11
 
12
+ const isDataAttr = name => name.length > 5 && name.substring(0, 5) === 'data-';
13
+
12
14
  export const setHTML = function setHTML(el, input, opts = defaultConfig) {
13
15
  const doc = safeParseHTML(input, opts);
14
16
  el.replaceChildren(documentToFragment(doc));
15
17
  };
16
18
 
17
- const allowProtocols = ['https:'];
19
+ const allowProtocols = ['https:', 'blob:'];
20
+
21
+ const HTML_NS = 'http://www.w3.org/1999/xhtml';
18
22
 
19
23
  if (! allowProtocols.includes(location.protocol)) {
20
24
  allowProtocols.push(location.protocol);
21
25
  }
26
+
22
27
  const policyName = 'sanitizer-raw#html';
23
28
  const getPolicy = callOnce(() => createPolicy(policyName, { createHTML: input => input }));
24
29
  const createHTML = input => getPolicy().createHTML(input);
@@ -30,6 +35,12 @@ export function documentToFragment(doc) {
30
35
  return frag;
31
36
  }
32
37
 
38
+ export function addNamesapces(list) {
39
+ const mapped = list.map(allow => typeof allow === 'string' ? ({ name: allow, namespace: HTML_NS }) : allow);
40
+
41
+ return Object.groupBy(mapped, ({ namespace = HTML_NS }) => namespace);
42
+ }
43
+
33
44
  /**
34
45
  * Helper function to adapt to changes in spec
35
46
  */
@@ -170,8 +181,10 @@ export function sanitizeNode(root, opts = defaultConfig) {
170
181
  }
171
182
  } else if (isObject(allowAttributes)) {
172
183
  if (
173
- ! (name in allowAttributes
174
- && ['*', tag].some(sel => allowAttributes[name].includes(sel)))
184
+ ! ((
185
+ name in allowAttributes
186
+ && ['*', tag].some(sel => allowAttributes[name].includes(sel))
187
+ ) || isDataAttr(name))
175
188
  ) {
176
189
  node.removeAttributeNode(attr);
177
190
  }
package/element.js CHANGED
@@ -3,6 +3,123 @@ import { overwriteMethod, polyfillGetterSetter } from './utils.js';
3
3
  import { SanitizerConfig as defaultConfig } from './assets/SanitizerConfigW3C.js';
4
4
  import { setHTML as safeSetHTML, convertToSanitizerConfig } from './assets/sanitizerUtils.js';
5
5
 
6
+ function handlePopover({ currentTarget }) {
7
+ switch(currentTarget.popoverTargetAction) {
8
+ case 'show':
9
+ currentTarget.popoverTargetElement.showPopover();
10
+ break;
11
+
12
+ case 'hide':
13
+ currentTarget.popoverTargetElement.hidePopover();
14
+ break;
15
+
16
+ default:
17
+ currentTarget.popoverTargetElement.togglePopover();
18
+ }
19
+ }
20
+
21
+ export function initPopover(target = document.body) {
22
+ target.querySelectorAll('button[popovertarget], input[type="button"][popovertarget]')
23
+ .forEach(el => el.addEventListener('click', handlePopover));
24
+ }
25
+
26
+ if ((globalThis.ToggleEvent instanceof Function)) {
27
+ class ToggleEvent extends Event {
28
+ #newState;
29
+ #oldState;
30
+
31
+ constructor(type, { newState, oldState }) {
32
+ super(type, { bubbles: true });
33
+ this.#newState = newState;
34
+ this.#oldState = oldState;
35
+ }
36
+
37
+ get newState() {
38
+ return this.#newState;
39
+ }
40
+
41
+ get oldState() {
42
+ return this.#oldState;
43
+ }
44
+ }
45
+
46
+ globalThis.ToggleEvent = ToggleEvent;
47
+ }
48
+
49
+ if (! (HTMLElement.prototype.showPopover instanceof Function)) {
50
+ const isPopoverOpen = el => el.classList.contains('_popover-open');
51
+
52
+ Object.defineProperties(HTMLElement.prototype, {
53
+ showPopover: {
54
+ value: function showPopover() {
55
+ if (! isPopoverOpen(this)) {
56
+ this.dispatchEvent(new ToggleEvent('beforetoggle', { oldState: 'closed', newState: 'open' }));
57
+
58
+ if (this.getAttribute('popover') === 'auto') {
59
+ const controller = new AbortController();
60
+
61
+ document.body.addEventListener('click', ({ target }) => {
62
+ if (! this.contains(target) && ! this.isSameNode(target.popoverTargetElement)) {
63
+ controller.abort();
64
+ this.hidePopover();
65
+ }
66
+ }, { signal: controller.signal, capture: true });
67
+
68
+ document.body.addEventListener('keydown', ({ key }) => {
69
+ if (key === 'Escape') {
70
+ controller.abort();
71
+ this.hidePopover();
72
+ }
73
+ }, { signal: controller.signal, capture: true });
74
+
75
+ document.addEventListener('beforetoggle', ({ target }) => {
76
+ if (! target.isSameNode(this) && target.getAttribute('popover') === 'auto') {
77
+ controller.abort();
78
+ this.hidePopover();
79
+ }
80
+ }, { signal: controller.signal });
81
+ }
82
+
83
+ this.classList.add('_popover-open');
84
+ this.dispatchEvent(new ToggleEvent('toggle', { oldState: 'closed', newState: 'open' }));
85
+ }
86
+ }
87
+ },
88
+ hidePopover: {
89
+ value: function hidePopover() {
90
+ if (isPopoverOpen(this)) {
91
+ this.dispatchEvent(new ToggleEvent('beforetoggle', { oldState: 'open', newState: 'closed' }));
92
+ queueMicrotask(() => this.classList.remove('_popover-open'));
93
+ this.dispatchEvent(new ToggleEvent('toggle', { oldState: 'open', newState: 'closed' }));
94
+ }
95
+ }
96
+ },
97
+ togglePopover: {
98
+ value: function togglePopover() {
99
+ isPopoverOpen(this) ? this.hidePopover() : this.showPopover();
100
+ }
101
+ }
102
+ });
103
+
104
+ Object.defineProperties(HTMLButtonElement.prototype, {
105
+ popoverTargetElement: {
106
+ get() {
107
+ return document.getElementById(this.getAttribute('popovertarget'));
108
+ }
109
+ },
110
+ popoverTargetAction: {
111
+ get() {
112
+ return this.getAttribute('popovertargetaction') || 'toggle';
113
+ },
114
+ set(val) {
115
+ this.setAttribute('popovertargetaction', val);
116
+ }
117
+ }
118
+ });
119
+
120
+ initPopover();
121
+ }
122
+
6
123
  if (! (HTMLScriptElement.supports instanceof Function)) {
7
124
  HTMLScriptElement.supports = function supports(type) {
8
125
  switch(type.toLowerCase()) {
package/iterator.js CHANGED
@@ -287,6 +287,45 @@ if (! (IteratorPrototype.indexed instanceof Function)) {
287
287
  };
288
288
  }
289
289
 
290
+ if (! (IteratorPrototype.chunks instanceof Function)) {
291
+ IteratorPrototype.chunks = function(size) {
292
+ if (! Number.isSafeInteger(size) || size < 1) {
293
+ throw new TypeError('Size must be a positive integer.');
294
+ } else {
295
+ const iter = this;
296
+ let done = false;
297
+
298
+ return Iterator.from({
299
+ next() {
300
+ if (done) {
301
+ return { done };
302
+ } else {
303
+ const items = [];
304
+ let i = 0;
305
+
306
+ while(i++ < size && ! done) {
307
+ const next = iter.next();
308
+
309
+ if (next.done) {
310
+ done = true;
311
+
312
+ if (typeof next.value !== 'undefined') {
313
+ items.push(next.value);
314
+ }
315
+ break;
316
+ } else {
317
+ items.push(next.value);
318
+ }
319
+ }
320
+
321
+ return { value: items, done: false };
322
+ }
323
+ }
324
+ });
325
+ }
326
+ };
327
+ }
328
+
290
329
  if (! (Iterator.from instanceof Function)) {
291
330
  Iterator.from = function from(obj) {
292
331
  if (typeof obj !== 'object' || obj === null) {
package/math.js CHANGED
@@ -1,69 +1,74 @@
1
- (function() {
2
- 'use strict';
1
+ if (! Number.hasOwnProperty('isSafeInteger')) {
2
+ Number.MAX_SAFE_INTEGER = 2**53 -1;
3
+ Number.MIN_SAFE_INTEGER = -Number.MAX_SAFE_INTEGER;
4
+ Number.isSafeInteger = num => num <= Number.MAX_SAFE_INTEGER && num >= Number.MIN_SAFE_INTEGER;
5
+ }
3
6
 
4
- if (! Number.hasOwnProperty('isSafeInteger')) {
5
- Number.MAX_SAFE_INTEGER = 2**53 -1;
6
- Number.MIN_SAFE_INTEGER = -Number.MAX_SAFE_INTEGER;
7
- Number.isSafeInteger = num => num <= Number.MAX_SAFE_INTEGER && num >= Number.MIN_SAFE_INTEGER;
8
- }
7
+ if (! Number.hasOwnProperty('EPSILON')) {
8
+ Number.EPSILON = 2**-52;
9
+ }
9
10
 
10
- if (! Number.hasOwnProperty('EPSILON')) {
11
- Number.EPSILON = 2**-52;
12
- }
11
+ if (! Math.hasOwnProperty('sign')) {
12
+ Math.sign = x => ((x > 0) - (x < 0)) || +x;
13
+ }
13
14
 
14
- if (! Math.hasOwnProperty('sign')) {
15
- Math.sign = x => ((x > 0) - (x < 0)) || +x;
16
- }
15
+ if (! Math.hasOwnProperty('trunc')) {
16
+ Math.trunc = x => {
17
+ const n = x - x%1;
18
+ return n===0 && (x<0 || (x===0 && (1/x !== 1/0))) ? -0 : n;
19
+ };
20
+ }
17
21
 
18
- if (! Math.hasOwnProperty('trunc')) {
19
- Math.trunc = x => {
20
- const n = x - x%1;
21
- return n===0 && (x<0 || (x===0 && (1/x !== 1/0))) ? -0 : n;
22
- };
23
- }
22
+ if (! Math.hasOwnProperty('expm1')) {
23
+ Math.expm1 = x => Math.exp(x) - 1;
24
+ }
24
25
 
25
- if (! Math.hasOwnProperty('expm1')) {
26
- Math.expm1 = x => Math.exp(x) - 1;
27
- }
26
+ if (! Math.hasOwnProperty('hypot')) {
27
+ Math.hypot = (...nums) => Math.sqrt(nums.reduce((sum, num) => sum + num**2, 0));
28
+ }
28
29
 
29
- if (! Math.hasOwnProperty('hypot')) {
30
- Math.hypot = (...nums) => Math.sqrt(nums.reduce((sum, num) => sum + num**2, 0));
31
- }
30
+ if (! Math.hasOwnProperty('cbrt')) {
31
+ Math.cbrt = x => x**(1/3);
32
+ }
32
33
 
33
- if (! Math.hasOwnProperty('cbrt')) {
34
- Math.cbrt = x => x**(1/3);
35
- }
34
+ if (! Math.hasOwnProperty('log10')) {
35
+ Math.log10 = x => Math.log(x) * Math.LOG10E;
36
+ }
36
37
 
37
- if (! Math.hasOwnProperty('log10')) {
38
- Math.log10 = x => Math.log(x) * Math.LOG10E;
39
- }
38
+ if (! Math.hasOwnProperty('log2')) {
39
+ Math.log2 = x => Math.log(x) * Math.LOG2E;
40
+ }
40
41
 
41
- if (! Math.hasOwnProperty('log2')) {
42
- Math.log2 = x => Math.log(x) * Math.LOG2E;
43
- }
42
+ if (! Math.hasOwnProperty('log1p')) {
43
+ Math.log1p = x => Math.log(1 + x);
44
+ }
44
45
 
45
- if (! Math.hasOwnProperty('log1p')) {
46
- Math.log1p = x => Math.log(1 + x);
47
- }
46
+ if (! Math.hasOwnProperty('fround')) {
47
+ Math.fround = (function (array) {
48
+ return function(x) {
49
+ return array[0] = x, array[0];
50
+ };
51
+ })(new Float32Array(1));
52
+ }
48
53
 
49
- if (! Math.hasOwnProperty('fround')) {
50
- Math.fround = (function (array) {
51
- return function(x) {
52
- return array[0] = x, array[0];
53
- };
54
- })(new Float32Array(1));
55
- }
54
+ if (! (Math.clamp instanceof Function)) {
55
+ Math.clamp = function(value, min, max) {
56
+ return Math.min(Math.max(value, min), max);
57
+ };
58
+ }
56
59
 
57
- if (! (Math.clamp instanceof Function)) {
58
- Math.clamp = function(value, min, max) {
59
- return Math.min(Math.max(value, min), max);
60
- };
61
- }
60
+ /*
61
+ * Question of if it will be `Math.clamp` or `Math.constrain`
62
+ */
63
+ if (! (Math.constrain instanceof Function)) {
64
+ Math.constrain = Math.clamp;
65
+ }
62
66
 
63
- /*
64
- * Question of if it will be `Math.clamp` or `Math.constrain`
65
- */
66
- if (! (Math.constrain instanceof Function)) {
67
- Math.constrain = Math.clamp;
68
- }
69
- })();
67
+ /**
68
+ * @see https://github.com/tc39/proposal-math-sum
69
+ */
70
+ if(! (Math.sum instanceof Function)) {
71
+ Math.sum = function(...nums) {
72
+ return nums.map(num => parseFloat(num)).reduce((sum, num) => sum + num);
73
+ };
74
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shgysk8zer0/polyfills",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "A collection of JavaScript polyfills",
package/popover.css ADDED
@@ -0,0 +1,25 @@
1
+ @supports not selector(:popover-open) {
2
+ :is([popover]:not(._popover-open)) {
3
+ display: none;
4
+ }
5
+
6
+ :is([popover]._popover-open) {
7
+ position: fixed;
8
+ z-index: 2147483647;
9
+ margin: auto;
10
+ inset: 0;
11
+ border: 3px solid currentColor;
12
+ height: fit-content;
13
+ width: fit-content;
14
+ padding: 0.25em;
15
+ background-color: #ffffff;
16
+ color: #070707;
17
+ }
18
+
19
+ @media (prefers-color-scheme: dark) {
20
+ :is([popover]._popover-open) {
21
+ background-color: #1c1b22;
22
+ color: #fbfbfe;
23
+ }
24
+ }
25
+ }
package/popover.js ADDED
@@ -0,0 +1,118 @@
1
+ // function handlePopover({ currentTarget }) {
2
+ // currentTarget.addEventListener('click', ({ currentTarget }) => {
3
+ // switch(currentTarget.popoverTargetAction) {
4
+ // case 'show':
5
+ // currentTarget.popoverTargetElement.showPopover();
6
+ // break;
7
+
8
+ // case 'hide':
9
+ // currentTarget.popoverTargetElement.hidePopover();
10
+ // break;
11
+
12
+ // default:
13
+ // currentTarget.popoverTargetElement.togglePopover();
14
+ // }
15
+ // }, { capture: true });
16
+ // }
17
+
18
+ // export function initPopover(target = document.body) {
19
+ // target.querySelectorAll('button[popovertarget], input[type="button"][popovertarget]')
20
+ // .forEach(el => el.addEventListener('click', handlePopover));
21
+ // }
22
+
23
+ // if ((globalThis.ToggleEvent instanceof Function)) {
24
+ // class ToggleEvent extends Event {
25
+ // #newState;
26
+ // #oldState;
27
+
28
+ // constructor(type, { newState, oldState }) {
29
+ // super(type, { bubbles: true });
30
+ // this.#newState = newState;
31
+ // this.#oldState = oldState;
32
+ // }
33
+
34
+ // get newState() {
35
+ // return this.#newState;
36
+ // }
37
+
38
+ // get oldState() {
39
+ // return this.#oldState;
40
+ // }
41
+ // }
42
+
43
+ // globalThis.ToggleEvent = ToggleEvent;
44
+ // }
45
+
46
+ // if (! (HTMLElement.prototype.showPopover instanceof Function)) {
47
+ // const isPopoverOpen = el => el.classList.contains('_popover-open');
48
+
49
+ // Object.defineProperties(HTMLElement.prototype, {
50
+ // showPopover: {
51
+ // value: function showPopover() {
52
+ // if (! isPopoverOpen(this)) {
53
+ // this.dispatchEvent(new ToggleEvent('beforetoggle', { oldState: 'closed', newState: 'open' }));
54
+
55
+ // if (this.getAttribute('popover') === 'auto') {
56
+ // const controller = new AbortController();
57
+
58
+ // document.body.addEventListener('click', ({ target }) => {
59
+ // if (! this.contains(target) && ! this.isSameNode(target.popoverTargetElement)) {
60
+ // controller.abort();
61
+ // this.hidePopover();
62
+ // }
63
+ // }, { signal: controller.signal, capture: true });
64
+
65
+ // document.body.addEventListener('keydown', ({ key }) => {
66
+ // if (key === 'Escape') {
67
+ // controller.abort();
68
+ // this.hidePopover();
69
+ // }
70
+ // }, { signal: controller.signal, capture: true });
71
+
72
+ // document.addEventListener('beforetoggle', ({ target }) => {
73
+ // if (! target.isSameNode(this) && target.getAttribute('popover') === 'auto') {
74
+ // controller.abort();
75
+ // this.hidePopover();
76
+ // }
77
+ // }, { signal: controller.signal });
78
+ // }
79
+
80
+ // this.classList.add('_popover-open');
81
+ // this.dispatchEvent(new ToggleEvent('toggle', { oldState: 'closed', newState: 'open' }));
82
+ // }
83
+ // }
84
+ // },
85
+ // hidePopover: {
86
+ // value: function hidePopover() {
87
+ // if (isPopoverOpen(this)) {
88
+ // this.dispatchEvent(new ToggleEvent('beforetoggle', { oldState: 'open', newState: 'closed' }));
89
+ // queueMicrotask(() => this.classList.remove('_popover-open'));
90
+ // this.dispatchEvent(new ToggleEvent('toggle', { oldState: 'open', newState: 'closed' }));
91
+ // }
92
+ // }
93
+ // },
94
+ // togglePopover: {
95
+ // value: function togglePopover() {
96
+ // isPopoverOpen(this) ? this.hidePopover() : this.showPopover();
97
+ // }
98
+ // }
99
+ // });
100
+
101
+ // Object.defineProperties(HTMLButtonElement.prototype, {
102
+ // popoverTargetElement: {
103
+ // get() {
104
+ // return document.getElementById(this.getAttribute('popovertarget'));
105
+ // }
106
+ // },
107
+ // popoverTargetAction: {
108
+ // get() {
109
+ // return this.getAttribute('popovertargetaction') || 'toggle';
110
+ // },
111
+ // set(val) {
112
+ // this.setAttribute('popovertargetaction', val);
113
+ // }
114
+ // }
115
+ // });
116
+
117
+ // initPopover();
118
+ // }