jaxs 0.2.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 (67) hide show
  1. package/.env +1 -0
  2. package/.parcel-cache/6f14daf302269614-BundleGraph-0 +0 -0
  3. package/.parcel-cache/7d3d872b02d671a6-AssetGraph-0 +0 -0
  4. package/.parcel-cache/8e029bb14f8992df-RequestGraph-0 +0 -0
  5. package/.parcel-cache/a5394978e4ece10b-AssetGraph-0 +0 -0
  6. package/.parcel-cache/b5686051ae060930.txt +2 -0
  7. package/.parcel-cache/data.mdb +0 -0
  8. package/.parcel-cache/lock.mdb +0 -0
  9. package/README.md +15 -0
  10. package/bun.lockb +0 -0
  11. package/bundle.ts +5 -0
  12. package/bunfig.toml +0 -0
  13. package/cypress/e2e/add-remove-nested-children.cy.js +39 -0
  14. package/cypress/e2e/add-remove-root-children.cy.js +37 -0
  15. package/cypress/jaxs-apps/add-remove-nested-children.html +12 -0
  16. package/cypress/jaxs-apps/add-remove-nested-children.jsx +84 -0
  17. package/cypress/jaxs-apps/add-remove-root-children.html +15 -0
  18. package/cypress/jaxs-apps/add-remove-root-children.jsx +53 -0
  19. package/cypress/jaxs-apps/dist/add-remove-nested-children.afcab974.js +717 -0
  20. package/cypress/jaxs-apps/dist/add-remove-nested-children.afcab974.js.map +1 -0
  21. package/cypress/jaxs-apps/dist/add-remove-nested-children.html +12 -0
  22. package/cypress/jaxs-apps/dist/add-remove-root-children.3bb9b3f5.js +1682 -0
  23. package/cypress/jaxs-apps/dist/add-remove-root-children.3bb9b3f5.js.map +1 -0
  24. package/cypress/jaxs-apps/dist/add-remove-root-children.fbb4ec9b.js +706 -0
  25. package/cypress/jaxs-apps/dist/add-remove-root-children.fbb4ec9b.js.map +1 -0
  26. package/cypress/jaxs-apps/dist/add-remove-root-children.html +15 -0
  27. package/cypress/support/commands.js +25 -0
  28. package/cypress/support/e2e.js +20 -0
  29. package/cypress.config.js +10 -0
  30. package/dist/jaxs.js +1154 -0
  31. package/package.json +38 -0
  32. package/src/app.ts +64 -0
  33. package/src/debugging.js +5 -0
  34. package/src/jaxs.ts +8 -0
  35. package/src/jsx.js +27 -0
  36. package/src/messageBus.ts +70 -0
  37. package/src/navigation/findHref.js +10 -0
  38. package/src/navigation/routeState.js +15 -0
  39. package/src/navigation/setupHistory.js +38 -0
  40. package/src/navigation/setupNavigation.js +25 -0
  41. package/src/navigation.ts +2 -0
  42. package/src/rendering/change/compile.ts +1 -0
  43. package/src/rendering/change/instructions/attributes.ts +78 -0
  44. package/src/rendering/change/instructions/children.ts +128 -0
  45. package/src/rendering/change/instructions/element.ts +42 -0
  46. package/src/rendering/change/instructions/events.ts +51 -0
  47. package/src/rendering/change/instructions/generate.ts +122 -0
  48. package/src/rendering/change/instructions/idMap.js +55 -0
  49. package/src/rendering/change/instructions/node.ts +38 -0
  50. package/src/rendering/change/instructions/text.ts +10 -0
  51. package/src/rendering/change.ts +131 -0
  52. package/src/rendering/dom/attributesAndEvents.ts +33 -0
  53. package/src/rendering/dom/create.ts +68 -0
  54. package/src/rendering/templates/bound.js +55 -0
  55. package/src/rendering/templates/children.ts +91 -0
  56. package/src/rendering/templates/root.ts +55 -0
  57. package/src/rendering/templates/tag.ts +70 -0
  58. package/src/rendering/templates/text.ts +17 -0
  59. package/src/state/equality.js +36 -0
  60. package/src/state/stores.js +63 -0
  61. package/src/state/testingTypes.js +6 -0
  62. package/src/state.js +89 -0
  63. package/src/types.ts +149 -0
  64. package/src/views/conditionals.jsx +18 -0
  65. package/src/views/link.jsx +5 -0
  66. package/src/views.js +7 -0
  67. package/tsconfig.json +26 -0
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "jaxs",
3
+ "version": "0.2.0",
4
+ "description": "Modular J/TSX application framework",
5
+ "module": "src/jaxs.ts",
6
+ "type": "module",
7
+ "devDependencies": {
8
+ "@types/bun": "latest",
9
+ "@types/jsdom": "^21.1.6",
10
+ "concurrently": "^8.2.2",
11
+ "cypress": "^13.6.2",
12
+ "jsdom": "^23.2.0",
13
+ "npm": "^10.3.0",
14
+ "parcel": "^2.11.0",
15
+ "standard": "^17.1.0"
16
+ },
17
+ "peerDependencies": {
18
+ "typescript": "^5.0.0"
19
+ },
20
+ "scripts": {
21
+ "lint": "standard --fix",
22
+ "build": "bun run bundle.ts",
23
+ "serve": "bun run build && parcel serve --dist-dir=cypress/jaxs-apps/dist",
24
+ "integration": "concurrently \"npm run serve\" \"npx cypress open\"",
25
+ "integration:ci": "concurrently \"npm run serve\" \"npx cypress run\""
26
+ },
27
+ "targets": {
28
+ "html": {
29
+ "source": [
30
+ "./cypress/jaxs-apps/add-remove-root-children.html",
31
+ "./cypress/jaxs-apps/add-remove-nested-children.html"
32
+ ]
33
+ }
34
+ },
35
+ "standard": {
36
+ "globals": [ "cy", "describe", "it", "beforeEach" ]
37
+ }
38
+ }
package/src/app.ts ADDED
@@ -0,0 +1,64 @@
1
+ import type { App, BusPublish, BusSubscribe } from './types';
2
+ import { createBus } from './messageBus';
3
+ import { State } from './state'
4
+ import {
5
+ locationChangeEvent,
6
+ setupHistory,
7
+ } from './navigation/setupHistory';
8
+ import { setupNavigation } from './navigation/setupNavigation';
9
+ import { render } from './rendering/templates/root';
10
+
11
+ const setupBus = (app: App) => {
12
+ const { publish, subscribe, bus } = createBus();
13
+
14
+ app.publish = publish;
15
+ app.subscribe = subscribe;
16
+ app.bus = bus;
17
+ };
18
+
19
+ const setupState = (app: App) => {
20
+ const state = new State(app.publish as BusPublish);
21
+ app.state = state;
22
+ };
23
+
24
+ const connectBusToState = (app: App) => {
25
+ const { bus } = app;
26
+ bus.addListenerOptions({ state: app.state });
27
+ };
28
+
29
+ const setupRenderKit = (app: App, document: Document) => {
30
+ app.renderKit = {
31
+ publish: app.publish as BusPublish,
32
+ subscribe: app.subscribe as BusSubscribe,
33
+ state: app.state as State,
34
+ document,
35
+ };
36
+ };
37
+
38
+ const triggerRoute = (app: App) => {
39
+ const publish = app.publish as BusPublish;
40
+ setTimeout(() => {
41
+ publish(locationChangeEvent, null);
42
+ }, 0);
43
+ };
44
+
45
+ const addRender = (app: App) => {
46
+ app.render = (template, selector) => {
47
+ return render(template, selector, app.renderKit);
48
+ };
49
+ };
50
+
51
+ export const createApp = (document = window.document) => {
52
+ const app = {} as App;
53
+
54
+ setupBus(app);
55
+ setupState(app);
56
+ connectBusToState(app);
57
+ setupRenderKit(app, document);
58
+ setupHistory(app);
59
+ setupNavigation(app);
60
+ triggerRoute(app);
61
+ addRender(app);
62
+
63
+ return app;
64
+ };
@@ -0,0 +1,5 @@
1
+ export const debug = (...message) => {
2
+ if (process.env.DEBUG === 'true') {
3
+ console.log(...message)
4
+ }
5
+ }
package/src/jaxs.ts ADDED
@@ -0,0 +1,8 @@
1
+ export { default as jsx } from './jsx'
2
+ export { render } from './rendering/templates/root'
3
+ export { bind } from './rendering/templates/bound'
4
+ export { createBus } from './messageBus'
5
+ export { createApp } from './app'
6
+ export { State } from './state'
7
+ export * as navigation from './navigation'
8
+ export { views } from './views'
package/src/jsx.js ADDED
@@ -0,0 +1,27 @@
1
+ import { Tag } from './rendering/templates/tag'
2
+ import { Children } from './rendering/templates/children'
3
+
4
+ const ensureChildrenArray = (maybeChildren, attributes) =>
5
+ maybeChildren || attributes.children || []
6
+
7
+ const packageAttributes = (maybeAttributes, maybeChildren) => {
8
+ const attributes = maybeAttributes || {}
9
+ const children = ensureChildrenArray(maybeChildren, attributes)
10
+ attributes.children = attributes.children || children
11
+ return attributes
12
+ }
13
+
14
+ const jsx = (type, attributes, ...children) => {
15
+ if (typeof type === 'string') {
16
+ return new Tag(type, attributes, children)
17
+ }
18
+
19
+ return type(packageAttributes(attributes, children))
20
+ }
21
+
22
+ jsx.fragment = (attributes, maybeChildren) => {
23
+ const children = ensureChildrenArray(maybeChildren, attributes)
24
+ return new Children(children)
25
+ }
26
+
27
+ export default jsx
@@ -0,0 +1,70 @@
1
+ import type {
2
+ BusEventName,
3
+ BusListener,
4
+ BusListenersMap,
5
+ BusOptions,
6
+ BusPayload,
7
+ } from './types';
8
+
9
+ export class MessageBus {
10
+ listeners: BusListenersMap;
11
+ options: BusOptions;
12
+
13
+ constructor() {
14
+ this.options = {};
15
+ this.listeners = {};
16
+ }
17
+
18
+ subscribe(eventName: BusEventName, listener: BusListener) {
19
+ this.ensureListenerCollection(eventName);
20
+
21
+ this.listeners[eventName].push(listener);
22
+ }
23
+
24
+ publish(eventName: BusEventName, payload: BusPayload) {
25
+ const listeners = this.listeners[eventName];
26
+ if (!listeners) return false;
27
+
28
+ listeners.forEach((listener: BusListener) => {
29
+ listener(
30
+ payload,
31
+ this.buildListenerKit(eventName),
32
+ );
33
+ });
34
+
35
+ return true;
36
+ }
37
+
38
+ ensureListenerCollection(eventName: BusEventName) {
39
+ if (this.listeners[eventName]) return;
40
+
41
+ this.listeners[eventName] = [];
42
+ }
43
+
44
+ buildListenerKit(eventName: BusEventName) {
45
+ return {
46
+ eventName,
47
+ publish: this.publish.bind(this),
48
+ ...this.options,
49
+ };
50
+ }
51
+
52
+ addListenerOptions(options: BusOptions) {
53
+ this.options = {
54
+ ...this.options,
55
+ ...options,
56
+ };
57
+ }
58
+ }
59
+
60
+ export const createBus = () => {
61
+ const bus = new MessageBus();
62
+ const publish = bus.publish.bind(bus);
63
+ const subscribe = bus.subscribe.bind(bus);
64
+
65
+ return {
66
+ bus,
67
+ publish,
68
+ subscribe,
69
+ };
70
+ };
@@ -0,0 +1,10 @@
1
+ export const findHref = (node) => {
2
+ if (!node || !node.getAttribute) return ''
3
+
4
+ while (!node.getAttribute('href')) {
5
+ node = node.parentNode
6
+ if (!node || !node.getAttribute) return ''
7
+ }
8
+
9
+ return node.getAttribute('href')
10
+ }
@@ -0,0 +1,15 @@
1
+ import { RecordStore } from '../state/stores'
2
+
3
+ export const createRouteState = (state) => {
4
+ const store = new RecordStore({
5
+ name: 'route',
6
+ value: {
7
+ host: '',
8
+ path: '',
9
+ query: ''
10
+ },
11
+ parent: state
12
+ })
13
+
14
+ state.add(store)
15
+ }
@@ -0,0 +1,38 @@
1
+ import { createRouteState } from './routeState'
2
+ export const locationChangeEvent = 'locationChange'
3
+ export const routeChangeEvent = 'routeChange'
4
+
5
+ export const extractQueryParams = (queryString) => {
6
+ return queryString
7
+ .replace(/^\?/, '')
8
+ .split('&')
9
+ .reduce((aggregate, pairString) => {
10
+ if (!pairString) return aggregate
11
+
12
+ const pair = pairString.split('=')
13
+ aggregate[pair[0]] = pair[1]
14
+ return aggregate
15
+ }, {})
16
+ }
17
+
18
+ export const onLocationChange = (_payload, { publish, state }) => {
19
+ const { host, pathname, search } = window.location
20
+ const path = pathname
21
+ const query = extractQueryParams(search)
22
+
23
+ state.route.update({
24
+ host,
25
+ path,
26
+ query
27
+ })
28
+
29
+ // subscribe for fetching data and other things
30
+ publish(routeChangeEvent, { host, path, query })
31
+ }
32
+
33
+ export const setupHistory = (app) => {
34
+ const { publish, subscribe, state } = app
35
+ createRouteState(state)
36
+ window.addEventListener('popstate', () => publish(locationChangeEvent))
37
+ subscribe(locationChangeEvent, onLocationChange)
38
+ }
@@ -0,0 +1,25 @@
1
+ import { findHref } from './findHref'
2
+ import { locationChangeEvent } from './setupHistory'
3
+
4
+ export const linkNavigationEvent = 'goToHref'
5
+ export const programmaticNavigationEvent = 'navigate'
6
+
7
+ export const navigate = (path, { publish }) => {
8
+ window.history.pushState(null, '', path)
9
+ publish(locationChangeEvent)
10
+ }
11
+
12
+ export const onLinkClick = (domEvent, { publish }) => {
13
+ if (!domEvent || !domEvent.target) return
14
+ domEvent.preventDefault()
15
+
16
+ const href = findHref(domEvent.target)
17
+ navigate(href, { publish })
18
+ }
19
+
20
+ export const setupNavigation = (app) => {
21
+ const { subscribe } = app
22
+
23
+ subscribe(linkNavigationEvent, onLinkClick)
24
+ subscribe(programmaticNavigationEvent, navigate)
25
+ }
@@ -0,0 +1,2 @@
1
+ export { setupHistory } from './navigation/setupHistory';
2
+ export { setupNavigation } from './navigation/setupNavigation';
@@ -0,0 +1 @@
1
+ export { compileChildren as compileChange } from './instructions/children';
@@ -0,0 +1,78 @@
1
+ import { ExpandedElement, Instructions } from '../../../types';
2
+ import { addAttribute, removeAttribute, updateAttribute } from './generate';
3
+
4
+ export const compileForAttributes = (
5
+ source: ExpandedElement,
6
+ target: ExpandedElement,
7
+ ) => {
8
+ const instructions = [] as Instructions;
9
+ const sourceAttributes = source.attributes;
10
+ const sourceLength = sourceAttributes.length;
11
+ const targetAttributes = target.attributes;
12
+ const targetLength = targetAttributes.length;
13
+
14
+ // NOTE: this implementation sucks, but JSDOM doesn't properly implement
15
+ // .getNamedItem and the deno dom doesn't implement the .length attribute.
16
+ // It's a total test cluster fuck. When there are integration-y tests that
17
+ // I can use for rerender via the DOM, I will get rid of unit tests and
18
+ // improve the implementation. Sucks all around.
19
+ let index;
20
+ let innerIndex;
21
+ let matchingAttribute;
22
+
23
+ // iterate through the source attributes to find removals and updates
24
+ for (index = 0; index < sourceLength; index++) {
25
+ matchingAttribute = null;
26
+ const sourceAttribute = sourceAttributes.item(index);
27
+ if (!sourceAttribute) continue;
28
+
29
+ for (innerIndex = 0; innerIndex < targetLength; innerIndex++) {
30
+ const targetAttribute = targetAttributes.item(innerIndex);
31
+ if (!targetAttribute) continue;
32
+ if (sourceAttribute.name == targetAttribute.name) {
33
+ matchingAttribute = targetAttribute;
34
+ break;
35
+ }
36
+ }
37
+
38
+ if (!matchingAttribute) {
39
+ instructions.push(
40
+ removeAttribute(source, target, { name: sourceAttribute.name }),
41
+ );
42
+ } else if (sourceAttribute.value !== matchingAttribute.value) {
43
+ instructions.push(
44
+ updateAttribute(source, target, {
45
+ name: sourceAttribute.name,
46
+ value: matchingAttribute.value,
47
+ }),
48
+ );
49
+ }
50
+ }
51
+
52
+ // iterate through the target to find additions
53
+ for (index = 0; index < targetLength; index++) {
54
+ matchingAttribute = null;
55
+ const targetAttribute = targetAttributes.item(index);
56
+ if (!targetAttribute) continue;
57
+
58
+ for (innerIndex = 0; innerIndex < sourceLength; innerIndex++) {
59
+ const sourceAttribute = sourceAttributes.item(innerIndex);
60
+ if (!sourceAttribute) continue;
61
+ if (sourceAttribute.name == targetAttribute.name) {
62
+ matchingAttribute = sourceAttribute;
63
+ break;
64
+ }
65
+ }
66
+
67
+ if (!matchingAttribute) {
68
+ instructions.push(
69
+ addAttribute(source, target, {
70
+ name: targetAttribute.name,
71
+ value: targetAttribute.value,
72
+ }),
73
+ );
74
+ }
75
+ }
76
+
77
+ return instructions;
78
+ };
@@ -0,0 +1,128 @@
1
+ import type {
2
+ Dom,
3
+ ExpandedElement,
4
+ HtmlChildren,
5
+ Instruction,
6
+ Instructions,
7
+ } from '../../../types';
8
+ import { insertNode, removeNode, replaceNode } from './generate';
9
+ import { createIdMap } from './idMap';
10
+ import { compileForNodeGenerator } from './node';
11
+ import { debug } from '../../../debugging';
12
+
13
+ type DiffPair = {
14
+ source: Dom;
15
+ target: Dom;
16
+ };
17
+
18
+ export const compileChildren = (
19
+ sourceList: HtmlChildren,
20
+ targetList: HtmlChildren,
21
+ parent: ExpandedElement,
22
+ ) => {
23
+ const baseInstructions = [] as Instructions;
24
+ const length = largerLength(sourceList, targetList);
25
+ const sourceMap = createIdMap(sourceList);
26
+ const targetMap = createIdMap(targetList);
27
+ const nodesPairsToDiff = [] as DiffPair[];
28
+
29
+ let index = 0;
30
+ for (; index < length; index++) {
31
+ const source = sourceList[index] as ExpandedElement;
32
+ const target = targetList[index] as ExpandedElement;
33
+ debug(
34
+ '\n',
35
+ 'loop index',
36
+ index,
37
+ source && source.__jsx,
38
+ target && target.__jsx,
39
+ );
40
+
41
+ // This algorithm uses the target as the source of truth, iterating
42
+ // through it first figuring out what to do. The length could be larger than
43
+ // the target length, which means that there are unresolved sources to remove.
44
+ // Part of the goal of this flow is to ensure that insertions happen in
45
+ // accending order.
46
+ if (target && targetMap.check(target)) {
47
+ debug('target', target.__jsx, 'index', index);
48
+ const matchingSource = sourceMap.pullMatch(target);
49
+ targetMap.clear(target); // mark target as resolved
50
+
51
+ if (matchingSource.element) {
52
+ debug('matching source found for target');
53
+ if (matchingSource.index !== index) {
54
+ // move source to index
55
+ debug('moving source', matchingSource.element.__jsx, index);
56
+ baseInstructions.push(
57
+ insertNode(matchingSource.element, { parent, index }),
58
+ );
59
+ }
60
+ // update element for attribute, event and child changes
61
+ debug('updating to match target',
62
+ matchingSource.element.__jsx,
63
+ matchingSource.element.classList,
64
+ target.__jsx,
65
+ target.classList
66
+ );
67
+ nodesPairsToDiff.push({
68
+ source: matchingSource.element,
69
+ target,
70
+ });
71
+ } else if (source) {
72
+ debug('NO matching source for target but source in slot', source.__jsx);
73
+
74
+ if (targetMap.check(source)) {
75
+ // the source is somewhere else in the target, so just add this
76
+ // target element and assume the source will get resolved later.
77
+ debug('adding', target.__jsx, 'at', index);
78
+ baseInstructions.push(insertNode(target, { parent, index }));
79
+ } else {
80
+ // no matching target, but something is in the index/slot ... so swap
81
+ debug('replacing', source.__jsx, target.__jsx, 'at', index);
82
+ sourceMap.clear(source); // resolve source
83
+ baseInstructions.push(replaceNode(source as Dom, target as Dom));
84
+ }
85
+ } else {
86
+ // extra targets, add these to the end of the parent in order received
87
+ debug('adding target to end', target.__jsx);
88
+ baseInstructions.push(insertNode(target, { parent, index }));
89
+ }
90
+ } else if (source) {
91
+ // stuff has been remove from the target
92
+ // check to see if source has been resolved in map
93
+ // if not remove from dom
94
+ const matchingSource = sourceMap.pullMatch(source);
95
+ if (matchingSource.element) {
96
+ debug('removing', source.__jsx);
97
+ baseInstructions.push(removeNode(source));
98
+ }
99
+ }
100
+ }
101
+
102
+ // deal with unresolved sources
103
+ sourceMap.remaining().forEach(({ element }) => {
104
+ debug('removing', element.__jsx);
105
+ baseInstructions.push(removeNode(element));
106
+ });
107
+
108
+ const nodeInstructions = nodesPairsToDiff
109
+ .reduce((collection, { source, target }) => {
110
+ return collection.concat(compileForNode(source, target));
111
+ }, [] as Instructions);
112
+
113
+ return baseInstructions.concat(nodeInstructions).sort(instructionsSorter);
114
+ };
115
+
116
+ const instructionsSorter = (left: Instruction, right: Instruction) => {
117
+ if (left.type > right.type) return 1;
118
+ if (left.type < right.type) return -1;
119
+ return 0;
120
+ };
121
+
122
+ const largerLength = (sourceList: HtmlChildren, targetList: HtmlChildren) => {
123
+ const sourceLength = Array.from(sourceList).length;
124
+ const targetLength = Array.from(targetList).length;
125
+ return sourceLength > targetLength ? sourceLength : targetLength;
126
+ };
127
+
128
+ const compileForNode = compileForNodeGenerator(compileChildren);
@@ -0,0 +1,42 @@
1
+ import { ExpandedElement, InputElement, Instructions } from '../../../types';
2
+ import { changeValue } from './generate';
3
+ import { compileForAttributes } from './attributes';
4
+ import { compileForEvents } from './events';
5
+
6
+ export const compileForElement = (
7
+ source: ExpandedElement,
8
+ target: ExpandedElement,
9
+ ) => {
10
+ const attributeInstructions = compileForAttributes(source, target);
11
+ const eventInstructions = compileForEvents(source, target);
12
+ const valueInstructions = compileForInputValue(source, target);
13
+
14
+ return attributeInstructions
15
+ .concat(eventInstructions)
16
+ .concat(valueInstructions);
17
+ };
18
+
19
+ const compileForInputValue = (
20
+ sourceElement: ExpandedElement,
21
+ targetElement: ExpandedElement,
22
+ ) => {
23
+ const instructions = [] as Instructions;
24
+ if (sourceElement.tagName !== 'INPUT') {
25
+ return instructions;
26
+ }
27
+
28
+ const source = sourceElement as InputElement;
29
+ const target = targetElement as InputElement;
30
+
31
+ if (source.value !== target.value) {
32
+ instructions.push(
33
+ changeValue(
34
+ source,
35
+ target,
36
+ { name: 'value', value: target.value },
37
+ ),
38
+ );
39
+ }
40
+
41
+ return instructions;
42
+ };
@@ -0,0 +1,51 @@
1
+ import { ExpandedElement, Instructions } from '../../../types';
2
+ import { addEvent, removeEvent, updateEvent } from './generate';
3
+
4
+ export const compileForEvents = (
5
+ source: ExpandedElement,
6
+ target: ExpandedElement,
7
+ ) => {
8
+ const instructions = [] as Instructions;
9
+ const sourceEventMaps = source.eventMaps;
10
+ const targetEventMaps = target.eventMaps;
11
+ const sourceDomEvents = Object.keys(sourceEventMaps);
12
+ const targetDomEvents = Object.keys(targetEventMaps);
13
+
14
+ sourceDomEvents.forEach((domEvent) => {
15
+ const sourceEventMap = sourceEventMaps[domEvent];
16
+ const targetEventMap = targetEventMaps[domEvent];
17
+
18
+ if (!targetEventMap) {
19
+ instructions.push(
20
+ removeEvent(source, target, {
21
+ name: sourceEventMap.domEvent,
22
+ value: sourceEventMap.listener,
23
+ }),
24
+ );
25
+ } else if (targetEventMap.busEvent !== sourceEventMap.busEvent) {
26
+ instructions.push(
27
+ updateEvent(source, target, {
28
+ name: domEvent,
29
+ targetValue: targetEventMap.listener,
30
+ sourceValue: sourceEventMap.listener,
31
+ }),
32
+ );
33
+ } // else events the same
34
+ });
35
+
36
+ targetDomEvents.forEach((domEvent) => {
37
+ const sourceEventMap = sourceEventMaps[domEvent];
38
+ const targetEventMap = targetEventMaps[domEvent];
39
+
40
+ if (!sourceEventMap) {
41
+ instructions.push(
42
+ addEvent(source, target, {
43
+ name: targetEventMap.domEvent,
44
+ value: targetEventMap.listener,
45
+ }),
46
+ );
47
+ }
48
+ });
49
+
50
+ return instructions;
51
+ };