@pageboard/html 0.11.5 → 0.11.8

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.
@@ -32,7 +32,7 @@ exports.fieldset = {
32
32
  }
33
33
  },
34
34
  contents: "fieldset_legend block+",
35
- html: '<fieldset class="[plain|?]" data-name="[name]" data-value="[value]" is="element-fieldset"></fieldset>',
35
+ html: '<fieldset class="[plain|?]" data-name="[name|split:.|slice:1|join:.]" data-value="[value]" is="element-fieldset"></fieldset>',
36
36
  scripts: ["../ui/fieldset.js"],
37
37
  stylesheets: ['../ui/fieldset.css']
38
38
  };
package/elements/form.js CHANGED
@@ -131,7 +131,8 @@ exports.api_form = {
131
131
  properties: {
132
132
  parameters: {
133
133
  title: 'Parameters',
134
- type: "object"
134
+ type: "object",
135
+ nullable: true
135
136
  }
136
137
  },
137
138
  nullable: true
@@ -142,7 +143,8 @@ exports.api_form = {
142
143
  properties: {
143
144
  parameters: {
144
145
  title: 'Parameters',
145
- type: "object"
146
+ type: "object",
147
+ nullable: true
146
148
  }
147
149
  },
148
150
  nullable: true
@@ -153,7 +155,8 @@ exports.api_form = {
153
155
  properties: {
154
156
  parameters: {
155
157
  title: 'Parameters',
156
- type: "object"
158
+ type: "object",
159
+ nullable: true
157
160
  }
158
161
  },
159
162
  nullable: true
package/elements/image.js CHANGED
@@ -132,7 +132,7 @@ exports.image = {
132
132
  class="[display.fit|or:none] [display.horizontal|or:] [display.vertical|or:]"
133
133
  alt="[alt]"
134
134
  data-src="[url|or:[$element.resources.empty]]"
135
- data-crop="[crop.x];[crop.y];[crop.width];[crop.height];[crop.zoom]"
135
+ data-crop="[crop.x|or:50];[crop.y|or:50];[crop.width|or:100];[crop.height|or:100];[crop.zoom|or:100]"
136
136
  >
137
137
  <div block-content="legend"></div>
138
138
  </element-image>`,
@@ -41,6 +41,7 @@ exports.input_property = {
41
41
  fuse: function(node, d, scope) {
42
42
  const view = scope.$view;
43
43
  const doc = scope.$doc;
44
+ const dateFormats = ["date", "time", "date-time"];
44
45
  let name = d.name;
45
46
  if (!name) {
46
47
  return node;
@@ -79,13 +80,17 @@ exports.input_property = {
79
80
  cases = null;
80
81
  discKey = null;
81
82
  } else {
83
+ if (prop.type == "array" && prop.items && !Array.isArray(prop.items)) {
84
+ prop = prop.items;
85
+ }
82
86
  if (prop.select && prop.select.$data == `0/${propKey}`) {
83
87
  cases = prop.selectCases;
84
88
  } else if (prop.discriminator && prop.discriminator.propertyName == propKey) {
85
89
  cases = prop.oneOf;
86
90
  discKey = propKey;
87
91
  }
88
- prop = (prop.items && prop.items.properties || prop.properties || {})[propKey] || null;
92
+ if (prop.properties) prop = prop.properties;
93
+ prop = prop[propKey];
89
94
  }
90
95
  if (prop == null) break;
91
96
  }
@@ -242,8 +247,40 @@ exports.input_property = {
242
247
  label: prop.title
243
248
  }
244
249
  }));
245
- } else if (propType.type == "string" && ["date", "time", "date-time"].includes(propType.format)) {
250
+ } else if (propType.type == "object" && Object.keys(propType.properties).sort().join(' ') == "end start" && dateFormats.includes(propType.properties.start.format) && dateFormats.includes(propType.properties.end.format)) {
246
251
  node.appendChild(view.render({
252
+ id,
253
+ type: 'input_date_slot',
254
+ data: {
255
+ nameStart: name,
256
+ nameEnd: name,
257
+ format: propType.properties.start.format.replace('-', ''),
258
+ disabled: d.disabled,
259
+ required: required,
260
+ step: propType.properties.start.step
261
+ },
262
+ content: {
263
+ label: prop.title
264
+ }
265
+ }));
266
+ } else if (propType.type == "string" && dateFormats.includes(propType.format)) {
267
+ if (d.multiple) node.appendChild(view.render({
268
+ id,
269
+ type: 'input_date_slot',
270
+ data: {
271
+ nameStart: name,
272
+ nameEnd: name,
273
+ format: propType.format.replace('-', ''),
274
+ default: propType.default,
275
+ disabled: d.disabled,
276
+ required: required,
277
+ step: propType.step
278
+ },
279
+ content: {
280
+ label: prop.title
281
+ }
282
+ }));
283
+ else node.appendChild(view.render({
247
284
  id,
248
285
  type: 'input_date_time',
249
286
  data: {
@@ -233,7 +233,7 @@ exports.input_range = {
233
233
  menu: "form",
234
234
  group: "block",
235
235
  context: 'form//',
236
- properties: {
236
+ properties: Object.assign({}, exports.input_number.properties, {
237
237
  multiple: {
238
238
  title: 'Multiple',
239
239
  type: 'boolean',
@@ -242,10 +242,14 @@ exports.input_range = {
242
242
  pips: {
243
243
  title: 'Pips',
244
244
  type: 'boolean',
245
- default: true
245
+ default: false
246
246
  },
247
- ...exports.input_number.properties
248
- },
247
+ step: {
248
+ title: 'Step',
249
+ type: "number",
250
+ default: 10
251
+ }
252
+ }),
249
253
  contents: {
250
254
  id: 'label',
251
255
  nodes: 'inline*'
package/lib/nouislider.js CHANGED
@@ -714,6 +714,7 @@
714
714
  var hover = entry.indexOf("hover") >= 0;
715
715
  var unconstrained = entry.indexOf("unconstrained") >= 0;
716
716
  var dragAll = entry.indexOf("drag-all") >= 0;
717
+ var smoothSteps = entry.indexOf("smooth-steps") >= 0;
717
718
  if (fixed) {
718
719
  if (parsed.handles !== 2) {
719
720
  throw new Error("noUiSlider: 'fixed' behaviour must be used with 2 handles");
@@ -728,6 +729,7 @@
728
729
  tap: tap || snap,
729
730
  drag: drag,
730
731
  dragAll: dragAll,
732
+ smoothSteps: smoothSteps,
731
733
  fixed: fixed,
732
734
  snap: snap,
733
735
  hover: hover,
@@ -1472,6 +1474,14 @@
1472
1474
  scope_Body.removeEventListener("selectstart", preventDefault);
1473
1475
  }
1474
1476
  }
1477
+ if (options.events.smoothSteps) {
1478
+ data.handleNumbers.forEach(function (handleNumber) {
1479
+ setHandle(handleNumber, scope_Locations[handleNumber], true, true, false, false);
1480
+ });
1481
+ data.handleNumbers.forEach(function (handleNumber) {
1482
+ fireEvent("update", handleNumber);
1483
+ });
1484
+ }
1475
1485
  data.handleNumbers.forEach(function (handleNumber) {
1476
1486
  fireEvent("change", handleNumber);
1477
1487
  fireEvent("set", handleNumber);
@@ -1771,7 +1781,7 @@
1771
1781
  });
1772
1782
  }
1773
1783
  // Split out the handle positioning logic so the Move event can use it, too
1774
- function checkHandlePosition(reference, handleNumber, to, lookBackward, lookForward, getValue) {
1784
+ function checkHandlePosition(reference, handleNumber, to, lookBackward, lookForward, getValue, smoothSteps) {
1775
1785
  var distance;
1776
1786
  // For sliders with multiple handles, limit movement to the other handle.
1777
1787
  // Apply the margin option by adding it to the handle positions.
@@ -1810,7 +1820,9 @@
1810
1820
  to = Math.min(to, distance);
1811
1821
  }
1812
1822
  }
1813
- to = scope_Spectrum.getStep(to);
1823
+ if (!smoothSteps) {
1824
+ to = scope_Spectrum.getStep(to);
1825
+ }
1814
1826
  // Limit percentage to the 0 - 100 range
1815
1827
  to = limit(to);
1816
1828
  // Return false if handle can't move
@@ -1830,6 +1842,7 @@
1830
1842
  var proposals = locations.slice();
1831
1843
  // Store first handle now, so we still have it in case handleNumbers is reversed
1832
1844
  var firstHandle = handleNumbers[0];
1845
+ var smoothSteps = options.events.smoothSteps;
1833
1846
  var b = [!upward, upward];
1834
1847
  var f = [upward, !upward];
1835
1848
  // Copy handleNumbers so we don't change the dataset
@@ -1842,7 +1855,7 @@
1842
1855
  // Step 1: get the maximum percentage that any of the handles can move
1843
1856
  if (handleNumbers.length > 1) {
1844
1857
  handleNumbers.forEach(function (handleNumber, o) {
1845
- var to = checkHandlePosition(proposals, handleNumber, proposals[handleNumber] + proposal, b[o], f[o], false);
1858
+ var to = checkHandlePosition(proposals, handleNumber, proposals[handleNumber] + proposal, b[o], f[o], false, smoothSteps);
1846
1859
  // Stop if one of the handles can't move.
1847
1860
  if (to === false) {
1848
1861
  proposal = 0;
@@ -1860,7 +1873,8 @@
1860
1873
  var state = false;
1861
1874
  // Step 2: Try to set the handles with the found percentage
1862
1875
  handleNumbers.forEach(function (handleNumber, o) {
1863
- state = setHandle(handleNumber, locations[handleNumber] + proposal, b[o], f[o]) || state;
1876
+ state =
1877
+ setHandle(handleNumber, locations[handleNumber] + proposal, b[o], f[o], false, smoothSteps) || state;
1864
1878
  });
1865
1879
  // Step 3: If a handle moved, fire events
1866
1880
  if (state) {
@@ -1905,9 +1919,9 @@
1905
1919
  }
1906
1920
  // Test suggested values and apply margin, step.
1907
1921
  // if exactInput is true, don't run checkHandlePosition, then the handle can be placed in between steps (#436)
1908
- function setHandle(handleNumber, to, lookBackward, lookForward, exactInput) {
1922
+ function setHandle(handleNumber, to, lookBackward, lookForward, exactInput, smoothSteps) {
1909
1923
  if (!exactInput) {
1910
- to = checkHandlePosition(scope_Locations, handleNumber, to, lookBackward, lookForward, false);
1924
+ to = checkHandlePosition(scope_Locations, handleNumber, to, lookBackward, lookForward, false, smoothSteps);
1911
1925
  }
1912
1926
  if (to === false) {
1913
1927
  return false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pageboard/html",
3
- "version": "0.11.5",
3
+ "version": "0.11.8",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -24,7 +24,7 @@
24
24
  },
25
25
  "devDependencies": {
26
26
  "formdata-polyfill": "^4.0.10",
27
- "nouislider": "^15.5.1",
27
+ "nouislider": "^15.6.0",
28
28
  "object-fit-images": "^3.2.4",
29
29
  "postinstall-bundle": "^0.7.4"
30
30
  },
@@ -29,15 +29,25 @@ class WalkIndex {
29
29
 
30
30
  class HTMLElementFieldsetList extends VirtualHTMLElement {
31
31
  #size;
32
+ #initialSize;
32
33
  #prefix;
33
34
  #model;
34
35
  #walk;
35
36
 
36
37
  fill(values, scope) {
37
38
  const list = this.#listFromValues({ ...values });
39
+ if (this.#initialSize == null) this.#initialSize = list.length;
38
40
  this.#resize(list.length, scope);
39
41
  }
40
42
 
43
+ reset() {
44
+ this.#resize(this.#initialSize, {}); // missing scope
45
+ }
46
+
47
+ save() {
48
+ this.#initialSize = this.#size;
49
+ }
50
+
41
51
  #prepare() {
42
52
  this.ownTpl.prerender();
43
53
  if (this.isContentEditable) return;
@@ -105,8 +115,24 @@ class HTMLElementFieldsetList extends VirtualHTMLElement {
105
115
  for (const node of inputs) {
106
116
  if (node.name.startsWith(prefix)) {
107
117
  node.name = `${prefix}[$field.index].${node.name.substring(prefix.length)}`;
118
+ if (node.id?.startsWith('for-' + prefix)) {
119
+ node.id = `for-${prefix}[$field.index].${node.id.substring(4 + prefix.length)}`;
120
+ }
108
121
  }
109
122
  }
123
+ const conditionalFieldsets = tpl.querySelectorAll('[is="element-fieldset"]');
124
+ for (const node of conditionalFieldsets) {
125
+ if (node.dataset.name?.startsWith(prefix)) {
126
+ node.dataset.name = `${prefix}[$field.index].${node.dataset.name.substring(prefix.length)}`;
127
+ }
128
+ }
129
+ const labels = tpl.querySelectorAll('label[for]');
130
+ for (const node of labels) {
131
+ if (node.htmlFor?.startsWith('for-' + prefix)) {
132
+ node.htmlFor = `for-${prefix}[$field.index].${node.htmlFor.substring(4 + prefix.length)}`;
133
+ }
134
+ }
135
+
110
136
  const subtpl = inputs.ancestor();
111
137
  if (!subtpl) {
112
138
  console.warn("fieldset-list should contain input[name]", this);
@@ -122,6 +148,12 @@ class HTMLElementFieldsetList extends VirtualHTMLElement {
122
148
  while (node.previousSibling) node.previousSibling.remove();
123
149
  node = node.parentNode;
124
150
  }
151
+ {
152
+ const hidden = tpl.ownerDocument.createElement('input');
153
+ hidden.type = "hidden";
154
+ hidden.name = prefix.slice(0, -1);
155
+ tpl.appendChild(hidden);
156
+ }
125
157
  }
126
158
  tpl = tpl.fuse({ $fieldset }, scope);
127
159
 
package/ui/fieldset.css CHANGED
@@ -11,7 +11,7 @@ fieldset.plain {
11
11
  fieldset.plain > legend {
12
12
  display:none;
13
13
  }
14
- fieldset:not(.plain) > legend {
14
+ fieldset:not(.plain) > legend:not(:empty) {
15
15
  padding-left:0.1em;
16
16
  padding-right:0.2em;
17
17
  }
package/ui/fieldset.js CHANGED
@@ -10,12 +10,13 @@ class HTMLCustomFieldSetElement extends HTMLFieldSetElement {
10
10
 
11
11
  #update() {
12
12
  if (this.isContentEditable || !this.options.name) return;
13
- const name = this.options.name.split(".").slice(1).join('.');
14
- const val = this.form?.read(true)?.[name];
13
+ const val = this.form?.read(true)?.[this.options.name];
15
14
  this.disabled = this.hidden = val != this.options.value;
16
15
  }
17
16
 
18
17
  patch(state) {
18
+ // before/after form#fill
19
+ this.#update();
19
20
  state.finish(() => this.#update());
20
21
  }
21
22
  setup() {
package/ui/form.js CHANGED
@@ -43,18 +43,20 @@ class HTMLCustomFormElement extends HTMLFormElement {
43
43
  const fd = new FormData(this);
44
44
  const query = {};
45
45
  fd.forEach((val, key) => {
46
- const cur = this.querySelectorAll(`[name="${key}"]`).slice(-1).pop();
47
- if (cur.type == "file") {
48
- val = cur.value;
49
- }
50
46
  if (val == "") val = null;
51
47
  // build array-like values
52
48
  const old = query[key];
53
49
  if (old !== undefined) {
54
- if (!Array.isArray(old)) {
55
- query[key] = old == null ? [] : [old];
50
+ if (old == null) {
51
+ if (val == null) {
52
+ // keep it that way
53
+ } else {
54
+ query[key] = [];
55
+ }
56
+ } else if (val != null) {
57
+ if (!Array.isArray(old)) query[key] = [old];
58
+ query[key].push(val);
56
59
  }
57
- if (val !== undefined) query[key].push(val);
58
60
  } else {
59
61
  query[key] = val;
60
62
  }
@@ -73,6 +75,9 @@ class HTMLCustomFormElement extends HTMLFormElement {
73
75
  if (defVal == "") defVal = null;
74
76
 
75
77
  switch (type) {
78
+ case "file":
79
+ query[name] = val;
80
+ break;
76
81
  case "radio":
77
82
  if (!withDefaults && node.checked == node.defaultChecked) {
78
83
  if (query[name] == val) {
@@ -119,9 +124,16 @@ class HTMLCustomFormElement extends HTMLFormElement {
119
124
  if (name in query && !vars.includes(name)) vars.push(name);
120
125
  const val = query[name];
121
126
  const str = ((v) => {
122
- if (v == null) return "";
123
- else if (typeof v == "object") return v;
124
- else return v.toString();
127
+ if (v == null) {
128
+ return "";
129
+ } else if (Array.isArray(v)) {
130
+ if (["radio", "checkbox"].includes(elem.type)) return v;
131
+ else return v.shift();
132
+ } else if (typeof v == "object") {
133
+ return v;
134
+ } else {
135
+ return v.toString();
136
+ }
125
137
  })(val);
126
138
  switch (elem.type) {
127
139
  case 'submit':
@@ -159,11 +171,17 @@ class HTMLCustomFormElement extends HTMLFormElement {
159
171
  return vars;
160
172
  }
161
173
  save() {
174
+ for (const node of this.querySelectorAll("element-fieldset-list")) {
175
+ node.save();
176
+ }
162
177
  for (const node of this.elements) {
163
178
  if (node.save) node.save();
164
179
  }
165
180
  }
166
181
  reset() {
182
+ for (const node of this.querySelectorAll("element-fieldset-list")) {
183
+ node.reset();
184
+ }
167
185
  for (const node of this.elements) {
168
186
  if (node.reset) node.reset();
169
187
  }
@@ -29,11 +29,14 @@ class HTMLElementInputDateSlot extends VirtualHTMLElement {
29
29
  const [startEl, endEl] = this.#inputs();
30
30
  const isStart = input == startEl;
31
31
 
32
- let start = startEl.valueAsDate;
32
+ const start = startEl.valueAsDate;
33
33
  let end = endEl.valueAsDate;
34
34
  if (!start && !end) return;
35
- if (!start) start = new Date(end);
36
- else if (!end) end = new Date(start);
35
+ if (!start) {
36
+ endEl.valueAsDate = null;
37
+ return;
38
+ }
39
+ if (!end) end = new Date(start);
37
40
  let startPart, endPart;
38
41
  let equal = true;
39
42
  for (const Part of ['FullYear', 'Month', 'Date', 'Hours', 'Minutes', 'Seconds']) {
package/ui/input-date.js CHANGED
@@ -26,7 +26,7 @@ class HTMLElementInputDate extends HTMLInputElement {
26
26
  return d;
27
27
  }
28
28
  set valueAsDate(d) {
29
- let t = d.getTime();
29
+ let t = d?.getTime();
30
30
  if (!d || Number.isNaN(t)) {
31
31
  super.value = "";
32
32
  return;
package/ui/input-range.js CHANGED
@@ -16,11 +16,12 @@ class HTMLElementInputRange extends HTMLInputElement {
16
16
  max: (x) => parseFloat(x) || 100,
17
17
  value: (x) => this.parse(x),
18
18
  step: (x) => parseFloat(x) || 1,
19
- multiple: true,
20
- pips: true,
19
+ multiple: false,
20
+ pips: false
21
21
  };
22
22
 
23
23
  get helper() {
24
+ if (!this.parentNode) return null;
24
25
  let node = this.parentNode.querySelector('.noUi-target');
25
26
  if (!node) {
26
27
  node = this.ownerDocument.createElement('div');
@@ -43,19 +44,35 @@ class HTMLElementInputRange extends HTMLInputElement {
43
44
  this.rangeValue = this.constructor.parse(str);
44
45
  this.updateSlider();
45
46
  }
47
+ convertOptions(opts) {
48
+ return {
49
+ step: Math.max(opts.step ?? 10, Math.round((opts.max - opts.min) / 10)),
50
+ range: {
51
+ min: opts.min ?? 0,
52
+ max: opts.max ?? 100
53
+ },
54
+ pips: opts.pips ? {
55
+ mode: 'steps',
56
+ density: 100
57
+ } : false,
58
+ };
59
+ }
46
60
  updateSlider() {
47
61
  const helper = this.helper;
48
- if (!helper.noUiSlider) return;
49
- let [start, stop] = this.options.value || [];
62
+ const slider = helper?.noUiSlider;
63
+ if (!slider) return;
64
+ const opts = this.options;
65
+ let [start, stop] = opts.value || [];
50
66
  let indet = false;
51
67
  if (start == null) {
52
- start = this.options.min;
53
- if (stop == null) stop = this.options.max;
68
+ start = opts.min;
69
+ if (stop == null) stop = opts.max;
54
70
  indet = true;
55
71
  }
56
72
  if (stop == null) stop = start;
57
73
 
58
- helper.noUiSlider.set([start, stop]);
74
+ slider.set([start, stop]);
75
+ slider.updateOptions(this.convertOptions(opts));
59
76
  helper.classList.toggle('indeterminate', indet);
60
77
  }
61
78
  patch(state) {
@@ -64,19 +81,12 @@ class HTMLElementInputRange extends HTMLInputElement {
64
81
  setup(state) {
65
82
  const opts = this.options;
66
83
  const helper = this.helper;
67
- if (!helper.noUiSlider) window.noUiSlider.create(helper, {
84
+ if (!helper) return;
85
+ if (!helper.noUiSlider) window.noUiSlider.create(helper, Object.assign({
68
86
  start: opts.multiple ? [null, null] : [null],
69
- step: opts.step,
70
- range: {
71
- min: opts.min,
72
- max: opts.max
73
- },
74
- pips: opts.pips ? {
75
- mode: 'steps',
76
- density: 100
77
- } : false,
78
87
  connect: true
79
- }).on('change', (values) => {
88
+ }, this.convertOptions(opts))).on('change', (values) => {
89
+ if (this.isContentEditable) return;
80
90
  const isInt = parseInt(opts.step) == opts.step;
81
91
  helper.classList.remove('indeterminate');
82
92
  if (isInt) values = values.map((n) => parseInt(n));
@@ -91,6 +101,7 @@ class HTMLElementInputRange extends HTMLInputElement {
91
101
  this.patch(state);
92
102
  }
93
103
  handleEvent(e) {
104
+ if (this.isContentEditable) return;
94
105
  if (e.type == "dblclick" || e.keyCode == 8 || e.keyCode == 46) {
95
106
  this.fill();
96
107
  this.dispatchEvent(new Event('change', {
@@ -102,6 +113,7 @@ class HTMLElementInputRange extends HTMLInputElement {
102
113
 
103
114
  close() {
104
115
  const helper = this.helper;
116
+ if (!helper) return;
105
117
  if (helper.noUiSlider) helper.noUiSlider.destroy();
106
118
  helper.removeEventListener('keydown', this, true);
107
119
  helper.removeEventListener('dblclick', this, true);
package/ui/menu.css CHANGED
@@ -45,14 +45,17 @@
45
45
  display: flex;
46
46
  flex-direction: column;
47
47
  }
48
+ .right.burger .popup.item > .placer {
49
+ left: auto;
50
+ right:0;
51
+ }
48
52
  .right.popup.item > .placer > div {
49
53
  left:auto;
50
54
  right:0;
51
55
  }
52
56
  .fixed.popup.item > .placer > div {
53
- position:fixed;
54
- max-width: 100%;
55
- max-height: 100%;
57
+ max-width: 100vw;
58
+ max-height: 100vh;
56
59
  }
57
60
  .center.popup.item > .placer > div {
58
61
  transform: translateX(-50%);
package/ui/menu.js CHANGED
@@ -57,7 +57,7 @@ class HTMLElementMenu extends VirtualHTMLElement {
57
57
  } else {
58
58
  const padding = this.offsetTop + this.offsetHeight;
59
59
  const menu = tosser.lastElementChild.lastElementChild;
60
- menu.style.maxHeight = `calc(100% - ${padding}px)`;
60
+ menu.style.maxHeight = `calc(100vh - ${padding}px)`;
61
61
  }
62
62
  } else if (item) {
63
63
  this.active = item != this.active ? item : null;