simplyflow 0.8.2 → 0.9.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.
package/src/edit.mjs CHANGED
@@ -1,108 +1,193 @@
1
- /**
2
- * This function returns the cursor position and height, if the cursor is in
3
- * the given element. The x and y position are calculated relative to the top
4
- * left of the given element. This function does not alter the DOM in any way.
5
- */
6
- function getCursorPosition(element) {
7
- const selection = window.getSelection();
8
- if (!selection.rangeCount) return null;
1
+ import toolbars from './edit/toolbars.mjs'
2
+ import '../src/flow.mjs'
9
3
 
10
- const range = document.createRange();
11
- range.setStart(selection.focusNode, selection.focusOffset);
12
- range.collapse(true);
4
+ const editSet = new Set()
13
5
 
14
- // Try getClientRects() first — often non-empty even on empty lines
15
- const elementRect = element.getBoundingClientRect();
16
-
17
- const cursorNode = selection.focusNode;
18
- const cursorElement = cursorNode.nodeType === Node.TEXT_NODE
19
- ? cursorNode.parentElement
20
- : cursorNode;
21
-
22
- let x,y,height;
23
- const rects = range.getClientRects();
24
- if (rects.length > 0) {
25
- x = rects[0].left - elementRect.left
26
- y = rects[0].top - elementRect.top
27
- height = rects[0].height
28
- } else {
29
- // Fallback for truly empty element: use padding from CSS
30
- const style = window.getComputedStyle(cursorElement);
31
- const lineHeight = parseFloat(style.lineHeight);
32
- height = isNaN(lineHeight) ? parseFloat(style.fontSize) : lineHeight
33
- const cursorElementRect = cursorElement.getBoundingClientRect();
34
- x = cursorElementRect.left - elementRect.left + parseFloat(style.paddingLeft)
35
- y = cursorElementRect.top - elementRect.top + parseFloat(style.paddingTop)
36
- }
37
- return {
38
- x,
39
- y,
40
- height,
41
- element: cursorElement
42
- }
43
- }
44
-
45
- export function edit(element)
6
+ export function edit(rootElement)
46
7
  {
47
8
  return simply.app({
48
- container: element,
9
+ container: rootElement,
49
10
  actions: {
50
- showToolbar: function(position) {
51
- const containerRect = this.container.getBoundingClientRect()
52
- this.toolbar.style.top = containerRect.top + position.y + position.height + 'px'
53
- this.toolbar.style.left = containerRect.left + position.x + 'px'
54
- this.toolbar.style.display = 'block'
11
+ editAll: function(elements) {
12
+ for (let el of elements) {
13
+ this.actions.edit(el)
14
+ }
55
15
  },
56
- hideToolbar: function() {
57
- this.toolbar.style.display = 'none'
16
+ edit: function(el) {
17
+ el.setAttribute('contenteditable', true)
18
+ editSet.add(el, true)
58
19
  },
59
- close: function() {
60
- this.container.removeAttribute('contenteditable')
20
+ editClose: function(el) {
21
+ el.removeAttribute('contenteditable')
22
+ editSet.remove(el)
23
+ },
24
+ editQuit: function() {
25
+ for (let el of editSet.entries()) {
26
+ removeAttribute('contenteditable')
27
+ }
61
28
  document.removeEventListener(this.selectionListener)
62
29
  }
63
30
  },
64
31
  keyboard: {
65
32
  default: {
66
33
  'Control+ ': function() {
67
- if (this.toolbar.style.display == 'none') {
68
- const position = getCursorPosition(this.container)
69
- this.actions.showToolbar(position)
34
+ if (this.state.anchor.visible) {
35
+ this.actions.showToolbar()
70
36
  } else {
71
37
  this.actions.hideToolbar()
72
38
  }
73
39
  }
74
40
  }
75
41
  },
76
- hooks: {
77
- start: function() {
78
- this.container.setAttribute('contenteditable', true)
79
- this.toolbar = document.querySelector('simply-edit-focus-toolbar')
80
- if (!this.toolbar) {
81
- this.toolbar = document.createElement('div')
82
- this.toolbar.id = 'simply-edit-focus-toolbar'
83
- this.toolbar.style.position ='absolute'
84
- this.toolbar.style['z-index'] = 10000
85
- this.toolbar.style.border = '1px solid blue'
86
- this.toolbar.innerHTML = 'toolbar'
87
- document.body.appendChild(this.toolbar)
88
- }
89
- this.selectionListener = document.addEventListener('selectionchange', () => {
90
- console.log('selectionchange')
91
- const selection = window.getSelection()
92
- if (!selection.rangeCount || selection.isCollapsed) {
93
- this.actions.hideToolbar()
94
- console.log('no selection')
95
- return
42
+ state: {
43
+ toolbars: {
44
+ mainToolbar: {
45
+ buttons: [
46
+ {
47
+ label: 'Save',
48
+ command: 'save',
49
+ icon: '#save'
50
+ },
51
+ {
52
+ label: 'Undo',
53
+ command: 'undo',
54
+ icon: '#rotate-ccw'
55
+ },
56
+ {
57
+ label: 'Redo',
58
+ command: 'redo',
59
+ icon: '#rotate-cw'
60
+ },
61
+ {
62
+ label: 'Help',
63
+ command: 'help-main',
64
+ icon: '#help-circle'
65
+ },
66
+ {
67
+ label: 'Close',
68
+ command: 'close',
69
+ icon: ''
70
+ }
71
+ ]
72
+ },
73
+ floatToolbarText: {
74
+ buttons: [
75
+ {
76
+ label: 'Text',
77
+ icon: '#type',
78
+ value: 'styleToolbar',
79
+ command: 'expand'
80
+ },
81
+ {
82
+ label: 'Align',
83
+ icon: '#align-left',
84
+ command: 'expand',
85
+ value: 'alignToolbar'
86
+ }
87
+ ],
88
+ toolbars: {
89
+ styleToolbar,
90
+ alignToolbar
96
91
  }
97
- if (!this.container.contains(selection.anchorNode)) {
98
- console.log('selection outside container')
99
- return
92
+ },
93
+ floatToolbarImg: {
94
+ buttons: [
95
+ {
96
+ label: 'Align',
97
+ icon: '#align-left',
98
+ command: 'expand',
99
+ value: 'alignToolbar'
100
+ }
101
+ ],
102
+ toolbars: {
103
+ alignToolbar
100
104
  }
101
- const position = getCursorPosition(this.container)
102
- console.log('position',position)
103
- this.actions.showToolbar(position)
105
+ }
106
+ }
107
+ },
108
+ hooks: {
109
+ start: function() {
110
+ // make sure this.state is a signal before calling start hooks of components
111
+ // and simply.bind - move to default start() hook in app.mjs (when simplyflow is in simplyview)
112
+ this.state = simply.state.signal(this.state),
113
+ simply.bind({
114
+ root: this.state
104
115
  })
116
+ // move this code to default start() hook in app.mja, run it before
117
+ // the app.hooks.start function
118
+ for (let component in this.components) {
119
+ if (this.components[component].hooks?.start) {
120
+ this.components[component].hooks.start.apply(this)
121
+ }
122
+ }
105
123
  }
124
+ },
125
+ components: {
126
+ toolbars
106
127
  }
107
128
  })
108
- }
129
+ }
130
+
131
+ const alignToolbar = {
132
+ buttons: [
133
+ {
134
+ label: 'Left',
135
+ icon: '#align-left',
136
+ command: 'align',
137
+ value: 'left'
138
+ },
139
+ {
140
+ label: 'Center',
141
+ icon: '#align-center',
142
+ command: 'align',
143
+ value: 'center'
144
+ },
145
+ {
146
+ label: 'Right',
147
+ icon: '#align-right',
148
+ command: 'align',
149
+ value: 'right'
150
+ },
151
+ {
152
+ label: 'Justify',
153
+ icon: '#align-justify',
154
+ command: 'align',
155
+ value: 'justify'
156
+ },
157
+ {
158
+ label: 'None',
159
+ icon: '#x',
160
+ command: 'align',
161
+ value: 'none'
162
+ }
163
+ ]
164
+ }
165
+
166
+ const styleToolbar = {
167
+ buttons: [
168
+ {
169
+ label: 'Bold',
170
+ icon: '#bold',
171
+ command: 'toggle',
172
+ value: '<strong>'
173
+ },
174
+ {
175
+ label: 'Italic',
176
+ icon: '#italic',
177
+ command: 'toggle',
178
+ value: '<em>'
179
+ },
180
+ {
181
+ label: 'Underline',
182
+ icon: '#underline',
183
+ command: 'toggle',
184
+ value: '<u>'
185
+ },
186
+ {
187
+ label: 'Code',
188
+ icon: '#code',
189
+ command: 'toggle',
190
+ value: '<code>'
191
+ }
192
+ ]
193
+ }
package/src/model.mjs CHANGED
@@ -1,4 +1,5 @@
1
1
  import {signal, effect, throttledEffect, batch} from './state.mjs'
2
+ import { DEP } from './symbols.mjs'
2
3
 
3
4
  /**
4
5
  * This class implements a pluggable data model, where you can
@@ -46,7 +47,7 @@ class SimplyFlowModel {
46
47
  }
47
48
  const dataSignal = this.effects[this.effects.length-1]
48
49
  const connectedSignal = fn.call(this, dataSignal)
49
- if (!connectedSignal || !connectedSignal[Symbol.Signal]) {
50
+ if (!connectedSignal || !connectedSignal[DEP.SIGNAL]) {
50
51
  throw new Error('addEffect function parameter must return a Signal', { cause: fn })
51
52
  }
52
53
  this.view = connectedSignal