@anton-gustafsson/snapshot-core 0.0.6 → 0.0.7

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.
@@ -9,6 +9,8 @@ export interface NavItem {
9
9
  description?: string;
10
10
  }
11
11
  export type SnapshotNavListVariant = 'list' | 'icon-only' | 'card';
12
+ /** `overlay` floats the edit button over the thumbnail (top-right, reveals on hover); `meta` pins it to the right edge of the title's line (description below), always visible. */
13
+ export type SnapshotNavListEditButtonPosition = 'overlay' | 'meta';
12
14
  /**
13
15
  * Visual identity: a contact sheet. Every tile is a "frame" — numbered like a strip
14
16
  * of negatives — because that's literally what a snapshot thumbnail is. All colors
@@ -31,8 +33,12 @@ export declare class SnapshotNavList extends LitElement {
31
33
  labelPosition: 'bottom' | 'center';
32
34
  /** Defaults to the shared singleton — set your own instance (e.g. a namespaced or custom-storage SnapshotService) per <snapshot-nav-list> if needed. */
33
35
  snapshotService: SnapshotService;
34
- /** Shows a top-right edit button per card. Off by default — clicking it fires `nav-edit` instead of `nav-select`; the host decides what "edit" means (e.g. open its own dialog component). */
36
+ /** Shows an edit button per card. Off by default — clicking it fires `nav-edit` instead of `nav-select`; the host decides what "edit" means (e.g. open its own dialog component). */
35
37
  editable: boolean;
38
+ /** Where the edit button sits: `overlay` (default) floats it over the thumbnail; `meta` pins it to the right edge of the title row, with the description below. Ignored by the icon-only variant, whose caption is itself an overlay. */
39
+ editButtonPosition: SnapshotNavListEditButtonPosition;
40
+ /** Edit button glyph. Same convention as `NavItem.icon`: a plain-text glyph (e.g. an emoji), or markup — a string starting with `<` renders as raw HTML/SVG, so a consumer can pass its own icon (e.g. `<svg>...</svg>`). */
41
+ editIcon: string;
36
42
  private thumbs;
37
43
  private loadingIds;
38
44
  /** In-flight dedup guard, separate from `loadingIds` (which is only for spinner display) so a repeat `loadThumb` call for an id already being fetched is a no-op. */
@@ -50,6 +56,7 @@ export declare class SnapshotNavList extends LitElement {
50
56
  private get metaStyle();
51
57
  private select;
52
58
  private edit;
59
+ private renderEditButton;
53
60
  render(): import("lit-html").TemplateResult<1>;
54
61
  }
55
62
  declare global {
@@ -12,6 +12,7 @@ import { snapshotService as defaultSnapshotService } from './snapshot-service';
12
12
  function isMarkupIcon(icon) {
13
13
  return icon.trimStart().startsWith('<');
14
14
  }
15
+ const DEFAULT_EDIT_ICON = '✎';
15
16
  /**
16
17
  * Visual identity: a contact sheet. Every tile is a "frame" — numbered like a strip
17
18
  * of negatives — because that's literally what a snapshot thumbnail is. All colors
@@ -35,8 +36,12 @@ export class SnapshotNavList extends LitElement {
35
36
  this.labelPosition = 'bottom';
36
37
  /** Defaults to the shared singleton — set your own instance (e.g. a namespaced or custom-storage SnapshotService) per <snapshot-nav-list> if needed. */
37
38
  this.snapshotService = defaultSnapshotService;
38
- /** Shows a top-right edit button per card. Off by default — clicking it fires `nav-edit` instead of `nav-select`; the host decides what "edit" means (e.g. open its own dialog component). */
39
+ /** Shows an edit button per card. Off by default — clicking it fires `nav-edit` instead of `nav-select`; the host decides what "edit" means (e.g. open its own dialog component). */
39
40
  this.editable = false;
41
+ /** Where the edit button sits: `overlay` (default) floats it over the thumbnail; `meta` pins it to the right edge of the title row, with the description below. Ignored by the icon-only variant, whose caption is itself an overlay. */
42
+ this.editButtonPosition = 'overlay';
43
+ /** Edit button glyph. Same convention as `NavItem.icon`: a plain-text glyph (e.g. an emoji), or markup — a string starting with `<` renders as raw HTML/SVG, so a consumer can pass its own icon (e.g. `<svg>...</svg>`). */
44
+ this.editIcon = DEFAULT_EDIT_ICON;
40
45
  this.thumbs = new Map();
41
46
  this.loadingIds = new Set();
42
47
  /** In-flight dedup guard, separate from `loadingIds` (which is only for spinner display) so a repeat `loadThumb` call for an id already being fetched is a no-op. */
@@ -243,6 +248,50 @@ export class SnapshotNavList extends LitElement {
243
248
  outline: 2px solid var(--frame-accent);
244
249
  outline-offset: 1px;
245
250
  }
251
+ .edit-button svg {
252
+ width: 1em;
253
+ height: 1em;
254
+ display: block;
255
+ fill: currentColor;
256
+ }
257
+
258
+ /* Transparent by default (display: contents) so the existing per-variant
259
+ .label/.meta rules — including icon-only's absolute caption strip —
260
+ keep applying unchanged; it only becomes a real row when the edit
261
+ button moves in beside the title. */
262
+ .label-row {
263
+ display: contents;
264
+ }
265
+ /* edit-button-position="meta": button on the title's line, description
266
+ still on its own line underneath. Not offered for icon-only, whose
267
+ .meta is an absolutely positioned overlay strip — the overlay button is
268
+ already the right place there. */
269
+ :host([edit-button-position='meta']:not([variant='icon-only'])) .label-row {
270
+ display: flex;
271
+ align-items: center;
272
+ gap: 0.5rem;
273
+ min-width: 0;
274
+ }
275
+ /* the title takes the whole row so the button lands on the card's right
276
+ edge, still on the title's own line (the description sits below it);
277
+ min-width: 0 keeps a long title ellipsising instead of pushing out. */
278
+ :host([edit-button-position='meta']:not([variant='icon-only'])) .label {
279
+ flex: 1;
280
+ min-width: 0;
281
+ }
282
+ :host([edit-button-position='meta']:not([variant='icon-only'])) .edit-button {
283
+ position: static;
284
+ flex-shrink: 0;
285
+ /* in-flow, over the host's own background: currentColor-derived instead
286
+ of the overlay's dark scrim, and always visible (it isn't covering
287
+ anything, so hiding it until hover would just make it hard to find) */
288
+ background: color-mix(in srgb, currentColor 10%, transparent);
289
+ color: inherit;
290
+ opacity: 1;
291
+ }
292
+ :host([edit-button-position='meta']:not([variant='icon-only'])) .edit-button:hover {
293
+ background: color-mix(in srgb, currentColor 20%, transparent);
294
+ }
246
295
 
247
296
  /* list: a compact thumb reads better in a narrow sidebar than the grid's 160x100 */
248
297
  :host([variant='list']) .thumb-wrap {
@@ -475,9 +524,24 @@ export class SnapshotNavList extends LitElement {
475
524
  composed: true,
476
525
  }));
