@muze-labs/simplyflow 0.9.0 → 0.10.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,14 +1,16 @@
1
1
  {
2
2
  "name": "@muze-labs/simplyflow",
3
- "version": "0.9.0",
4
- "description": "Reactive browser applications with signals, effects, data binding and an app layer",
3
+ "version": "0.10.2",
4
+ "description": "Reactive browser applications with signals, effects, data binding and an app layer.",
5
5
  "type": "module",
6
- "main": "./src/flow.mjs",
6
+ "main": "./src/index.mjs",
7
7
  "exports": {
8
- ".": "./src/flow.mjs",
8
+ ".": "./src/index.mjs",
9
9
  "./app": "./src/app.mjs",
10
10
  "./state": "./src/state.mjs",
11
11
  "./bind": "./src/bind.mjs",
12
+ "./bind/render": "./src/bind-render.mjs",
13
+ "./bind/transformers": "./src/bind-transformers.mjs",
12
14
  "./model": "./src/model.mjs",
13
15
  "./dom": "./src/dom.mjs",
14
16
  "./path": "./src/path.mjs",
@@ -19,54 +21,39 @@
19
21
  "./includes": "./src/include.mjs",
20
22
  "./shortcuts": "./src/shortcut.mjs",
21
23
  "./highlight": "./src/highlight.mjs",
24
+ "./render": "./src/render.mjs",
25
+ "./suggest": "./src/suggest.mjs",
26
+ "./symbols": "./src/symbols.mjs",
22
27
  "./dist/simply.flow.js": "./dist/simply.flow.js",
23
28
  "./dist/simply.flow.min.js": "./dist/simply.flow.min.js",
24
29
  "./package.json": "./package.json"
25
30
  },
26
- "directories": {
27
- "example": "examples"
28
- },
31
+ "sideEffects": [
32
+ "./src/index.mjs",
33
+ "./src/render.mjs"
34
+ ],
35
+ "files": [
36
+ "src/",
37
+ "dist/"
38
+ ],
29
39
  "scripts": {
30
- "build": "esbuild src/flow.mjs --bundle --outfile=dist/simply.flow.min.js --minify --sourcemap",
31
- "build-dev": "esbuild src/flow.mjs --bundle --outfile=dist/simply.flow.js",
32
- "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
33
- "dev": "watch 'npm run build-dev' ./src & npx http-server ."
40
+ "build": "esbuild src/index.mjs --bundle --outfile=dist/simply.flow.min.js --minify --sourcemap",
41
+ "build-dev": "esbuild src/index.mjs --bundle --outfile=dist/simply.flow.js",
42
+ "dev": "watch 'npm run build-dev' ./src ../state/src ../bind/src ../model/src ../app/src & npx http-server ../.."
43
+ },
44
+ "dependencies": {
45
+ "@muze-labs/simplyflow-state": "0.10.1",
46
+ "@muze-labs/simplyflow-bind": "0.10.1",
47
+ "@muze-labs/simplyflow-model": "0.10.1",
48
+ "@muze-labs/simplyflow-app": "0.10.1"
34
49
  },
50
+ "license": "MIT",
35
51
  "repository": {
36
52
  "type": "git",
37
53
  "url": "git+https://github.com/muze-labs/simplyflow.git"
38
54
  },
39
- "keywords": [
40
- "UI",
41
- "components",
42
- "declarative",
43
- "reactive"
44
- ],
45
- "author": "auke@muze.nl",
46
- "license": "MIT",
47
55
  "bugs": {
48
56
  "url": "https://github.com/muze-labs/simplyflow/issues"
49
57
  },
50
- "homepage": "https://github.com/muze-labs/simplyflow#readme",
51
- "engines": {
52
- "node": ">=20.0.0"
53
- },
54
- "devDependencies": {
55
- "@eslint/js": "^9.17.0",
56
- "esbuild": "^0.24.2",
57
- "globals": "^15.13.0",
58
- "jest": "^29.7.0",
59
- "jest-environment-jsdom": "^29.7.0",
60
- "watch": "^1.0.2"
61
- },
62
- "files": [
63
- "README.md",
64
- "MUZE_ALIGNMENT.md",
65
- "dist/",
66
- "src/"
67
- ],
68
- "jest": {
69
- "transform": {},
70
- "testEnvironment": "jsdom"
71
- }
72
- }
58
+ "homepage": "https://github.com/muze-labs/simplyflow#readme"
59
+ }
package/src/action.mjs CHANGED
@@ -1,64 +1 @@
1
- import { closest } from './suggest.mjs'
2
-
3
- const warnedUnknownActions = new WeakMap()
4
-
5
- export function actions(options)
6
- {
7
- if (options.app) {
8
- const functionHandler = {
9
- apply(target, thisArg, argumentsList)
10
- {
11
- try {
12
- const result = target(...argumentsList)
13
- if (result instanceof Promise) {
14
- return result.catch(err => {
15
- return options.app.onError.call(this, err, target)
16
- })
17
- }
18
- return result
19
- } catch(err) {
20
- return options.app.onError.call(this, err, target)
21
- }
22
- }
23
- }
24
-
25
- const actionHandler = {
26
- get(target, property)
27
- {
28
- if (!Object.hasOwn(target, property)) {
29
- warnUnknownAction(target, property)
30
- return undefined
31
- }
32
- if (options.app.onError) {
33
- return new Proxy(target[property].bind(options.app), functionHandler)
34
- } else {
35
- return target[property].bind(options.app)
36
- }
37
- }
38
- }
39
- return new Proxy(options.actions, actionHandler)
40
- } else {
41
- return options
42
- }
43
- }
44
-
45
- function warnUnknownAction(actions, property)
46
- {
47
- if (typeof property !== 'string') {
48
- return
49
- }
50
-
51
- let warned = warnedUnknownActions.get(actions)
52
- if (!warned) {
53
- warned = new Set()
54
- warnedUnknownActions.set(actions, warned)
55
- }
56
- if (warned.has(property)) {
57
- return
58
- }
59
- warned.add(property)
60
-
61
- const suggestion = closest(property, Object.keys(actions))
62
- const suffix = suggestion ? `. Did you mean "${suggestion}"?` : ''
63
- console.warn(`simplyflow/action: unknown action "${property}"${suffix}`)
64
- }
1
+ export * from '@muze-labs/simplyflow-app/action'
package/src/app.mjs CHANGED
@@ -1,282 +1 @@
1
- import { bind } from './bind.mjs'
2
- import { signal } from './state.mjs'
3
- import { routes } from './route.mjs'
4
- import { commands, destroyCommands } from './command.mjs'
5
- import { actions } from './action.mjs'
6
- import { shortcuts, destroyShortcuts, accesskeys, destroyAccesskeys } from './shortcut.mjs'
7
- import { behaviors } from './behavior.mjs'
8
- import { includes } from './include.mjs'
9
- import { closest } from './suggest.mjs'
10
-
11
- const APP_OPTIONS = [
12
- 'container',
13
- 'data',
14
- 'templates',
15
- 'styles',
16
- 'start',
17
- 'onError',
18
- 'components',
19
- 'behaviors',
20
- 'baseURL',
21
- 'commands',
22
- 'shortcuts',
23
- 'routes',
24
- 'actions'
25
- ]
26
-
27
- class SimplyApp
28
- {
29
- constructor(options={})
30
- {
31
- if (options.components) {
32
- const mergedOptions = {}
33
- mergeComponents(mergedOptions, options.components)
34
- mergeOptions(mergedOptions, options) // app options override component options
35
- options = mergedOptions
36
- }
37
-
38
-
39
- this.container = options.container || document.body
40
- this.destroyed = false
41
- this.data = signal(options.data || {})
42
- this.start = options.start
43
- this.onError = options.onError
44
- this.components = options.components
45
- this.baseURL = options.baseURL
46
-
47
- installTemplates(this.container, options.templates)
48
- installStyles(this.container, options.styles)
49
-
50
- for (const key of Object.keys(options)) {
51
- switch(key) {
52
- case 'container':
53
- case 'data':
54
- case 'templates':
55
- case 'styles':
56
- case 'start':
57
- case 'onError':
58
- case 'components':
59
- case 'baseURL':
60
- break
61
- case 'commands':
62
- this.commands = commands({ app: this, container: this.container, commands: options.commands})
63
- break
64
- case 'shortcuts':
65
- this.shortcuts = shortcuts({ app: this, shortcuts: options.shortcuts })
66
- break
67
- case 'behaviors':
68
- this.behaviors = behaviors({ app: this, container: this.container, behaviors: options.behaviors })
69
- break
70
- case 'routes':
71
- this.routes = routes({ app: this, routes: options.routes})
72
- break
73
- case 'actions':
74
- this.actions = actions({app: this, actions: options.actions})
75
- break
76
- case 'prototype':
77
- case '__proto__':
78
- // ignore this to avoid prototype pollution
79
- break
80
- default:
81
- // Unknown options become app properties. Warn only when the
82
- // name is close to a built-in option, which usually means a typo.
83
- warnLikelyOptionTypo(key)
84
- this[key] = options[key]
85
- break
86
- }
87
- }
88
-
89
- this.binding = bind({
90
- root: this.data,
91
- container: this.container,
92
- attribute: 'data-simply'
93
- })
94
-
95
- this.includes = includes({ container: this.container })
96
-
97
- this.accesskeys = accesskeys({ app: this, container: this.container })
98
- }
99
-
100
- get app()
101
- {
102
- return this
103
- }
104
-
105
- destroy()
106
- {
107
- this.destroyed = true
108
- if (this.binding) {
109
- this.binding.destroy()
110
- this.binding = undefined
111
- }
112
- if (this.commands) {
113
- destroyCommands(this.commands)
114
- }
115
- if (this.shortcuts) {
116
- destroyShortcuts(this.shortcuts)
117
- }
118
- if (this.accesskeys) {
119
- destroyAccesskeys(this.accesskeys)
120
- this.accesskeys = undefined
121
- }
122
- if (this.routes) {
123
- this.routes.destroy()
124
- this.routes = undefined
125
- }
126
- if (this.behaviors) {
127
- this.behaviors.destroy()
128
- this.behaviors = undefined
129
- }
130
- if (this.includes) {
131
- this.includes.destroy()
132
- this.includes = undefined
133
- }
134
- }
135
- }
136
-
137
- function installTemplates(container, templates)
138
- {
139
- if (!templates) {
140
- return
141
- }
142
- for (const name of Object.keys(templates)) {
143
- const element = document.createElement('div')
144
- element.innerHTML = templates[name]
145
- let template = container.querySelector('template#'+name)
146
- if (!template) {
147
- template = document.createElement('template')
148
- template.id = name
149
- template.content.append(...element.children)
150
- container.appendChild(template)
151
- } else {
152
- template.content.replaceChildren(...element.children)
153
- }
154
- }
155
- }
156
-
157
- function installStyles(container, styles)
158
- {
159
- if (!styles) {
160
- return
161
- }
162
- for (const name of Object.keys(styles)) {
163
- let style = container.querySelector('style#'+name+'.css')
164
- if (!style) {
165
- style = document.createElement('style')
166
- style.id = name+'.css'
167
- container.appendChild(style)
168
- }
169
- style.innerHTML = styles[name]
170
- }
171
- }
172
-
173
-
174
- function warnLikelyOptionTypo(key)
175
- {
176
- const suggestion = closest(key, APP_OPTIONS)
177
- if (suggestion) {
178
- console.warn(`simplyflow/app: unknown option "${key}". Did you mean "${suggestion}"? The option was still added to the app as "app.${key}".`)
179
- }
180
- }
181
-
182
- function initRoutes(app) {
183
- if (app.destroyed) {
184
- return
185
- }
186
- if (app.routes) {
187
- if (app.baseURL) {
188
- app.routes.init({ baseURL: app.baseURL })
189
- }
190
- app.routes.handleEvents()
191
- globalThis.setTimeout(() => {
192
- if (app.destroyed || !app.routes) {
193
- return
194
- }
195
- if (app.routes.has(globalThis.location?.hash)) {
196
- app.routes.match(globalThis.location.hash)
197
- } else {
198
- app.routes.match(globalThis.location?.pathname+globalThis.location?.hash)
199
- }
200
- })
201
- }
202
- }
203
-
204
-
205
- function handleAppError(app, error, context)
206
- {
207
- if (app.onError) {
208
- return app.onError.call(app, error, context)
209
- }
210
- throw error
211
- }
212
-
213
- export function app(options={})
214
- {
215
- const app = new SimplyApp(options)
216
- if (!app.start) {
217
- initRoutes(app)
218
- return app
219
- }
220
-
221
- try {
222
- const result = app.start.call(app)
223
- if (result instanceof Promise) {
224
- result.then(() => initRoutes(app)).catch(error => handleAppError(app, error, app.start))
225
- } else {
226
- initRoutes(app)
227
- }
228
- } catch (error) {
229
- handleAppError(app, error, app.start)
230
- }
231
- return app
232
- }
233
-
234
-
235
- function mergeOptions(options, otherOptions)
236
- {
237
- for (const key of Object.keys(otherOptions)) {
238
- switch(typeof otherOptions[key]) {
239
- case 'object':
240
- if (!otherOptions[key]) {
241
- continue // null
242
- }
243
- if (!options[key]) {
244
- options[key] = otherOptions[key]
245
- } else {
246
- mergeOptions(options[key], otherOptions[key])
247
- }
248
- break
249
- default:
250
- options[key] = otherOptions[key]
251
- }
252
- }
253
- }
254
-
255
- function mergeComponents(options, components) {
256
- for (const name of Object.keys(components)) {
257
- const component = components[name]
258
- if (component.components) {
259
- mergeComponents(options, component.components)
260
- }
261
- if (!options.components) {
262
- options.components = {}
263
- }
264
- options.components[name] = component
265
- for (const key of Object.keys(component)) {
266
- switch(key) {
267
- case 'start':
268
- case 'onError':
269
- // App lifecycle functions are app-level behavior, not merged component state.
270
- case 'components':
271
- // already handled
272
- break
273
- default:
274
- if (!options[key]) {
275
- options[key] = Object.create(null)
276
- }
277
- mergeOptions(options[key], component[key])
278
- break
279
- }
280
- }
281
- }
282
- }
1
+ export * from '@muze-labs/simplyflow-app'
package/src/behavior.mjs CHANGED
@@ -1,121 +1 @@
1
- import { closest } from './suggest.mjs'
2
-
3
- const BEHAVIOR_SELECTOR = '[data-simply-behavior]'
4
-
5
- class SimplyBehaviors
6
- {
7
- constructor(options = {})
8
- {
9
- this.app = options.app
10
- this.container = options.container || document.body
11
- this.behaviors = options.behaviors || {}
12
- this.active = new Set()
13
- this.cleanups = new WeakMap()
14
- this.unknown = new Set()
15
-
16
- this.observer = new MutationObserver((changes) => this.handleChanges(changes))
17
- this.observer.observe(this.container, {
18
- subtree: true,
19
- childList: true
20
- })
21
-
22
- for (const node of behaviorNodes(this.container)) {
23
- this.start(node)
24
- }
25
- }
26
-
27
- start(node)
28
- {
29
- if (this.active.has(node)) {
30
- return
31
- }
32
-
33
- const name = node?.dataset?.simplyBehavior
34
- const behavior = this.behaviors[name]
35
- if (!name || typeof behavior !== 'function') {
36
- this.warnUnknown(name, node)
37
- return
38
- }
39
-
40
- this.active.add(node)
41
- const cleanup = behavior.call(this.app || node, node)
42
- if (typeof cleanup === 'function') {
43
- this.cleanups.set(node, cleanup)
44
- } else if (typeof cleanup !== 'undefined') {
45
- console.warn('simplyflow/behavior: behavior may only return a cleanup function', { cause: cleanup })
46
- }
47
- }
48
-
49
- stop(node)
50
- {
51
- if (!this.active.has(node)) {
52
- return
53
- }
54
- this.active.delete(node)
55
-
56
- const cleanup = this.cleanups.get(node)
57
- this.cleanups.delete(node)
58
- if (cleanup) {
59
- cleanup.call(this.app || node, node)
60
- }
61
- }
62
-
63
- handleChanges(changes)
64
- {
65
- const added = []
66
- for (const change of changes) {
67
- if (change.type !== 'childList') {
68
- continue
69
- }
70
- for (const node of change.removedNodes) {
71
- for (const behaviorNode of behaviorNodes(node)) {
72
- this.stop(behaviorNode)
73
- }
74
- }
75
- for (const node of change.addedNodes) {
76
- added.push(...behaviorNodes(node))
77
- }
78
- }
79
- for (const node of added) {
80
- this.start(node)
81
- }
82
- }
83
-
84
- warnUnknown(name, node)
85
- {
86
- if (!name || this.unknown.has(name)) {
87
- return
88
- }
89
- this.unknown.add(name)
90
-
91
- const suggestion = closest(name, Object.keys(this.behaviors))
92
- const suffix = suggestion ? `. Did you mean "${suggestion}"?` : ''
93
- console.warn(`simplyflow/behavior: unknown behavior "${name}"${suffix}`, { cause: node })
94
- }
95
-
96
- destroy()
97
- {
98
- this.observer.disconnect()
99
- for (const node of Array.from(this.active)) {
100
- this.stop(node)
101
- }
102
- }
103
- }
104
-
105
- export function behaviors(options = {})
106
- {
107
- return new SimplyBehaviors(options)
108
- }
109
-
110
- function behaviorNodes(root)
111
- {
112
- if (!root?.querySelectorAll) {
113
- return []
114
- }
115
-
116
- const nodes = Array.from(root.querySelectorAll(BEHAVIOR_SELECTOR))
117
- if (root.matches?.(BEHAVIOR_SELECTOR)) {
118
- nodes.unshift(root)
119
- }
120
- return nodes
121
- }
1
+ export * from '@muze-labs/simplyflow-app/behavior'
@@ -0,0 +1 @@
1
+ export * from '@muze-labs/simplyflow-bind/render'
@@ -0,0 +1 @@
1
+ export * from '@muze-labs/simplyflow-bind/transformers'