@3sln/dodo 0.0.2 → 0.0.4

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/dodo.js CHANGED
@@ -39,22 +39,25 @@ function defaultFlattenSeq(items) {
39
39
  }
40
40
 
41
41
  function isIterable(x) {
42
- return Array.isArray(x) || (x != null && typeof x[Symbol.iterator] === 'function' && typeof x !== 'string');
42
+ return (
43
+ Array.isArray(x) ||
44
+ (x != null && typeof x[Symbol.iterator] === 'function' && typeof x !== 'string')
45
+ );
43
46
  }
44
47
 
45
48
  export const settings = {
46
49
  shouldUpdate: defaultShouldUpdate,
47
- isMap: (x) => x?.constructor === Object,
48
- mapIter: (m) => Object.entries(m),
50
+ isMap: x => x?.constructor === Object,
51
+ mapIter: m => Object.entries(m),
49
52
  mapGet: (m, k) => m[k],
50
53
  isSeq: isIterable,
51
54
  flattenSeq: defaultFlattenSeq,
52
- seqIter: (s) => s,
53
- convertTagName: (t) => t,
54
- convertPropName: (p) => p,
55
- convertStyleName: (s) => s,
56
- convertDataName: (d) => d,
57
- convertClassName: (c) => c,
55
+ seqIter: s => s,
56
+ convertTagName: t => t,
57
+ convertPropName: p => p,
58
+ convertStyleName: s => s,
59
+ convertDataName: d => d,
60
+ convertClassName: c => c,
58
61
  };
59
62
 
60
63
  function flattenVNodeChildrenIntoArray(array, children) {
@@ -107,7 +110,10 @@ export function h(tag, props, ...children) {
107
110
  children.unshift(props);
108
111
  props = EMPTY_OBJECT;
109
112
  }
110
- return new VNode(ELEMENT_NODE, settings.convertTagName(tag), [props ?? EMPTY_OBJECT, ...children]);
113
+ return new VNode(ELEMENT_NODE, settings.convertTagName(tag), [
114
+ props ?? EMPTY_OBJECT,
115
+ ...children,
116
+ ]);
111
117
  }
112
118
 
113
119
  export function o(tag, props) {
@@ -293,7 +299,10 @@ function reconcileListeners(target, hooks) {
293
299
  target.addEventListener(name, listener);
294
300
  break;
295
301
  case 'object':
296
- target.addEventListener(name, listener.listener, { capture: listener.capture, passive: listener.passive });
302
+ target.addEventListener(name, listener.listener, {
303
+ capture: listener.capture,
304
+ passive: listener.passive,
305
+ });
297
306
  break;
298
307
  }
299
308
  }
@@ -321,7 +330,10 @@ function reconcileListeners(target, hooks) {
321
330
  target.addEventListener(name, listener);
322
331
  break;
323
332
  case 'object':
324
- target.addEventListener(name, listener.listener, { capture: listener.capture, passive: listener.passive });
333
+ target.addEventListener(name, listener.listener, {
334
+ capture: listener.capture,
335
+ passive: listener.passive,
336
+ });
325
337
  break;
326
338
  }
327
339
  }