477
526
  }
527
+ renderEditButton(item) {
528
+ const icon = this.editIcon || DEFAULT_EDIT_ICON;
529
+ return html `<button
530
+ type="button"
531
+ class="edit-button"
532
+ part="edit-button"
533
+ aria-label="Edit ${item.label}"
534
+ @click=${(e) => this.edit(e, item)}
535
+ >
536
+ ${isMarkupIcon(icon) ? unsafeHTML(icon) : icon}
537
+ </button>`;
538
+ }
478
539
  render() {
479
540
  const imageOverlayStyle = this.imageOverlayStyle;
480
541
  const metaStyle = this.metaStyle;
542
+ // icon-only's caption is itself an overlay strip on the image, so there's
543
+ // no in-flow text row to put the button in — fall back to the overlay.
544
+ const editInMeta = this.editButtonPosition === 'meta' && this.variant !== 'icon-only';
481
545
  return html `
482
546
  <ul role="listbox">
483
547
  ${this.items.map((item) => html `
@@ -501,20 +565,13 @@ export class SnapshotNavList extends LitElement {
501
565
  >
502
566
  </div>`}
503
567
  <div class="image-overlay" part="overlay" style=${styleMap(imageOverlayStyle)}></div>
504
- ${this.editable
505
- ? html `<button
506
- type="button"
507
- class="edit-button"
508
- part="edit-button"
509
- aria-label="Edit ${item.label}"
510
- @click=${(e) => this.edit(e, item)}
511
- >
512
-
513
- </button>`
514
- : ''}
568
+ ${this.editable && !editInMeta ? this.renderEditButton(item) : ''}
515
569
  </div>
516
570
  <div class="meta" part="meta" style=${styleMap(metaStyle)}>
517
- <span class="label" part="label">${item.label}</span>
571
+ <div class="label-row" part="label-row">
572
+ <span class="label" part="label">${item.label}</span>
573
+ ${this.editable && editInMeta ? this.renderEditButton(item) : ''}
574
+ </div>
518
575
  ${item.description ? html `<span class="description" part="description">${item.description}</span>` : ''}
519
576
  </div>
520
577
  </li>
@@ -550,6 +607,12 @@ __decorate([
550
607
  __decorate([
551
608
  property({ type: Boolean })
552
609
  ], SnapshotNavList.prototype, "editable", void 0);
610
+ __decorate([
611
+ property({ reflect: true, attribute: 'edit-button-position' })
612
+ ], SnapshotNavList.prototype, "editButtonPosition", void 0);
613
+ __decorate([
614
+ property({ attribute: 'edit-icon' })
615
+ ], SnapshotNavList.prototype, "editIcon", void 0);
553
616
  __decorate([
554
617
  state()
555
618
  ], SnapshotNavList.prototype, "thumbs", void 0);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anton-gustafsson/snapshot-core",
3
- "version": "0.0.6",
3
+ "version": "0.0.7",
4
4
  "description": "A pluggable snapshot service that turns any DOM element into a stored, shareable image, plus an optional <snapshot-nav-list> web component to display them.",
5
5
  "license": "MIT",
6
6
  "repository": {