@jinntec/fore 1.2.0 → 1.4.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 (51) hide show
  1. package/dist/fore-dev.js +8 -8
  2. package/dist/fore-dev.js.map +1 -1
  3. package/dist/fore.js +7 -7
  4. package/dist/fore.js.map +1 -1
  5. package/index.js +1 -0
  6. package/package.json +2 -2
  7. package/resources/fore.css +100 -72
  8. package/src/ForeElementMixin.js +4 -0
  9. package/src/actions/abstract-action.js +102 -18
  10. package/src/actions/fx-action.js +9 -10
  11. package/src/actions/fx-append.js +1 -1
  12. package/src/actions/fx-confirm.js +3 -3
  13. package/src/actions/fx-copy.js +68 -0
  14. package/src/actions/fx-delete.js +53 -62
  15. package/src/actions/fx-dispatch.js +1 -1
  16. package/src/actions/fx-hide.js +4 -2
  17. package/src/actions/fx-insert.js +1 -1
  18. package/src/actions/fx-message.js +26 -2
  19. package/src/actions/fx-refresh.js +2 -2
  20. package/src/actions/fx-reload.js +30 -0
  21. package/src/actions/fx-replace.js +1 -1
  22. package/src/actions/fx-return.js +1 -1
  23. package/src/actions/fx-send.js +13 -3
  24. package/src/actions/fx-setfocus.js +33 -6
  25. package/src/actions/fx-setvalue.js +5 -5
  26. package/src/actions/fx-show.js +2 -1
  27. package/src/actions/fx-toggle.js +6 -1
  28. package/src/actions/fx-update.js +1 -1
  29. package/src/events.js +24 -0
  30. package/src/fore.js +128 -17
  31. package/src/fx-bind.js +6 -1
  32. package/src/fx-fore.js +93 -41
  33. package/src/fx-instance.js +95 -70
  34. package/src/fx-model.js +430 -441
  35. package/src/fx-submission.js +423 -405
  36. package/src/getInScopeContext.js +88 -82
  37. package/src/modelitem.js +5 -3
  38. package/src/relevance.js +1 -1
  39. package/src/ui/abstract-control.js +108 -22
  40. package/src/ui/fx-alert.js +0 -1
  41. package/src/ui/fx-container.js +22 -26
  42. package/src/ui/fx-control.js +84 -34
  43. package/src/ui/fx-group.js +5 -0
  44. package/src/ui/fx-inspector.js +5 -0
  45. package/src/ui/fx-output.js +14 -14
  46. package/src/ui/fx-repeat.js +2 -2
  47. package/src/ui/fx-repeatitem.js +5 -3
  48. package/src/ui/fx-switch.js +20 -8
  49. package/src/ui/fx-trigger.js +26 -19
  50. package/src/xpath-evaluation.js +49 -26
  51. package/src/xpath-util.js +26 -28
package/index.js CHANGED
@@ -41,6 +41,7 @@ import './src/actions/fx-return.js';
41
41
  import './src/actions/fx-confirm.js';
42
42
  import './src/actions/fx-show.js';
43
43
  import './src/actions/fx-hide.js';
44
+ import './src/actions/fx-reload.js';
44
45
 
45
46
  import './src/functions/fx-function.js';
46
47
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jinntec/fore",
3
- "version": "1.2.0",
3
+ "version": "1.4.0",
4
4
  "description": "Fore - declarative user interfaces in plain HTML",
5
5
  "module": "./index.js",
6
6
  "publishConfig": {
@@ -31,7 +31,7 @@
31
31
  ],
32
32
  "dependencies": {
33
33
  "@jinntec/jinn-toast": "^1.0.4",
34
- "fontoxpath": "^3.26.0"
34
+ "fontoxpath": "^3.27.1"
35
35
  },
