wallace 0.0.1

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/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "wallace",
3
+ "version": "0.0.1",
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
+ },
15
+ "files": [
16
+ "dist",
17
+ "src"
18
+ ],
19
+ "main": "dist/wallace.js",
20
+ "scripts": {
21
+ "prepublish": "npm run build",
22
+ "build": "NODE_ENV=production rollup -c",
23
+ "test": "jest --clearCache && jest",
24
+ "clear-jest": "jest --clearCache"
25
+ },
26
+ "author": "Andrew Buchan",
27
+ "license": "MIT",
28
+ "dependencies": {
29
+ "@babel/plugin-proposal-class-properties": "^7.18.6",
30
+ "babel-plugin-wallace": "^0.0.1"
31
+ },
32
+ "gitHead": "eab798895e65906cfab812010aafd131a74cb72d",
33
+ "devDependencies": {
34
+ "rollup": "^3.27.0"
35
+ }
36
+ }
@@ -0,0 +1,346 @@
1
+ import {KeyedPool, InstancePool, SequentialPool} from './pool'
2
+ import {buildComponent, createComponent} from './utils'
3
+ import {und, makeEl} from './helpers'
4
+ import mountie from './mountie'
5
+ import {Wrapper} from './wrapper'
6
+ import {Lookup} from './lookup'
7
+
8
+ const noop = function() {}
9
+
10
+ /**
11
+ * Represents a component.
12
+ */
13
+ export function Component(parent) {
14
+ const s = this
15
+ s.parent = parent // The parent component
16
+ s.props = undefined // The props passed to the component. May be changed.
17
+
18
+ // These will be set during build
19
+ s.e = null // the element
20
+ s.el = null // the named wrappers
21
+
22
+ // Internal state objects
23
+ s.__nv = [] // Nested components
24
+ s.__ov = {} // The old values for watches to compare against
25
+ }
26
+
27
+
28
+ var proto = Component.prototype
29
+
30
+ proto.onUpdate = noop
31
+ proto.afterUpdate = noop
32
+ proto.onInit = noop
33
+ proto.afterInit = noop
34
+
35
+ /**
36
+ * Gets called once immediately after building.
37
+ * Sets initial props extracted from __html__.
38
+ * Note there is an issue here, in that we rely on there being initial props to call init
39
+ * on nested components.
40
+ */
41
+ proto.init = function() {
42
+ this.onInit()
43
+ for (let key in this.__ip) {
44
+ let nestedComponent = this.el[key]
45
+ let callback = this.__ip[key]
46
+ if (callback) {
47
+ nestedComponent.props = callback(this, this.props)
48
+ }
49
+ nestedComponent.init()
50
+ }
51
+ this.afterInit()
52
+ }
53
+ /**
54
+ * Calls a function somewhere up the parent tree.
55
+ */
56
+ proto.bubble = function(name) {
57
+ let target = this.parent
58
+ while (!und(target)) {
59
+ if (target[name]) {
60
+ // We don't really care about performance here, so accessing arguments is fine.
61
+ return target[name].apply(target, Array.prototype.slice.call(arguments, 1))
62
+ }
63
+ target = target.parent
64
+ }
65
+ throw 'Bubble popped.'
66
+ }
67
+ /**
68
+ * Move the component to new parent. Necessary if sharing a pool.
69
+ */
70
+ proto.move = function(newParent) {
71
+ if (this.parent && this.parent.__nv) {
72
+ const nv = this.parent.__nv
73
+ nv.splice(nv.indexOf(this), 1)
74
+ }
75
+ this.parent = newParent
76
+ }
77
+ /**
78
+ * Builds a nested component of the specified class. Its up to you how you use it.
79
+ */
80
+ proto.nest = function(cls, props) {
81
+ const child = createComponent(cls, this, props || this.props)
82
+ this.__nv.push(child)
83
+ return child
84
+ }
85
+ /**
86
+ * Lookup a watched value during update. Returns an object with {o, n, c}
87
+ * (oldValue, newValue, changed).
88
+ * You must call this.resetLookups before calling this during an update.
89
+ * The point is to pool the result so it doesn't have to be repeated.
90
+ */
91
+ proto.lookup = function(query) {
92
+ return this.__qc.get(this, query)
93
+ }
94
+ /**
95
+ * Resets the lookups, must be called before calling this.lookup() during an update.
96
+ */
97
+ proto.resetLookups = function() {
98
+ this.__qc.reset()
99
+ }
100
+ /**
101
+ * Sets the props and updates the component.
102
+ */
103
+ proto.setProps = function(props) {
104
+ this.props = props
105
+ this.update()
106
+ return this
107
+ }
108
+ /**
109
+ * Call this if you want to get mount() and unmount() callbacks.
110
+ */
111
+ proto.trackMounting = function() {
112
+ this.__mt.track(this)
113
+ }
114
+ /**
115
+ * Updates the component.
116
+ */
117
+ proto.update = function() {
118
+ this.onUpdate()
119
+ this.resetLookups()
120
+ this.updateSelf()
121
+ this.updateNested()
122
+ this.afterUpdate()
123
+ }
124
+ /**
125
+ * Loops over watches skipping shielded watches if elements are hidden.
126
+ */
127
+ proto.updateSelf = function() {
128
+ let i = 0, watch, wrapper, shieldCount, shieldQuery, shieldQueryResult, shouldBeVisible
129
+ const watches = this.__wc
130
+ const il = watches.length
131
+ while (i < il) {
132
+ watch = watches[i]
133
+ wrapper = this.el[watch.wk]
134
+ shieldQuery = watch.sq
135
+ i ++
136
+ shouldBeVisible = true
137
+ if (shieldQuery) {
138
+ // Get the newValue for shieldQuery using lookup
139
+ shieldQueryResult = this.lookup(shieldQuery).n
140
+
141
+ // Determine if shouldBeVisible based on reverseShield
142
+ // i.e. whether "shieldQuery===true" means show or hide.
143
+ shouldBeVisible = watch.rv ? shieldQueryResult : !shieldQueryResult
144
+
145
+ // The number of watches to skip if this element is not visible
146
+ shieldCount = shouldBeVisible ? 0 : watch.sc
147
+
148
+ // Set the element visibility
149
+ wrapper.visible(shouldBeVisible)
150
+ i += shieldCount
151
+ }
152
+ if (shouldBeVisible) {
153
+ applyWatchCallbacks(this, wrapper, watch.cb)
154
+ }
155
+ }
156
+ }
157
+ /**
158
+ * Update nested components (but not repeat elements).
159
+ */
160
+ proto.updateNested = function() {
161
+ // These are user created by calling nest()
162
+ const items = this.__nv
163
+ for (let i=0, il=items.length; i<il; i++) {
164
+ let child = items[i]
165
+ if (child.__ia()) {
166
+ child.update()
167
+ }
168
+ }
169
+ // These are created with directives, and whose props arguments may need reprocessed.
170
+ for (let key in this.__ip) {
171
+ let callback = this.__ip[key]
172
+ let nestedComponent = this.el[key]
173
+ if (callback) {
174
+ nestedComponent.setProps(callback(this, this.props))
175
+ } else {
176
+ nestedComponent.update()
177
+ }
178
+ }
179
+ }
180
+ /**
181
+ * Calls the callback if the value has changed (
182
+ */
183
+ // changed(name, callback) {
184
+ // const n = this.__ov[name]
185
+ // const o = this.props[name]
186
+ // if (n !== o) {
187
+ // callback(n, o)
188
+ // }
189
+ // }
190
+
191
+
192
+ /**
193
+ * Creates a watch.
194
+ */
195
+ proto.__wa = function(wrapperKey, shieldQuery, reverseShield, shieldCount, callbacks) {
196
+ return {
197
+ wk: wrapperKey, // The key of the corresponding wrapper.
198
+ sq: shieldQuery, // The shield query key
199
+ rv: reverseShield, // whether shieldQuery should be flipped
200
+ sc: shieldCount, // The number of items to shield
201
+ cb: callbacks // The callbacks - object
202
+ }
203
+ }
204
+
205
+
206
+ const applyWatchCallbacks = (component, wrapper, callbacks) => {
207
+
208
+ for (let key in callbacks) {
209
+ let callback = callbacks[key]
210
+ if (key === '*') {
211
+ callback.call(component, wrapper, component.props, component)
212
+ } else {
213
+ // means: {new, old, changed}
214
+ const {n, o, c} = component.lookup(key)
215
+ if (c) {
216
+ callback.call(component, n, o, wrapper, component.props, component)
217
+ }
218
+ }
219
+ }
220
+ }
221
+
222
+
223
+ /**
224
+ * The global mount tracker.
225
+ */
226
+ proto.__mt = mountie
227
+
228
+ /**
229
+ * Nest Internal. For building a nested component declared in the html.
230
+ */
231
+ proto.__ni = function(path, cls) {
232
+ const child = buildComponent(cls, this)
233
+ this.__gw(path).replace(child.e)
234
+ return child
235
+ }
236
+
237
+ /**
238
+ *
239
+ * @param {function} baseClass - the base class to extend from
240
+ * @param {object} [prototypeExtras] - an object with extra things to be added to the prototype
241
+ * @param {function} [prototypeExtras] - the function to be used as constructor
242
+ */
243
+ Component.prototype.__ex = function(baseClass, prototypeExtras, constructorFunction) {
244
+ var subClass = constructorFunction || function(parent) {
245
+ baseClass.call(this, parent)
246
+ }
247
+ subClass.prototype = Object.create(baseClass && baseClass.prototype, {
248
+ constructor: { value: subClass, writable: true, configurable: true }
249
+ });
250
+ if (prototypeExtras) {
251
+ Object.assign(subClass.prototype, prototypeExtras);
252
+ }
253
+ return subClass
254
+ }
255
+
256
+
257
+ /**
258
+ * Create a component pool.
259
+ */
260
+ proto.pool = function(cls, keyFn) {
261
+ return keyFn ? new KeyedPool(cls, keyFn) : new SequentialPool(cls)
262
+ }
263
+
264
+ /**
265
+ * Create an instance pool, for switches.
266
+ */
267
+ proto.__ic = function(mappings, fallback) {
268
+ return new InstancePool(mappings, fallback)
269
+ }
270
+
271
+ /**
272
+ * Build the DOM. We pass prototype as local var for compactness.
273
+ */
274
+ proto.__bd = function(prototype) {
275
+ if (prototype.__cn === undefined) {
276
+ prototype.__cn = makeEl(prototype.__ht)
277
+ }
278
+ this.e = prototype.__cn.cloneNode(true)
279
+ }
280
+
281
+ // proto.__bd = function(prototype, clone) {
282
+ // if (clone && !prototype.__cn) {
283
+ // prototype.__cn = makeEl(prototype.__ht)
284
+ // }
285
+ // this.e = clone ? prototype.__cn.cloneNode(true) : makeEl(prototype.__ht)
286
+ // }
287
+
288
+ // proto.__bd = function(prototype) {
289
+ // this.e = makeEl(prototype.__ht)
290
+ // }
291
+
292
+ /**
293
+ * Returns a regular wrapper around element at path, where path is an array of indices.
294
+ * This is used by the babel plugin.
295
+ */
296
+ proto.__gw = function(path) {
297
+ return new Wrapper(this.__fe(path))
298
+ }
299
+
300
+ /**
301
+ * Finds an element at specified path, where path is an array of indices.
302
+ * This is used by the babel plugin.
303
+ */
304
+ proto.__fe = function(path) {
305
+ return path.reduce((acc, index) => acc.childNodes[index], this.e)
306
+ }
307
+
308
+ /**
309
+ * Is Attached.
310
+ * Determines whether this component is attached to the DOM.
311
+ */
312
+ proto.__ia = function() {
313
+ let e = this.e
314
+ while (e) {
315
+ if (e === document) {
316
+ return true
317
+ }
318
+ e = e.parentNode
319
+ }
320
+ return false
321
+ }
322
+
323
+ /**
324
+ * Creates a lookup.
325
+ */
326
+ proto.__lu = function(callbacks) {
327
+ return new Lookup(callbacks)
328
+ }
329
+
330
+ /**
331
+ * Creates an anonymous stub component class
332
+ */
333
+ proto.__sv = function() {
334
+ const cls = function(parent) {
335
+ Component.call(this, parent)
336
+ }
337
+ cls.prototype = new Component()
338
+ return cls
339
+ }
340
+
341
+ /**
342
+ * Toggles visibility, like wrapper.
343
+ */
344
+ proto.visible = function(visible) {
345
+ this.e.classList.toggle('hidden', !visible)
346
+ }
package/src/helpers.js ADDED
@@ -0,0 +1,16 @@
1
+ export const doc = document
2
+ const throwAway = doc.createElement('template')
3
+
4
+ /**
5
+ * Create an element from html string
6
+ */
7
+ export function makeEl(html) {
8
+ throwAway.innerHTML = html
9
+ return throwAway.content.firstChild
10
+ }
11
+
12
+ /**
13
+ * Some utility functions
14
+ */
15
+ export const und = x => x === undefined
16
+ export const isStr = x => typeof x === 'string'
package/src/index.js ADDED
@@ -0,0 +1,18 @@
1
+ import {createComponent, h, mount, wrap} from './utils'
2
+ import {isStr} from './helpers'
3
+ import {Component} from './component'
4
+ import {KeyedPool, InstancePool, SequentialPool} from './pool'
5
+ import {Wrapper} from './wrapper'
6
+
7
+ module.exports = {
8
+ createComponent,
9
+ h,
10
+ mount,
11
+ KeyedPool,
12
+ InstancePool,
13
+ isStr,
14
+ SequentialPool,
15
+ Component,
16
+ Wrapper,
17
+ wrap
18
+ }
package/src/lookup.js ADDED
@@ -0,0 +1,36 @@
1
+ import {und} from './helpers'
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
+ get: function(component, key) {
17
+ const run = this.run
18
+ if (run[key] === undefined) {
19
+ // Verbose but efficient way as it avoids lookups?
20
+ // Or is this harmful to performance because we're just reading values more than calling functions?
21
+ let o = component.__ov[key]
22
+ o = und(o) ? '' : o
23
+ const n = this.callbacks[key](component, component.props)
24
+ const c = n !== o
25
+ component.__ov[key] = n
26
+ const rtn = {n, o, c}
27
+ run[key] = rtn
28
+ return rtn
29
+ }
30
+ return run[key]
31
+ },
32
+ reset: function() {
33
+ this.run = {}
34
+ }
35
+ }
36
+
package/src/mountie.js ADDED
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Wallace's crude way of tracking mounting and unmounting.
3
+ */
4
+
5
+ const trackedComponents = []
6
+
7
+ export default {
8
+ track: function (component) {
9
+ trackedComponents.push({component: component, isAttached: component.__ia()})
10
+ },
11
+ flush: function () {
12
+ for (let i=0, il=trackedComponents.length; i<il; i++) {
13
+ let trackedComponent = trackedComponents[i]
14
+ let component = trackedComponent.component
15
+ let attachedNow = component.__ia()
16
+ if (attachedNow !== trackedComponent.isAttached) {
17
+ let fn = attachedNow ? component.mount : component.unmount
18
+ fn.apply(component)
19
+ trackedComponent.isAttached = attachedNow
20
+ }
21
+ }
22
+ }
23
+ }
package/src/pool.js ADDED
@@ -0,0 +1,173 @@
1
+ import {createComponent} from './utils'
2
+
3
+ /**
4
+ * Pools same type components, retrieving by sequence.
5
+ * Must not be shared.
6
+ *
7
+ * @param {class} componentClass - The class of Component to create.
8
+ * @param {function} keyFn - A function which obtains the key to pool by
9
+ */
10
+ export function KeyedPool(componentClass, keyFn) {
11
+ this._v = componentClass
12
+ this._f = keyFn
13
+ this._k = [] // keys
14
+ this._p = {} // pool of component instances
15
+ }
16
+ const proto = KeyedPool.prototype
17
+
18
+ /**
19
+ * Retrieves a single component. Though not used in Wallace itself, it may
20
+ * be used elsewhere, such as in the router.
21
+ *
22
+ * @param {Object} item - The item which will be passed as props.
23
+ * @param {Component} parent - The parent component.
24
+ */
25
+ proto.getOne = function(item, parent) {
26
+ return this._get(this._p, this._v, this._f(item), item, parent)
27
+ }
28
+
29
+ /**
30
+ * Updates the element's childNodes to match the items.
31
+ * Performance is important.
32
+ *
33
+ * @param {DOMElement} e - The DOM element to patch.
34
+ * @param {Array} items - Array of items which will be passed as props.
35
+ * @param {Component} parent - The parent component.
36
+ */
37
+ proto.patch = function(e, items, parent) {
38
+ const pool = this._p
39
+ const componentClass = this._v
40
+ const keyFn = this._f
41
+ const childNodes = e.childNodes
42
+ const itemsLength = items.length
43
+ const oldKeySequence = this._k
44
+ const newKeys = []
45
+ let item, key, component, childElementCount = oldKeySequence.length + 1
46
+ for (let i=0; i<itemsLength; i++) {
47
+ item = items[i]
48
+ key = keyFn(item)
49
+ component = this._get(pool, componentClass, key, item, parent)
50
+ newKeys.push(key)
51
+ if (i > childElementCount) {
52
+ e.appendChild(component.e)
53
+ } else if (key !== oldKeySequence[i]) {
54
+ e.insertBefore(component.e, childNodes[i])
55
+ pull(oldKeySequence, key, i)
56
+ }
57
+ }
58
+ this._k = newKeys
59
+ trimChildren(e, childNodes, itemsLength)
60
+ }
61
+
62
+ // Internal
63
+ proto._get = function(pool, componentClass, key, item, parent) {
64
+ let component
65
+ if (pool.hasOwnProperty(key)) {
66
+ component = pool[key]
67
+ component.setProps(item)
68
+ } else {
69
+ component = createComponent(componentClass, parent, item)
70
+ pool[key] = component;
71
+ }
72
+ return component
73
+ }
74
+
75
+ /**
76
+ * Pools same type components, retrieving by sequence.
77
+ * Must not be shared.
78
+ *
79
+ * @param {class} componentClass - The class of Component to create.
80
+ */
81
+ export function SequentialPool(componentClass) {
82
+ this._v = componentClass
83
+ this._p = [] // pool of component instances
84
+ this._c = 0 // Child element count
85
+ }
86
+
87
+ /**
88
+ * Updates the element's childNodes to match the items.
89
+ * Performance is important.
90
+ *
91
+ * @param {DOMElement} e - The DOM element to patch.
92
+ * @param {Array} items - Array of items which will be passed as props.
93
+ * @param {Component} parent - The parent component.
94
+ */
95
+ SequentialPool.prototype.patch = function(e, items, parent) {
96
+ const pool = this._p
97
+ const componentClass = this._v
98
+ const childNodes = e.childNodes
99
+ const itemsLength = items.length
100
+ let item, component, poolCount = pool.length, childElementCount = this._c
101
+
102
+ for (let i=0; i<itemsLength; i++) {
103
+ item = items[i]
104
+ if (i < poolCount) {
105
+ component = pool[i]
106
+ component.setProps(item)
107
+ } else {
108
+ component = createComponent(componentClass, parent, item)
109
+ pool.push(component)
110
+ poolCount ++
111
+ }
112
+ if (i >= childElementCount) {
113
+ e.appendChild(component.e)
114
+ }
115
+ }
116
+ this._c = itemsLength
117
+ trimChildren(e, childNodes, itemsLength)
118
+ }
119
+
120
+ /**
121
+ * An object which creates and pools components according to the mappings provided.
122
+ * If there is no match in the mappings, the fallback function is called.
123
+ *
124
+ * Note that the fallback must return an instance (of Component or Wrapper) whereas
125
+ * mappings must specify component classes.
126
+ *
127
+ * You can rely solely on the fallback if you like.
128
+ *
129
+ * @param {Object} mappings - a mapping of format key->componentClass
130
+ * @param {function} fallback - a function to call when no key is provided.
131
+ *
132
+ */
133
+ export function InstancePool(mappings, fallback) {
134
+ this._m = mappings
135
+ this._f = fallback
136
+ this._i = {} // Instances
137
+ }
138
+
139
+ InstancePool.prototype.getOne = function(key, parentComponent) {
140
+ if (!this._i.hasOwnProperty(key)) {
141
+ this._i[key] = this._m.hasOwnProperty(key) ?
142
+ parentComponent.nest(this._m[key]) : this._f(key, parentComponent)
143
+ }
144
+ return this._i[key]
145
+ }
146
+
147
+ /**
148
+ * Trims the unwanted child elements from the end.
149
+ *
150
+ * @param {Node} e
151
+ * @param {Array} childNodes
152
+ * @param {Int} itemsLength
153
+ */
154
+ function trimChildren(e, childNodes, itemsLength) {
155
+ let lastIndex = childNodes.length - 1
156
+ let keepIndex = itemsLength - 1
157
+ for (let i=lastIndex; i>keepIndex; i--) {
158
+ e.removeChild(childNodes[i])
159
+ }
160
+ }
161
+
162
+ /**
163
+ * Pulls an item forward in an array, to replicate insertBefore.
164
+ * @param {Array} arr
165
+ * @param {any} item
166
+ * @param {Int} to
167
+ */
168
+ function pull(arr, item, to) {
169
+ const position = arr.indexOf(item)
170
+ if (position != to) {
171
+ arr.splice(to, 0, arr.splice(position, 1)[0])
172
+ }
173
+ }