wallace 0.0.2 → 0.0.6

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/LICENSE.md CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2023 Andrew Buchan
3
+ Copyright (c) 2025 Andrew Buchan
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -0,0 +1,64 @@
1
+ /**
2
+ * The base component.
3
+ */
4
+ export function Component(parent) {
5
+ this.parent = parent; // The parent component
6
+ this.props = undefined; // The props passed to the component. May be changed.
7
+ this.el = null; // the element - will be set during build
8
+ this.ref = {}; // user set references to elements or components
9
+ // Internal state objects
10
+ this._o = []; // stashed objects like pools.
11
+ this._e = {}; // stashed elements.
12
+ this._p = {}; // The previous values for watches to compare against
13
+ }
14
+
15
+ var proto = Component.prototype;
16
+
17
+ Object.defineProperty(proto, "hidden", {
18
+ set: function (value) {
19
+ this.el.hidden = value;
20
+ },
21
+ });
22
+
23
+ /**
24
+ * Sets the props and updates the component.
25
+ */
26
+ proto.render = function (props) {
27
+ this.props = props;
28
+ this.update();
29
+ };
30
+
31
+ /**
32
+ * Updates the DOM.
33
+ * Loops over watches, skipping n watches if elements are hidden.
34
+ */
35
+ proto.update = function () {
36
+ let i = 0,
37
+ watch,
38
+ element,
39
+ shieldQuery,
40
+ shieldQueryResult,
41
+ shouldBeVisible;
42
+
43
+ const watches = this.__wc;
44
+ const lookup = this._l;
45
+ const props = this.props;
46
+ lookup.reset();
47
+ const il = watches.length;
48
+ while (i < il) {
49
+ watch = watches[i];
50
+ element = this._e[watch.wk];
51
+ shieldQuery = watch.sq;
52
+ i++;
53
+ shouldBeVisible = true;
54
+ if (shieldQuery) {
55
+ shieldQueryResult = !!lookup.get(this, props, shieldQuery).n;
56
+ shouldBeVisible = watch.rv ? shieldQueryResult : !shieldQueryResult;
57
+ element.hidden = !shouldBeVisible;
58
+ i += shouldBeVisible ? 0 : watch.sc;
59
+ }
60
+ if (shouldBeVisible) {
61
+ lookup.applyCallbacks(this, props, element, watch.cb);
62
+ }
63
+ }
64
+ };
package/lib/index.js ADDED
@@ -0,0 +1,34 @@
1
+ import { createComponent, createProxy, mount } from "./utils";
2
+ import { Component } from "./component";
3
+ import { KeyedPool, SequentialPool } from "./pool";
4
+ import {
5
+ extendComponent,
6
+ findElement,
7
+ getKeyedPool,
8
+ getSequentialPool,
9
+ onEvent,
10
+ nestComponent,
11
+ defineComponent,
12
+ extendPrototype,
13
+ saveMiscObject,
14
+ saveRef,
15
+ } from "./initCalls";
16
+
17
+ export {
18
+ extendComponent,
19
+ Component,
20
+ createComponent,
21
+ createProxy,
22
+ defineComponent,
23
+ extendPrototype,
24
+ findElement,
25
+ getKeyedPool,
26
+ getSequentialPool,
27
+ KeyedPool,
28
+ mount,
29
+ nestComponent,
30
+ onEvent,
31
+ saveMiscObject,
32
+ saveRef,
33
+ SequentialPool,
34
+ };
@@ -0,0 +1,110 @@
1
+ import { Component } from "./component";
2
+ import { Lookup } from "./lookup";
3
+ import { buildComponent, replaceNode } from "./utils";
4
+ import { KeyedPool, SequentialPool } from "./pool";
5
+ const throwAway = document.createElement("template");
6
+
7
+ /**
8
+ * Create an element from html string
9
+ */
10
+ function makeEl(html) {
11
+ throwAway.innerHTML = html;
12
+ return throwAway.content.firstChild;
13
+ }
14
+
15
+ export const getProps = (component) => {
16
+ return component.props;
17
+ };
18
+
19
+ export const findElement = (rootElement, path) => {
20
+ return path.reduce((acc, index) => acc.childNodes[index], rootElement);
21
+ };
22
+
23
+ export const nestComponent = (rootElement, path, cls, parent) => {
24
+ const el = findElement(rootElement, path),
25
+ child = buildComponent(cls, parent);
26
+ replaceNode(el, child.el);
27
+ return child;
28
+ };
29
+
30
+ /**
31
+ * Saves a reference to element or nested component. Can be used to wrap a stash call.
32
+ */
33
+ export const saveRef = (element, component, name) => {
34
+ component.ref[name] = element;
35
+ return element;
36
+ };
37
+
38
+ /**
39
+ * Saves a misc object (anything that's not an element). Can be used to wrap a stash call.
40
+ */
41
+ export const saveMiscObject = (element, component, object) => {
42
+ component._o.push(object);
43
+ return element;
44
+ };
45
+
46
+ export const onEvent = (element, eventName, callback) => {
47
+ element.addEventListener(eventName, callback);
48
+ return element;
49
+ };
50
+
51
+ /**
52
+ * Creates a pool.
53
+ */
54
+ export const getKeyedPool = (cls, keyFn) => {
55
+ return new KeyedPool(cls, keyFn);
56
+ };
57
+
58
+ export const getSequentialPool = (cls) => {
59
+ return new SequentialPool(cls);
60
+ };
61
+
62
+ export function defineComponent(
63
+ html,
64
+ watches,
65
+ lookups,
66
+ buildFunction,
67
+ inheritFrom,
68
+ prototypeExtras,
69
+ ) {
70
+ const Constructor = extendPrototype(
71
+ inheritFrom || Component,
72
+ prototypeExtras,
73
+ );
74
+ extendComponent(Constructor.prototype, html, watches, lookups, buildFunction);
75
+ return Constructor;
76
+ }
77
+
78
+ export function extendComponent(
79
+ prototype,
80
+ html,
81
+ watches,
82
+ lookups,
83
+ buildFunction,
84
+ ) {
85
+ prototype.__wc = watches.map((arr) => ({
86
+ wk: arr[0], // The key of the corresponding element.
87
+ sq: arr[1], // The shield query key
88
+ rv: arr[2], // whether shieldQuery should be flipped
89
+ sc: arr[3], // The number of items to shield
90
+ cb: arr[4], // The callbacks - object
91
+ }));
92
+ prototype._l = new Lookup(lookups);
93
+ prototype._b = buildFunction;
94
+ prototype._n = makeEl(html);
95
+ }
96
+
97
+ export function extendPrototype(base, prototypeExtras) {
98
+ const Constructor = function (parent) {
99
+ base.call(this, parent);
100
+ };
101
+ Constructor.prototype = Object.create(base && base.prototype, {
102
+ constructor: {
103
+ value: Constructor,
104
+ writable: true,
105
+ configurable: true,
106
+ },
107
+ });
108
+ Object.assign(Constructor.prototype, prototypeExtras);
109
+ return Constructor;
110
+ }
package/lib/lookup.js ADDED
@@ -0,0 +1,62 @@
1
+ const ALWAYS_UPDATE = "__";
2
+
3
+ /**
4
+ * Used internally.
5
+ * An object which pools the results of lookup queries so we don't have to
6
+ * repeat them in the same component.
7
+ * The Lookup instance will be shared between instances of a component.
8
+ * Must call reset() on every update.
9
+ */
10
+ export function Lookup(callbacks) {
11
+ this.callbacks = callbacks;
12
+ this.run = {};
13
+ }
14
+
15
+ Lookup.prototype = {
16
+ /**
17
+ * Lookup a watched value during update. Returns an object with {o, n, c}
18
+ * (oldValue, newValue, changed).
19
+ * You must resetLookups before calling get during an update.
20
+ * The point is to pool the result so it doesn't have to be repeated.
21
+ */
22
+ get: function (component, props, key) {
23
+ const run = this.run;
24
+ if (run[key] === undefined) {
25
+ // Verbose but efficient way as it avoids lookups?
26
+ // Or is this harmful to performance because we're just reading values more than calling functions?
27
+ let o = component._p[key];
28
+ // TODO: is this checking for watchOnce?
29
+ o = o === undefined ? "" : o;
30
+ const n = this.callbacks[key](props, component);
31
+ const c = n !== o;
32
+ component._p[key] = n;
33
+ const rtn = { n, o, c };
34
+ run[key] = rtn;
35
+ return rtn;
36
+ }
37
+ return run[key];
38
+ },
39
+ reset: function () {
40
+ this.run = {};
41
+ },
42
+ applyCallbacks: function (component, props, element, callbacks) {
43
+ for (let key in callbacks) {
44
+ let callback = callbacks[key];
45
+ if (key === ALWAYS_UPDATE) {
46
+ callback.call(component, element, component.props, component);
47
+ } else {
48
+ const result = this.get(component, props, key);
49
+ if (result.c) {
50
+ callback.call(
51
+ component,
52
+ result.n,
53
+ result.o,
54
+ element,
55
+ props,
56
+ component,
57
+ );
58
+ }
59
+ }
60
+ }
61
+ },
62
+ };
package/lib/pool.js ADDED
@@ -0,0 +1,155 @@
1
+ import { createComponent } from "./utils";
2
+
3
+ /*
4
+ * Gets a component from the pool.
5
+ */
6
+ const getComponent = (pool, componentClass, key, item, parent) => {
7
+ let component;
8
+ if (pool.hasOwnProperty(key)) {
9
+ component = pool[key];
10
+ component.render(item);
11
+ } else {
12
+ component = createComponent(componentClass, parent, item);
13
+ pool[key] = component;
14
+ }
15
+ return component;
16
+ };
17
+
18
+ /**
19
+ * Trims the unwanted child elements from the end.
20
+ *
21
+ * @param {Node} e
22
+ * @param {Array} childNodes
23
+ * @param {Int} itemsLength
24
+ */
25
+ function trimChildren(e, childNodes, itemsLength) {
26
+ let lastIndex = childNodes.length - 1;
27
+ let keepIndex = itemsLength - 1;
28
+ for (let i = lastIndex; i > keepIndex; i--) {
29
+ e.removeChild(childNodes[i]);
30
+ }
31
+ }
32
+
33
+ /**
34
+ * Pulls an item forward in an array, to replicate insertBefore.
35
+ * @param {Array} arr
36
+ * @param {any} item
37
+ * @param {Int} to
38
+ */
39
+ function pull(arr, item, to) {
40
+ const position = arr.indexOf(item);
41
+ if (position != to) {
42
+ arr.splice(to, 0, arr.splice(position, 1)[0]);
43
+ }
44
+ }
45
+
46
+ /**
47
+ * Pools same type components, retrieving by key.
48
+ * Must not be shared.
49
+ *
50
+ * @param {class} componentClass - The class of Component to create.
51
+ * @param {function} keyFn - A function which obtains the key to pool by
52
+ */
53
+ export function KeyedPool(componentClass, keyFn) {
54
+ this._v = componentClass;
55
+ this._f = keyFn;
56
+ this._k = []; // keys
57
+ this._p = {}; // pool of component instances
58
+ }
59
+ const proto = KeyedPool.prototype;
60
+
61
+ /**
62
+ * Retrieves a single component. Though not used in wallace itself, it may
63
+ * be used elsewhere, such as in the router.
64
+ *
65
+ * @param {Object} item - The item which will be passed as props.
66
+ * @param {Component} parent - The parent component.
67
+ */
68
+ proto.getOne = function (item, parent) {
69
+ return getComponent(this._p, this._v, this._f(item), item, parent);
70
+ };
71
+
72
+ /**
73
+ * Updates the element's childNodes to match the items.
74
+ * Performance is important.
75
+ *
76
+ * @param {DOMElement} e - The DOM element to patch.
77
+ * @param {Array} items - Array of items which will be passed as props.
78
+ * @param {Component} parent - The parent component.
79
+ */
80
+ proto.patch = function (e, items, parent) {
81
+ // Attempt to speed up by reducing lookups. Does this even do anything?
82
+ // Does webpack undo this/do it for for me? Does the engine?
83
+ const pool = this._p;
84
+ const componentClass = this._v;
85
+ const keyFn = this._f;
86
+ const childNodes = e.childNodes;
87
+ const itemsLength = items.length;
88
+ const oldKeySequence = this._k;
89
+ const newKeys = [];
90
+ let item,
91
+ key,
92
+ component,
93
+ childElementCount = oldKeySequence.length + 1;
94
+ for (let i = 0; i < itemsLength; i++) {
95
+ item = items[i];
96
+ key = keyFn(item);
97
+ component = getComponent(pool, componentClass, key, item, parent);
98
+ newKeys.push(key);
99
+ if (i > childElementCount) {
100
+ e.appendChild(component.el);
101
+ } else if (key !== oldKeySequence[i]) {
102
+ e.insertBefore(component.el, childNodes[i]);
103
+ pull(oldKeySequence, key, i);
104
+ }
105
+ }
106
+ this._k = newKeys;
107
+ trimChildren(e, childNodes, itemsLength);
108
+ };
109
+
110
+ /**
111
+ * Pools same type components, retrieving by sequence.
112
+ *
113
+ * @param {class} componentClass - The class of Component to create.
114
+ */
115
+ export function SequentialPool(componentClass) {
116
+ this._v = componentClass;
117
+ this._p = []; // pool of component instances
118
+ this._c = 0; // Child element count
119
+ }
120
+
121
+ /**
122
+ * Updates the element's childNodes to match the items.
123
+ * Performance is important.
124
+ *
125
+ * @param {DOMElement} e - The DOM element to patch.
126
+ * @param {Array} items - Array of items which will be passed as props.
127
+ * @param {Component} parent - The parent component.
128
+ */
129
+ SequentialPool.prototype.patch = function (e, items, parent) {
130
+ const pool = this._p;
131
+ const componentClass = this._v;
132
+ const childNodes = e.childNodes;
133
+ const itemsLength = items.length;
134
+ let item,
135
+ component,
136
+ poolCount = pool.length,
137
+ childElementCount = this._c;
138
+
139
+ for (let i = 0; i < itemsLength; i++) {
140
+ item = items[i];
141
+ if (i < poolCount) {
142
+ component = pool[i];
143
+ component.render(item);
144
+ } else {
145
+ component = createComponent(componentClass, parent, item);
146
+ pool.push(component);
147
+ poolCount++;
148
+ }
149
+ if (i >= childElementCount) {
150
+ e.appendChild(component.el);
151
+ }
152
+ }
153
+ this._c = itemsLength;
154
+ trimChildren(e, childNodes, itemsLength);
155
+ };
package/lib/types.d.ts ADDED
@@ -0,0 +1,50 @@
1
+ // This module lies about several definitions in order to make TypeScript work with JSX.
2
+ // Note that docstrings will appear in most IDE tooltips, but only the latest overload.
3
+
4
+ declare module "wallace" {
5
+ interface ComponentFunction<Type> extends Function {
6
+ (props: Type, ...rest: Array<any>): JSX.Element;
7
+ nest?({ props, showIf }: { props?: Type; showIf?: boolean }): JSX.Element;
8
+ repeat?({
9
+ props,
10
+ showIf,
11
+ }: {
12
+ props: Array<Type>;
13
+ showIf?: boolean;
14
+ }): JSX.Element;
15
+ }
16
+
17
+ export type Accepts<Type> = ComponentFunction<Type>;
18
+
19
+ export interface Component<T> {
20
+ update(): void;
21
+ render(props: T): void;
22
+ el: HTMLElement;
23
+ }
24
+
25
+ // TODO: add more to this, maybe overload for each case.
26
+ export function mount<T>(
27
+ element: string | HTMLElement,
28
+ component: Accepts<T>,
29
+ props?: T,
30
+ ): Component<T>;
31
+
32
+ export function createProxy<T>(obj: T, component: Component<T>): T;
33
+
34
+ export function extendPrototype<T>(
35
+ base: Accepts<T>,
36
+ extras?: { [key: string]: any },
37
+ ): Accepts<T>;
38
+ }
39
+
40
+ // TODO: beef these up.
41
+ declare namespace JSX {
42
+ interface Element {}
43
+ interface IntrinsicElements {}
44
+ interface ElementClass {}
45
+ interface IntrinsicAttributes {}
46
+ interface ElementAttributesProperty {}
47
+ }
48
+
49
+ // This is necessary for weird TypeScript reasons.
50
+ export {};
package/lib/utils.js ADDED
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Creates and mounts a component onto an element.
3
+ *
4
+ * @param {unsure} elementOrId Either a string representing an id, or an element.
5
+ * @param {class} cls The class of Component to create
6
+ * @param {object} props The props to pass to the component (optional)
7
+ * @param {object} parent The parent component (optional)
8
+ */
9
+ export function mount(elementOrId, cls, props, parent) {
10
+ const component = createComponent(cls, parent, props);
11
+ replaceNode(getElement(elementOrId), component.el);
12
+ return component;
13
+ }
14
+
15
+ export function replaceNode(nodeToReplace, newNode) {
16
+ nodeToReplace.parentNode.replaceChild(newNode, nodeToReplace);
17
+ }
18
+
19
+ export function getElement(elementOrId) {
20
+ return typeof elementOrId === "string"
21
+ ? document.getElementById(elementOrId)
22
+ : elementOrId;
23
+ }
24
+
25
+ /**
26
+ * Creates a component and initialises it.
27
+ *
28
+ * @param {class} cls The class of Component to create
29
+ * @param {object} parent The parent component (optional)
30
+ * @param {object} props The props to pass to the component (optional)
31
+ */
32
+ export function createComponent(cls, parent, props) {
33
+ const component = buildComponent(cls, parent);
34
+ component.render(props);
35
+ return component;
36
+ }
37
+
38
+ /**
39
+ * Builds a component.
40
+ */
41
+ export function buildComponent(cls, parent) {
42
+ const component = new cls(parent);
43
+ const prototype = cls.prototype;
44
+ const dom = prototype._n.cloneNode(true);
45
+ component.el = dom;
46
+ component._b(component, dom);
47
+ return component;
48
+ }
49
+
50
+ /**
51
+ * Wraps target in a Proxy which calls component.update() whenever it is modified.
52
+ *
53
+ * @param {*} target - Any object, including arrays.
54
+ * @param {*} component - A component.
55
+ * @returns a Proxy object.
56
+ */
57
+ export const createProxy = (target, component) => {
58
+ const handler = {
59
+ get(target, key) {
60
+ if (key == "isProxy") return true;
61
+ const prop = target[key];
62
+ if (typeof prop == "undefined") return;
63
+ // set value as proxy if object
64
+ if (!prop.isProxy && typeof prop === "object")
65
+ target[key] = new Proxy(prop, handler);
66
+ return target[key];
67
+ },
68
+ set(target, key, value) {
69
+ target[key] = value;
70
+ component.update();
71
+ return true;
72
+ },
73
+ };
74
+ return new Proxy(target, handler);
75
+ };
package/package.json CHANGED
@@ -1,38 +1,20 @@
1
1
  {
2
2
  "name": "wallace",
3
- "version": "0.0.2",
4
- "description": "The framework that brings you freedom.",
5
- "babel": {
6
- "presets": [
7
- "@babel/preset-env"
8
- ],
9
- "plugins": [
10
- "babel-plugin-wallace",
11
- "@babel/plugin-syntax-jsx",
12
- "@babel/plugin-proposal-class-properties"
13
- ]
14
- },
3
+ "version": "0.0.6",
4
+ "author": "Andrew Buchan",
5
+ "description": "The framework that brings you glee.",
6
+ "license": "ISC",
7
+ "main": "lib/index.js",
15
8
  "files": [
16
- "dist",
17
- "src"
9
+ "/lib"
18
10
  ],
19
- "main": "dist/wallace.js",
11
+ "types": "./lib/types.d.ts",
20
12
  "scripts": {
21
- "prepublish": "npm run build",
22
- "build": "NODE_ENV=production rollup -c",
23
- "test": "jest --clearCache && jest",
24
- "jest-clear": "jest --clearCache"
13
+ "test": "jest --clearCache && jest"
25
14
  },
26
- "author": "Andrew Buchan",
27
- "license": "MIT",
28
15
  "dependencies": {
29
- "@babel/plugin-proposal-class-properties": "^7.18.6",
30
- "@babel/plugin-syntax-jsx": "^7.22.5",
31
- "babel-plugin-wallace": "^0.0.2",
32
- "jest-summary-reporter": "^0.0.2"
16
+ "babel-plugin-wallace": "^0.0.6",
17
+ "browserify": "^17.0.1"
33
18
  },
34
- "gitHead": "922a989fa10d19dbcd2aaffe725d075a85214a73",
35
- "devDependencies": {
36
- "rollup": "^3.27.0"
37
- }
19
+ "gitHead": "3742fbcfdc21146c90252c2436fde91634fafa9e"
38
20
  }
package/README.md DELETED
@@ -1,3 +0,0 @@
1
- # Wallace
2
-
3
- See https://github.com/andyhasit/wallace