36
36
  "devDependencies": {
37
37
  "@babel/plugin-proposal-class-properties": "^7.17.12",
@@ -17,32 +17,34 @@ html{
17
17
  pointer-events: none;
18
18
  cursor: default;
19
19
  }
20
- input[readonly]{
21
- background: lightgrey;
20
+ [refresh-on-view]{
21
+ /*opacity: 0;*/
22
22
  }
23
+ /*
24
+ defaults for relevance
23
25
 
24
- /* fx-fore{ */
25
- /* opacity: 0; */
26
- /* } */
27
- /* fx-fore.fx-ready{ */
28
- /* opacity: 1; */
29
- /* } */
30
- fx-group, fx-switch, fx-repeat, fx-dialog{
31
- display: block;
32
- }
33
- fx-trigger a[disabled] {
34
- color:lightgrey;
26
+ You can overwrite these matchers to use transistions
27
+ */
28
+ [nonrelevant]{
29
+ display: none;
35
30
  }
36
- fx-trigger img[disabled]{
37
- filter:blur(2px);
31
+ [relevant]{
32
+ display: block;
38
33
  }
39
34
 
40
- .error{
41
- background: var(--paper-red-500);
35
+ [required]:after {
36
+ content: "*";
37
+ display: inline;
38
+ color: red;
42
39
  }
40
+
43
41
  fx-alert{
44
42
  color:darkred;
45
43
  font-size: 0.9rem;
44
+ display: none;
45
+ }
46
+ .visited[invalid] fx-alert{
47
+ display: block;
46
48
  }
47
49
 
48
50
  /* case not displayed by default - if you want e.g. apply transitions you have to overwrite this rule with display='inline' or similar */
@@ -50,36 +52,18 @@ fx-case{
50
52
  display: none;
51
53
  }
52
54
 
53
- fx-model, fx-model *, fx-model ::slotted(fx-instance), fx-instance{
54
- display:none;
55
+ fx-output[readonly] img{
56
+ background: inherit;
55
57
  }
56
58
 
57
- fx-control, fx-trigger{
58
- white-space: nowrap;
59
- /* position: relative; */
59
+ .error{
60
+ background: var(--paper-red-500);
60
61
  }
61
- .fx-checkbox{
62
+
63
+ fx-control, fx-trigger , .fx-checkbox{
62
64
  white-space: nowrap;
65
+ /* position: relative; */
63
66
  }
64
- fx-hint{
65
- display: none;
66
- }
67
- fx-repeatitem{
68
- position:relative;
69
- /*
70
- opacity:1;
71
- -webkit-transition: opacity 3s;
72
- -moz-transition: opacity 3s;
73
- transition: opacity 3s;
74
- */
75
- }
76
- .hidden {
77
- visibility: hidden;
78
- opacity: 0;
79
- transition: visibility 0s 2s, opacity 2s linear;
80
- }
81
-
82
- /* fx-inspector styles */
83
67
 
84
68
  /* ### FX-DIALOG STYLES ### */
85
69
  /* ### FX-DIALOG STYLES ### */
@@ -87,7 +71,11 @@ fx-repeatitem{
87
71
  fx-dialog{
88
72
  display: none;
89
73
  opacity: 0;
74
+ width:100vw;
75
+ height: 100vh;
90
76
  transition: opacity 1s linear;
77
+ z-index: -1;
78
+ transition: none;
91
79
  }
92
80
  fx-dialog.show{
93
81
  display: block;
@@ -96,7 +84,7 @@ fx-dialog.show{
96
84
  opacity: 1;
97
85
  background:rgba(0,0,0,0.5);
98
86
  z-index: 10;
99
- transition: opacity 2s linear;
87
+ transition: opacity 0.4s linear;
100
88
  }
101
89
 
102
90
  fx-dialog.show .dialog-content{
@@ -126,48 +114,59 @@ fx-dialog .action{
126
114
  display: block;
127
115
  }
128
116
 
117
+ /* fx-fore{ */
118
+ /* opacity: 0; */
119
+ /* } */
120
+ /* fx-fore.fx-ready{ */
121
+ /* opacity: 1; */
122
+ /* } */
129
123
 
130
124
 
131
- /*
132
- .visible {
133
- visibility: visible;
134
- opacity: 1;
135
- transition: opacity 2s linear;
125
+ fx-group, fx-switch, fx-repeat, fx-dialog{
126
+ display: block;
136
127
  }
137
- */
138
128
 
139
- .required:after {
140
- content: '*';
141
- color: red;
142
- padding-left: 4px;
143
- right:3px;
144
- top:3px;
145
- z-index: 1;
129
+ fx-hint{
130
+ display: none;
146
131
  }
147
132
 
148
- [required]:after {
149
- content: "*";
150
- display: inline;
151
- color: red;
133
+ fx-model,
134
+ fx-model *,
135
+ fx-model ::slotted(fx-instance),
136
+ fx-instance,
137
+ fx-action,
138
+ fx-setvalue{
139
+ display:none;
152
140
  }
153
- .logtree details{
154
- padding:0.1rem 1rem;
155
- margin:0;
141
+
142
+ fx-trigger a[disabled] {
143
+ color:lightgrey;
144
+ }
145
+ fx-trigger img[disabled]{
146
+ filter:blur(2px);
156
147
  }
157
- .logtree details summary{
158
148
 
149
+
150
+ fx-repeatitem{
151
+ position:relative;
152
+ opacity:1;
153
+ /*
154
+ -webkit-transition: opacity 3s;
155
+ -moz-transition: opacity 3s;
156
+ transition: opacity 3s;
157
+ */
159
158
  }
160
- .non-relevant{
159
+
160
+ .hidden {
161
+ visibility: hidden;
161
162
  opacity: 0;
162
- height: 0;
163
- transition: opacity 1s;
164
- }
165
- .vertical label{
166
- display: block;
163
+ transition: visibility 0s 2s, opacity 2s linear;
167
164
  }
168
- [refresh-on-view]{
169
- /*opacity: 0;*/
165
+
166
+ .isEmpty.visited .widget{
167
+ background: lightpink;
170
168
  }
169
+
171
170
  .loaded{
172
171
  animation: fadein 0.3s forwards;
173
172
  }
@@ -176,6 +175,35 @@ fx-dialog .action{
176
175
  animation: none;
177
176
  opacity: 1;
178
177
  }
178
+
179
+ .logtree details{
180
+ padding:0.1rem 1rem;
181
+ margin:0;
182
+ }
183
+ .logtree details summary{
184
+
185
+ }
186
+
187
+ /*
188
+ .nonrelevant{
189
+ opacity: 0;
190
+ height: 0;
191
+ display: block;
192
+ padding: 0;
193
+ margin: 0;
194
+ border: none;
195
+ transition: opacity 0.4s;
196
+ }
197
+ */
198
+
199
+
200
+ .submit-validation-failed .isEmpty .widget{
201
+ background: lightpink;
202
+ }
203
+ .vertical label{
204
+ display: block;
205
+ }
206
+
179
207
  /* @keyframes fadein { */
180
208
  /* 0% { */
181
209
  /* opacity:0; */
@@ -100,6 +100,10 @@ export const foreElementMixin = superclass =>
100
100
  */
101
101
  evalInContext() {
102
102
  // const inscopeContext = this.getInScopeContext();
103
+ const model = this.getModel();
104
+ if(!model){
105
+ return;
106
+ }
103
107
  let inscopeContext;
104
108
  if (this.hasAttribute('context')) {
105
109
  inscopeContext = getInScopeContext(this.getAttributeNode('context') || this, this.context);
@@ -16,6 +16,9 @@ async function wait(howLong) {
16
16
  * @demo demo/index.html
17
17
  */
18
18
  export class AbstractAction extends foreElementMixin(HTMLElement) {
19
+ static outermostHandler = null;
20
+ static dataChanged = false;
21
+
19
22
  static get properties() {
20
23
  return {
21
24
  ...super.properties,
@@ -114,33 +117,65 @@ export class AbstractAction extends foreElementMixin(HTMLElement) {
114
117
  * @param e
115
118
  */
116
119
  async execute(e) {
120
+ let resolveThisEvent = () => {};
121
+ if (e && e.listenerPromises) {
122
+ e.listenerPromises.push(
123
+ new Promise(resolve => {
124
+ resolveThisEvent = resolve;
125
+ }),
126
+ );
127
+ }
128
+
117
129
  // console.log('executing', this);
130
+
118
131
  // console.log('executing e', e);
119
132
  // console.log('executing e phase', e.eventPhase);
120
- if(e && e.code){
133
+ if (AbstractAction.outermostHandler === null) {
134
+ console.time('outermostHandler');
135
+ console.info(
136
+ `%coutermost Action `,
137
+ 'background:#e65100; color:white; padding:0.3rem; display:inline-block; white-space: nowrap; border-radius:0.3rem;',
138
+ this,
139
+ );
140
+ // console.log('starting outermost handler',this);
141
+ AbstractAction.outermostHandler = this;
142
+ }
143
+
144
+ if (AbstractAction.outermostHandler !== this) {
145
+ console.info(
146
+ `%cAction `,
147
+ 'background:orange; color:white; padding:0.3rem; display:inline-block; white-space: nowrap; border-radius:0.3rem;',
148
+ this,
149
+ );
150
+ }
151
+ // console.log('>>> outermostHandler', AbstractAction.outermostHandler);
152
+
153
+ if (e && e.code) {
121
154
  const vars = new Map();
122
- vars.set('code',e.code);
155
+ vars.set('code', e.code);
123
156
  // this.setInScopeVariables(vars);
124
- this.setInScopeVariables(new Map([...vars, ...this.inScopeVariables]));
157
+ this.setInScopeVariables(new Map([...this.inScopeVariables, ...vars]));
125
158
  }
126
159
 
127
160
  if (e && e.detail) {
128
161
  this.detail = e.detail;
129
162
  const vars = new Map();
130
- Object.keys(e.detail).forEach(function(key,index) {
163
+ Object.keys(e.detail).forEach(function(key, index) {
131
164
  // key: the name of the object key
132
165
  // index: the ordinal position of the key within the object
133
- vars.set(key,e.detail[key]);
166
+ vars.set(key, e.detail[key]);
134
167
  });
135
- console.log("event detail vars", vars);
136
- this.setInScopeVariables(new Map([...vars, ...this.inScopeVariables]));
168
+ if (vars.size !== 0) {
169
+ console.log('event detail vars', vars);
170
+ }
171
+ this.setInScopeVariables(new Map([...this.inScopeVariables, ...vars]));
137
172
  }
138
173
  this.needsUpdate = false;
139
174
 
140
- try{
175
+ try {
141
176
  this.evalInContext();
142
- }catch (error){
143
- console.warn('evaluation faild',error);
177
+ } catch (error) {
178
+ console.warn('evaluation faild', error);
144
179
  }
145
180
  if (this.targetElement && this.targetElement.nodeset) {
146
181
  this.nodeset = this.targetElement.nodeset;
@@ -148,6 +183,7 @@ export class AbstractAction extends foreElementMixin(HTMLElement) {
148
183
 
149
184
  // First check if 'if' condition is true - otherwise exist right away
150
185
  if (this.ifExpr && !evaluateXPathToBoolean(this.ifExpr, getInScopeContext(this), this)) {
186
+ this._finalizePerform(resolveThisEvent);
151
187
  return;
152
188
  }
153
189
 
@@ -171,13 +207,20 @@ export class AbstractAction extends foreElementMixin(HTMLElement) {
171
207
  this.perform();
172
208
 
173
209
  // Go for one more iteration
210
+ if (this.delay) {
211
+ // If we have a delay, fire and forget this.
212
+ // Otherwise, if we have no delay, keep waiting for all iterations to be done.
213
+ // The while is then uninteruptable and immediate
214
+ loop();
215
+ return;
216
+ }
174
217
  await loop();
175
218
  };
176
219
 
177
220
  // After loop is done call actionPerformed to update the model and UI
178
221
  await loop();
179
- this.actionPerformed();
180
- Fore.dispatch(this, 'while-performed', {});
222
+
223
+ this._finalizePerform(resolveThisEvent);
181
224
  return;
182
225
  }
183
226
 
@@ -187,21 +230,39 @@ export class AbstractAction extends foreElementMixin(HTMLElement) {
187
230
  if (!this.ownerDocument.contains(this)) {
188
231
  // We are no longer in the document. Stop working
189
232
  this.actionPerformed();
233
+ resolveThisEvent();
190
234
  return;
191
235
  }
192
236
  }
193
237
 
194
- this.perform();
238
+ await this.perform();
239
+ this._finalizePerform(resolveThisEvent);
240
+
241
+ }
242
+
243
+ _finalizePerform(resolveThisEvent) {
195
244
  this.actionPerformed();
245
+ if (AbstractAction.outermostHandler === this) {
246
+ AbstractAction.outermostHandler = null;
247
+ console.info(
248
+ `%coutermost Action done`,
249
+ 'background:#e65100; color:white; padding:0.3rem; display:inline-block; white-space: nowrap; border-radius:0.3rem;',
250
+ this,
251
+ );
252
+ console.timeEnd('outermostHandler');
253
+
254
+ }
255
+ resolveThisEvent();
196
256
  }
197
257
 
198
258
  /**
199
259
  * Template method to be implemented by each action that is called by execute() as part of
200
260
  * the processing.
201
261
  *
202
- * todo: review - this could probably just be empty or throw error signalling that extender needs to implement it
262
+ * This function should not called on any action directly - call execute() instead to ensure proper execution of 'if' and 'while'
203
263
  */
204
- perform() {
264
+ async perform() {
265
+ //todo: review - this evaluation seems redundant as we already evaluated in execute
205
266
  if (this.isBound() || this.nodeName === 'FX-ACTION') {
206
267
  this.evalInContext();
207
268
  }
@@ -211,13 +272,36 @@ export class AbstractAction extends foreElementMixin(HTMLElement) {
211
272
  * calls the update cycle if action signalled that update is needed.
212
273
  */
213
274
  actionPerformed() {
275
+ const model = this.getModel();
276
+ if(!model){
277
+ return;
278
+ }
279
+ if (!model.inited) {
280
+ return;
281
+ }
282
+ if (
283
+ AbstractAction.outermostHandler &&
284
+ !AbstractAction.outermostHandler.ownerDocument.contains(AbstractAction.outermostHandler)
285
+ ) {
286
+ // The old outermosthandler fell out of the document. An error has happened.
287
+ // Just remove the old one and act like we are starting anew.
288
+ console.warn('Unsetting outermost handler');
289
+ AbstractAction.outermostHandler = null;
290
+ }
214
291
  // console.log('actionPerformed action parentNode ', this.parentNode);
215
- if (this.needsUpdate) {
216
- const model = this.getModel();
292
+ if (
293
+ this.needsUpdate &&
294
+ (AbstractAction.outermostHandler === this || !AbstractAction.outermostHandler)
295
+ ) {
296
+ console.log('Running actionperformed');
217
297
  model.recalculate();
218
298
  model.revalidate();
219
299
  model.parentNode.refresh(true);
220
300
  this.dispatchActionPerformed();
301
+ } else if (this.needsUpdate) {
302
+ // We need an update, but the outermost action handler may not. Make this clear!
303
+ AbstractAction.outermostHandler.needsUpdate = true;
304
+ console.log('Update surpressed!');
221
305
  }
222
306
  }
223
307
 
@@ -227,7 +311,7 @@ export class AbstractAction extends foreElementMixin(HTMLElement) {
227
311
  * @event action-performed - whenever an action has been run
228
312
  */
229
313
  dispatchActionPerformed() {
230
- console.log('action-performed ', this);
314
+ // console.log('action-performed ', this);
231
315
  Fore.dispatch(this, 'action-performed', {});
232
316
  }
233
317
  }
@@ -31,7 +31,7 @@ export class FxAction extends AbstractAction {
31
31
  `;
32
32
  }
33
33
 
34
- perform() {
34
+ async perform() {
35
35
  super.perform();
36
36
  const { children } = this;
37
37
 
@@ -42,15 +42,14 @@ export class FxAction extends AbstractAction {
42
42
  script.src = this.src;
43
43
  this.appendChild(script);
44
44
  } else {
45
- Array.from(children).forEach(actionOrVar => {
46
- if (actionOrVar.localName === 'fx-var') {
47
- return;
48
- }
49
- const action = actionOrVar;
50
- action.detail = this.detail;
51
- action.perform();
52
- // action.execute();
53
- });
45
+ for (const actionOrVar of children) {
46
+ if (actionOrVar.localName === 'fx-var') {
47
+ continue;
48
+ }
49
+ const action = actionOrVar;
50
+ action.detail = this.detail;
51
+ await action.execute();
52
+ }
54
53
  this.dispatchActionPerformed();
55
54
  this.needsUpdate = true;
56
55
  }
@@ -58,7 +58,7 @@ class FxAppend extends AbstractAction {
58
58
  * behavior should be needed for some reason later on, it can be added easier by a providing an 'duplicate' action.
59
59
  *
60
60
  */
61
- perform() {
61
+ async perform() {
62
62
  super.perform();
63
63
 
64
64
  this._dataFromTemplate();
@@ -1,4 +1,4 @@
1
- import {AbstractAction} from "./abstract-action";
1
+ import {FxAction} from "./fx-action.js";
2
2
 
3
3
  /**
4
4
  * `fx-confirm`
@@ -7,12 +7,12 @@ import {AbstractAction} from "./abstract-action";
7
7
  * @customElement
8
8
  * @demo demo/project.html
9
9
  */
10
- export class FxConfirm extends AbstractAction {
10
+ export class FxConfirm extends FxAction {
11
11
  connectedCallback() {
12
12
  this.message = this.hasAttribute('message') ? this.getAttribute('message') : null;
13
13
  }
14
14
 
15
- perform() {
15
+ async perform() {
16
16
  if (window.confirm(this.message)) {
17
17
  super.perform();
18
18
  }
@@ -0,0 +1,68 @@
1
+ import '../fx-model.js';
2
+ import {AbstractAction} from './abstract-action.js';
3
+
4
+ /**
5
+ * `fx-copy`
6
+ * todo: demo + tests
7
+ * @customElement
8
+ */
9
+ export default class FxCopy extends AbstractAction {
10
+ static get properties() {
11
+ return {
12
+ ...super.properties,
13
+ ref: {
14
+ type: String,
15
+ },
16
+ to: {
17
+ type: String,
18
+ },
19
+ };
20
+ }
21
+
22
+ constructor() {
23
+ super();
24
+ this.ref = '';
25
+ this.to = '';
26
+ }
27
+
28
+ connectedCallback() {
29
+ if (super.connectedCallback) {
30
+ super.connectedCallback();
31
+ }
32
+
33
+ if (this.hasAttribute('ref')) {
34
+ this.ref = this.getAttribute('ref');
35
+ } else {
36
+ throw new Error('fx-copy must specify a "ref" attribute');
37
+ }
38
+ this.to = this.getAttribute('to');
39
+ }
40
+
41
+ perform() {
42
+ console.log('copying to clipboard')
43
+ super.perform();
44
+
45
+ if(this.nodeset.nodeType === Node.ATTRIBUTE_NODE){
46
+ navigator.clipboard.writeText(this.nodeset.nodeValue);
47
+ }else{
48
+ navigator.clipboard.writeText(this.nodeset);
49
+ }
50
+ }
51
+
52
+ /*
53
+ setValue(modelItem, newVal) {
54
+ const item = modelItem;
55
+ if (!item) return;
56
+
57
+ if (item.value !== newVal) {
58
+ item.value = newVal;
59
+ this.getModel().changed.push(modelItem);
60
+ this.needsUpdate = true;
61
+ }
62
+ }
63
+ */
64
+ }
65
+
66
+ if (!customElements.get('fx-copy')) {
67
+ window.customElements.define('fx-copy', FxCopy);
68
+ }