@@ -345,8 +357,10 @@ function reconcileListeners(target, hooks) {
345
357
  function newElementNamespace(parentNode, newNodeTag) {
346
358
  if (parentNode.namespaceURI === 'http://www.w3.org/1999/xhtml') {
347
359
  switch (newNodeTag) {
348
- case 'svg': return 'http://www.w3.org/2000/svg';
349
- case 'math': return 'http://www.w3.org/1998/Math/MathML';
360
+ case 'svg':
361
+ return 'http://www.w3.org/2000/svg';
362
+ case 'math':
363
+ return 'http://www.w3.org/1998/Math/MathML';
350
364
  }
351
365
  }
352
366
  return parentNode.namespaceURI ?? parentNode.host?.namespaceURI ?? 'http://www.w3.org/1999/xhtml';
@@ -404,7 +418,7 @@ function reconcileNode(target) {
404
418
 
405
419
  function createElementNode(parentNode, tag, vdom) {
406
420
  const el = parentNode.ownerDocument.createElementNS(newElementNamespace(parentNode, tag), tag);
407
- el[NODE_STATE] = { originalProps: {}, newVdom: vdom };
421
+ el[NODE_STATE] = {originalProps: {}, newVdom: vdom};
408
422
  return el;
409
423
  }
410
424
 
@@ -466,10 +480,10 @@ function removePathFromFocusWithinSet(set, newPath) {
466
480
 
467
481
  function installFocusTrackingForDocument(doc) {
468
482
  const focusWithinSet = new Set();
469
- doc.addEventListener('focusin', (event) => {
483
+ doc.addEventListener('focusin', event => {
470
484
  addPathToFocusWithinSet(focusWithinSet, event.composedPath());
471
485
  });
472
- doc.addEventListener('focusout', (event) => {
486
+ doc.addEventListener('focusout', event => {
473
487
  const newPath = event.relatedTarget ? event.composedPath() : [];
474
488
  removePathFromFocusWithinSet(focusWithinSet, newPath);
475
489
  });
@@ -524,7 +538,7 @@ function reconcileElementChildren(target, childrenIterable) {
524
538
 
525
539
  let oldNodesPoolForTag = oldVNodeNodesPool.get(vdom.tag);
526
540
  if (!oldNodesPoolForTag) {
527
- oldNodesPoolForTag = { nodesForKey: new Map(), nodesWithoutKey: [] };
541
+ oldNodesPoolForTag = {nodesForKey: new Map(), nodesWithoutKey: []};
528
542
  oldVNodeNodesPool.set(vdom.tag, oldNodesPoolForTag);
529
543
  }
530
544
 
@@ -597,7 +611,11 @@ function reconcileElementChildren(target, childrenIterable) {
597
611
  const newChild = newDomChildren[i];
598
612
  const existingChildAtPosition = target.childNodes[i];
599
613
  if (newChild !== existingChildAtPosition) {
600
- (newChild.isConnected ? moveBefore : insertBefore).call(target, newChild, existingChildAtPosition);
614
+ (newChild.isConnected ? moveBefore : insertBefore).call(
615
+ target,
616
+ newChild,
617
+ existingChildAtPosition,
618
+ );
601
619
  }
602
620
  const state = newChild[NODE_STATE];
603
621
  if (state?.newVdom) {
@@ -673,13 +691,13 @@ export function reconcile(target, vdom) {
673
691
  switch (vdom.type) {
674
692
  case ELEMENT_NODE:
675
693
  case OPAQUE_NODE:
676
- if (0 !== target.nodeName.localeCompare(vdom.tag, undefined, { sensitivity: 'base' })) {
694
+ if (0 !== target.nodeName.localeCompare(vdom.tag, undefined, {sensitivity: 'base'})) {
677
695
  throw new Error('incompatible target for vdom');
678
696
  }
679
697
  break;
680
698
  }
681
699
 
682
- target[NODE_STATE] = state = { originalProps: {}, newVdom: vdom };
700
+ target[NODE_STATE] = state = {originalProps: {}, newVdom: vdom};
683
701
  try {
684
702
  vdom.hooks?.$attach?.(target);
685
703
  if (vdom.type === SPECIAL_NODE) {
package/index.js CHANGED
@@ -1,10 +1,10 @@
1
1
  import vdom from './src/vdom.js';
2
2
 
3
3
  // Create a default API instance by calling the factory with no settings.
4
- const { h, alias, special, reconcile, settings } = vdom();
4
+ const {h, alias, special, reconcile, settings} = vdom();
5
5
 
6
- export { vdom, h, alias, special, reconcile, settings };
6
+ export {vdom, h, alias, special, reconcile, settings};
7
7
 
8
8
  // Export all the HTML helpers.
9
9
  export * from './src/html.js';
10
- export { schedule, flush, clear } from './src/scheduler.js';
10
+ export {schedule, flush, clear} from './src/scheduler.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@3sln/dodo",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "description": "A minimal, configurable virtual DOM library.",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -23,9 +23,13 @@
23
23
  ],
24
24
  "devDependencies": {
25
25
  "@types/bun": "latest",
26
- "happy-dom": "^18.0.1"
26
+ "happy-dom": "^18.0.1",
27
+ "prettier": "^3.6.2"
27
28
  },
28
29
  "publishConfig": {
29
30
  "access": "public"
31
+ },
32
+ "scripts": {
33
+ "format": "prettier -w \"**/*.js\""
30
34
  }
31
35
  }
package/src/html.js CHANGED
@@ -1,9 +1,9 @@
1
- import { h } from '../index.js';
1
+ import {h} from '../index.js';
2
2
 
3
3
  // Document metadata
4
4
  export const title = (...args) => h('title', ...args);
5
- export const meta = (props) => h('meta', props);
6
- export const link = (props) => h('link', props);
5
+ export const meta = props => h('meta', props);
6
+ export const link = props => h('link', props);
7
7
  export const style = (...args) => h('style', ...args);
8
8
  export const script = (...args) => h('script', ...args);
9
9
  export const noscript = (...args) => h('noscript', ...args);
@@ -67,21 +67,21 @@ export const u = (...args) => h('u', ...args);
67
67
  export const wbr = () => h('wbr');
68
68
 
69
69
  // Image and multimedia
70
- export const area = (props) => h('area', props);
70
+ export const area = props => h('area', props);
71
71
  export const audio = (...args) => h('audio', ...args);
72
- export const img = (props) => h('img', props);
72
+ export const img = props => h('img', props);
73
73
  export const map = (...args) => h('map', ...args);
74
- export const track = (props) => h('track', props);
74
+ export const track = props => h('track', props);
75
75
  export const video = (...args) => h('video', ...args);
76
76
 
77
77
  // Embedded content
78
- export const embed = (props) => h('embed', props);
78
+ export const embed = props => h('embed', props);
79
79
  export const iframe = (...args) => h('iframe', ...args);
80
80
  export const object = (...args) => h('object', ...args);
81
- export const param = (props) => h('param', props);
81
+ export const param = props => h('param', props);
82
82
  export const picture = (...args) => h('picture', ...args);
83
83
  export const portal = (...args) => h('portal', ...args);
84
- export const source = (props) => h('source', props);
84
+ export const source = props => h('source', props);
85
85
 
86
86
  // SVG and MathML
87
87
  export const svg = (...args) => h('svg', ...args);
@@ -92,7 +92,7 @@ export const canvas = (...args) => h('canvas', ...args);
92
92
 
93
93
  // Table content
94
94
  export const caption = (...args) => h('caption', ...args);
95
- export const col = (props) => h('col', props);
95
+ export const col = props => h('col', props);
96
96
  export const colgroup = (...args) => h('colgroup', ...args);
97
97
  export const table = (...args) => h('table', ...args);
98
98
  export const tbody = (...args) => h('tbody', ...args);
@@ -107,7 +107,7 @@ export const button = (...args) => h('button', ...args);
107
107
  export const datalist = (...args) => h('datalist', ...args);
108
108
  export const fieldset = (...args) => h('fieldset', ...args);
109
109
  export const form = (...args) => h('form', ...args);
110
- export const input = (props) => h('input', props);
110
+ export const input = props => h('input', props);
111
111
  export const label = (...args) => h('label', ...args);
112
112
  export const legend = (...args) => h('legend', ...args);
113
113
  export const meter = (...args) => h('meter', ...args);
package/src/scheduler.js CHANGED
@@ -6,74 +6,74 @@ const FRAME_BUDGET = 10; // ms
6
6
  const CHUNK_SIZE = 100;
7
7
 
8
8
  function _runTasks(tasks) {
9
- for (const f of tasks) {
10
- try {
11
- f();
12
- } catch (err) {
13
- console.error("Error in scheduled function:", err);
14
- }
9
+ for (const f of tasks) {
10
+ try {
11
+ f();
12
+ } catch (err) {
13
+ console.error('Error in scheduled function:', err);
15
14
  }
15
+ }
16
16
  }
17
17
 
18
18
  function runQueue() {
19
- const startTime = performance.now();
20
- frameId = 0;
19
+ const startTime = performance.now();
20
+ frameId = 0;
21
21
 
22
- while (queue.length > 0) {
23
- const chunk = queue.splice(0, CHUNK_SIZE);
24
- _runTasks(chunk);
22
+ while (queue.length > 0) {
23
+ const chunk = queue.splice(0, CHUNK_SIZE);
24
+ _runTasks(chunk);
25
25
 
26
- if (performance.now() - startTime > FRAME_BUDGET && queue.length > 0) {
27
- frameId = requestAnimationFrame(runQueue);
28
- return;
29
- }
26
+ if (performance.now() - startTime > FRAME_BUDGET && queue.length > 0) {
27
+ frameId = requestAnimationFrame(runQueue);
28
+ return;
30
29
  }
30
+ }
31
31
 
32
- scheduled = false;
32
+ scheduled = false;
33
33
  }
34
34
 
35
35
  // Schedules a function to be executed on the next animation frame.
36
- export function schedule(f, { signal } = {}) {
37
- if (signal?.aborted) {
38
- return;
39
- }
36
+ export function schedule(f, {signal} = {}) {
37
+ if (signal?.aborted) {
38
+ return;
39
+ }
40
40
 
41
- let task = f;
42
- // If a signal is provided, wrap the task to check for abortion before execution.
43
- if (signal) {
44
- task = () => {
45
- if (!signal.aborted) {
46
- f();
47
- }
48
- };
49
- }
41
+ let task = f;
42
+ // If a signal is provided, wrap the task to check for abortion before execution.
43
+ if (signal) {
44
+ task = () => {
45
+ if (!signal.aborted) {
46
+ f();
47
+ }
48
+ };
49
+ }
50
50
 
51
- queue.push(task);
51
+ queue.push(task);
52
52
 
53
- if (!scheduled) {
54
- scheduled = true;
55
- frameId = requestAnimationFrame(runQueue);
56
- }
53
+ if (!scheduled) {
54
+ scheduled = true;
55
+ frameId = requestAnimationFrame(runQueue);
56
+ }
57
57
  }
58
58
 
59
59
  // Immediately runs all queued tasks synchronously.
60
60
  export function flush() {
61
- if (frameId) {
62
- cancelAnimationFrame(frameId);
63
- }
64
- const toRun = queue;
65
- queue = [];
66
- _runTasks(toRun);
67
- scheduled = false;
68
- frameId = 0;
61
+ if (frameId) {
62
+ cancelAnimationFrame(frameId);
63
+ }
64
+ const toRun = queue;
65
+ queue = [];
66
+ _runTasks(toRun);
67
+ scheduled = false;
68
+ frameId = 0;
69
69
  }
70
70
 
71
71
  // Clears all pending tasks.
72
72
  export function clear() {
73
- if (frameId) {
74
- cancelAnimationFrame(frameId);
75
- }
76
- queue = [];
77
- scheduled = false;
78
- frameId = 0;
73
+ if (frameId) {
74
+ cancelAnimationFrame(frameId);
75
+ }
76
+ queue = [];
77
+ scheduled = false;
78
+ frameId = 0;
79
79
  }
package/src/vdom.js CHANGED
@@ -32,32 +32,35 @@ class VNode {
32
32
  }
33
33
 
34
34
  function defaultShouldUpdate(a, b) {
35
- if (a === b) return false;
36
- if (a && b && typeof a === 'object' && typeof b === 'object') {
37
- if (a.constructor !== b.constructor) return true;
38
-
39
- if (Array.isArray(a)) {
40
- if (a.length !== b.length) return true;
41
- for (let j = 0; j < a.length; j++) {
42
- if (a[j] !== b[j]) return true;
43
- }
44
- return false;
45
- }
35
+ if (a === b) return false;
36
+ if (a && b && typeof a === 'object' && typeof b === 'object') {
37
+ if (a.constructor !== b.constructor) return true;
38
+
39
+ if (Array.isArray(a)) {
40
+ if (a.length !== b.length) return true;
41
+ for (let j = 0; j < a.length; j++) {
42
+ if (a[j] !== b[j]) return true;
43
+ }
44
+ return false;
45
+ }
46
46
 
47
- if (a.constructor === Object) {
48
- const keysA = Object.keys(a);
49
- if (keysA.length !== Object.keys(b).length) return true;
50
- for (const key of keysA) {
51
- if (!b.hasOwnProperty(key) || a[key] !== b[key]) return true;
52
- }
53
- return false;
54
- }
47
+ if (a.constructor === Object) {
48
+ const keysA = Object.keys(a);
49
+ if (keysA.length !== Object.keys(b).length) return true;
50
+ for (const key of keysA) {
51
+ if (!b.hasOwnProperty(key) || a[key] !== b[key]) return true;
52
+ }
53
+ return false;
55
54
  }
56
- return true;
55
+ }
56
+ return true;
57
57
  }
58
58
 
59
59
  function isIterable(x) {
60
- return Array.isArray(x) || (x != null && typeof x[Symbol.iterator] === 'function' && typeof x !== 'string');
60
+ return (
61
+ Array.isArray(x) ||
62
+ (x != null && typeof x[Symbol.iterator] === 'function' && typeof x !== 'string')
63
+ );
61
64
  }
62
65
 
63
66
  function alias(f) {
@@ -71,8 +74,10 @@ function special(o) {
71
74
  function newElementNamespace(parentNode, newNodeTag) {
72
75
  if (parentNode.namespaceURI === 'http://www.w3.org/1999/xhtml') {
73
76
  switch (newNodeTag) {
74
- case 'svg': return 'http://www.w3.org/2000/svg';
75
- case 'math': return 'http://www.w3.org/1998/Math/MathML';
77
+ case 'svg':
78
+ return 'http://www.w3.org/2000/svg';
79
+ case 'math':
80
+ return 'http://www.w3.org/1998/Math/MathML';
76
81
  }
77
82
  }
78
83
  return parentNode.namespaceURI ?? parentNode.host?.namespaceURI ?? 'http://www.w3.org/1999/xhtml';
@@ -80,7 +85,7 @@ function newElementNamespace(parentNode, newNodeTag) {
80
85
 
81
86
  function createElementNode(parentNode, tag, vdom) {
82
87
  const el = parentNode.ownerDocument.createElementNS(newElementNamespace(parentNode, tag), tag);
83
- el[NODE_STATE] = { originalProps: {}, newVdom: vdom };
88
+ el[NODE_STATE] = {originalProps: {}, newVdom: vdom};
84
89
  return el;
85
90
  }
86
91
 
@@ -91,10 +96,10 @@ function getPathFromElement(element) {
91
96
  let current = element;
92
97
  while (current) {
93
98
  path.push(current);
94
- if (current.shadowRoot) {
95
- current = current.shadowRoot.host;
99
+ if (current.parentElement) {
100
+ current = current.parentElement;
96
101
  } else {
97
- current = current.parentNode;
102
+ current = current.getRootNode()?.host;
98
103
  }
99
104
  }
100
105
  return path;
@@ -117,10 +122,10 @@ function removePathFromFocusWithinSet(set, newPath) {
117
122
 
118
123
  function installFocusTrackingForDocument(doc) {
119
124
  const focusWithinSet = new Set();
120
- doc.addEventListener('focusin', (event) => {
125
+ doc.addEventListener('focusin', event => {
121
126
  addPathToFocusWithinSet(focusWithinSet, event.composedPath());
122
127
  });
123
- doc.addEventListener('focusout', (event) => {
128
+ doc.addEventListener('focusout', event => {
124
129
  const newPath = event.relatedTarget ? event.composedPath() : [];
125
130
  removePathFromFocusWithinSet(focusWithinSet, newPath);
126
131
  });
@@ -129,21 +134,26 @@ function installFocusTrackingForDocument(doc) {
129
134
  return focusWithinSet;
130
135
  }
131
136
 
132
- export default (userSettings) => {
137
+ export default userSettings => {
133
138
  const shouldUpdate = userSettings?.shouldUpdate ?? defaultShouldUpdate;
134
- const isMap = userSettings?.isMap ?? ((x) => x?.constructor === Object);
135
- const mapIter = userSettings?.mapIter ?? ((m) => Object.entries(m));
139
+ const isMap = userSettings?.isMap ?? (x => x?.constructor === Object);
140
+ const mapIter = userSettings?.mapIter ?? (m => Object.entries(m));
136
141
  const mapGet = userSettings?.mapGet ?? ((m, k) => m[k]);
137
142
  const mapMerge = userSettings?.mapMerge ?? ((...maps) => Object.assign({}, ...maps));
138
- const newMap = userSettings?.newMap ?? ((obj) => ({...obj}));
139
- const mapPut = userSettings?.mapPut ?? ((m, k, v) => { m[k] = v; return m; });
143
+ const newMap = userSettings?.newMap ?? (obj => ({...obj}));
144
+ const mapPut =
145
+ userSettings?.mapPut ??
146
+ ((m, k, v) => {
147
+ m[k] = v;
148
+ return m;
149
+ });
140
150
  const isSeq = userSettings?.isSeq ?? isIterable;
141
- const seqIter = userSettings?.seqIter ?? ((s) => s);
142
- const convertTagName = userSettings?.convertTagName ?? ((t) => t);
143
- const convertPropName = userSettings?.convertPropName ?? ((p) => p);
144
- const convertStyleName = userSettings?.convertStyleName ?? ((s) => s);
145
- const convertDataName = userSettings?.convertDataName ?? ((d) => d);
146
- const convertClassName = userSettings?.convertClassName ?? ((c) => c);
151
+ const seqIter = userSettings?.seqIter ?? (s => s);
152
+ const convertTagName = userSettings?.convertTagName ?? (t => t);
153
+ const convertPropName = userSettings?.convertPropName ?? (p => p);
154
+ const convertStyleName = userSettings?.convertStyleName ?? (s => s);
155
+ const convertDataName = userSettings?.convertDataName ?? (d => d);
156
+ const convertClassName = userSettings?.convertClassName ?? (c => c);
147
157
  const listenerKey = userSettings?.listenerKey ?? 'listener';
148
158
  const captureKey = userSettings?.captureKey ?? 'capture';
149
159
  const passiveKey = userSettings?.passiveKey ?? 'passive';
@@ -244,86 +254,86 @@ export default (userSettings) => {
244
254
 
245
255
  // Handle new and changed props
246
256
  for (const [name, newValue] of mapIter(props)) {
247
- const propName = convertPropName(name);
248
- const oldValue = mapGet(oldProps, name);
257
+ const propName = convertPropName(name);
258
+ const oldValue = mapGet(oldProps, name);
249
259
 
250
- if (Object.is(newValue, oldValue)) continue;
260
+ if (Object.is(newValue, oldValue)) continue;
251
261
 
252
- switch (propName) {
253
- case '$styling': {
254
- if (!isMap(newValue)) throw new Error('invalid value for styling prop');
255
- reconcileElementStyling(target, oldValue ?? EMPTY_OBJECT, newValue ?? EMPTY_OBJECT);
256
- break;
257
- }
258
- case '$classes': {
259
- if (!isSeq(newValue)) throw new Error('invalid value for classes prop');
260
- reconcileElementClasses(target, oldValue ?? [], newValue ?? []);
261
- break;
262
- }
263
- case '$attrs': {
264
- if (!isMap(newValue)) throw new Error('invalid value for attrs prop');
265
- reconcileElementAttributes(target, oldValue ?? EMPTY_OBJECT, newValue ?? EMPTY_OBJECT);
266
- break;
262
+ switch (propName) {
263
+ case '$styling': {
264
+ if (!isMap(newValue)) throw new Error('invalid value for styling prop');
265
+ reconcileElementStyling(target, oldValue ?? EMPTY_OBJECT, newValue ?? EMPTY_OBJECT);
266
+ break;
267
+ }
268
+ case '$classes': {
269
+ if (!isSeq(newValue)) throw new Error('invalid value for classes prop');
270
+ reconcileElementClasses(target, oldValue ?? [], newValue ?? []);
271
+ break;
272
+ }
273
+ case '$attrs': {
274
+ if (!isMap(newValue)) throw new Error('invalid value for attrs prop');
275
+ reconcileElementAttributes(target, oldValue ?? EMPTY_OBJECT, newValue ?? EMPTY_OBJECT);
276
+ break;
277
+ }
278
+ case '$dataset': {
279
+ if (!isMap(newValue)) throw new Error('invalid value for dataset prop');
280
+ reconcileElementDataset(target, oldValue ?? EMPTY_OBJECT, newValue ?? EMPTY_OBJECT);
281
+ break;
282
+ }
283
+ default: {
284
+ if (isHtml) {
285
+ const originalProps = nodeState.originalProps;
286
+ if (!(propName in originalProps)) {
287
+ originalProps[propName] = target[propName];
267
288
  }
268
- case '$dataset': {
269
- if (!isMap(newValue)) throw new Error('invalid value for dataset prop');
270
- reconcileElementDataset(target, oldValue ?? EMPTY_OBJECT, newValue ?? EMPTY_OBJECT);
271
- break;
289
+ if (newValue === undefined) {
290
+ target[propName] = originalProps[propName];
291
+ } else {
292
+ target[propName] = newValue;
272
293
  }
273
- default: {
274
- if (isHtml) {
275
- const originalProps = nodeState.originalProps;
276
- if (!(propName in originalProps)) {
277
- originalProps[propName] = target[propName];
278
- }
279
- if (newValue === undefined) {
280
- target[propName] = originalProps[propName];
281
- } else {
282
- target[propName] = newValue;
283
- }
284
- } else {
285
- if (newValue === undefined) {
286
- target.removeAttribute(propName);
287
- } else {
288
- target.setAttribute(propName, newValue);
289
- }
290
- }
291
- break;
294
+ } else {
295
+ if (newValue === undefined) {
296
+ target.removeAttribute(propName);
297
+ } else {
298
+ target.setAttribute(propName, newValue);
292
299
  }
300
+ }
301
+ break;
293
302
  }
303
+ }
294
304
  }
295
305
 
296
306
  // Handle removed props
297
307
  for (const [name, oldValue] of mapIter(oldProps)) {
298
- if (mapGet(props, name) !== undefined) continue; // it wasn't removed
299
-
300
- const propName = convertPropName(name);
301
- switch (propName) {
302
- case '$styling':
303
- reconcileElementStyling(target, oldValue ?? EMPTY_OBJECT, EMPTY_OBJECT);
304
- break;
305
- case '$classes':
306
- reconcileElementClasses(target, oldValue ?? [], []);
307
- break;
308
- case '$attrs':
309
- reconcileElementAttributes(target, oldValue ?? EMPTY_OBJECT, EMPTY_OBJECT);
310
- break;
311
- case '$dataset':
312
- reconcileElementDataset(target, oldValue ?? EMPTY_OBJECT, EMPTY_OBJECT);
313
- break;
314
- default: {
315
- if (isHtml) {
316
- const originalProps = nodeState.originalProps;
317
- if (propName in originalProps) {
318
- target[propName] = originalProps[propName];
319
- delete originalProps[propName];
320
- }
321
- } else {
322
- target.removeAttribute(propName);
323
- }
324
- break;
308
+ if (mapGet(props, name) !== undefined) continue; // it wasn't removed
309
+
310
+ const propName = convertPropName(name);
311
+ switch (propName) {
312
+ case '$styling':
313
+ reconcileElementStyling(target, oldValue ?? EMPTY_OBJECT, EMPTY_OBJECT);
314
+ break;
315
+ case '$classes':
316
+ reconcileElementClasses(target, oldValue ?? [], []);
317
+ break;
318
+ case '$attrs':
319
+ reconcileElementAttributes(target, oldValue ?? EMPTY_OBJECT, EMPTY_OBJECT);
320
+ break;
321
+ case '$dataset':
322
+ reconcileElementDataset(target, oldValue ?? EMPTY_OBJECT, EMPTY_OBJECT);
323
+ break;
324
+ default: {
325
+ if (isHtml) {
326
+ const originalProps = nodeState.originalProps;
327
+ if (propName in originalProps) {
328
+ target[propName] = originalProps[propName];
329
+ delete originalProps[propName];
325
330
  }
331
+ } else {
332
+ target.removeAttribute(propName);
333
+ }
334
+ break;
326
335
  }
336
+ }
327
337
  }
328
338
  }
329
339
 
@@ -335,9 +345,9 @@ export default (userSettings) => {
335
345
  if (typeof listener === 'function') {
336
346
  target.addEventListener(name, listener);
337
347
  } else if (listener != null) {
338
- target.addEventListener(name, mapGet(listener, listenerKey), {
339
- capture: !!mapGet(listener, captureKey),
340
- passive: !!mapGet(listener, passiveKey)
348
+ target.addEventListener(name, mapGet(listener, listenerKey), {
349
+ capture: !!mapGet(listener, captureKey),
350
+ passive: !!mapGet(listener, passiveKey),
341
351
  });
342
352
  }
343
353
  }
@@ -351,15 +361,19 @@ export default (userSettings) => {
351
361
  if (typeof oldListener === 'function') {
352
362
  target.removeEventListener(name, oldListener);
353
363
  } else if (oldListener != null) {
354
- target.removeEventListener(name, mapGet(oldListener, listenerKey), !!mapGet(oldListener, captureKey));
364
+ target.removeEventListener(
365
+ name,
366
+ mapGet(oldListener, listenerKey),
367
+ !!mapGet(oldListener, captureKey),
368
+ );
355
369
  }
356
-
370
+
357
371
  if (typeof listener === 'function') {
358
372
  target.addEventListener(name, listener);
359
373
  } else if (listener != null) {
360
- target.addEventListener(name, mapGet(listener, listenerKey), {
361
- capture: !!mapGet(listener, captureKey),
362
- passive: !!mapGet(listener, passiveKey)
374
+ target.addEventListener(name, mapGet(listener, listenerKey), {
375
+ capture: !!mapGet(listener, captureKey),
376
+ passive: !!mapGet(listener, passiveKey),
363
377
  });
364
378
  }
365
379
  }
@@ -369,7 +383,11 @@ export default (userSettings) => {
369
383
  if (typeof oldListener === 'function') {
370
384
  target.removeEventListener(name, oldListener);
371
385
  } else if (oldListener != null) {
372
- target.removeEventListener(name, mapGet(oldListener, listenerKey), !!mapGet(oldListener, captureKey));
386
+ target.removeEventListener(
387
+ name,
388
+ mapGet(oldListener, listenerKey),
389
+ !!mapGet(oldListener, captureKey),
390
+ );
373
391
  }
374
392
  }
375
393
  }
@@ -459,7 +477,6 @@ export default (userSettings) => {
459
477
  function cleanupTarget(target) {
460
478
  const state = target[NODE_STATE];
461
479
  if (state) {
462
-
463
480
  switch (state.vdom.type) {
464
481
  case ELEMENT_NODE:
465
482
  reconcileElementProps(target, {});
@@ -509,7 +526,7 @@ export default (userSettings) => {
509
526
 
510
527
  let oldNodesPoolForTag = oldVNodeNodesPool.get(vdom.tag);
511
528
  if (!oldNodesPoolForTag) {
512
- oldNodesPoolForTag = { nodesForKey: new Map(), nodesWithoutKey: [] };
529
+ oldNodesPoolForTag = {nodesForKey: new Map(), nodesWithoutKey: []};
513
530
  oldVNodeNodesPool.set(vdom.tag, oldNodesPoolForTag);
514
531
  }
515
532
 
@@ -539,7 +556,10 @@ export default (userSettings) => {
539
556
  if (pool && pool.length > 0) {
540
557
  newDomNode = pool.shift();
541
558
  const state = newDomNode[NODE_STATE];
542
- if (shouldUpdate(state.vdom.args, newVdom.args)) {
559
+ if (
560
+ shouldUpdate(state.vdom.args, newVdom.args) ||
561
+ shouldUpdate(state.vdom.hooks, newVdom.hooks)
562
+ ) {
543
563
  state.newVdom = newVdom;
544
564
  }
545
565
  } else {
@@ -550,7 +570,10 @@ export default (userSettings) => {
550
570
  if (unkeyedOldNode) {
551
571
  newDomNode = unkeyedOldNode;
552
572
  const state = newDomNode[NODE_STATE];
553
- if (shouldUpdate(state.vdom.args, newVdom.args)) {
573
+ if (
574
+ shouldUpdate(state.vdom.args, newVdom.args) ||
575
+ shouldUpdate(state.vdom.hooks, newVdom.hooks)
576
+ ) {
554
577
  state.newVdom = newVdom;
555
578
  }
556
579
  } else {
@@ -577,41 +600,69 @@ export default (userSettings) => {
577
600
 
578
601
  const moveBefore = window.Element.prototype.moveBefore;
579
602
  const insertBefore = window.Element.prototype.insertBefore;
580
- if (target.isConnected && typeof moveBefore === 'function') {
581
- for (let i = 0; i < newDomChildren.length; i++) {
582
- const newChild = newDomChildren[i];
583
- const existingChildAtPosition = target.childNodes[i];
584
- if (newChild !== existingChildAtPosition) {
585
- (newChild.isConnected ? moveBefore : insertBefore).call(target, newChild, existingChildAtPosition);
603
+ if (target.isConnected) {
604
+ if (typeof moveBefore === 'function') {
605
+ for (let i = 0; i < newDomChildren.length; i++) {
606
+ const newChild = newDomChildren[i];
607
+ const existingChildAtPosition = target.childNodes[i];
608
+ if (newChild !== existingChildAtPosition) {
609
+ (newChild.isConnected ? moveBefore : insertBefore).call(
610
+ target,
611
+ newChild,
612
+ existingChildAtPosition,
613
+ );
614
+ }
615
+ const state = newChild[NODE_STATE];
616
+ if (state?.newVdom) {
617
+ if (!state.vdom) {
618
+ try {
619
+ state.newVdom.hooks?.$attach?.(newChild);
620
+ if (state.newVdom.type === SPECIAL_NODE) {
621
+ state.newVdom.tag.attach?.(newChild);
622
+ }
623
+ } catch (err) {
624
+ console.error(err);
625
+ }
626
+ }
627
+ reconcileNode(newChild);
628
+ }
586
629
  }
587
- const state = newChild[NODE_STATE];
588
- if (state?.newVdom) {
589
- if (!state.vdom) {
590
- try {
591
- state.newVdom.hooks?.$attach?.(newChild);
592
- if (state.newVdom.type === SPECIAL_NODE) {
593
- state.newVdom.tag.attach?.(newChild);
630
+ } else {
631
+ const doc = target.ownerDocument;
632
+ let focusWithin = documentToFocusWithinSet.get(doc);
633
+ if (!focusWithin) {
634
+ focusWithin = installFocusTrackingForDocument(doc);
635
+ }
636
+ for (let i = 0; i < newDomChildren.length; i++) {
637
+ const newChild = newDomChildren[i];
638
+ const existingChildAtPosition = target.childNodes[i];
639
+ if (newChild !== existingChildAtPosition) {
640
+ if (!focusWithin.has(newChild)) {
641
+ insertBefore.call(target, newChild, existingChildAtPosition);
642
+ }
643
+ }
644
+ const state = newChild[NODE_STATE];
645
+ if (state?.newVdom) {
646
+ if (!state.vdom) {
647
+ try {
648
+ state.newVdom.hooks?.$attach?.(newChild);
649
+ if (state.newVdom.type === SPECIAL_NODE) {
650
+ state.newVdom.tag.attach?.(newChild);
651
+ }
652
+ } catch (err) {
653
+ console.error(err);
594
654
  }
595
- } catch (err) {
596
- console.error(err);
597
655
  }
656
+ reconcileNode(newChild);
598
657
  }
599
- reconcileNode(newChild);
600
658
  }
601
659
  }
602
660
  } else {
603
- const doc = target.ownerDocument;
604
- let focusWithin = documentToFocusWithinSet.get(doc);
605
- if (!focusWithin) {
606
- focusWithin = installFocusTrackingForDocument(doc);
607
- }
608
661
  for (let i = 0; i < newDomChildren.length; i++) {
609
662
  const newChild = newDomChildren[i];
610
663
  const existingChildAtPosition = target.childNodes[i];
611
664
  if (newChild !== existingChildAtPosition) {
612
- if (!focusWithin.has(newChild)) {
613
- insertBefore.call(target, newChild, existingChildAtPosition);
614
- }
665
+ insertBefore.call(target, newChild, existingChildAtPosition);
615
666
  }
616
667
  const state = newChild[NODE_STATE];
617
668
  if (state?.newVdom) {
@@ -651,7 +702,10 @@ export default (userSettings) => {
651
702
  if (vdom instanceof VNode) {
652
703
  if (state) {
653
704
  if (state.vdom.type === vdom.type) {
654
- if (shouldUpdate(state.vdom.args, vdom.args)) {
705
+ if (
706
+ shouldUpdate(state.vdom.args, vdom.args) ||
707
+ shouldUpdate(state.vdom.hooks, vdom.hooks)
708
+ ) {
655
709
  state.newVdom = vdom;
656
710
  reconcileNode(target);
657
711
  }
@@ -663,13 +717,18 @@ export default (userSettings) => {
663
717
  switch (vdom.type) {
664
718
  case ELEMENT_NODE:
665
719
  case OPAQUE_NODE:
666
- if (0 !== target.nodeName.localeCompare(convertTagName(vdom.tag), undefined, { sensitivity: 'base' })) {
720
+ if (
721
+ 0 !==
722
+ target.nodeName.localeCompare(convertTagName(vdom.tag), undefined, {
723
+ sensitivity: 'base',
724
+ })
725
+ ) {
667
726
  throw new Error('incompatible target for vdom');
668
727
  }
669
728
  break;
670
729
  }
671
730
 
672
- target[NODE_STATE] = { originalProps: {}, newVdom: vdom };
731
+ target[NODE_STATE] = {originalProps: {}, newVdom: vdom};
673
732
  try {
674
733
  vdom.hooks?.$attach?.(target);
675
734
  if (vdom.type === SPECIAL_NODE) {
@@ -685,9 +744,30 @@ export default (userSettings) => {
685
744
  throw new Error('invalid vdom');
686
745
  }
687
746
 
688
- return { h, alias, special, reconcile, settings: {
689
- shouldUpdate, isMap, mapIter, mapGet, mapMerge, newMap, mapPut, isSeq, flattenSeq, seqIter,
690
- convertTagName, convertPropName, convertStyleName, convertDataName, convertClassName,
691
- listenerKey, captureKey, passiveKey
692
- } };
693
- }
747
+ return {
748
+ h,
749
+ alias,
750
+ special,
751
+ reconcile,
752
+ settings: {
753
+ shouldUpdate,
754
+ isMap,
755
+ mapIter,
756
+ mapGet,
757
+ mapMerge,
758
+ newMap,
759
+ mapPut,
760
+ isSeq,
761
+ flattenSeq,
762
+ seqIter,
763
+ convertTagName,
764
+ convertPropName,
765
+ convertStyleName,
766
+ convertDataName,
767
+ convertClassName,
768
+ listenerKey,
769
+ captureKey,
770
+ passiveKey,
771
+ },
772
+ };
773
+ };