@pageboard/html 0.12.13 → 0.12.15

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.
@@ -74,7 +74,7 @@ exports.fieldlist_button = {
74
74
  title: 'Field List Button',
75
75
  menu: "form",
76
76
  icon: '<i class="icons"><i class="folder outline icon"></i><i class="corner hand pointer icon"></i></i>',
77
- group: 'block',
77
+ group: 'block input_field',
78
78
  context: 'fieldset_list//',
79
79
  properties: {
80
80
  type: {
package/elements/image.js CHANGED
@@ -129,7 +129,7 @@ exports.image = {
129
129
  nodes: "inline*"
130
130
  },
131
131
  html: `<element-image
132
- class="[display.fit|or:none] [display.horizontal|or:] [display.vertical|or:]"
132
+ class="[display.fit|or:none] [display.horizontal?] [display.vertical?]"
133
133
  alt="[alt]"
134
134
  data-src="[url]"
135
135
  data-crop="[crop.x|or:50];[crop.y|or:50];[crop.width|or:100];[crop.height|or:100];[crop.zoom|or:100]"
@@ -66,10 +66,15 @@ exports.input_fields = {
66
66
  title: "Inline",
67
67
  type: 'boolean',
68
68
  default: false
69
+ },
70
+ full: {
71
+ title: 'Full width',
72
+ type: 'boolean',
73
+ default: false
69
74
  }
70
75
  },
71
76
  contents: "input_field+",
72
- html: `<div class="[inline] fields"></div>`
77
+ html: `<div class="[inline] [full|alt:fluid:] fields"></div>`
73
78
  };
74
79
 
75
80
  exports.input_text = {
@@ -160,9 +165,10 @@ exports.input_text = {
160
165
  tel: /^(\(\d+\))? *\d+([ .-]?\d+)*$/.source,
161
166
  email: /^[\w.!#$%&'*+/=?^`{|}~-]+@\w(?:[\w-]{0,61}\w)?(?:\.\w(?:[\w-]{0,61}\w)?)*$/.source
162
167
  },
163
- html: `<div class="[width|as:colnums|post: wide] field [type|eq:hidden|or:]">
168
+ html: `<div class="[width|as:colnums|post: wide] field [type|eq:hidden]">
164
169
  <label block-content="label">Label</label>
165
170
  [type|eq:textarea|prune:*:1]<textarea
171
+ is="element-textarea"
166
172
  name="[name]"
167
173
  required="[required]"
168
174
  readonly="[readonly]"
@@ -178,7 +184,9 @@ exports.input_text = {
178
184
  pattern="[$element.patterns.[type]]"
179
185
  value="[value]"
180
186
  autocomplete="[type|eq:new-password|fail:]" />
181
- </div>`
187
+ </div>`,
188
+ scripts: ['../ui/textarea.js'],
189
+ stylesheets: ['../ui/textarea.css']
182
190
  };
183
191
 
184
192
  exports.input_number = {
@@ -346,18 +354,13 @@ exports.input_radio = {
346
354
  format: "singleline",
347
355
  $helper: 'element-property'
348
356
  },
349
- checked: {
350
- title: "Checked",
351
- type: "boolean",
352
- default: false
353
- },
354
357
  value: {
355
358
  title: "Value",
356
359
  type: "string",
357
360
  format: "singleline"
358
361
  },
359
- disabled: {
360
- title: "Disabled",
362
+ checked: {
363
+ title: "Checked",
361
364
  type: "boolean",
362
365
  default: false
363
366
  },
@@ -365,22 +368,34 @@ exports.input_radio = {
365
368
  title: 'Required',
366
369
  type: 'boolean',
367
370
  default: false
371
+ },
372
+ disabled: {
373
+ title: "Disabled",
374
+ type: "boolean",
375
+ default: false
376
+ },
377
+ button: {
378
+ title: 'Button',
379
+ description:'hide radio toggle opacity',
380
+ type: "boolean",
381
+ default: false
368
382
  }
369
383
  },
370
384
  contents: {
371
385
  id: 'label',
372
386
  nodes: 'inline*'
373
387
  },
374
- html: `<div class="field">
375
- <div class="ui radio checkbox">
388
+ html: `<div class="field [button]">
389
+ <div class="ui radio [button|alt::checkbox]">
376
390
  <input type="radio" disabled="[disabled]" required="[required]"
377
- name="[name]" value="[value|or:]" checked="[checked]"
391
+ name="[name]" value="[value]" checked="[checked]"
378
392
  id="for-[name][value|pre:-]" />
379
393
  <label block-content="label" for="for-[name][value|pre:-]">Label</label>
380
394
  </div>
381
395
  </div>`,
382
396
  stylesheets: [
383
- '../lib/components/checkbox.css'
397
+ '../lib/components/checkbox.css',
398
+ '../ui/input_radio.css'
384
399
  ]
385
400
  };
386
401
 
@@ -470,7 +485,7 @@ exports.input_select_option = {
470
485
  }
471
486
  },
472
487
  contents: 'inline*',
473
- html: `<element-select-option class="item" data-value="[value|or:]"
488
+ html: `<element-select-option class="item" data-value="[value]"
474
489
  ></element-select-option>`
475
490
  };
476
491
 
@@ -58,6 +58,19 @@ exports.layout = {
58
58
  title: "Row"
59
59
  }]
