@mosaicajs/components 0.1.0 → 0.3.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.
@@ -211,6 +211,8 @@ export declare class MosaicaExplorer extends MosaicaElement {
211
211
  accessor subtitleField: string;
212
212
  /** The fields the item card shows before "Show all", in order, as for `<mosaica-item-card>`. Unset, the first three. */
213
213
  accessor cardFields: string;
214
+ /** The fields the item card draws as images, as for `<mosaica-item-card>`'s `image-fields`. */
215
+ accessor imageFields: string;
214
216
  /** The link template for the item card's Open button, as for `<mosaica-item-card>`. Without it or `open`, there is no Open button. */
215
217
  accessor openUrl: string;
216
218
  /** A function the item card calls when Open is pressed, as for `<mosaica-item-card>`. Takes precedence over `open-url`. */
package/dist/explorer.js CHANGED
@@ -305,6 +305,9 @@ let MosaicaExplorer = (() => {
305
305
  let _cardFields_decorators;
306
306
  let _cardFields_initializers = [];
307
307
  let _cardFields_extraInitializers = [];
308
+ let _imageFields_decorators;
309
+ let _imageFields_initializers = [];
310
+ let _imageFields_extraInitializers = [];
308
311
  let _openUrl_decorators;
309
312
  let _openUrl_initializers = [];
310
313
  let _openUrl_extraInitializers = [];
@@ -448,6 +451,7 @@ let MosaicaExplorer = (() => {
448
451
  _titleField_decorators = [property({ attribute: 'title-field' })];
449
452
  _subtitleField_decorators = [property({ attribute: 'subtitle-field' })];
450
453
  _cardFields_decorators = [property({ attribute: 'card-fields' })];
454
+ _imageFields_decorators = [property({ attribute: 'image-fields' })];
451
455
  _openUrl_decorators = [property({ attribute: 'open-url' })];
452
456
  _open_decorators = [property({ attribute: false })];
453
457
  _budget_decorators = [property({ type: Number })];
@@ -500,6 +504,7 @@ let MosaicaExplorer = (() => {
500
504
  __esDecorate(this, null, _titleField_decorators, { kind: "accessor", name: "titleField", static: false, private: false, access: { has: obj => "titleField" in obj, get: obj => obj.titleField, set: (obj, value) => { obj.titleField = value; } }, metadata: _metadata }, _titleField_initializers, _titleField_extraInitializers);
501
505
  __esDecorate(this, null, _subtitleField_decorators, { kind: "accessor", name: "subtitleField", static: false, private: false, access: { has: obj => "subtitleField" in obj, get: obj => obj.subtitleField, set: (obj, value) => { obj.subtitleField = value; } }, metadata: _metadata }, _subtitleField_initializers, _subtitleField_extraInitializers);
502
506
  __esDecorate(this, null, _cardFields_decorators, { kind: "accessor", name: "cardFields", static: false, private: false, access: { has: obj => "cardFields" in obj, get: obj => obj.cardFields, set: (obj, value) => { obj.cardFields = value; } }, metadata: _metadata }, _cardFields_initializers, _cardFields_extraInitializers);
507
+ __esDecorate(this, null, _imageFields_decorators, { kind: "accessor", name: "imageFields", static: false, private: false, access: { has: obj => "imageFields" in obj, get: obj => obj.imageFields, set: (obj, value) => { obj.imageFields = value; } }, metadata: _metadata }, _imageFields_initializers, _imageFields_extraInitializers);
503
508
  __esDecorate(this, null, _openUrl_decorators, { kind: "accessor", name: "openUrl", static: false, private: false, access: { has: obj => "openUrl" in obj, get: obj => obj.openUrl, set: (obj, value) => { obj.openUrl = value; } }, metadata: _metadata }, _openUrl_initializers, _openUrl_extraInitializers);
504
509
  __esDecorate(this, null, _open_decorators, { kind: "accessor", name: "open", static: false, private: false, access: { has: obj => "open" in obj, get: obj => obj.open, set: (obj, value) => { obj.open = value; } }, metadata: _metadata }, _open_initializers, _open_extraInitializers);
505
510
  __esDecorate(this, null, _budget_decorators, { kind: "accessor", name: "budget", static: false, private: false, access: { has: obj => "budget" in obj, get: obj => obj.budget, set: (obj, value) => { obj.budget = value; } }, metadata: _metadata }, _budget_initializers, _budget_extraInitializers);
@@ -1038,7 +1043,11 @@ let MosaicaExplorer = (() => {
1038
1043
  /** The fields the item card shows before "Show all", in order, as for `<mosaica-item-card>`. Unset, the first three. */
1039
1044
  get cardFields() { return this.#cardFields_accessor_storage; }
1040
1045
  set cardFields(value) { this.#cardFields_accessor_storage = value; }
1041
- #openUrl_accessor_storage = (__runInitializers(this, _cardFields_extraInitializers), __runInitializers(this, _openUrl_initializers, ''));
1046
+ #imageFields_accessor_storage = (__runInitializers(this, _cardFields_extraInitializers), __runInitializers(this, _imageFields_initializers, ''));
1047
+ /** The fields the item card draws as images, as for `<mosaica-item-card>`'s `image-fields`. */
1048
+ get imageFields() { return this.#imageFields_accessor_storage; }
1049
+ set imageFields(value) { this.#imageFields_accessor_storage = value; }
1050
+ #openUrl_accessor_storage = (__runInitializers(this, _imageFields_extraInitializers), __runInitializers(this, _openUrl_initializers, ''));
1042
1051
  /** The link template for the item card's Open button, as for `<mosaica-item-card>`. Without it or `open`, there is no Open button. */
1043
1052
  get openUrl() { return this.#openUrl_accessor_storage; }
1044
1053
  set openUrl(value) { this.#openUrl_accessor_storage = value; }
@@ -1514,7 +1523,7 @@ let MosaicaExplorer = (() => {
1514
1523
  */
1515
1524
  itemCard(item, actions) {
1516
1525
  return html `<mosaica-item-card exportparts=${FORWARD['item-card']} compact .item=${item} .pick=${item ? null : (this.map?.lastPick ?? null)}
1517
- title-field=${this.titleField || nothing} subtitle-field=${this.subtitleField || nothing} card-fields=${this.cardFields || nothing} open-url=${this.openUrl || nothing} .open=${this.open}>${actions}</mosaica-item-card>`;
1526
+ title-field=${this.titleField || nothing} subtitle-field=${this.subtitleField || nothing} card-fields=${this.cardFields || nothing} image-fields=${this.imageFields || nothing} open-url=${this.openUrl || nothing} .open=${this.open}>${actions}</mosaica-item-card>`;
1518
1527
  }
1519
1528
  /** Whether the card for the selection is the artifact card: the artifact, or its refusal, changed last. */
1520
1529
  showsArtifact() {
@@ -11,6 +11,11 @@ import { MosaicaElement, type PickOutcome } from './base.js';
11
11
  * empty. To render a field your own way, as a link for example, put an element in its
12
12
  * `field-<name>` slot.
13
13
  *
14
+ * A field named in `image-fields` whose value is an `http:` or `https:` address, or a path
15
+ * resolved against the page, is drawn as the image at that address, across the card's width. The viewer's browser loads it from the host
16
+ * that serves it, without a referrer. A value that is not such an address, and an image that does
17
+ * not load, are shown as text.
18
+ *
14
19
  * The Open button appears only when you set `open-url` or `open`. `open-url` is a link template
15
20
  * such as `https://www.geonames.org/{geonameid}`, in which each `{name}` is replaced by the item's
16
21
  * value for that field. Because the values are URL-encoded, a template cannot open an address that
@@ -43,6 +48,7 @@ import { MosaicaElement, type PickOutcome } from './base.js';
43
48
  * @csspart field - One field, with `data-name`, and `data-prose` on a value over 60 characters.
44
49
  * @csspart label - A field's name, and the heading above the view chips.
45
50
  * @csspart value - A field's value.
51
+ * @csspart image - A field's value drawn as an image, under `image-fields`.
46
52
  * @csspart scoped - The group-scoped values, grouped by key.
47
53
  * @csspart key - One key's heading among the group-scoped values, with `data-key`.
48
54
  * @csspart open - The Open button.
@@ -75,6 +81,11 @@ export declare class MosaicaItemCard extends MosaicaElement {
75
81
  * Unset, the first three. The full list shows these fields first, then the rest.
76
82
  */
77
83
  accessor cardFields: string;
84
+ /**
85
+ * The fields whose value is the address of an image, separated by spaces or commas. Each is drawn
86
+ * as its image rather than as text.
87
+ */
88
+ accessor imageFields: string;
78
89
  /**
79
90
  * The link template for Open, with `{name}` for the value of the field `name` and `{mosaica_id}`
80
91
  * for the item's id. Without it or `open`, there is no Open button.
@@ -85,6 +96,10 @@ export declare class MosaicaItemCard extends MosaicaElement {
85
96
  /** Whether "Show all" was pressed, for the item it was pressed on. @internal */
86
97
  accessor expanded: boolean;
87
98
  private expandedFor;
99
+ /** The image addresses that failed to load, shown as text. @internal */
100
+ accessor broken: ReadonlySet<string>;
101
+ /** `image-fields` as a list, kept in step by `willUpdate`. */
102
+ private imageNames;
88
103
  /** A category value's title as the store holds it, else from the legend or a suggestion page, else its key. */
89
104
  private titleOf;
90
105
  /** A field's value as text: a category value by its title, anything else by its declared type. */
@@ -105,6 +120,8 @@ export declare class MosaicaItemCard extends MosaicaElement {
105
120
  private openButton;
106
121
  /** The fields of `fields` that `card-fields` names, in its order. */
107
122
  private named;
123
+ /** The address of the image a field's value names, where `image-fields` lists the field. */
124
+ private imageOf;
108
125
  /** `fields` with those `card-fields` names first, in its order. */
109
126
  private namedFirst;
110
127
  /**
@@ -124,7 +141,7 @@ export declare class MosaicaItemCard extends MosaicaElement {
124
141
  * key through a `members` group share a heading.
125
142
  */
126
143
  private scoped;
127
- /** One field, presented by its declared type; prose spans the grid. */
144
+ /** One field, presented by its declared type; prose and images span the grid. */
128
145
  private field;
129
146
  }
130
147
  declare global {
package/dist/item-card.js CHANGED
@@ -49,6 +49,11 @@ import { chrome, tokens } from './tokens.js';
49
49
  * empty. To render a field your own way, as a link for example, put an element in its
50
50
  * `field-<name>` slot.
51
51
  *
52
+ * A field named in `image-fields` whose value is an `http:` or `https:` address, or a path
53
+ * resolved against the page, is drawn as the image at that address, across the card's width. The viewer's browser loads it from the host
54
+ * that serves it, without a referrer. A value that is not such an address, and an image that does
55
+ * not load, are shown as text.
56
+ *
52
57
  * The Open button appears only when you set `open-url` or `open`. `open-url` is a link template
53
58
  * such as `https://www.geonames.org/{geonameid}`, in which each `{name}` is replaced by the item's
54
59
  * value for that field. Because the values are URL-encoded, a template cannot open an address that
@@ -81,6 +86,7 @@ import { chrome, tokens } from './tokens.js';
81
86
  * @csspart field - One field, with `data-name`, and `data-prose` on a value over 60 characters.
82
87
  * @csspart label - A field's name, and the heading above the view chips.
83
88
  * @csspart value - A field's value.
89
+ * @csspart image - A field's value drawn as an image, under `image-fields`.
84
90
  * @csspart scoped - The group-scoped values, grouped by key.
85
91
  * @csspart key - One key's heading among the group-scoped values, with `data-key`.
86
92
  * @csspart open - The Open button.
@@ -112,6 +118,9 @@ let MosaicaItemCard = (() => {
112
118
  let _cardFields_decorators;
113
119
  let _cardFields_initializers = [];
114
120
  let _cardFields_extraInitializers = [];
121
+ let _imageFields_decorators;
122
+ let _imageFields_initializers = [];
123
+ let _imageFields_extraInitializers = [];
115
124
  let _openUrl_decorators;
116
125
  let _openUrl_initializers = [];
117
126
  let _openUrl_extraInitializers = [];
@@ -121,6 +130,9 @@ let MosaicaItemCard = (() => {
121
130
  let _expanded_decorators;
122
131
  let _expanded_initializers = [];
123
132
  let _expanded_extraInitializers = [];
133
+ let _broken_decorators;
134
+ let _broken_initializers = [];
135
+ let _broken_extraInitializers = [];
124
136
  return class MosaicaItemCard extends _classSuper {
125
137
  static {
126
138
  const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
@@ -132,9 +144,11 @@ let MosaicaItemCard = (() => {
132
144
  _compact_decorators = [property({ type: Boolean, reflect: true })];
133
145
  _subtitleField_decorators = [property({ attribute: 'subtitle-field' })];
134
146
  _cardFields_decorators = [property({ attribute: 'card-fields' })];
147
+ _imageFields_decorators = [property({ attribute: 'image-fields' })];
135
148
  _openUrl_decorators = [property({ attribute: 'open-url' })];
136
149
  _open_decorators = [property({ attribute: false })];
137
150
  _expanded_decorators = [state()];
151
+ _broken_decorators = [state()];
138
152
  __esDecorate(this, null, _item_decorators, { kind: "accessor", name: "item", static: false, private: false, access: { has: obj => "item" in obj, get: obj => obj.item, set: (obj, value) => { obj.item = value; } }, metadata: _metadata }, _item_initializers, _item_extraInitializers);
139
153
  __esDecorate(this, null, _refusal_decorators, { kind: "accessor", name: "refusal", static: false, private: false, access: { has: obj => "refusal" in obj, get: obj => obj.refusal, set: (obj, value) => { obj.refusal = value; } }, metadata: _metadata }, _refusal_initializers, _refusal_extraInitializers);
140
154
  __esDecorate(this, null, _pick_decorators, { kind: "accessor", name: "pick", static: false, private: false, access: { has: obj => "pick" in obj, get: obj => obj.pick, set: (obj, value) => { obj.pick = value; } }, metadata: _metadata }, _pick_initializers, _pick_extraInitializers);
@@ -143,9 +157,11 @@ let MosaicaItemCard = (() => {
143
157
  __esDecorate(this, null, _compact_decorators, { kind: "accessor", name: "compact", static: false, private: false, access: { has: obj => "compact" in obj, get: obj => obj.compact, set: (obj, value) => { obj.compact = value; } }, metadata: _metadata }, _compact_initializers, _compact_extraInitializers);
144
158
  __esDecorate(this, null, _subtitleField_decorators, { kind: "accessor", name: "subtitleField", static: false, private: false, access: { has: obj => "subtitleField" in obj, get: obj => obj.subtitleField, set: (obj, value) => { obj.subtitleField = value; } }, metadata: _metadata }, _subtitleField_initializers, _subtitleField_extraInitializers);
145
159
  __esDecorate(this, null, _cardFields_decorators, { kind: "accessor", name: "cardFields", static: false, private: false, access: { has: obj => "cardFields" in obj, get: obj => obj.cardFields, set: (obj, value) => { obj.cardFields = value; } }, metadata: _metadata }, _cardFields_initializers, _cardFields_extraInitializers);
160
+ __esDecorate(this, null, _imageFields_decorators, { kind: "accessor", name: "imageFields", static: false, private: false, access: { has: obj => "imageFields" in obj, get: obj => obj.imageFields, set: (obj, value) => { obj.imageFields = value; } }, metadata: _metadata }, _imageFields_initializers, _imageFields_extraInitializers);
146
161
  __esDecorate(this, null, _openUrl_decorators, { kind: "accessor", name: "openUrl", static: false, private: false, access: { has: obj => "openUrl" in obj, get: obj => obj.openUrl, set: (obj, value) => { obj.openUrl = value; } }, metadata: _metadata }, _openUrl_initializers, _openUrl_extraInitializers);
147
162
  __esDecorate(this, null, _open_decorators, { kind: "accessor", name: "open", static: false, private: false, access: { has: obj => "open" in obj, get: obj => obj.open, set: (obj, value) => { obj.open = value; } }, metadata: _metadata }, _open_initializers, _open_extraInitializers);
148
163
  __esDecorate(this, null, _expanded_decorators, { kind: "accessor", name: "expanded", static: false, private: false, access: { has: obj => "expanded" in obj, get: obj => obj.expanded, set: (obj, value) => { obj.expanded = value; } }, metadata: _metadata }, _expanded_initializers, _expanded_extraInitializers);
164
+ __esDecorate(this, null, _broken_decorators, { kind: "accessor", name: "broken", static: false, private: false, access: { has: obj => "broken" in obj, get: obj => obj.broken, set: (obj, value) => { obj.broken = value; } }, metadata: _metadata }, _broken_initializers, _broken_extraInitializers);
149
165
  if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
150
166
  }
151
167
  static styles = [
@@ -225,9 +241,18 @@ let MosaicaItemCard = (() => {
225
241
  .head [part='state'] {
226
242
  min-height: 24px;
227
243
  }
228
- [part='field'][data-prose] {
244
+ [part='field'][data-prose],
245
+ [part='field'][data-image] {
229
246
  grid-column: 1 / -1;
230
247
  }
248
+ [part='image'] {
249
+ display: block;
250
+ max-width: 100%;
251
+ max-height: 240px;
252
+ margin-top: 4px;
253
+ border-radius: 6px;
254
+ object-fit: contain;
255
+ }
231
256
  [part='field'][data-prose] [part='value'] {
232
257
  display: -webkit-box;
233
258
  -webkit-line-clamp: 6;
@@ -317,7 +342,14 @@ let MosaicaItemCard = (() => {
317
342
  */
318
343
  get cardFields() { return this.#cardFields_accessor_storage; }
319
344
  set cardFields(value) { this.#cardFields_accessor_storage = value; }
320
- #openUrl_accessor_storage = (__runInitializers(this, _cardFields_extraInitializers), __runInitializers(this, _openUrl_initializers, ''));
345
+ #imageFields_accessor_storage = (__runInitializers(this, _cardFields_extraInitializers), __runInitializers(this, _imageFields_initializers, ''));
346
+ /**
347
+ * The fields whose value is the address of an image, separated by spaces or commas. Each is drawn
348
+ * as its image rather than as text.
349
+ */
350
+ get imageFields() { return this.#imageFields_accessor_storage; }
351
+ set imageFields(value) { this.#imageFields_accessor_storage = value; }
352
+ #openUrl_accessor_storage = (__runInitializers(this, _imageFields_extraInitializers), __runInitializers(this, _openUrl_initializers, ''));
321
353
  /**
322
354
  * The link template for Open, with `{name}` for the value of the field `name` and `{mosaica_id}`
323
355
  * for the item's id. Without it or `open`, there is no Open button.
@@ -333,6 +365,12 @@ let MosaicaItemCard = (() => {
333
365
  get expanded() { return this.#expanded_accessor_storage; }
334
366
  set expanded(value) { this.#expanded_accessor_storage = value; }
335
367
  expandedFor = (__runInitializers(this, _expanded_extraInitializers), null);
368
+ #broken_accessor_storage = __runInitializers(this, _broken_initializers, new Set());
369
+ /** The image addresses that failed to load, shown as text. @internal */
370
+ get broken() { return this.#broken_accessor_storage; }
371
+ set broken(value) { this.#broken_accessor_storage = value; }
372
+ /** `image-fields` as a list, kept in step by `willUpdate`. */
373
+ imageNames = (__runInitializers(this, _broken_extraInitializers), []);
336
374
  /** A category value's title as the store holds it, else from the legend or a suggestion page, else its key. */
337
375
  titleOf(field, key) {
338
376
  return this.resolvedStore?.heldTitle(field, key) ?? keyTitle(this.resolvedStore, field, key);
@@ -347,6 +385,8 @@ let MosaicaItemCard = (() => {
347
385
  */
348
386
  willUpdate(changed) {
349
387
  super.willUpdate(changed);
388
+ if (changed.has('imageFields'))
389
+ this.imageNames = listed(this.imageFields);
350
390
  const { item, meta } = this.shown;
351
391
  const id = item?.id ?? null;
352
392
  if (id !== this.expandedFor) {
@@ -484,8 +524,15 @@ let MosaicaItemCard = (() => {
484
524
  }
485
525
  /** The fields of `fields` that `card-fields` names, in its order. */
486
526
  named(fields) {
487
- const names = this.cardFields.split(/[\s,]+/);
488
- return names.filter((n, i) => n && fields.includes(n) && names.indexOf(n) === i);
527
+ const names = listed(this.cardFields);
528
+ return names.filter((n, i) => fields.includes(n) && names.indexOf(n) === i);
529
+ }
530
+ /** The address of the image a field's value names, where `image-fields` lists the field. */
531
+ imageOf(name, value) {
532
+ if (typeof value !== 'string' || !this.imageNames.includes(name))
533
+ return null;
534
+ const address = webAddress(value);
535
+ return address && !this.broken.has(address) ? address : null;
489
536
  }
490
537
  /** `fields` with those `card-fields` names first, in its order. */
491
538
  namedFirst(fields) {
@@ -547,12 +594,21 @@ let MosaicaItemCard = (() => {
547
594
  </div>`)}
548
595
  </div>`;
549
596
  }
550
- /** One field, presented by its declared type; prose spans the grid. */
597
+ /** One field, presented by its declared type; prose and images span the grid. */
551
598
  field(name, value, column) {
599
+ const caption = column ? columnCaption(name) : name;
600
+ const image = this.imageOf(name, value);
601
+ if (image) {
602
+ const failed = () => (this.broken = new Set([...this.broken, image]));
603
+ return html `<div part="field" data-name=${name} data-image>
604
+ <span part="label" class="k">${caption}</span>
605
+ <slot name=${`field-${name}`}><img part="image" src=${image} alt=${caption} loading="lazy" referrerpolicy="no-referrer" @error=${failed} /></slot>
606
+ </div>`;
607
+ }
552
608
  const text = this.text(name, value, column);
553
609
  const prose = text.length > 60;
554
610
  return html `<div part="field" data-name=${name} ?data-prose=${prose} style=${prose ? nothing : 'display:contents'}>
555
- <span part="label" class="k">${column ? columnCaption(name) : name}</span>
611
+ <span part="label" class="k">${caption}</span>
556
612
  <slot name=${`field-${name}`}><span part="value" class="v" title=${prose ? text : nothing}>${text}</span></slot>
557
613
  </div>`;
558
614
  }
@@ -573,15 +629,26 @@ function openAddress(template, values) {
573
629
  missing = true;
574
630
  return encodeURIComponent(String(value));
575
631
  });
576
- if (missing)
577
- return null;
578
- const url = new URL(filled, document.baseURI);
632
+ return missing ? null : webAddress(filled);
633
+ }
634
+ catch {
635
+ return null;
636
+ }
637
+ }
638
+ /** `text` resolved against the page, where it is an `http:` or `https:` address. */
639
+ function webAddress(text) {
640
+ try {
641
+ const url = new URL(text, document.baseURI);
579
642
  return url.protocol === 'http:' || url.protocol === 'https:' ? url.href : null;
580
643
  }
581
644
  catch {
582
645
  return null;
583
646
  }
584
647
  }
648
+ /** The names in a list separated by spaces or commas. */
649
+ function listed(names) {
650
+ return names.split(/[\s,]+/).filter(Boolean);
651
+ }
585
652
  /** A value as text, by the column's declared type. */
586
653
  function present(value, column) {
587
654
  if (value === null || value === undefined)