@pageboard/html 0.11.26 → 0.11.28

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/image.js CHANGED
@@ -131,14 +131,11 @@ exports.image = {
131
131
  html: `<element-image
132
132
  class="[display.fit|or:none] [display.horizontal|or:] [display.vertical|or:]"
133
133
  alt="[alt]"
134
- data-src="[url|or:[$element.resources.empty]]"
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]"
136
136
  >
137
137
  <div block-content="legend"></div>
138
138
  </element-image>`,
139
- resources: {
140
- empty: '../ui/empty.png'
141
- },
142
139
  stylesheets: [
143
140
  '../ui/loading.css',
144
141
  '../ui/image.css'
@@ -281,7 +278,7 @@ exports.inlineImage = {
281
278
  group: "inline",
282
279
  tag: "img",
283
280
  html: `<img is="element-img"
284
- data-src="[url|or:[$element.resources.empty]]"
281
+ data-src="[url]"
285
282
  data-crop="[crop.x];[crop.y];[crop.width];[crop.height];[crop.zoom]"
286
283
  alt="" class="ui inline image
287
284
  [display.avatar|?]
@@ -290,9 +287,6 @@ exports.inlineImage = {
290
287
  [display.spaced|?]
291
288
  [display.floated|pre:floated ]
292
289
  [display.align|post: aligned]" />`,
293
- resources: {
294
- empty: '../ui/empty.png'
295
- },
296
290
  stylesheets: [
297
291
  '../lib/components/image.css'
298
292
  ],
package/elements/list.js CHANGED
@@ -1,18 +1,20 @@
1
1
  exports.list_item = {
2
2
  title: 'Item',
3
3
  inplace: true,
4
- contents: "textblock list?",
4
+ contents: "textblock (ul|ol)?",
5
5
  icon: '<i class="list icon"></i>',
6
6
  html: `<li></li>`,
7
7
  };
8
+
8
9
  exports.textblock = {
9
10
  title: 'Text',
10
11
  inplace: true,
11
12
  contents: "inline*",
12
13
  html: '<span class="textblock"></span>'
13
14
  };
14
- exports.list = {
15
- title: 'List',
15
+
16
+ exports.ul = {
17
+ title: 'Unordered List',
16
18
  properties: {
17
19
  marker: {
18
20
  title: 'Marker',
@@ -25,6 +27,36 @@ exports.list = {
25
27
  }, {
26
28
  const: 'square',
27
29
  title: 'Square'
30
+ }, {
31
+ const: 'circle',
32
+ title: 'Circle'
33
+ }]
34
+ }
35
+ },
36
+ inplace: true,
37
+ contents: "list_item+",
38
+ group: "block",
39
+ icon: '<i class="list ul icon"></i>',
40
+ tag: 'ul',
41
+ parse: function(dom) {
42
+ let marker = null;
43
+ const style = dom.style.listStyleType;
44
+ if (style && this.properties.marker.anyOf.some(item => item.const == style)) {
45
+ marker = style;
46
+ }
47
+ return { marker };
48
+ },
49
+ html: `<ul style-list-style-type="[marker]"></ul>`
50
+ };
51
+
52
+ exports.ol = {
53
+ title: 'Ordered List',
54
+ properties: {
55
+ marker: {
56
+ title: 'Marker',
57
+ anyOf: [{
58
+ const: null,
59
+ title: 'Default'
28
60
  }, {
29
61
  const: 'decimal',
30
62
  title: '1, 2, 3'
@@ -65,20 +97,15 @@ exports.list = {
65
97
  contents: "list_item+",
66
98
  group: "block",
67
99
  icon: '<i class="list ol icon"></i>',
68
- tag: 'ul,ol',
69
- parse: function(dom) {
100
+ tag: 'ol',
101
+ parse: function (dom) {
70
102
  let marker = null;
71
103
  const style = dom.style.listStyleType;
72
104
  if (style && this.properties.marker.anyOf.some(item => item.const == style)) {
73
105
  marker = style;
74
- } else if (dom.nodeName == "OL") {
75
- marker = 'decimal';
76
106
  }
77
- return { marker };
107
+ const start = dom.getAttribute('start') || null;
108
+ return { marker, start };
78
109
  },
79
- html: `
80
- <ul style-list-style-type="[marker]">[marker|eq:square:|eq:disc:|!|bmagnet:*]</ul>
81
- <ol style-list-style-type="[marker]" start="[start]">[marker|eq:square:|eq:disc:|bmagnet:*]</ol>
82
- `
110
+ html: `<ol style-list-style-type="[marker]" start="[start]"></ol>`
83
111
  };
84
-
@@ -0,0 +1,176 @@
1
+ exports.time = {
2
+ title: "Date Time",
3
+ icon: '<i class="clock outline icon"></i>',
4
+ inline: true,
5
+ inplace: true,
6
+ group: "inline nolink",
7
+ properties: {
8
+ datetime: {
9
+ title: 'Date and time',
10
+ type: 'string',
11
+ format: 'date-time',
12
+ nullable: true
13
+ },
14
+ timezone: {
15
+ title: ' Time zone',
16
+ type: 'string',
17
+ pattern: /\w+\/\w+/.source,
18
+ nullable: true,
19
+ $filter: {
20
+ name: 'intl',
21
+ of: 'timeZone'
22
+ }
23
+ },
24
+ format: {
25
+ title: 'Format',
26
+ type: 'object',
27
+ properties: {
28
+ weekday: {
29
+ title: 'Weekday',
30
+ anyOf: [
31
+ {
32
+ const: null,
33
+ title: 'none'
34
+ }, {
35
+ const: 'd',
36
+ title: 'short'
37
+ }, {
38
+ const: 'day',
39
+ title: 'long'
40
+ }
41
+ ]
42
+ },
43
+ day: {
44
+ title: 'Day of month',
45
+ anyOf: [
46
+ {
47
+ const: null,
48
+ title: 'none'
49
+ }, {
50
+ const: 'D',
51
+ title: 'numeric'
52
+ }, {
53
+ const: 'DD',
54
+ title: 'two-digits'
55
+ }
56
+ ]
57
+ },
58
+ month: {
59
+ title: 'Month',
60
+ anyOf: [
61
+ {
62
+ const: null,
63
+ title: 'none'
64
+ }, {
65
+ const: 'mon',
66
+ title: 'short'
67
+ }, {
68
+ const: 'month',
69
+ title: 'long'
70
+ }, {
71
+ const: 'M',
72
+ title: 'numeric'
73
+ }, {
74
+ const: 'MM',
75
+ title: 'two-digits'
76
+ }
77
+ ]
78
+ },
79
+ year: {
80
+ title: 'Year',
81
+ anyOf: [
82
+ {
83
+ const: null,
84
+ title: 'none'
85
+ }, {
86
+ const: 'Y',
87
+ title: 'numeric'
88
+ }, {
89
+ const: 'YY',
90
+ title: 'two-digits'
91
+ }
92
+ ]
93
+ },
94
+ hour: {
95
+ title: 'Hour',
96
+ anyOf: [
97
+ {
98
+ const: null,
99
+ title: 'none'
100
+ }, {
101
+ const: 'H',
102
+ title: 'numeric'
103
+ }, {
104
+ const: 'HH',
105
+ title: 'two-digits'
106
+ }
107
+ ]
108
+ },
109
+ minute: {
110
+ title: 'Minute',
111
+ anyOf: [
112
+ {
113
+ const: null,
114
+ title: 'none'
115
+ }, {
116
+ const: 'm',
117
+ title: 'numeric'
118
+ }, {
119
+ const: 'mm',
120
+ title: 'two-digits'
121
+ }
122
+ ]
123
+ },
124
+ second: {
125
+ title: 'Second',
126
+ anyOf: [
127
+ {
128
+ const: null,
129
+ title: 'none'
130
+ }, {
131
+ const: 's',
132
+ title: 'numeric'
133
+ }, {
134
+ const: 'ss',
135
+ title: 'two-digits'
136
+ }
137
+ ]
138
+ },
139
+ timeZoneName: {
140
+ title: 'Timezone',
141
+ anyOf: [
142
+ {
143
+ const: null,
144
+ title: 'none'
145
+ }, {
146
+ const: 'tz',
147
+ title: 'offset'
148
+ }, {
149
+ const: 'timezone',
150
+ title: 'short'
151
+ }
152
+ ]
153
+ }
154
+ }
155
+ }
156
+ },
157
+ parse: function (dom) {
158
+ const format = {};
159
+ const list = (dom.dataset.format ?? "").split(':');
160
+ for (const [key, schema] of Object.entries(this.properties.format.properties)) {
161
+ for (const tok in list) {
162
+ if (tok) {
163
+ const item = schema.anyOf.find(item => item.const === tok);
164
+ if (item) format[key] = item.const;
165
+ }
166
+ }
167
+ }
168
+ return {
169
+ datetime: dom.dateTime,
170
+ timezone: dom.dataset.timezone,
171
+ format
172
+ };
173
+ },
174
+ html: `<time datetime="[datetime|now|isoDate]" data-format="[format|values|join:%3A]" data-timezone="[timezone]" is="element-time"></time>`,
175
+ scripts: ['../ui/time.js']
176
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pageboard/html",
3
- "version": "0.11.26",
3
+ "version": "0.11.28",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "repository": {
package/ui/consent.js CHANGED
@@ -40,7 +40,7 @@ class HTMLCustomConsentElement extends HTMLFormElement {
40
40
  if (consent == null) {
41
41
  return;
42
42
  }
43
- Page.storage.set('consent', consent);
43
+ state.scope.storage.set('consent', consent);
44
44
  state.scope.$consent = consent;
45
45
  state.runChain('consent');
46
46
  }
@@ -67,9 +67,9 @@ Page.ready(() => {
67
67
  VirtualHTMLElement.define(`element-consent`, HTMLCustomConsentElement, 'form');
68
68
  });
69
69
 
70
- Page.State.prototype.consent = function (fn) {
70
+ Page.constructor.prototype.consent = function (fn) {
71
71
  const initial = this.scope.$consent === undefined;
72
- let consent = Page.storage.get('consent');
72
+ let consent = this.scope.storage.get('consent');
73
73
  if (consent == null && initial) consent = undefined;
74
74
  this.scope.$consent = consent;
75
75
  this.chain('consent', fn);
@@ -81,7 +81,7 @@ Page.State.prototype.consent = function (fn) {
81
81
  }
82
82
  };
83
83
 
84
- Page.State.prototype.reconsent = function (fn) {
84
+ Page.constructor.prototype.reconsent = function (fn) {
85
85
  if (fn) this.consent(fn);
86
86
  const consent = this.scope.$consent;
87
87
  let asking = false;
package/ui/image.css CHANGED
@@ -1,10 +1,3 @@
1
- img.loading[data-src]::after {
2
- content: none;
3
- }
4
- img.error::after {
5
- width: 100%;
6
- }
7
-
8
1
  element-image {
9
2
  display:flex;
10
3
  position:relative;
@@ -146,3 +139,7 @@ img[is="element-img"] {
146
139
  max-width: 100%;
147
140
  height:auto;
148
141
  }
142
+
143
+ element-image[block-focused="last"] > [block-content="legend"] {
144
+ background-color: rgb(255 255 255 / 50%);
145
+ }
package/ui/image.js CHANGED
@@ -4,6 +4,9 @@ class HTMLElementImage extends VirtualHTMLElement {
4
4
  crop: null
5
5
  };
6
6
 
7
+ static defaultWidth = 240;
8
+ static defaultHeight = 180;
9
+
7
10
  static getZoom({ w, h, rw, rh, fit }) {
8
11
  let z = 100;
9
12
  if (!rw && !rh) return z;
@@ -89,21 +92,37 @@ class HTMLElementImage extends VirtualHTMLElement {
89
92
  }
90
93
  patch(state) {
91
94
  this.classList.remove('loading');
92
- if (!this.options.src) return;
93
95
  if (this.currentSrc != this.options.src) {
94
96
  this.classList.remove('error');
95
97
  }
96
- const loc = Page.parse(this.options.src);
97
- const meta = state.scope.$hrefs?.[loc.pathname] ?? {};
98
- if (!meta || !meta.width || !meta.height) return;
99
- this.dataset.width = meta.width;
100
- this.dataset.height = meta.height;
101
- if (!this.currentSrc) this.placeholder();
98
+ this.dataset.width = this.constructor.defaultWidth;
99
+ this.dataset.height = this.constructor.defaultHeight;
100
+ if (this.options.src) {
101
+ const loc = Page.parse(this.options.src);
102
+ const meta = state.scope.$hrefs?.[loc.pathname];
103
+ if (meta) {
104
+ this.dataset.width = meta.width;
105
+ this.dataset.height = meta.height;
106
+ }
107
+ }
108
+ const { w, h } = this.dimensions;
109
+ this.image.width = w || this.dataset.width;
110
+ this.image.height = h || this.dataset.height;
111
+ if (!this.currentSrc) {
112
+ this.placeholder();
113
+ }
102
114
  }
115
+
116
+ get currentSrc() {
117
+ const src = this.image?.currentSrc;
118
+ if (src?.startsWith('data:image/svg')) return null;
119
+ else return src;
120
+ }
121
+
103
122
  reveal(state) {
104
123
  const img = this.image;
105
124
  if (!this.options.src) {
106
- img.removeAttribute('src');
125
+ this.placeholder(true);
107
126
  return;
108
127
  }
109
128
  const fit = this.fit;
@@ -143,11 +162,6 @@ class HTMLElementImage extends VirtualHTMLElement {
143
162
  }
144
163
  const curSrc = loc.toString();
145
164
  if (curSrc != this.currentSrc) {
146
- try {
147
- this.currentSrc = curSrc;
148
- } catch (e) {
149
- // pass
150
- }
151
165
  this.classList.add('loading');
152
166
  let done;
153
167
  this.promise = new Promise(resolve => done = resolve);
@@ -157,23 +171,18 @@ class HTMLElementImage extends VirtualHTMLElement {
157
171
  return this.promise;
158
172
  }
159
173
  captureLoad() {
160
- this.promise.done();
174
+ this.promise?.done();
161
175
  this.classList.remove('loading');
162
176
  this.fix(this.image);
163
177
  }
164
178
  captureError() {
165
- this.promise.done();
179
+ this.promise?.done();
166
180
  this.classList.remove('loading');
167
181
  this.classList.add('error');
168
- this.placeholder();
182
+ this.placeholder(true);
169
183
  }
170
- placeholder() {
171
- const { w, h } = this.dimensions;
172
- this.image.width = w;
173
- this.image.height = h;
174
- this.image.src = "data:image/svg+xml," + encodeURIComponent(
175
- `<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 ${w} ${h}"></svg>`
176
- );
184
+ placeholder(error) {
185
+ this.image.removeAttribute('src');
177
186
  }
178
187
  }
179
188
 
@@ -187,28 +196,29 @@ class HTMLElementInlineImage extends HTMLImageElement {
187
196
  dataCrop: null
188
197
  };
189
198
 
199
+ static defaultWidth = 40;
200
+ static defaultHeight = 30;
201
+
190
202
  get image() {
191
203
  return this;
192
204
  }
193
- captureLoad() {
194
- this.promise.done();
195
- this.classList.remove('loading');
196
- this.fix(this.image);
197
- }
198
- placeholder() {
199
- const { w, h } = this.dimensions;
200
- this.width = w;
201
- this.height = h;
205
+
206
+ placeholder(error) {
207
+ if (error) {
208
+ this.image.src = "data:image/svg+xml," + encodeURIComponent(
209
+ `<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 ${this.image.width} ${this.image.height}"><text text-anchor="middle" dominant-baseline="central" x="50%" y="50%" fill="#aaa">${error ? '∅' : ''}</text></svg>`);
210
+ } else {
211
+ this.image.removeAttribute('src');
212
+ }
202
213
  }
203
- }
204
214
 
205
- for (const name of ['patch', 'reveal', 'captureError', 'crop', 'dimensions', 'position', 'fit', 'findClass', 'fix']) {
206
- Object.defineProperty(
207
- HTMLElementInlineImage.prototype,
208
- name,
209
- Object.getOwnPropertyDescriptor(HTMLElementImage.prototype, name)
210
- );
215
+ get currentSrc() {
216
+ const cur = super.currentSrc;
217
+ if (!cur && this.image.src?.startsWith('data:')) return this.src;
218
+ else return cur;
219
+ }
211
220
  }
212
221
 
222
+ VirtualHTMLElement.inherits(HTMLElementInlineImage, HTMLElementImage);
213
223
  VirtualHTMLElement.define('element-image', HTMLElementImage);
214
224
  VirtualHTMLElement.define(`element-img`, HTMLElementInlineImage, 'img');
package/ui/loading.css CHANGED
@@ -9,6 +9,7 @@
9
9
  width: 100%;
10
10
  top: 50%;
11
11
  margin-top: -0.5rem;
12
+ line-height: 1rem;
12
13
  }
13
14
 
14
15
  @keyframes spinner {
package/ui/storage.js CHANGED
@@ -68,5 +68,5 @@ class UserStore {
68
68
  }
69
69
  }
70
70
 
71
- Page.setup(() => Page.storage = new UserStore());
71
+ Page.setup(state => state.scope.storage = new UserStore());
72
72
 
package/ui/time.js ADDED
@@ -0,0 +1,22 @@
1
+ class HTMLElementTime extends HTMLTimeElement {
2
+ constructor() {
3
+ super();
4
+ if (this.init) this.init();
5
+ }
6
+
7
+ static defaults = {
8
+ dataFormat: null,
9
+ dataTimezone: null,
10
+ datetime: null
11
+ };
12
+
13
+ patch(state) {
14
+ this.textContent = `[stamp|formatDate:[fmt]:[tz]]`.fuse({
15
+ stamp: this.dateTime,
16
+ fmt: this.dataset.format,
17
+ tz: this.dataset.timezone
18
+ }, state.scope);
19
+ }
20
+ }
21
+
22
+ VirtualHTMLElement.define(`element-time`, HTMLElementTime, 'time');
package/ui/transition.js CHANGED
@@ -4,6 +4,7 @@ Object.defineProperty(document, 'body', {
4
4
  return this.documentElement.querySelector('body:last-of-type');
5
5
  }
6
6
  });
7
+
7
8
  Page.init(state => {
8
9
  const root = document.documentElement;
9
10
  function dtr(state) {
@@ -16,33 +17,10 @@ Page.init(state => {
16
17
  Page.ready(dtr);
17
18
  Page.patch(dtr);
18
19
  Page.setup(dtr);
19
- Page.error(dtr);
20
+ Page.catch(dtr);
20
21
  });
21
22
 
22
- Page.State.prototype.mergeBody = function(body, corpse) {
23
- if (this.referrer.transition) {
24
- this.referrer.transition.end();
25
- }
26
- if (body.isContentEditable || body.getAttribute('block-type') != corpse.getAttribute('block-type')) {
27
- corpse.replaceWith(body);
28
- } else {
29
- this.transition = new Page.Transition(this, body, corpse);
30
- }
31
- };
32
-
33
- Page.setup(state => {
34
- if (state.transition) {
35
- if (state.transition.ok) {
36
- state.finish(() => {
37
- return state.transition.start();
38
- });
39
- } else {
40
- state.transition.end();
41
- }
42
- }
43
- });
44
-
45
- Page.Transition = class {
23
+ class Transition {
46
24
  static event(name) {
47
25
  const low = name.toLowerCase();
48
26
  const caps = name[0].toUpperCase() + low.substring(1);
@@ -144,9 +122,32 @@ Page.Transition = class {
144
122
  this.root.scrollLeft = left;
145
123
  delete this.from;
146
124
  delete this.to;
147
- delete this.state.transition;
125
+ delete this.state.scope.transition;
148
126
  delete this.state;
149
127
  }
128
+ }
129
+
130
+ Page.constructor.prototype.mergeBody = function (body, corpse) {
131
+ if (this.referrer.scope.transition) {
132
+ this.referrer.scope.transition.end();
133
+ }
134
+ if (body.isContentEditable || body.getAttribute('block-type') != corpse.getAttribute('block-type')) {
135
+ corpse.replaceWith(body);
136
+ } else {
137
+ this.scope.transition = new Transition(this, body, corpse);
138
+ }
150
139
  };
151
140
 
141
+ Page.setup(state => {
142
+ const tr = state.scope.transition;
143
+ if (tr) {
144
+ if (tr.ok) {
145
+ state.finish(() => {
146
+ return tr.start();
147
+ });
148
+ } else {
149
+ tr.end();
150
+ }
151
+ }
152
+ });
152
153