60
60
  },
61
+ wrap: {
62
+ title: 'Wrap',
63
+ anyOf: [{
64
+ type: 'null',
65
+ title: 'No wrap'
66
+ }, {
67
+ const: 'wrap',
68
+ title: 'Wrap'
69
+ }, {
70
+ const: 'wrap-reverse',
71
+ title: 'Reverse'
72
+ }]
73
+ },
61
74
  width: {
62
75
  title: 'Width',
63
76
  anyOf: [{
@@ -293,6 +306,7 @@ exports.layout = {
293
306
  [width|switch:full:fullwidth:contained:ui container]
294
307
  [horizontal]
295
308
  [vertical]
309
+ [wrap]
296
310
  [direction]
297
311
  [background.invert|alt:inverted]"
298
312
  is="element-layout"
package/elements/page.js CHANGED
@@ -65,7 +65,7 @@ exports.page.properties.transition = {
65
65
  exports.page.fragments.push({
66
66
  path: 'body',
67
67
  attributes: {
68
- "data-transition-close": "[transition.close|ornull]",
69
- "data-transition-open": "[transition.open|ornull]",
68
+ "data-transition-close": "[transition.close?]",
69
+ "data-transition-open": "[transition.open?]",
70
70
  }
71
71
  });
@@ -49,7 +49,7 @@ exports.sitemap = {
49
49
  <span class="ui mini type label">[$grants.webmaster|prune:*][$type]</span>
50
50
  <span class="ui mini black label">[$grants.webmaster|prune:*][nositemap|prune:*]no sitemap</span>
51
51
  <span class="ui mini orange label">[$grants.webmaster|prune:*][noindex|prune:*]no index</span>
52
- <span class="ui mini red label">[$grants.webmaster|prune:*][$lock?.read|fail:*]</span>
52
+ <span class="ui mini red label">[$grants.webmaster|prune:*][$lock|fail:*]</span>
53
53
  <br>
54
54
  <a href="[url]" class="description">[url|or:-]</a>
55
55
  <a href="[redirect|fail:*]" class="redirection"> ➜ [redirect]</a>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pageboard/html",
3
- "version": "0.12.13",
3
+ "version": "0.12.15",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "repository": {
package/ui/embed.js CHANGED
@@ -1,7 +1,6 @@
1
1
  class HTMLElementEmbed extends Page.Element {
2
2
  static defaults = {
3
3
  src: null,
4
- query: null,
5
4
  hash: null
6
5
  };
7
6
  static revealRatio = 0.2;
@@ -35,6 +35,17 @@ class HTMLElementFieldsetList extends Page.Element {
35
35
  #walk;
36
36
 
37
37
  fill(values, scope) {
38
+ // unflatten array-values
39
+ if (this.#prefix == null) return;
40
+ for (const [key, val] of Object.entries(values)) {
41
+ if (!this.#prefixed(key)) continue;
42
+ if (Array.isArray(val)) {
43
+ for (let i = 0; i < val.length; i++) {
44
+ values[key + '.' + i] = val[i];
45
+ }
46
+ delete values[key];
47
+ }
48
+ }
38
49
  const list = this.#listFromValues({ ...values });
39
50
  if (this.#initialSize == null) this.#initialSize = list.length;
40
51
  this.#resize(list.length, scope);
@@ -198,7 +209,6 @@ class HTMLElementFieldsetList extends Page.Element {
198
209
 
199
210
  #listFromValues(values) {
200
211
  const list = [];
201
- // just unflatten the array
202
212
  for (const [key, val] of Object.entries(values)) {
203
213
  if (!this.#prefixed(key)) continue;
204
214
  const parts = this.#parts(key).slice(this.#prefix.length);
@@ -278,6 +288,10 @@ class HTMLElementFieldsetList extends Page.Element {
278
288
  live.replaceWith(node);
279
289
  }
280
290
  }
291
+ this.dispatchEvent(new Event('change', {
292
+ bubbles: true,
293
+ cancelable: true
294
+ }));
281
295
  }
282
296
 
283
297
  #parseName(name) {
package/ui/form.css CHANGED
@@ -22,3 +22,21 @@ element-select {
22
22
  [contenteditable] .ui.form .field.hidden {
23
23
  display: block !important;
24
24
  }
25
+ .ui.form {
26
+ width: 100%;
27
+ }
28
+ .ui.form .inline.fluid.fields {
29
+ width:100%;
30
+ column-gap: 1em;
31
+ padding:0 1em;
32
+ }
33
+ .ui.form .inline.fluid.fields > .field {
34
+ margin:0;
35
+ padding:0;
36
+ flex: 1 1 auto;
37
+ position:relative;
38
+ }
39
+
40
+ .ui.form[method="post"]:not(.unsaved,:hover) button[type="submit"] {
41
+ opacity:0.2;
42
+ }
package/ui/form.js CHANGED
@@ -34,21 +34,33 @@ class HTMLElementForm extends Page.create(HTMLFormElement) {
34
34
 
35
35
  state.finish(() => {
36
36
  if (action == "enable") {
37
- form.enable();
37
+ form.enable?.();
38
38
  } else if (action == "disable") {
39
- form.disable();
39
+ form.disable?.();
40
40
  } else if (action == "fill") {
41
41
  if (val == null) {
42
- form.reset();
42
+ form.reset?.();
43
43
  } else if (typeof val == "object") {
44
- form.fill(this.linearizeValues(val), state.scope);
45
- form.save();
44
+ form.fill?.(this.linearizeValues(val), state.scope);
45
+ form.save?.();
46
46
  }
47
47
  }
48
48
  });
49
49
 
50
50
  return val;
51
51
  };
52
+ state.finish(() => {
53
+ let index = 0;
54
+ for (const node of document.querySelectorAll('label[for]')) {
55
+ const prev = node.previousElementSibling;
56
+ if (prev?.nodeName != "INPUT") continue;
57
+ const others = document.querySelectorAll(`input[id="${node.htmlFor}"]`);
58
+ if (others.length > 1) {
59
+ node.htmlFor += `-${index++}`;
60
+ prev.id = node.htmlFor;
61
+ }
62
+ }
63
+ });
52
64
  }
53
65
 
54
66
  static setup(state) {
@@ -79,6 +91,10 @@ class HTMLElementForm extends Page.create(HTMLFormElement) {
79
91
  }
80
92
  }
81
93
 
94
+ #isPureButtons() {
95
+ return this.elements.every(item => item.type == "submit");
96
+ }
97
+
82
98
  toggleMessages(status) {
83
99
  return window.HTMLElementTemplate.prototype.toggleMessages.call(this, status, this);
84
100
  }
@@ -98,6 +114,7 @@ class HTMLElementForm extends Page.create(HTMLFormElement) {
98
114
  this.setAttribute('data-' + key, val);
99
115
  }
100
116
  this.restore(state.scope);
117
+ if (this.#isPureButtons()) this.classList.add('unsaved');
101
118
  } else {
102
119
  for (const name of this.fill(state.query, state.scope)) {
103
120
  state.vars[name] = true;
@@ -162,7 +179,7 @@ class HTMLElementForm extends Page.create(HTMLFormElement) {
162
179
  query[name] = val;
163
180
  break;
164
181
  case "radio":
165
- if (!withDefaults && node.checked == node.defaultChecked) {
182
+ if (!withDefaults && (node.checked == node.defaultChecked || val == null)) {
166
183
  if (query[name] == val) {
167
184
  query[name] = undefined;
168
185
  }
@@ -264,7 +281,12 @@ class HTMLElementForm extends Page.create(HTMLFormElement) {
264
281
  if (state.scope.$write) return;
265
282
  this.toggleMessages();
266
283
  if (this.matches('.loading')) return;
267
- if (e.type != "submit" && this.querySelector('[type="submit"]')) return;
284
+ if (e.type != "submit" && this.querySelector('[type="submit"]')) {
285
+ if (this.method == "post") {
286
+ this.classList.add('unsaved');
287
+ }
288
+ return;
289
+ }
268
290
  let fn = this[this.method + 'Method'];
269
291
  if (e.type == "input" && (!e.target || !["radio", "checkbox"].includes(e.target.type))) {
270
292
  fn = this[this.method + 'MethodLater'] || fn;
@@ -307,6 +329,7 @@ class HTMLElementForm extends Page.create(HTMLFormElement) {
307
329
  async postMethod(e, state) {
308
330
  if (e.type != "submit") return;
309
331
  const form = this;
332
+ if (!this.#isPureButtons()) form.classList.remove('unsaved');
310
333
  form.classList.add('loading');
311
334
 
312
335
  await Promise.all(
@@ -381,9 +404,6 @@ HTMLFormElement.prototype.disable = function () {
381
404
  }
382
405
  };
383
406
 
384
-
385
-
386
-
387
407
  HTMLSelectElement.prototype.fill = function (val) {
388
408
  if (!Array.isArray(val)) val = [val];
389
409
  for (let i = 0; i < this.options.length; i++) {
@@ -394,7 +414,12 @@ HTMLSelectElement.prototype.fill = function (val) {
394
414
  HTMLSelectElement.prototype.reset = function () {
395
415
  for (let i = 0; i < this.options.length; i++) this.options[i].selected = false;
396
416
  };
397
-
417
+ HTMLButtonElement.prototype.fill = function(val) {
418
+ if (this.name && this.type == "submit") this.value = val;
419
+ };
420
+ HTMLButtonElement.prototype.fill = function (val) {
421
+ if (this.name && this.type == "submit") this.value = val;
422
+ };
398
423
  HTMLInputElement.prototype.fill = function (val) {
399
424
  if (val == null) val = "";
400
425
  if (this.type == "radio" || this.type == "checkbox") {
@@ -0,0 +1,18 @@
1
+ .field.button > .radio > input {
2
+ display:none;
3
+ }
4
+ .field.button > .radio > input + label {
5
+ opacity: 0.5;
6
+ padding: 0.5em;
7
+ display: block;
8
+ position: relative;
9
+ border: 1px solid gray;
10
+ border-radius: 4px;
11
+ }
12
+ .field.button > .radio > input + label:hover {
13
+ opacity:1;
14
+ }
15
+ .field.button > .radio > input:checked + label {
16
+ opacity:1;
17
+ font-weight: bold;
18
+ }
package/ui/layout.css CHANGED
@@ -1,7 +1,16 @@
1
- .layout,
2
- .ui.container.layout {
1
+ .layout {
2
+ display:flex;
3
3
  flex: 0 0 auto;
4
+ }
5
+ .ui.container.layout {
4
6
  display:flex;
7
+ flex: 1 0 auto;
8
+ }
9
+ .layout.wrap {
10
+ flex-wrap:wrap;
11
+ }
12
+ .layout.wrap-reverse {
13
+ flex-wrap:wrap-reverse;
5
14
  }
6
15
  .layout.column {
7
16
  flex-direction: column;
@@ -0,0 +1,3 @@
1
+ textarea[is="element-textarea"] {
2
+ min-height:2em !important;
3
+ }
package/ui/textarea.js ADDED
@@ -0,0 +1,21 @@
1
+ class HTMLElementTextArea extends Page.create(HTMLTextAreaElement) {
2
+ handleChange(e, state) {
3
+ this.#resize(state);
4
+ }
5
+ handleInput(e, state) {
6
+ this.#resize(state);
7
+ }
8
+ setup(state) {
9
+ this.#resize(state);
10
+ }
11
+ #resize(state) {
12
+ if (state.scope.$write) {
13
+ delete this.style.height;
14
+ return;
15
+ }
16
+ this.style.height = 0;
17
+ this.style.height = `calc(${this.scrollHeight}px + 1em)`;
18
+ }
19
+ }
20
+
21
+ Page.define('element-textarea', HTMLElementTextArea, 'textarea');