@pageboard/html 0.12.14 → 0.12.16

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/elements/embed.js CHANGED
@@ -40,8 +40,8 @@ exports.embed = {
40
40
  };
41
41
  },
42
42
  tag: 'iframe,element-embed',
43
- html: `<element-embed data-src="[url|url:query:query]" id="[id]">
44
- <a aria-hidden="true" class="linkable" href="[$loc|as:url|set:hash:[id]]">[linkable|prune:*]#</a>
43
+ html: `<element-embed data-src="[url]" data-query="[query|as:query]" id="[id]">
44
+ <a aria-hidden="true" class="linkable" href="[$loc.pathname][$loc.search][id|pre:%23]">[linkable|prune:*]#</a>
45
45
  <iframe loading="lazy" allowfullscreen frameborder="0" scrolling="no"></iframe>
46
46
  </element-embed>`,
47
47
  scripts: [
@@ -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/form.js CHANGED
@@ -47,7 +47,7 @@ exports.query_form = {
47
47
  contents: 'block+',
48
48
  tag: 'form[method="get"]',
49
49
  html: `<form is="element-form" method="get" name="[name]"
50
- action="[redirection.url][redirection.parameters|as:query]"
50
+ action="[redirection|urltpl:url:parameters]"
51
51
  autocomplete="off" class="ui form"></form>`,
52
52
  stylesheets: [
53
53
  '../lib/components/form.css',
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 = {
@@ -380,7 +388,7 @@ exports.input_radio = {
380
388
  html: `<div class="field [button]">
381
389
  <div class="ui radio [button|alt::checkbox]">
382
390
  <input type="radio" disabled="[disabled]" required="[required]"
383
- name="[name]" value="[value|or:]" checked="[checked]"
391
+ name="[name]" value="[value]" checked="[checked]"
384
392
  id="for-[name][value|pre:-]" />
385
393
  <label block-content="label" for="for-[name][value|pre:-]">Label</label>
386
394
  </div>
@@ -477,7 +485,7 @@ exports.input_select_option = {
477
485
  }
478
486
  },
479
487
  contents: 'inline*',
480
- html: `<element-select-option class="item" data-value="[value|or:]"
488
+ html: `<element-select-option class="item" data-value="[value]"
481
489
  ></element-select-option>`
482
490
  };
483
491
 
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.14",
3
+ "version": "0.12.16",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "repository": {
@@ -35,6 +35,17 @@ class HTMLElementFieldsetList extends Page.Element {
35
35
  #walk;
36
36
 
37
37
  fill(values, scope) {
38
+ if (scope.$write || this.prefix == null) return;
39
+ // unflatten array-values
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);
@@ -83,13 +94,9 @@ class HTMLElementFieldsetList extends Page.Element {
83
94
  this.#model = model;
84
95
  }
85
96
 
86
- #prepare(scope) {
97
+ #prepare() {
87
98
  const tpl = this.ownTpl;
88
99
  tpl.prerender();
89
- if (scope.$write) {
90
- this.#modelize(tpl);
91
- return;
92
- }
93
100
  this.#modelize(tpl.content);
94
101
  for (const node of tpl.content.querySelectorAll('[block-id]')) {
95
102
  node.removeAttribute('block-id');
@@ -97,19 +104,18 @@ class HTMLElementFieldsetList extends Page.Element {
97
104
  }
98
105
 
99
106
  patch({ scope }) {
100
- this.#prepare(scope);
101
- if (!this.#size) this.#resize(0, scope);
102
- }
103
-
104
- setup({ scope }) {
105
- this.#prepare(scope);
107
+ if (scope.$write) {
108
+ this.#modelize(this.ownTpl);
109
+ } else if (!this.#size) {
110
+ this.#resize(0, scope);
111
+ }
106
112
  }
107
113
 
108
114
  #selector(name) {
109
115
  return `[block-type="fieldlist_button"][value="${name}"]`;
110
116
  }
111
117
 
112
- #prefixed(key, p = this.#prefix) {
118
+ #prefixed(key, p = this.prefix) {
113
119
  const parts = this.#parts(key);
114
120
  for (let i = 0; i < p.length; i++) {
115
121
  if (parts[i] != p[i]) return false;
@@ -198,7 +204,6 @@ class HTMLElementFieldsetList extends Page.Element {
198
204
 
199
205
  #listFromValues(values) {
200
206
  const list = [];
201
- // just unflatten the array
202
207
  for (const [key, val] of Object.entries(values)) {
203
208
  if (!this.#prefixed(key)) continue;
204
209
  const parts = this.#parts(key).slice(this.#prefix.length);
@@ -278,6 +283,10 @@ class HTMLElementFieldsetList extends Page.Element {
278
283
  live.replaceWith(node);
279
284
  }
280
285
  }
286
+ this.dispatchEvent(new Event('change', {
287
+ bubbles: true,
288
+ cancelable: true
289
+ }));
281
290
  }
282
291
 
283
292
  #parseName(name) {
@@ -299,6 +308,9 @@ class HTMLElementFieldsetList extends Page.Element {
299
308
  return this.children.find(node => node.matches('.view'));
300
309
  }
301
310
  get prefix() {
311
+ if (this.#prefix == null) {
312
+ this.#prepare();
313
+ }
302
314
  return this.#prefix;
303
315
  }
304
316
  }
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") {
@@ -2,14 +2,17 @@
2
2
  display:none;
3
3
  }
4
4
  .field.button > .radio > input + label {
5
- opacity:0.7;
5
+ opacity: 0.5;
6
6
  padding: 0.5em;
7
7
  display: block;
8
8
  position: relative;
9
+ border: 1px solid gray;
10
+ border-radius: 4px;
9
11
  }
10
12
  .field.button > .radio > input + label:hover {
11
13
  opacity:1;
12
14
  }
13
15
  .field.button > .radio > input:checked + label {
14
16
  opacity:1;
17
+ font-weight: bold;
15
18
  }
@@ -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');