@taprootio/espalier 4.9.0 → 4.10.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.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,23 @@
1
+ # 4.10.0
2
+
3
+ Photo galleries can keep preview rows compact and predictable.
4
+
5
+ - `calculatePhotoLayout()` accepts an optional `maxImagesPerRow` argument, and
6
+ `esp-image-upload` exposes the matching `max-images-per-row` property and
7
+ attribute. Set a positive integer to cap each justified row; leave it unset
8
+ for the existing unlimited layout.
9
+ - `normalizeMaxImagesPerRow(value, fallback, maximum)` is available to apply
10
+ the same bounded positive-integer contract in gallery consumers.
11
+ - Projected lazy images can opt into `defer-offscreen`, which keeps their
12
+ server-rendered `sizes` hint until the image approaches the viewport and
13
+ avoids fetching a larger responsive candidate far below the fold.
14
+
15
+ # 4.9.1
16
+
17
+ Album previews recover from temporary image delivery failures.
18
+
19
+ - Load nearby image previews with bounded concurrency, retry temporary failures, and offer Retry image without re-uploading a photo.
20
+
1
21
  # 4.9.0
2
22
 
3
23
  The date picker reaches distant dates in a few taps.
@@ -879,6 +879,14 @@
879
879
  "module": "./shared/justified-layout.js"
880
880
  }
881
881
  },
882
+ {
883
+ "kind": "js",
884
+ "name": "normalizeMaxImagesPerRow",
885
+ "declaration": {
886
+ "name": "normalizeMaxImagesPerRow",
887
+ "module": "./shared/justified-layout.js"
888
+ }
889
+ },
882
890
  {
883
891
  "kind": "js",
884
892
  "name": "LayoutImage",
@@ -36212,7 +36220,7 @@
36212
36220
  },
36213
36221
  {
36214
36222
  "kind": "class",
36215
- "description": "A responsive image with a native `<picture>` and an optional\nfocal-point-aware banner mode.\n\n`esp-image` accepts two authoring shapes:\n\n1. **Project a light-DOM `<picture>`** — the recommended form for published /\n server-rendered markup. Provide a real `<picture>` (or a bare `<img>`) as\n a child and `esp-image` projects it through a `<slot>`, adding only the\n layout box, border, and lightbox wiring. Because the image lives in the\n served HTML it is discoverable by the preload scanner and visible to\n crawlers, and it is fetched exactly once.\n2. **Declare [esp-image-option](/components/image/image-option) children** —\n the editor / fallback form. `esp-image` builds a shadow `<picture>` and\n `srcset` from them. Use `local-image` when an editor needs a blob preview.\n\nSource precedence is `local-image` → a projected `<picture>` / `<img>` →\n`esp-image-option` children. In every mode the host reserves its layout box\nup front from `original-width` / `original-height`, when supplied.\n\n```html\n<!-- Projected native <picture> — published markup, crawlable + preloadable -->\n<esp-image original-width=\"1200\" original-height=\"800\" caption=\"A scenic mountain lake\">\n <picture>\n <source\n srcset=\"https://picsum.photos/600/400 400w, https://picsum.photos/900/600 800w, https://picsum.photos/1200/800 1200w\"\n sizes=\"100vw\"\n />\n <img src=\"https://picsum.photos/1200/800\" alt=\"A scenic mountain lake\" />\n </picture>\n</esp-image>\n```\n\n```html\n<!-- esp-image-option children — editor / fallback form -->\n<esp-image\n original-width=\"1200\"\n original-height=\"800\"\n low-res=\"https://picsum.photos/300/200\"\n caption=\"A scenic mountain lake\"\n sizes=\"100vw\"\n>\n <esp-image-option width=\"400\" url=\"https://picsum.photos/600/400\"></esp-image-option>\n <esp-image-option width=\"800\" url=\"https://picsum.photos/900/600\"></esp-image-option>\n <esp-image-option width=\"1200\" url=\"https://picsum.photos/1200/800\"></esp-image-option>\n</esp-image>\n```\n\nOnce upgraded, the component measures its own width and updates `sizes` so a\ncard or gallery column does not over-fetch. The `sizes` attribute covers the\nserver-rendered initial fetch and the brief window before measurement:\n\n```html\n<esp-image\n original-width=\"1200\"\n original-height=\"800\"\n low-res=\"https://picsum.photos/300/200\"\n sizes=\"(max-width: 600px) 100vw, 320px\"\n style=\"max-width: 320px;\"\n>\n <esp-image-option width=\"320\" url=\"https://picsum.photos/640/427\"></esp-image-option>\n <esp-image-option width=\"640\" url=\"https://picsum.photos/960/640\"></esp-image-option>\n</esp-image>\n```\n\nBanner mode gives all three source modes the same focal position, scrim,\ntexture, and semantic overlay stack. `ratio` applies at wide widths and\n`compact-ratio` below the component-relative compact threshold.\n\n```html\n<esp-image\n banner\n ratio=\"3/1\"\n compact-ratio=\"3/2\"\n focus=\"0.65 0.4\"\n scrim=\"auto\"\n scrim-strength=\"medium\"\n content-position=\"bottom-start\"\n>\n <img src=\"/assets/focus-picker-unsplash.jpg\" alt=\"A woman standing in a sunlit field with her back to the camera\" />\n <h2 slot=\"overlay\">Grow somewhere wonderful</h2>\n</esp-image>\n```\n\n`banner-scheme` sets the banner's polarity and defaults to the active\nscheme: a light page lightens the image behind dark text, a dark page\ndarkens it behind light text. Fix either polarity explicitly when a banner\nmust not follow the page scheme:\n\n```html\n<esp-image banner ratio=\"3/1\" banner-scheme=\"light\" focus=\"0.65 0.4\">\n <img src=\"/assets/focus-picker-unsplash.jpg\" alt=\"A woman standing in a sunlit field with her back to the camera\" />\n <h2 slot=\"overlay\">Light banner: dark ink on a lifted image</h2>\n</esp-image>\n<esp-image banner ratio=\"3/1\" banner-scheme=\"dark\" focus=\"0.65 0.4\">\n <img src=\"/assets/focus-picker-unsplash.jpg\" alt=\"A woman standing in a sunlit field with her back to the camera\" />\n <h2 slot=\"overlay\">Dark banner: light ink on a deepened image</h2>\n</esp-image>\n```\n\nEvery procedural preset supports `fine`, `medium`, and `coarse` scales.\n\n### Dots\n\nA uniform tiled ink screen (shown at `fine`):\n\n```html\n<esp-image banner ratio=\"3/1\" texture=\"dots\" texture-scale=\"fine\"><img src=\"/assets/canvas-bg.jpg\" alt=\"A colorful floral pattern\" /></esp-image>\n```\n\n### Halftone\n\nThe dot screen ramping toward the content edge (shown at `medium`):\n\n```html\n<esp-image banner ratio=\"3/1\" texture=\"halftone\" texture-scale=\"medium\"><img src=\"/assets/canvas-bg.jpg\" alt=\"A colorful floral pattern\" /></esp-image>\n```\n\n### Paper\n\nSoft fractal-noise mottle, like printed stock (shown at `coarse`):\n\n```html\n<esp-image banner ratio=\"3/1\" texture=\"paper\" texture-scale=\"coarse\"><img src=\"/assets/canvas-bg.jpg\" alt=\"A colorful floral pattern\" /></esp-image>\n```\n\n### Grain\n\nHigh-frequency film grain that also kills banding (shown at `fine`):\n\n```html\n<esp-image banner ratio=\"3/1\" texture=\"grain\" texture-scale=\"fine\"><img src=\"/assets/canvas-bg.jpg\" alt=\"A colorful floral pattern\" /></esp-image>\n```\n\n### Grunge\n\nThresholded turbulence, a distressed ink wash (shown at `medium`):\n\n```html\n<esp-image banner ratio=\"3/1\" texture=\"grunge\" texture-scale=\"medium\"><img src=\"/assets/canvas-bg.jpg\" alt=\"A colorful floral pattern\" /></esp-image>\n```\n\n### Scanlines\n\nHairline rules along the block axis (shown at `coarse`):\n\n```html\n<esp-image banner ratio=\"3/1\" texture=\"scanlines\" texture-scale=\"coarse\"><img src=\"/assets/canvas-bg.jpg\" alt=\"A colorful floral pattern\" /></esp-image>\n```\n\n### Duotone\n\nThe photo mapped onto two theme-derived hues:\n\n```html\n<esp-image banner ratio=\"3/1\" texture=\"duotone\"><img src=\"/assets/canvas-bg.jpg\" alt=\"A colorful floral pattern\" /></esp-image>\n```\n\n### Consumer texture tokens\n\nAny `background-image` value — a tile the application serves, a data URI,\nor a gradient — can be wired directly through the public consumer-image\ntoken path (the optional `@taprootio/espalier/textures/banner-textures.css`\nstylesheet ships `.esp-texture-paper`/`-grain`/`-grunge` classes with the\ntiles inlined as data URIs, so nothing needs asset configuration):\n\n```html\n<esp-image\n banner\n ratio=\"3/1\"\n style=\"--esp-image-texture-image: repeating-linear-gradient(45deg, oklch(0.1 0 0 / 0.2) 0 2px, transparent 2px 7px); --esp-image-texture-blend-mode: multiply;\"\n>\n <img src=\"/assets/canvas-bg.jpg\" alt=\"A colorful floral pattern\" />\n</esp-image>\n```\n\n### Registered textures\n\nWiring those tokens on every banner is verbose and not reusable. The\napplication's texture vocabulary lives on [esp-root](/components/root):\nregister the look once — `registerImageTexture(name, definition)` from\nthe module, or the root's `registerTexture` static in script-only\ncontexts — and any banner under the root selects it by name. Live\nbanners re-render on registration, so markup order and registration\norder never race, and the root's `registeredTextures()` enumerates the\npresets for an editor's texture picker:\n\n```html\n<esp-image id=\"registered-texture-banner\" banner ratio=\"3/1\" texture=\"pressed-paper\">\n <img src=\"/assets/canvas-bg.jpg\" alt=\"A colorful floral pattern\" />\n</esp-image>\n<script>\n const banner = findById(\"registered-texture-banner\");\n const root = banner.closest(\"esp-root\");\n root.constructor.registerTexture(\"pressed-paper\", {\n image:\n \"repeating-linear-gradient(-45deg, oklch(1 0 0 / 0.3) 0 3px, transparent 3px 9px)\",\n blendMode: \"soft-light\",\n opacity: 0.9,\n });\n</script>\n```",
36223
+ "description": "A responsive image with a native `<picture>` and an optional\nfocal-point-aware banner mode.\n\n`esp-image` accepts two authoring shapes:\n\n1. **Project a light-DOM `<picture>`** — the recommended form for published /\n server-rendered markup. Provide a real `<picture>` (or a bare `<img>`) as\n a child and `esp-image` projects it through a `<slot>`, adding only the\n layout box, border, and lightbox wiring. Because the image lives in the\n served HTML it is discoverable by the preload scanner and visible to\n crawlers, and it is fetched exactly once.\n2. **Declare [esp-image-option](/components/image/image-option) children** —\n the editor / fallback form. `esp-image` builds a shadow `<picture>` and\n `srcset` from them. Use `local-image` when an editor needs a blob preview.\n\nSource precedence is `local-image` → a projected `<picture>` / `<img>` →\n`esp-image-option` children. In every mode the host reserves its layout box\nup front from `original-width` / `original-height`, when supplied.\n\n```html\n<!-- Projected native <picture> — published markup, crawlable + preloadable -->\n<esp-image original-width=\"1200\" original-height=\"800\" caption=\"A scenic mountain lake\">\n <picture>\n <source\n srcset=\"https://picsum.photos/600/400 400w, https://picsum.photos/900/600 800w, https://picsum.photos/1200/800 1200w\"\n sizes=\"100vw\"\n />\n <img src=\"https://picsum.photos/1200/800\" alt=\"A scenic mountain lake\" />\n </picture>\n</esp-image>\n```\n\n```html\n<!-- esp-image-option children — editor / fallback form -->\n<esp-image\n original-width=\"1200\"\n original-height=\"800\"\n low-res=\"https://picsum.photos/300/200\"\n caption=\"A scenic mountain lake\"\n sizes=\"100vw\"\n>\n <esp-image-option width=\"400\" url=\"https://picsum.photos/600/400\"></esp-image-option>\n <esp-image-option width=\"800\" url=\"https://picsum.photos/900/600\"></esp-image-option>\n <esp-image-option width=\"1200\" url=\"https://picsum.photos/1200/800\"></esp-image-option>\n</esp-image>\n```\n\nOnce upgraded, the component measures its own width and updates `sizes` so a\ncard or gallery column does not over-fetch. The `sizes` attribute covers the\nserver-rendered initial fetch and the brief window before measurement:\n\n```html\n<esp-image\n original-width=\"1200\"\n original-height=\"800\"\n low-res=\"https://picsum.photos/300/200\"\n sizes=\"(max-width: 600px) 100vw, 320px\"\n style=\"max-width: 320px;\"\n>\n <esp-image-option width=\"320\" url=\"https://picsum.photos/640/427\"></esp-image-option>\n <esp-image-option width=\"640\" url=\"https://picsum.photos/960/640\"></esp-image-option>\n</esp-image>\n```\n\nFor a long projected lazy-image list, `defer-offscreen` keeps the served\n`sizes` hint until an image approaches the viewport. This avoids replacing a\nsmall fallback selection with a larger candidate before the image can be\nseen. Eager projected images and every non-projected source keep the normal\nimmediate measured-size behavior.\n\nBanner mode gives all three source modes the same focal position, scrim,\ntexture, and semantic overlay stack. `ratio` applies at wide widths and\n`compact-ratio` below the component-relative compact threshold.\n\n```html\n<esp-image\n banner\n ratio=\"3/1\"\n compact-ratio=\"3/2\"\n focus=\"0.65 0.4\"\n scrim=\"auto\"\n scrim-strength=\"medium\"\n content-position=\"bottom-start\"\n>\n <img src=\"/assets/focus-picker-unsplash.jpg\" alt=\"A woman standing in a sunlit field with her back to the camera\" />\n <h2 slot=\"overlay\">Grow somewhere wonderful</h2>\n</esp-image>\n```\n\n`banner-scheme` sets the banner's polarity and defaults to the active\nscheme: a light page lightens the image behind dark text, a dark page\ndarkens it behind light text. Fix either polarity explicitly when a banner\nmust not follow the page scheme:\n\n```html\n<esp-image banner ratio=\"3/1\" banner-scheme=\"light\" focus=\"0.65 0.4\">\n <img src=\"/assets/focus-picker-unsplash.jpg\" alt=\"A woman standing in a sunlit field with her back to the camera\" />\n <h2 slot=\"overlay\">Light banner: dark ink on a lifted image</h2>\n</esp-image>\n<esp-image banner ratio=\"3/1\" banner-scheme=\"dark\" focus=\"0.65 0.4\">\n <img src=\"/assets/focus-picker-unsplash.jpg\" alt=\"A woman standing in a sunlit field with her back to the camera\" />\n <h2 slot=\"overlay\">Dark banner: light ink on a deepened image</h2>\n</esp-image>\n```\n\nEvery procedural preset supports `fine`, `medium`, and `coarse` scales.\n\n### Dots\n\nA uniform tiled ink screen (shown at `fine`):\n\n```html\n<esp-image banner ratio=\"3/1\" texture=\"dots\" texture-scale=\"fine\"><img src=\"/assets/canvas-bg.jpg\" alt=\"A colorful floral pattern\" /></esp-image>\n```\n\n### Halftone\n\nThe dot screen ramping toward the content edge (shown at `medium`):\n\n```html\n<esp-image banner ratio=\"3/1\" texture=\"halftone\" texture-scale=\"medium\"><img src=\"/assets/canvas-bg.jpg\" alt=\"A colorful floral pattern\" /></esp-image>\n```\n\n### Paper\n\nSoft fractal-noise mottle, like printed stock (shown at `coarse`):\n\n```html\n<esp-image banner ratio=\"3/1\" texture=\"paper\" texture-scale=\"coarse\"><img src=\"/assets/canvas-bg.jpg\" alt=\"A colorful floral pattern\" /></esp-image>\n```\n\n### Grain\n\nHigh-frequency film grain that also kills banding (shown at `fine`):\n\n```html\n<esp-image banner ratio=\"3/1\" texture=\"grain\" texture-scale=\"fine\"><img src=\"/assets/canvas-bg.jpg\" alt=\"A colorful floral pattern\" /></esp-image>\n```\n\n### Grunge\n\nThresholded turbulence, a distressed ink wash (shown at `medium`):\n\n```html\n<esp-image banner ratio=\"3/1\" texture=\"grunge\" texture-scale=\"medium\"><img src=\"/assets/canvas-bg.jpg\" alt=\"A colorful floral pattern\" /></esp-image>\n```\n\n### Scanlines\n\nHairline rules along the block axis (shown at `coarse`):\n\n```html\n<esp-image banner ratio=\"3/1\" texture=\"scanlines\" texture-scale=\"coarse\"><img src=\"/assets/canvas-bg.jpg\" alt=\"A colorful floral pattern\" /></esp-image>\n```\n\n### Duotone\n\nThe photo mapped onto two theme-derived hues:\n\n```html\n<esp-image banner ratio=\"3/1\" texture=\"duotone\"><img src=\"/assets/canvas-bg.jpg\" alt=\"A colorful floral pattern\" /></esp-image>\n```\n\n### Consumer texture tokens\n\nAny `background-image` value — a tile the application serves, a data URI,\nor a gradient — can be wired directly through the public consumer-image\ntoken path (the optional `@taprootio/espalier/textures/banner-textures.css`\nstylesheet ships `.esp-texture-paper`/`-grain`/`-grunge` classes with the\ntiles inlined as data URIs, so nothing needs asset configuration):\n\n```html\n<esp-image\n banner\n ratio=\"3/1\"\n style=\"--esp-image-texture-image: repeating-linear-gradient(45deg, oklch(0.1 0 0 / 0.2) 0 2px, transparent 2px 7px); --esp-image-texture-blend-mode: multiply;\"\n>\n <img src=\"/assets/canvas-bg.jpg\" alt=\"A colorful floral pattern\" />\n</esp-image>\n```\n\n### Registered textures\n\nWiring those tokens on every banner is verbose and not reusable. The\napplication's texture vocabulary lives on [esp-root](/components/root):\nregister the look once — `registerImageTexture(name, definition)` from\nthe module, or the root's `registerTexture` static in script-only\ncontexts — and any banner under the root selects it by name. Live\nbanners re-render on registration, so markup order and registration\norder never race, and the root's `registeredTextures()` enumerates the\npresets for an editor's texture picker:\n\n```html\n<esp-image id=\"registered-texture-banner\" banner ratio=\"3/1\" texture=\"pressed-paper\">\n <img src=\"/assets/canvas-bg.jpg\" alt=\"A colorful floral pattern\" />\n</esp-image>\n<script>\n const banner = findById(\"registered-texture-banner\");\n const root = banner.closest(\"esp-root\");\n root.constructor.registerTexture(\"pressed-paper\", {\n image:\n \"repeating-linear-gradient(-45deg, oklch(1 0 0 / 0.3) 0 3px, transparent 3px 9px)\",\n blendMode: \"soft-light\",\n opacity: 0.9,\n });\n</script>\n```",
36216
36224
  "name": "EspalierImage",
36217
36225
  "cssProperties": [
36218
36226
  {
@@ -36462,6 +36470,17 @@
36462
36470
  "description": "Native loading strategy for internally rendered images. Projected images\nretain the consumer's own loading attributes.",
36463
36471
  "attribute": "loading"
36464
36472
  },
36473
+ {
36474
+ "kind": "field",
36475
+ "name": "deferOffscreen",
36476
+ "type": {
36477
+ "text": "boolean"
36478
+ },
36479
+ "privacy": "public",
36480
+ "default": "false",
36481
+ "description": "For lazy projected images, defer measured `sizes` upgrades until the image\napproaches the viewport. A 200px prefetch margin keeps the full-size\ncandidate ready before it is visible. Eager projections and internally\nrendered images keep their normal immediate measurements.",
36482
+ "attribute": "defer-offscreen"
36483
+ },
36465
36484
  {
36466
36485
  "kind": "field",
36467
36486
  "name": "banner",
@@ -36589,6 +36608,16 @@
36589
36608
  "privacy": "private",
36590
36609
  "default": "0"
36591
36610
  },
36611
+ {
36612
+ "kind": "field",
36613
+ "name": "measuredWidth",
36614
+ "type": {
36615
+ "text": "number"
36616
+ },
36617
+ "privacy": "private",
36618
+ "default": "0",
36619
+ "description": "The latest ResizeObserver width, including while a lazy projection waits."
36620
+ },
36592
36621
  {
36593
36622
  "kind": "field",
36594
36623
  "name": "compact",
@@ -36616,6 +36645,33 @@
36616
36645
  "privacy": "private",
36617
36646
  "default": "null"
36618
36647
  },
36648
+ {
36649
+ "kind": "field",
36650
+ "name": "offscreenObserver",
36651
+ "type": {
36652
+ "text": "IntersectionObserver | null"
36653
+ },
36654
+ "privacy": "private",
36655
+ "default": "null"
36656
+ },
36657
+ {
36658
+ "kind": "field",
36659
+ "name": "offscreenAdmitted",
36660
+ "type": {
36661
+ "text": "boolean"
36662
+ },
36663
+ "privacy": "private",
36664
+ "default": "false"
36665
+ },
36666
+ {
36667
+ "kind": "field",
36668
+ "name": "deferredProjectedImage",
36669
+ "type": {
36670
+ "text": "HTMLImageElement | null"
36671
+ },
36672
+ "privacy": "private",
36673
+ "default": "null"
36674
+ },
36619
36675
  {
36620
36676
  "kind": "field",
36621
36677
  "name": "unsubscribeTextures",
@@ -36649,6 +36705,71 @@
36649
36705
  }
36650
36706
  }
36651
36707
  },
36708
+ {
36709
+ "kind": "method",
36710
+ "name": "findProjectedImage",
36711
+ "privacy": "private",
36712
+ "return": {
36713
+ "type": {
36714
+ "text": "HTMLImageElement | null"
36715
+ }
36716
+ },
36717
+ "parameters": [
36718
+ {
36719
+ "name": "projected",
36720
+ "default": "this.findProjected()",
36721
+ "type": {
36722
+ "text": "Element | null"
36723
+ }
36724
+ }
36725
+ ]
36726
+ },
36727
+ {
36728
+ "kind": "method",
36729
+ "name": "requiresOffscreenDeferral",
36730
+ "privacy": "private",
36731
+ "return": {
36732
+ "type": {
36733
+ "text": "boolean"
36734
+ }
36735
+ },
36736
+ "parameters": [
36737
+ {
36738
+ "name": "projectedImage",
36739
+ "default": "this.findProjectedImage()"
36740
+ }
36741
+ ]
36742
+ },
36743
+ {
36744
+ "kind": "method",
36745
+ "name": "shouldDeferProjectedSizes",
36746
+ "privacy": "private",
36747
+ "return": {
36748
+ "type": {
36749
+ "text": "boolean"
36750
+ }
36751
+ }
36752
+ },
36753
+ {
36754
+ "kind": "method",
36755
+ "name": "applyMeasuredWidth",
36756
+ "privacy": "private",
36757
+ "return": {
36758
+ "type": {
36759
+ "text": "void"
36760
+ }
36761
+ }
36762
+ },
36763
+ {
36764
+ "kind": "method",
36765
+ "name": "syncOffscreenDeferral",
36766
+ "privacy": "private",
36767
+ "return": {
36768
+ "type": {
36769
+ "text": "void"
36770
+ }
36771
+ }
36772
+ },
36652
36773
  {
36653
36774
  "kind": "method",
36654
36775
  "name": "effectiveSizes",
@@ -37549,6 +37670,15 @@
37549
37670
  "description": "Native loading strategy for internally rendered images. Projected images\nretain the consumer's own loading attributes.",
37550
37671
  "fieldName": "loading"
37551
37672
  },
37673
+ {
37674
+ "name": "defer-offscreen",
37675
+ "type": {
37676
+ "text": "boolean"
37677
+ },
37678
+ "default": "false",
37679
+ "description": "For lazy projected images, defer measured `sizes` upgrades until the image\napproaches the viewport. A 200px prefetch margin keeps the full-size\ncandidate ready before it is visible. Eager projections and internally\nrendered images keep their normal immediate measurements.",
37680
+ "fieldName": "deferOffscreen"
37681
+ },
37552
37682
  {
37553
37683
  "name": "banner",
37554
37684
  "type": {
@@ -38101,6 +38231,306 @@
38101
38231
  "description": "Whether the upload for this image has failed.\nShows an error overlay with a retry button.",
38102
38232
  "attribute": "failed"
38103
38233
  },
38234
+ {
38235
+ "kind": "field",
38236
+ "name": "sourceToken",
38237
+ "type": {
38238
+ "text": "number"
38239
+ },
38240
+ "privacy": "private",
38241
+ "default": "0",
38242
+ "description": "The source token keys a fresh image element for every exact-URL retry."
38243
+ },
38244
+ {
38245
+ "kind": "field",
38246
+ "name": "admittedUrl",
38247
+ "type": {
38248
+ "text": "string"
38249
+ },
38250
+ "privacy": "private",
38251
+ "default": "\"\"",
38252
+ "description": "The remote URL currently assigned to an admitted image element."
38253
+ },
38254
+ {
38255
+ "kind": "field",
38256
+ "name": "remoteImageLoaded",
38257
+ "type": {
38258
+ "text": "boolean"
38259
+ },
38260
+ "privacy": "private",
38261
+ "default": "false",
38262
+ "description": "A successful image stays rendered without holding a shared queue slot."
38263
+ },
38264
+ {
38265
+ "kind": "field",
38266
+ "name": "imageLoadFailed",
38267
+ "type": {
38268
+ "text": "boolean"
38269
+ },
38270
+ "privacy": "private",
38271
+ "default": "false",
38272
+ "description": "True after automatic retries are exhausted, until the user retries the image."
38273
+ },
38274
+ {
38275
+ "kind": "field",
38276
+ "name": "automaticRetryCount",
38277
+ "type": {
38278
+ "text": "number"
38279
+ },
38280
+ "privacy": "private",
38281
+ "default": "0",
38282
+ "description": "Number of automatic retries already scheduled for the current URL."
38283
+ },
38284
+ {
38285
+ "kind": "field",
38286
+ "name": "remotePreviewVisible",
38287
+ "type": {
38288
+ "text": "boolean"
38289
+ },
38290
+ "privacy": "private",
38291
+ "default": "false",
38292
+ "description": "Whether IntersectionObserver currently considers this preview visible."
38293
+ },
38294
+ {
38295
+ "kind": "field",
38296
+ "name": "visibilityObserver",
38297
+ "type": {
38298
+ "text": "IntersectionObserver | null"
38299
+ },
38300
+ "privacy": "private",
38301
+ "default": "null"
38302
+ },
38303
+ {
38304
+ "kind": "field",
38305
+ "name": "queuedLoadCancel",
38306
+ "type": {
38307
+ "text": "(() => void) | null"
38308
+ },
38309
+ "privacy": "private",
38310
+ "default": "null"
38311
+ },
38312
+ {
38313
+ "kind": "field",
38314
+ "name": "loadSlotRelease",
38315
+ "type": {
38316
+ "text": "(() => void) | null"
38317
+ },
38318
+ "privacy": "private",
38319
+ "default": "null"
38320
+ },
38321
+ {
38322
+ "kind": "field",
38323
+ "name": "retryTimer",
38324
+ "type": {
38325
+ "text": "ReturnType<typeof setTimeout> | null"
38326
+ },
38327
+ "privacy": "private",
38328
+ "default": "null"
38329
+ },
38330
+ {
38331
+ "kind": "field",
38332
+ "name": "loadTimeoutTimer",
38333
+ "type": {
38334
+ "text": "ReturnType<typeof setTimeout> | null"
38335
+ },
38336
+ "privacy": "private",
38337
+ "default": "null"
38338
+ },
38339
+ {
38340
+ "kind": "field",
38341
+ "name": "activeSourceToken",
38342
+ "type": {
38343
+ "text": "number | null"
38344
+ },
38345
+ "privacy": "private",
38346
+ "default": "null"
38347
+ },
38348
+ {
38349
+ "kind": "method",
38350
+ "name": "startCurrentPreview",
38351
+ "privacy": "private",
38352
+ "return": {
38353
+ "type": {
38354
+ "text": "void"
38355
+ }
38356
+ }
38357
+ },
38358
+ {
38359
+ "kind": "method",
38360
+ "name": "stopVisibilityObserver",
38361
+ "privacy": "private",
38362
+ "return": {
38363
+ "type": {
38364
+ "text": "void"
38365
+ }
38366
+ }
38367
+ },
38368
+ {
38369
+ "kind": "method",
38370
+ "name": "requestRemoteLoad",
38371
+ "privacy": "private",
38372
+ "return": {
38373
+ "type": {
38374
+ "text": "void"
38375
+ }
38376
+ }
38377
+ },
38378
+ {
38379
+ "kind": "method",
38380
+ "name": "handleImageLoad",
38381
+ "privacy": "private",
38382
+ "return": {
38383
+ "type": {
38384
+ "text": "void"
38385
+ }
38386
+ },
38387
+ "parameters": [
38388
+ {
38389
+ "name": "event",
38390
+ "type": {
38391
+ "text": "Event"
38392
+ }
38393
+ },
38394
+ {
38395
+ "name": "token",
38396
+ "type": {
38397
+ "text": "number"
38398
+ }
38399
+ }
38400
+ ]
38401
+ },
38402
+ {
38403
+ "kind": "method",
38404
+ "name": "handleImageError",
38405
+ "privacy": "private",
38406
+ "return": {
38407
+ "type": {
38408
+ "text": "void"
38409
+ }
38410
+ },
38411
+ "parameters": [
38412
+ {
38413
+ "name": "event",
38414
+ "type": {
38415
+ "text": "Event"
38416
+ }
38417
+ },
38418
+ {
38419
+ "name": "token",
38420
+ "type": {
38421
+ "text": "number"
38422
+ }
38423
+ }
38424
+ ]
38425
+ },
38426
+ {
38427
+ "kind": "method",
38428
+ "name": "isActiveImageEvent",
38429
+ "privacy": "private",
38430
+ "return": {
38431
+ "type": {
38432
+ "text": "boolean"
38433
+ }
38434
+ },
38435
+ "parameters": [
38436
+ {
38437
+ "name": "event",
38438
+ "type": {
38439
+ "text": "Event"
38440
+ }
38441
+ },
38442
+ {
38443
+ "name": "token",
38444
+ "type": {
38445
+ "text": "number"
38446
+ }
38447
+ }
38448
+ ]
38449
+ },
38450
+ {
38451
+ "kind": "method",
38452
+ "name": "handleImageFailure",
38453
+ "privacy": "private",
38454
+ "return": {
38455
+ "type": {
38456
+ "text": "void"
38457
+ }
38458
+ },
38459
+ "parameters": [
38460
+ {
38461
+ "name": "token",
38462
+ "type": {
38463
+ "text": "number"
38464
+ }
38465
+ }
38466
+ ]
38467
+ },
38468
+ {
38469
+ "kind": "method",
38470
+ "name": "completeActiveLoad",
38471
+ "privacy": "private",
38472
+ "return": {
38473
+ "type": {
38474
+ "text": "void"
38475
+ }
38476
+ }
38477
+ },
38478
+ {
38479
+ "kind": "method",
38480
+ "name": "abandonActiveLoad",
38481
+ "privacy": "private",
38482
+ "return": {
38483
+ "type": {
38484
+ "text": "void"
38485
+ }
38486
+ },
38487
+ "description": "Cancel an in-flight browser request before returning its shared slot.\nA settled image never reaches this path, so its successfully loaded source\nremains visible through a disconnect or later update."
38488
+ },
38489
+ {
38490
+ "kind": "method",
38491
+ "name": "releaseActiveLoad",
38492
+ "privacy": "private",
38493
+ "return": {
38494
+ "type": {
38495
+ "text": "void"
38496
+ }
38497
+ }
38498
+ },
38499
+ {
38500
+ "kind": "method",
38501
+ "name": "cancelQueuedLoad",
38502
+ "privacy": "private",
38503
+ "return": {
38504
+ "type": {
38505
+ "text": "void"
38506
+ }
38507
+ }
38508
+ },
38509
+ {
38510
+ "kind": "method",
38511
+ "name": "cancelRemoteWork",
38512
+ "privacy": "private",
38513
+ "return": {
38514
+ "type": {
38515
+ "text": "void"
38516
+ }
38517
+ }
38518
+ },
38519
+ {
38520
+ "kind": "method",
38521
+ "name": "retryImage",
38522
+ "privacy": "private",
38523
+ "return": {
38524
+ "type": {
38525
+ "text": "void"
38526
+ }
38527
+ }
38528
+ },
38529
+ {
38530
+ "kind": "method",
38531
+ "name": "renderImage",
38532
+ "privacy": "private"
38533
+ },
38104
38534
  {
38105
38535
  "kind": "field",
38106
38536
  "name": "seedColorBacker",
@@ -39040,6 +39470,15 @@
39040
39470
  "description": "Comma-separated list of accepted file types, in the same format as the\nnative input `accept` attribute: MIME types (`image/png`), MIME\nwildcards (`image/*`), or extensions (`.png`). Files outside this list\nare skipped and reported via the `esp-image-upload-files-rejected` event.",
39041
39471
  "attribute": "accept"
39042
39472
  },
39473
+ {
39474
+ "kind": "field",
39475
+ "name": "maxImagesPerRow",
39476
+ "type": {
39477
+ "text": "number"
39478
+ },
39479
+ "description": "Maximum number of image previews in each justified row. Positive integers\ncreate a hard row boundary; omitted, non-finite, non-integer, and\nnonpositive values leave rows unlimited.",
39480
+ "attribute": "max-images-per-row"
39481
+ },
39043
39482
  {
39044
39483
  "kind": "field",
39045
39484
  "name": "_rejectionNotice",
@@ -40272,6 +40711,14 @@
40272
40711
  "description": "Comma-separated list of accepted file types, in the same format as the\nnative input `accept` attribute: MIME types (`image/png`), MIME\nwildcards (`image/*`), or extensions (`.png`). Files outside this list\nare skipped and reported via the `esp-image-upload-files-rejected` event.",
40273
40712
  "fieldName": "accept"
40274
40713
  },
40714
+ {
40715
+ "name": "max-images-per-row",
40716
+ "type": {
40717
+ "text": "number"
40718
+ },
40719
+ "description": "Maximum number of image previews in each justified row. Positive integers\ncreate a hard row boundary; omitted, non-finite, non-integer, and\nnonpositive values leave rows unlimited.",
40720
+ "fieldName": "maxImagesPerRow"
40721
+ },
40275
40722
  {
40276
40723
  "name": "scheme",
40277
40724
  "type": {
@@ -40424,6 +40871,28 @@
40424
40871
  }
40425
40872
  ]
40426
40873
  },
40874
+ {
40875
+ "kind": "javascript-module",
40876
+ "path": "dist/image-upload/preview-load-queue.js",
40877
+ "declarations": [
40878
+ {
40879
+ "kind": "variable",
40880
+ "name": "previewLoadQueue",
40881
+ "default": "new PreviewLoadQueue(4)",
40882
+ "description": "Four remote downloads is enough to fill a row without overwhelming an image service."
40883
+ }
40884
+ ],
40885
+ "exports": [
40886
+ {
40887
+ "kind": "js",
40888
+ "name": "previewLoadQueue",
40889
+ "declaration": {
40890
+ "name": "previewLoadQueue",
40891
+ "module": "dist/image-upload/preview-load-queue.js"
40892
+ }
40893
+ }
40894
+ ]
40895
+ },
40427
40896
  {
40428
40897
  "kind": "javascript-module",
40429
40898
  "path": "dist/info/esp-info.js",
@@ -74652,6 +75121,41 @@
74652
75121
  "kind": "javascript-module",
74653
75122
  "path": "dist/shared/justified-layout.js",
74654
75123
  "declarations": [
75124
+ {
75125
+ "kind": "function",
75126
+ "name": "normalizeMaxImagesPerRow",
75127
+ "return": {
75128
+ "type": {
75129
+ "text": "number"
75130
+ }
75131
+ },
75132
+ "parameters": [
75133
+ {
75134
+ "name": "value",
75135
+ "type": {
75136
+ "text": "unknown"
75137
+ },
75138
+ "description": "Consumer-provided row ceiling to validate"
75139
+ },
75140
+ {
75141
+ "name": "fallback",
75142
+ "default": "Number.POSITIVE_INFINITY",
75143
+ "type": {
75144
+ "text": "number"
75145
+ },
75146
+ "description": "Value returned when `value` is invalid"
75147
+ },
75148
+ {
75149
+ "name": "maximum",
75150
+ "default": "Number.POSITIVE_INFINITY",
75151
+ "type": {
75152
+ "text": "number"
75153
+ },
75154
+ "description": "Largest accepted row ceiling"
75155
+ }
75156
+ ],
75157
+ "description": "Resolve a consumer-provided image-row ceiling to a usable positive integer.\nValues outside `1…maximum`, including non-numbers and non-finite values,\nreturn `fallback`. The default fallback and maximum leave row lengths\nunlimited."
75158
+ },
74655
75159
  {
74656
75160
  "kind": "function",
74657
75161
  "name": "calculatePhotoLayout",
@@ -74698,12 +75202,28 @@
74698
75202
  "text": "number"
74699
75203
  },
74700
75204
  "description": "Ceiling for row heights — keeps a stretched final row\n(or a single image) from ballooning past e.g. half the viewport height"
75205
+ },
75206
+ {
75207
+ "name": "maxImagesPerRow",
75208
+ "default": "Number.POSITIVE_INFINITY",
75209
+ "type": {
75210
+ "text": "number"
75211
+ },
75212
+ "description": "Optional positive-integer ceiling for the number of\nimages in each row. Omit it, or pass an invalid value, for an unlimited row\nlength."
74701
75213
  }
74702
75214
  ],
74703
75215
  "description": "Compute justified layout rows for a list of images.\n\nAlgorithm:\n1. Add images to a row one at a time, scaled to the target row height\n2. When the combined width overflows the container, the row breaks either\n before or after the overflowing image — whichever leaves the row height\n closer to the target. Keeping the image shrinks the row slightly below\n target; pushing it to the next row stretches the row above target.\n3. Scale the finished row so it fills the container width exactly.\n\nEvery row — including the last — is scaled to fill `containerWidth`\nexactly, except rows clamped by `maxRowHeight`: those keep their aspect\nratios at the clamped height and no longer span the full width. Items are\nreturned by reference in their original order."
74704
75216
  }
74705
75217
  ],
74706
75218
  "exports": [
75219
+ {
75220
+ "kind": "js",
75221
+ "name": "normalizeMaxImagesPerRow",
75222
+ "declaration": {
75223
+ "name": "normalizeMaxImagesPerRow",
75224
+ "module": "dist/shared/justified-layout.js"
75225
+ }
75226
+ },
74707
75227
  {
74708
75228
  "kind": "js",
74709
75229
  "name": "calculatePhotoLayout",
@@ -75,6 +75,12 @@ export declare function parseImageRatio(value: string): string | null;
75
75
  * </esp-image>
76
76
  * ```
77
77
  *
78
+ * For a long projected lazy-image list, `defer-offscreen` keeps the served
79
+ * `sizes` hint until an image approaches the viewport. This avoids replacing a
80
+ * small fallback selection with a larger candidate before the image can be
81
+ * seen. Eager projected images and every non-projected source keep the normal
82
+ * immediate measured-size behavior.
83
+ *
78
84
  * Banner mode gives all three source modes the same focal position, scrim,
79
85
  * texture, and semantic overlay stack. `ratio` applies at wide widths and
80
86
  * `compact-ratio` below the component-relative compact threshold.
@@ -284,6 +290,13 @@ export declare class EspalierImage extends EspalierElementBase {
284
290
  * retain the consumer's own loading attributes.
285
291
  */
286
292
  loading: "eager" | "lazy";
293
+ /**
294
+ * For lazy projected images, defer measured `sizes` upgrades until the image
295
+ * approaches the viewport. A 200px prefetch margin keeps the full-size
296
+ * candidate ready before it is visible. Eager projections and internally
297
+ * rendered images keep their normal immediate measurements.
298
+ */
299
+ deferOffscreen: boolean;
287
300
  /** Enable borderless banner defaults and the decorative/content layer stack. */
288
301
  banner: boolean;
289
302
  /**
@@ -1,21 +1,21 @@
1
- var n=function(p,e,t,r){var i=arguments.length,o=i<3?e:r===null?r=Object.getOwnPropertyDescriptor(e,t):r,s;if(typeof Reflect=="object"&&typeof Reflect.decorate=="function")o=Reflect.decorate(p,e,t,r);else for(var c=p.length-1;c>=0;c--)(s=p[c])&&(o=(i<3?s(o):i>3?s(e,t,o):s(e,t))||o);return i>3&&o&&Object.defineProperty(e,t,o),o},m;import{css as T,html as d,nothing as N}from"lit";import{customElement as P,property as l,state as u}from"lit/decorators.js";import{EspalierElementBase as y}from"../shared/esp-element-base.js";import{slotHasContent as f}from"../shared/slot-content.js";import{renderVisualOverlay as R,visualOverlayStyles as I}from"../shared/visual-overlay.js";import{focusConverter as E,normalizeFocus as O}from"./image-focus.js";import{ensureImageTextureFilters as A,imageTextureFilterId as W}from"./image-texture-filters.js";import{getImageTexture as b,isBuiltInImageTexture as x,subscribeToImageTextureRegistry as H}from"../shared/image-texture-registry.js";import{validImageDimensions as _}from"./image-dimensions.js";import"./esp-image-option.js";const k=new Set(["auto","none","flat","top","bottom","left","right","radial"]),S=new Set(["soft","medium","strong"]),w=new Set(["fine","medium","coarse"]),z=new Set(["auto","light","dark"]),j=new Set(["bottom-start","bottom","bottom-end","center","top-start","top","top-end"]);function h(p,e){return{fromAttribute(t){return t&&p.has(t)?t:e},toAttribute(t){return p.has(t)?t:e}}}function C(p){const e=p.trim();if(!e)return null;const t=e.split("/").map(i=>i.trim());if(t.length>2||t.some(i=>i.length===0))return null;const r=t.map(Number);return r.some(i=>!Number.isFinite(i)||i<=0)?null:r.length===1?String(r[0]):`${r[0]} / ${r[1]}`}let a=m=class extends y{constructor(){super(...arguments),this.originalHeight=0,this.originalWidth=0,this.imageUrl="",this.localImage="",this.caption="",this.sizes="100vw",this.loading="eager",this.banner=!1,this.bannerScheme="auto",this.scrim="auto",this.scrimStrength="medium",this.texture="none",this.textureScale="medium",this.contentPosition="bottom-start",this.focusPoint={x:.5,y:.5},this.ratio="",this.compactRatio="",this.renderedWidth=0,this.compact=!1,this.overlayHasContent=!1,this.resizeObserver=null,this.unsubscribeTextures=null,this.handleSlotChange=e=>{if(e.target instanceof HTMLSlotElement){if(e.target.name==="overlay"){const t=f(e.target);t!==this.overlayHasContent&&(this.overlayHasContent=t);return}this.requestUpdate()}}}get isPortrait(){const e=_(this.originalWidth,this.originalHeight);return e!==null&&e.height>e.width}findProjected(){for(const e of this.children)if(e.tagName==="PICTURE"||e.tagName==="IMG")return e;return null}connectedCallback(){super.connectedCallback(),m.ensureProjectionStyles(this.getRootNode()),this.unsubscribeTextures??=H(()=>this.requestUpdate()),this.resizeObserver??=new ResizeObserver(e=>{const t=e[0]?.contentBoxSize?.[0],r=Math.round(t?t.inlineSize:e[0]?.contentRect.width??0);r>0&&r!==this.renderedWidth&&(this.renderedWidth=r)}),this.resizeObserver.observe(this)}disconnectedCallback(){super.disconnectedCallback(),this.unsubscribeTextures?.(),this.unsubscribeTextures=null,this.resizeObserver?.disconnect(),this.resizeObserver=null}effectiveSizes(){return this.renderedWidth>0?`${this.renderedWidth}px`:this.sizes}buildSources(){const e=new Map;for(const t of this.querySelectorAll("esp-image-option")){const r=t.getAttribute("url"),i=Number(t.getAttribute("width"));if(!r||!Number.isFinite(i)||i<=0)continue;const o=t.getAttribute("type")??"",s=e.get(o)??[];s.push({url:r,width:i}),e.set(o,s)}return Array.from(e,([t,r])=>({type:t,srcset:r.sort((i,o)=>i.width-o.width).map(i=>`${i.url} ${i.width}w`).join(", ")})).filter(t=>t.srcset.length>0)}resolvedBannerScheme(){return this.bannerScheme!=="auto"?this.bannerScheme:this.scheme==="dark"?"dark":"light"}resolvedTexture(){return x(this.texture)?this.texture:b(this.texture)?this.texture:"none"}resolvedScrim(){return this.scrim!=="auto"?this.scrim:this.overlayHasContent?this.contentPosition.startsWith("top")?"top":this.contentPosition.startsWith("bottom")?"bottom":"radial":"none"}resolvedAspectRatio(){const e=_(this.originalWidth,this.originalHeight);if(!this.banner)return e?`${e.width} / ${e.height}`:null;const t=C(this.ratio);return(this.compact?C(this.compactRatio):null)??t??(e?`${e.width} / ${e.height}`:null)}normalizeEnums(e){e.has("scrim")&&!k.has(this.scrim)&&(this.scrim="auto"),e.has("bannerScheme")&&!z.has(this.bannerScheme)&&(this.bannerScheme="auto"),e.has("scrimStrength")&&!S.has(this.scrimStrength)&&(this.scrimStrength="medium"),e.has("textureScale")&&!w.has(this.textureScale)&&(this.textureScale="medium"),e.has("contentPosition")&&!j.has(this.contentPosition)&&(this.contentPosition="bottom-start")}willUpdate(e){super.willUpdate(e),this.normalizeEnums(e);const t=O(this.focusPoint),r=Number((t.x*100).toFixed(4)),i=Number((t.y*100).toFixed(4));this.style.setProperty("--_esp-image-object-position",`${r}% ${i}%`);const o=this.resolvedAspectRatio();o?this.style.setProperty("--_esp-image-aspect-ratio",o):this.style.removeProperty("--_esp-image-aspect-ratio");const s=this.resolvedTexture();if(["paper","grain","grunge"].includes(s)){const g=s==="grunge"&&this.resolvedBannerScheme()==="light"?"grunge-light":s;this.style.setProperty("--_esp-image-texture-filter",`url(#${W(g,this.textureScale)})`)}else this.style.removeProperty("--_esp-image-texture-filter");const c=x(s)?void 0:b(s),$=[["--_esp-image-registered-image",c?.image],["--_esp-image-registered-size",c?.size],["--_esp-image-registered-repeat",c?.repeat],["--_esp-image-registered-position",c?.position],["--_esp-image-registered-opacity",c?.opacity?.toString()],["--_esp-image-registered-blend-mode",c?.blendMode]];for(const[g,v]of $)v===void 0?this.style.removeProperty(g):this.style.setProperty(g,v)}firstUpdated(e){super.firstUpdated(e);const t=this.shadowRoot?.querySelector('slot[name="overlay"]');t&&(this.overlayHasContent=f(t))}updated(e){if(super.updated(e),["paper","grain","grunge"].includes(this.resolvedTexture())&&this.renderRoot&&A(this.renderRoot),this.renderedWidth>0){const s=this.shadowRoot?.querySelector(".compact-sentinel")?.getBoundingClientRect().width??0,c=s>0&&this.renderedWidth<s;c!==this.compact&&(this.compact=c)}if(this.renderedWidth<=0)return;const t=this.findProjected();if(!t)return;const r=this.effectiveSizes();if(t.tagName==="PICTURE")for(const o of t.querySelectorAll("source"))o.setAttribute("sizes",r);const i=t.tagName==="IMG"?t:t.querySelector("img");i?.hasAttribute("srcset")&&i.setAttribute("sizes",r)}renderMedia(){if(this.localImage)return d`<img
1
+ var n=function(d,e,t,r){var i=arguments.length,s=i<3?e:r===null?r=Object.getOwnPropertyDescriptor(e,t):r,o;if(typeof Reflect=="object"&&typeof Reflect.decorate=="function")s=Reflect.decorate(d,e,t,r);else for(var l=d.length-1;l>=0;l--)(o=d[l])&&(s=(i<3?o(s):i>3?o(e,t,s):o(e,t))||s);return i>3&&s&&Object.defineProperty(e,t,s),s},p;import{css as I,html as m,nothing as C}from"lit";import{customElement as $,property as c,state as g}from"lit/decorators.js";import{EspalierElementBase as v}from"../shared/esp-element-base.js";import{slotHasContent as y}from"../shared/slot-content.js";import{renderVisualOverlay as T,visualOverlayStyles as A}from"../shared/visual-overlay.js";import{focusConverter as N,normalizeFocus as R}from"./image-focus.js";import{ensureImageTextureFilters as W,imageTextureFilterId as E}from"./image-texture-filters.js";import{getImageTexture as b,isBuiltInImageTexture as x,subscribeToImageTextureRegistry as M}from"../shared/image-texture-registry.js";import{validImageDimensions as _}from"./image-dimensions.js";import"./esp-image-option.js";const k=new Set(["auto","none","flat","top","bottom","left","right","radial"]),S=new Set(["soft","medium","strong"]),w=new Set(["fine","medium","coarse"]),j=new Set(["auto","light","dark"]),O=new Set(["bottom-start","bottom","bottom-end","center","top-start","top","top-end"]);function h(d,e){return{fromAttribute(t){return t&&d.has(t)?t:e},toAttribute(t){return d.has(t)?t:e}}}function z(d){const e=d.trim();if(!e)return null;const t=e.split("/").map(i=>i.trim());if(t.length>2||t.some(i=>i.length===0))return null;const r=t.map(Number);return r.some(i=>!Number.isFinite(i)||i<=0)?null:r.length===1?String(r[0]):`${r[0]} / ${r[1]}`}let a=p=class extends v{constructor(){super(...arguments),this.originalHeight=0,this.originalWidth=0,this.imageUrl="",this.localImage="",this.caption="",this.sizes="100vw",this.loading="eager",this.deferOffscreen=!1,this.banner=!1,this.bannerScheme="auto",this.scrim="auto",this.scrimStrength="medium",this.texture="none",this.textureScale="medium",this.contentPosition="bottom-start",this.focusPoint={x:.5,y:.5},this.ratio="",this.compactRatio="",this.renderedWidth=0,this.measuredWidth=0,this.compact=!1,this.overlayHasContent=!1,this.resizeObserver=null,this.offscreenObserver=null,this.offscreenAdmitted=!1,this.deferredProjectedImage=null,this.unsubscribeTextures=null,this.handleSlotChange=e=>{if(e.target instanceof HTMLSlotElement){if(e.target.name==="overlay"){const t=y(e.target);t!==this.overlayHasContent&&(this.overlayHasContent=t);return}this.requestUpdate()}}}get isPortrait(){const e=_(this.originalWidth,this.originalHeight);return e!==null&&e.height>e.width}findProjected(){for(const e of this.children)if(e.tagName==="PICTURE"||e.tagName==="IMG")return e;return null}findProjectedImage(e=this.findProjected()){return e?e.tagName==="IMG"?e:e.querySelector("img"):null}requiresOffscreenDeferral(e=this.findProjectedImage()){return this.deferOffscreen&&e?.getAttribute("loading")?.toLowerCase()==="lazy"}shouldDeferProjectedSizes(){return this.requiresOffscreenDeferral()&&!this.offscreenAdmitted}applyMeasuredWidth(){this.measuredWidth>0&&this.measuredWidth!==this.renderedWidth&&(this.renderedWidth=this.measuredWidth)}syncOffscreenDeferral(){const e=this.findProjectedImage();if(e!==this.deferredProjectedImage&&(this.offscreenAdmitted=!1,this.offscreenObserver?.disconnect(),this.offscreenObserver=null,this.deferredProjectedImage=e),!this.requiresOffscreenDeferral(e)){this.offscreenObserver?.disconnect(),this.offscreenObserver=null,this.offscreenAdmitted=!1,this.deferredProjectedImage=null,this.applyMeasuredWidth();return}if(!this.offscreenAdmitted){if(typeof IntersectionObserver>"u"){this.offscreenAdmitted=!0,this.applyMeasuredWidth();return}if(!this.offscreenObserver){const t=e,r=new IntersectionObserver(i=>{if(this.offscreenObserver===r){if(this.findProjectedImage()!==t){this.syncOffscreenDeferral();return}i.some(s=>s.isIntersecting)&&(this.offscreenAdmitted=!0,this.offscreenObserver?.disconnect(),this.offscreenObserver=null,this.applyMeasuredWidth(),this.requestUpdate())}},{rootMargin:"200px"});this.offscreenObserver=r}this.offscreenObserver.observe(this)}}connectedCallback(){super.connectedCallback(),p.ensureProjectionStyles(this.getRootNode()),this.unsubscribeTextures??=M(()=>this.requestUpdate()),this.resizeObserver??=new ResizeObserver(e=>{const t=e[0]?.contentBoxSize?.[0],r=Math.round(t?t.inlineSize:e[0]?.contentRect.width??0);r<=0||(this.measuredWidth=r,this.syncOffscreenDeferral(),this.applyMeasuredWidth())}),this.resizeObserver.observe(this),this.syncOffscreenDeferral()}disconnectedCallback(){super.disconnectedCallback(),this.unsubscribeTextures?.(),this.unsubscribeTextures=null,this.resizeObserver?.disconnect(),this.resizeObserver=null,this.offscreenObserver?.disconnect(),this.offscreenObserver=null,this.offscreenAdmitted=!1,this.deferredProjectedImage=null}effectiveSizes(){return this.renderedWidth>0?`${this.renderedWidth}px`:this.sizes}buildSources(){const e=new Map;for(const t of this.querySelectorAll("esp-image-option")){const r=t.getAttribute("url"),i=Number(t.getAttribute("width"));if(!r||!Number.isFinite(i)||i<=0)continue;const s=t.getAttribute("type")??"",o=e.get(s)??[];o.push({url:r,width:i}),e.set(s,o)}return Array.from(e,([t,r])=>({type:t,srcset:r.sort((i,s)=>i.width-s.width).map(i=>`${i.url} ${i.width}w`).join(", ")})).filter(t=>t.srcset.length>0)}resolvedBannerScheme(){return this.bannerScheme!=="auto"?this.bannerScheme:this.scheme==="dark"?"dark":"light"}resolvedTexture(){return x(this.texture)?this.texture:b(this.texture)?this.texture:"none"}resolvedScrim(){return this.scrim!=="auto"?this.scrim:this.overlayHasContent?this.contentPosition.startsWith("top")?"top":this.contentPosition.startsWith("bottom")?"bottom":"radial":"none"}resolvedAspectRatio(){const e=_(this.originalWidth,this.originalHeight);if(!this.banner)return e?`${e.width} / ${e.height}`:null;const t=z(this.ratio);return(this.compact?z(this.compactRatio):null)??t??(e?`${e.width} / ${e.height}`:null)}normalizeEnums(e){e.has("scrim")&&!k.has(this.scrim)&&(this.scrim="auto"),e.has("bannerScheme")&&!j.has(this.bannerScheme)&&(this.bannerScheme="auto"),e.has("scrimStrength")&&!S.has(this.scrimStrength)&&(this.scrimStrength="medium"),e.has("textureScale")&&!w.has(this.textureScale)&&(this.textureScale="medium"),e.has("contentPosition")&&!O.has(this.contentPosition)&&(this.contentPosition="bottom-start")}willUpdate(e){super.willUpdate(e),this.normalizeEnums(e);const t=R(this.focusPoint),r=Number((t.x*100).toFixed(4)),i=Number((t.y*100).toFixed(4));this.style.setProperty("--_esp-image-object-position",`${r}% ${i}%`);const s=this.resolvedAspectRatio();s?this.style.setProperty("--_esp-image-aspect-ratio",s):this.style.removeProperty("--_esp-image-aspect-ratio");const o=this.resolvedTexture();if(["paper","grain","grunge"].includes(o)){const u=o==="grunge"&&this.resolvedBannerScheme()==="light"?"grunge-light":o;this.style.setProperty("--_esp-image-texture-filter",`url(#${E(u,this.textureScale)})`)}else this.style.removeProperty("--_esp-image-texture-filter");const l=x(o)?void 0:b(o),P=[["--_esp-image-registered-image",l?.image],["--_esp-image-registered-size",l?.size],["--_esp-image-registered-repeat",l?.repeat],["--_esp-image-registered-position",l?.position],["--_esp-image-registered-opacity",l?.opacity?.toString()],["--_esp-image-registered-blend-mode",l?.blendMode]];for(const[u,f]of P)f===void 0?this.style.removeProperty(u):this.style.setProperty(u,f)}firstUpdated(e){super.firstUpdated(e);const t=this.shadowRoot?.querySelector('slot[name="overlay"]');t&&(this.overlayHasContent=y(t))}updated(e){if(super.updated(e),this.syncOffscreenDeferral(),["paper","grain","grunge"].includes(this.resolvedTexture())&&this.renderRoot&&W(this.renderRoot),this.renderedWidth>0){const o=this.shadowRoot?.querySelector(".compact-sentinel")?.getBoundingClientRect().width??0,l=o>0&&this.renderedWidth<o;l!==this.compact&&(this.compact=l)}if(this.renderedWidth<=0||this.shouldDeferProjectedSizes())return;const t=this.findProjected();if(!t)return;const r=this.effectiveSizes();if(t.tagName==="PICTURE")for(const s of t.querySelectorAll("source"))s.setAttribute("sizes",r);const i=this.findProjectedImage(t);i?.hasAttribute("srcset")&&i.setAttribute("sizes",r)}renderMedia(){if(this.localImage)return m`<img
2
2
  src=${this.localImage}
3
3
  alt=${this.caption}
4
4
  decoding="async"
5
5
  loading=${this.loading}
6
- />`;const e=d`<slot @slotchange=${this.handleSlotChange}></slot>`;if(this.findProjected())return e;const t=this.buildSources();return d`
6
+ />`;const e=m`<slot @slotchange=${this.handleSlotChange}></slot>`;if(this.findProjected())return e;const t=this.buildSources();return m`
7
7
  <picture>
8
- ${t.map(r=>d`
8
+ ${t.map(r=>m`
9
9
  <source
10
10
  srcset=${r.srcset}
11
11
  sizes=${this.effectiveSizes()}
12
- type=${r.type||N}
12
+ type=${r.type||C}
13
13
  />
14
14
  `)}
15
15
  <img src=${this.imageUrl} alt=${this.caption} decoding="async" loading=${this.loading} />
16
16
  </picture>
17
17
  ${e}
18
- `}render(){return d`
18
+ `}render(){return m`
19
19
  <div
20
20
  part="frame"
21
21
  class="frame"
@@ -27,13 +27,13 @@ var n=function(p,e,t,r){var i=arguments.length,o=i<3?e:r===null?r=Object.getOwnP
27
27
  data-content-position=${this.contentPosition}
28
28
  >
29
29
  <div part="image" class="image">${this.renderMedia()}</div>
30
- ${R({className:"banner-layers",imagePart:"texture",scrimPart:"scrim",texturePart:"texture"})}
30
+ ${T({className:"banner-layers",imagePart:"texture",scrimPart:"scrim",texturePart:"texture"})}
31
31
  <div part="overlay" class="overlay" ?hidden=${!this.banner||!this.overlayHasContent}>
32
32
  <slot name="overlay" @slotchange=${this.handleSlotChange}></slot>
33
33
  </div>
34
34
  <span class="compact-sentinel" aria-hidden="true"></span>
35
35
  </div>
36
- `}static ensureProjectionStyles(e){const t=e.nodeType===Node.DOCUMENT_FRAGMENT_NODE&&"host"in e?e:e.nodeType===Node.DOCUMENT_NODE?e.head:e.ownerDocument?.head??document.head,r=Array.from(t.querySelectorAll("style[data-esp-image-projection-styles]")),i=r.find(s=>s.textContent===m.projectionStyles);if(i){for(const s of r)s!==i&&s.remove();return}for(const s of r)s.remove();const o=t.ownerDocument.createElement("style");o.dataset.espImageProjectionStyles="",o.textContent=m.projectionStyles,t.append(o)}};a.projectionStyles=`
36
+ `}static ensureProjectionStyles(e){const t=e.nodeType===Node.DOCUMENT_FRAGMENT_NODE&&"host"in e?e:e.nodeType===Node.DOCUMENT_NODE?e.head:e.ownerDocument?.head??document.head,r=Array.from(t.querySelectorAll("style[data-esp-image-projection-styles]")),i=r.find(o=>o.textContent===p.projectionStyles);if(i){for(const o of r)o!==i&&o.remove();return}for(const o of r)o.remove();const s=t.ownerDocument.createElement("style");s.dataset.espImageProjectionStyles="",s.textContent=p.projectionStyles,t.append(s)}};a.projectionStyles=`
37
37
  esp-image > picture,
38
38
  esp-image > img {
39
39
  display: block;
@@ -49,7 +49,7 @@ var n=function(p,e,t,r){var i=arguments.length,o=i<3?e:r===null?r=Object.getOwnP
49
49
  object-fit: var(--_esp-image-object-fit, cover);
50
50
  object-position: var(--esp-image-object-position, var(--_esp-image-object-position, center));
51
51
  }
52
- `,a.styles=[...y.styles,T`
52
+ `,a.styles=[...v.styles,I`
53
53
  :host {
54
54
  display: block;
55
55
  aspect-ratio: var(--_esp-image-aspect-ratio, auto);
@@ -439,4 +439,4 @@ var n=function(p,e,t,r){var i=arguments.length,o=i<3?e:r===null?r=Object.getOwnP
439
439
  transition: none !important;
440
440
  }
441
441
  }
442
- `,I],n([l({attribute:"original-height",type:Number})],a.prototype,"originalHeight",void 0),n([l({attribute:"original-width",type:Number})],a.prototype,"originalWidth",void 0),n([l({attribute:"low-res",type:String})],a.prototype,"imageUrl",void 0),n([l({attribute:"local-image",type:String})],a.prototype,"localImage",void 0),n([l({type:String})],a.prototype,"caption",void 0),n([l({attribute:"sizes",type:String})],a.prototype,"sizes",void 0),n([l({attribute:"loading",type:String})],a.prototype,"loading",void 0),n([l({type:Boolean,reflect:!0})],a.prototype,"banner",void 0),n([l({attribute:"banner-scheme",converter:h(z,"auto"),reflect:!0})],a.prototype,"bannerScheme",void 0),n([l({converter:h(k,"auto"),reflect:!0})],a.prototype,"scrim",void 0),n([l({attribute:"scrim-strength",converter:h(S,"medium"),reflect:!0})],a.prototype,"scrimStrength",void 0),n([l({type:String,reflect:!0})],a.prototype,"texture",void 0),n([l({attribute:"texture-scale",converter:h(w,"medium"),reflect:!0})],a.prototype,"textureScale",void 0),n([l({attribute:"content-position",converter:h(j,"bottom-start"),reflect:!0})],a.prototype,"contentPosition",void 0),n([l({attribute:"focus",converter:E,reflect:!0})],a.prototype,"focusPoint",void 0),n([l({type:String})],a.prototype,"ratio",void 0),n([l({attribute:"compact-ratio",type:String})],a.prototype,"compactRatio",void 0),n([u()],a.prototype,"renderedWidth",void 0),n([u()],a.prototype,"compact",void 0),n([u()],a.prototype,"overlayHasContent",void 0),a=m=n([P("esp-image")],a);export{a as EspalierImage,C as parseImageRatio};
442
+ `,A],n([c({attribute:"original-height",type:Number})],a.prototype,"originalHeight",void 0),n([c({attribute:"original-width",type:Number})],a.prototype,"originalWidth",void 0),n([c({attribute:"low-res",type:String})],a.prototype,"imageUrl",void 0),n([c({attribute:"local-image",type:String})],a.prototype,"localImage",void 0),n([c({type:String})],a.prototype,"caption",void 0),n([c({attribute:"sizes",type:String})],a.prototype,"sizes",void 0),n([c({attribute:"loading",type:String})],a.prototype,"loading",void 0),n([c({attribute:"defer-offscreen",type:Boolean})],a.prototype,"deferOffscreen",void 0),n([c({type:Boolean,reflect:!0})],a.prototype,"banner",void 0),n([c({attribute:"banner-scheme",converter:h(j,"auto"),reflect:!0})],a.prototype,"bannerScheme",void 0),n([c({converter:h(k,"auto"),reflect:!0})],a.prototype,"scrim",void 0),n([c({attribute:"scrim-strength",converter:h(S,"medium"),reflect:!0})],a.prototype,"scrimStrength",void 0),n([c({type:String,reflect:!0})],a.prototype,"texture",void 0),n([c({attribute:"texture-scale",converter:h(w,"medium"),reflect:!0})],a.prototype,"textureScale",void 0),n([c({attribute:"content-position",converter:h(O,"bottom-start"),reflect:!0})],a.prototype,"contentPosition",void 0),n([c({attribute:"focus",converter:N,reflect:!0})],a.prototype,"focusPoint",void 0),n([c({type:String})],a.prototype,"ratio",void 0),n([c({attribute:"compact-ratio",type:String})],a.prototype,"compactRatio",void 0),n([g()],a.prototype,"renderedWidth",void 0),n([g()],a.prototype,"compact",void 0),n([g()],a.prototype,"overlayHasContent",void 0),a=p=n([$("esp-image")],a);export{a as EspalierImage,z as parseImageRatio};
@@ -1,10 +1,18 @@
1
- var i=function(l,e,s,t){var a=arguments.length,r=a<3?e:t===null?t=Object.getOwnPropertyDescriptor(e,s):t,p;if(typeof Reflect=="object"&&typeof Reflect.decorate=="function")r=Reflect.decorate(l,e,s,t);else for(var d=l.length-1;d>=0;d--)(p=l[d])&&(r=(a<3?p(r):a>3?p(e,s,r):p(e,s))||r);return a>3&&r&&Object.defineProperty(e,s,r),r};import{css as g,html as c,nothing as v}from"lit";import{customElement as u,property as n}from"lit/decorators.js";import{classMap as b}from"lit/directives/class-map.js";import{createRef as m,ref as f}from"lit/directives/ref.js";import"../button/esp-button.js";import"../progress/esp-progress.js";import{EspalierElementBase as h}from"../shared/esp-element-base.js";const w="esp-internal-image-preview-remove",y="esp-internal-image-preview-retry";let o=class extends h{constructor(){super(...arguments),this.image=m(),this.url="",this.alt="",this.failed=!1}render(){const e=this.progress!==void 0,s=this.failed&&!e,t=e?this.alt?`Cancel upload and remove ${this.alt}`:"Cancel upload and remove image":this.alt?`Remove ${this.alt}`:"Remove image";return c`<div>
2
- <img ${f(this.image)} src=${this.url} alt=${this.alt} draggable="false" decoding="async" />
1
+ var n=function(a,e,t,r){var i=arguments.length,o=i<3?e:r===null?r=Object.getOwnPropertyDescriptor(e,t):r,h;if(typeof Reflect=="object"&&typeof Reflect.decorate=="function")o=Reflect.decorate(a,e,t,r);else for(var p=a.length-1;p>=0;p--)(h=a[p])&&(o=(i<3?h(o):i>3?h(e,t,o):h(e,t))||o);return i>3&&o&&Object.defineProperty(e,t,o),o};import{css as v,html as d,nothing as c}from"lit";import{customElement as g,property as u}from"lit/decorators.js";import{classMap as b}from"lit/directives/class-map.js";import{keyed as f}from"lit/directives/keyed.js";import{createRef as y,ref as w}from"lit/directives/ref.js";import"../button/esp-button.js";import"../progress/esp-progress.js";import{EspalierElementBase as m}from"../shared/esp-element-base.js";import{previewLoadQueue as k}from"./preview-load-queue.js";const T="esp-internal-image-preview-remove",R="esp-internal-image-preview-retry",L=2,E=250,I=6e4;function l(a){const e=a.trim().toLowerCase();return e.length>0&&!e.startsWith("blob:")&&!e.startsWith("data:")}let s=class extends m{constructor(){super(...arguments),this.image=y(),this.url="",this.alt="",this.failed=!1,this.sourceToken=0,this.admittedUrl="",this.remoteImageLoaded=!1,this.imageLoadFailed=!1,this.automaticRetryCount=0,this.remotePreviewVisible=!1,this.visibilityObserver=null,this.queuedLoadCancel=null,this.loadSlotRelease=null,this.retryTimer=null,this.loadTimeoutTimer=null,this.activeSourceToken=null}connectedCallback(){super.connectedCallback(),this.startCurrentPreview()}disconnectedCallback(){this.stopVisibilityObserver(),this.cancelRemoteWork(),this.remoteImageLoaded=!1,this.admittedUrl="",this.sourceToken+=1,this.remotePreviewVisible=!1,super.disconnectedCallback()}willUpdate(e){super.willUpdate(e),e.has("url")&&(this.stopVisibilityObserver(),this.cancelRemoteWork(),this.sourceToken+=1,this.admittedUrl="",this.remoteImageLoaded=!1,this.imageLoadFailed=!1,this.automaticRetryCount=0,this.remotePreviewVisible=!1)}updated(e){super.updated(e),e.has("url")&&this.startCurrentPreview()}startCurrentPreview(){if(!(!this.isConnected||!l(this.url))){if(typeof IntersectionObserver>"u"){this.remotePreviewVisible=!0,this.requestRemoteLoad();return}this.visibilityObserver??=new IntersectionObserver((e,t)=>{if(t!==this.visibilityObserver)return;const r=e.find(i=>i.target===this);!r||!this.isConnected||!l(this.url)||(this.remotePreviewVisible=r.isIntersecting,r.isIntersecting?this.requestRemoteLoad():!this.loadSlotRelease&&!this.remoteImageLoaded&&this.cancelQueuedLoad())},{threshold:0}),this.visibilityObserver.observe(this)}}stopVisibilityObserver(){this.visibilityObserver?.disconnect(),this.visibilityObserver=null}requestRemoteLoad(){if(!this.isConnected||!l(this.url)||!this.remotePreviewVisible||this.imageLoadFailed||this.remoteImageLoaded||this.loadSlotRelease||this.queuedLoadCancel||this.retryTimer)return;let e=!1;const t=k.request(r=>{if(e=!0,this.queuedLoadCancel=null,!this.isConnected||!l(this.url)||!this.remotePreviewVisible||this.imageLoadFailed||this.remoteImageLoaded){r();return}this.loadSlotRelease=r;const i=++this.sourceToken;this.activeSourceToken=i,this.admittedUrl=this.url,this.loadTimeoutTimer=setTimeout(()=>this.handleImageFailure(i),I),this.requestUpdate()});e||(this.queuedLoadCancel=t)}handleImageLoad(e,t){this.isActiveImageEvent(e,t)&&(this.completeActiveLoad(),this.remoteImageLoaded=!0,this.stopVisibilityObserver())}handleImageError(e,t){this.isActiveImageEvent(e,t)&&this.handleImageFailure(t)}isActiveImageEvent(e,t){return e.currentTarget instanceof HTMLImageElement&&e.currentTarget.getAttribute("src")===this.url&&t===this.activeSourceToken&&t===this.sourceToken&&this.loadSlotRelease!==null}handleImageFailure(e){if(e!==this.activeSourceToken||e!==this.sourceToken||!this.loadSlotRelease)return;if(this.abandonActiveLoad(),this.automaticRetryCount>=L){this.imageLoadFailed=!0,this.requestUpdate();return}this.automaticRetryCount+=1;const t=.75+Math.random()*.5,r=Math.round(E*2**(this.automaticRetryCount-1)*t),i=this.sourceToken;this.retryTimer=setTimeout(()=>{this.retryTimer=null,i===this.sourceToken&&this.requestRemoteLoad()},r)}completeActiveLoad(){this.releaseActiveLoad()}abandonActiveLoad(){if(!this.loadSlotRelease)return;const e=this.shadowRoot?.querySelector("img");e?.getAttribute("src")===this.admittedUrl&&e.removeAttribute("src"),this.admittedUrl="",this.sourceToken+=1,this.releaseActiveLoad(),this.isConnected&&this.requestUpdate()}releaseActiveLoad(){this.loadTimeoutTimer!==null&&(clearTimeout(this.loadTimeoutTimer),this.loadTimeoutTimer=null),this.activeSourceToken=null;const e=this.loadSlotRelease;this.loadSlotRelease=null,e?.()}cancelQueuedLoad(){const e=this.queuedLoadCancel;this.queuedLoadCancel=null,e?.()}cancelRemoteWork(){this.retryTimer!==null&&(clearTimeout(this.retryTimer),this.retryTimer=null),this.cancelQueuedLoad(),this.abandonActiveLoad()}retryImage(){l(this.url)&&(this.cancelRemoteWork(),this.sourceToken+=1,this.admittedUrl="",this.remoteImageLoaded=!1,this.imageLoadFailed=!1,this.automaticRetryCount=0,this.remotePreviewVisible=!0,this.requestRemoteLoad())}renderImage(){const t=l(this.url)?this.admittedUrl:this.url,r=this.sourceToken;return f(`${t}:${r}`,d`<img
2
+ ${w(this.image)}
3
+ src=${t||c}
4
+ alt=${this.alt}
5
+ draggable="false"
6
+ decoding="async"
7
+ @load=${i=>this.handleImageLoad(i,r)}
8
+ @error=${i=>this.handleImageError(i,r)}
9
+ />`)}render(){const e=this.progress!==void 0,t=this.failed&&!e,r=this.imageLoadFailed&&!e&&!t,i=e?this.alt?`Cancel upload and remove ${this.alt}`:"Cancel upload and remove image":this.alt?`Remove ${this.alt}`:"Remove image";return d`<div>
10
+ ${this.renderImage()}
3
11
  <button
4
12
  type="button"
5
- aria-label=${t}
13
+ aria-label=${i}
6
14
  class=${b({uploading:e})}
7
- @click=${()=>{this.dispatchEvent(new CustomEvent(w,{detail:this,bubbles:!0,composed:!0}))}}
15
+ @click=${()=>{this.dispatchEvent(new CustomEvent(T,{detail:this,bubbles:!0,composed:!0}))}}
8
16
  >
9
17
  <svg
10
18
  xmlns="http://www.w3.org/2000/svg"
@@ -20,7 +28,7 @@ var i=function(l,e,s,t){var a=arguments.length,r=a<3?e:t===null?t=Object.getOwnP
20
28
  <path d="M6 6l12 12" />
21
29
  </svg>
22
30
  </button>
23
- ${e?c`
31
+ ${e?d`
24
32
  <div class="upload-overlay">
25
33
  <esp-progress
26
34
  mode="circle"
@@ -30,8 +38,8 @@ var i=function(l,e,s,t){var a=arguments.length,r=a<3?e:t===null?t=Object.getOwnP
30
38
  label="Upload progress"
31
39
  ></esp-progress>
32
40
  </div>
33
- `:v}
34
- ${s?c`
41
+ `:c}
42
+ ${t?d`
35
43
  <div class="upload-overlay failed">
36
44
  <svg
37
45
  class="alert-icon"
@@ -55,13 +63,43 @@ var i=function(l,e,s,t){var a=arguments.length,r=a<3?e:t===null?t=Object.getOwnP
55
63
  class="retry-btn"
56
64
  collapsed
57
65
  label="Retry upload"
58
- @esp-clicked=${a=>{a.stopPropagation(),this.dispatchEvent(new CustomEvent(y,{detail:this,bubbles:!0,composed:!0}))}}
66
+ @esp-clicked=${o=>{o.stopPropagation(),this.dispatchEvent(new CustomEvent(R,{detail:this,bubbles:!0,composed:!0}))}}
59
67
  >
60
68
  Retry
61
69
  </esp-button>
62
70
  </div>
63
- `:v}
64
- </div>`}};o.styles=[...h.styles,g`
71
+ `:c}
72
+ ${r?d`
73
+ <div class="upload-overlay failed image-load-failed">
74
+ <svg
75
+ class="alert-icon"
76
+ xmlns="http://www.w3.org/2000/svg"
77
+ viewBox="0 0 24 24"
78
+ fill="none"
79
+ stroke="currentColor"
80
+ stroke-width="2"
81
+ stroke-linecap="round"
82
+ stroke-linejoin="round"
83
+ >
84
+ <path stroke="none" d="M0 0h24v24H0z" fill="none" />
85
+ <path d="M12 9v4" />
86
+ <path
87
+ d="M10.363 3.591l-8.106 13.534a1.914 1.914 0 0 0 1.636 2.871h16.214a1.914 1.914 0 0 0 1.636 -2.87l-8.106 -13.536a1.914 1.914 0 0 0 -3.274 0z"
88
+ />
89
+ <path d="M12 16h.01" />
90
+ </svg>
91
+ <span class="error-label">Image failed to load</span>
92
+ <esp-button
93
+ class="image-retry-btn"
94
+ collapsed
95
+ label="Retry image"
96
+ @esp-clicked=${o=>{o.stopPropagation(),this.retryImage()}}
97
+ >
98
+ Retry image
99
+ </esp-button>
100
+ </div>
101
+ `:c}
102
+ </div>`}};s.styles=[...m.styles,v`
65
103
  :host {
66
104
  display: block;
67
105
  box-sizing: border-box;
@@ -164,9 +202,10 @@ var i=function(l,e,s,t){var a=arguments.length,r=a<3?e:t===null?t=Object.getOwnP
164
202
  font-weight: 600;
165
203
  }
166
204
 
167
- .retry-btn {
205
+ .retry-btn,
206
+ .image-retry-btn {
168
207
  --esp-color-action-background: transparent;
169
208
  --esp-color-action-text: var(--esp-image-preview-overlay-text, var(--esp-color-layer-1));
170
209
  --esp-color-border: var(--esp-image-preview-overlay-text, var(--esp-color-layer-1));
171
210
  }
172
- `],i([n()],o.prototype,"url",void 0),i([n()],o.prototype,"alt",void 0),i([n({type:Number})],o.prototype,"progress",void 0),i([n({type:Boolean})],o.prototype,"failed",void 0),o=i([u("esp-image-preview")],o);export{o as EspalierImagePreview};
211
+ `],n([u()],s.prototype,"url",void 0),n([u()],s.prototype,"alt",void 0),n([u({type:Number})],s.prototype,"progress",void 0),n([u({type:Boolean})],s.prototype,"failed",void 0),s=n([g("esp-image-preview")],s);export{s as EspalierImagePreview};
@@ -157,6 +157,14 @@ export declare class EspalierImageUpload extends EspalierElementBase {
157
157
  * are skipped and reported via the `esp-image-upload-files-rejected` event.
158
158
  */
159
159
  accept: string;
160
+ /**
161
+ * Maximum number of image previews in each justified row. Positive integers
162
+ * create a hard row boundary; omitted, non-finite, non-integer, and
163
+ * nonpositive values leave rows unlimited.
164
+ *
165
+ * @type {number}
166
+ */
167
+ maxImagesPerRow?: number;
160
168
  connectedCallback(): void;
161
169
  disconnectedCallback(): void;
162
170
  /**
@@ -1,10 +1,10 @@
1
- var g=function(v,e,t,r){var n=arguments.length,s=n<3?e:r===null?r=Object.getOwnPropertyDescriptor(e,t):r,l;if(typeof Reflect=="object"&&typeof Reflect.decorate=="function")s=Reflect.decorate(v,e,t,r);else for(var i=v.length-1;i>=0;i--)(l=v[i])&&(s=(n<3?l(s):n>3?l(e,t,s):l(e,t))||s);return n>3&&s&&Object.defineProperty(e,t,s),s},_;import{css as P,html as m}from"lit";import{customElement as D,property as A,state as w}from"lit/decorators.js";import{createRef as b,ref as x}from"lit/directives/ref.js";import"./esp-image-preview.js";import{classMap as S}from"lit/directives/class-map.js";import{getImageDetails as k,releasePreviewUrl as I}from"./image-helpers.js";const C=`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
1
+ var g=function(v,e,t,i){var n=arguments.length,s=n<3?e:i===null?i=Object.getOwnPropertyDescriptor(e,t):i,l;if(typeof Reflect=="object"&&typeof Reflect.decorate=="function")s=Reflect.decorate(v,e,t,i);else for(var r=v.length-1;r>=0;r--)(l=v[r])&&(s=(n<3?l(s):n>3?l(e,t,s):l(e,t))||s);return n>3&&s&&Object.defineProperty(e,t,s),s},_;import{css as D,html as m}from"lit";import{customElement as A,property as S,state as w}from"lit/decorators.js";import{createRef as b,ref as x}from"lit/directives/ref.js";import"./esp-image-preview.js";import{classMap as y}from"lit/directives/class-map.js";import{getImageDetails as R,releasePreviewUrl as I}from"./image-helpers.js";const k=`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
2
2
  <path stroke="none" d="M0 0h24v24H0z" fill="none" />
3
3
  <path d="M11.911 3.634a2 2 0 0 1 1.089 1.78l.001 2.586h6.999a2 2 0 0 1 2 2v4l-.005 .15a2 2 0 0 1 -1.995 1.85l-6.999 -.001l-.001 2.587a2 2 0 0 1 -3.414 1.414l-6.586 -6.586a2 2 0 0 1 0 -2.828l6.586 -6.586a2 2 0 0 1 2.18 -.434l.145 .068z" />
4
- </svg>`,M=`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
4
+ </svg>`,C=`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
5
5
  <path stroke="none" d="M0 0h24v24H0z" fill="none" />
6
6
  <path d="M12.089 3.634a2 2 0 0 0 -1.089 1.78l-.001 2.586h-6.999a2 2 0 0 0 -2 2v4l.005 .15a2 2 0 0 0 1.995 1.85l6.999 -.001l.001 2.587a2 2 0 0 0 3.414 1.414l6.586 -6.586a2 2 0 0 0 0 -2.828l-6.586 -6.586a2 2 0 0 0 -2.18 -.434l-.145 .068z" />
7
- </svg>`;import{EspalierElementBase as y}from"../shared/esp-element-base.js";import{calculatePhotoLayout as R}from"../shared/justified-layout.js";import{ESP_EVENTS as f}from"../shared/events.js";const E=220,U=4,L=8e3;let u=_=class extends y{constructor(){super(...arguments),this.uploadInput=b(),this.previewsDiv=b(),this.dropAreaDiv=b(),this.draggingOver=!1,this.uploadedImages=[],this.accept="image/jpeg, image/png, image/webp",this._rejectionNotice="",this._uploadState=new WeakMap,this._selectionGeneration=0,this._chromeWidth=null,this._resizeObserver=new ResizeObserver(()=>{this._chromeWidth=null,this.requestUpdate()}),this._dragPointerId=null,this._dragSourceIndex=null,this._dragOverIndex=null,this._dragStartX=0,this._dragStartY=0,this._dragActive=!1,this._ghostEl=null,this._ghostHalfW=0,this._ghostHalfH=0,this._arrowEl=null,this._cachedPreviews=null,this._lastDropHit=null,this._savedBodyUserSelect="",this._handleDragPointerDown=e=>{if(e.button!==0||this._dragPointerId!==null||e.composedPath().some(n=>n instanceof HTMLElement&&(n.tagName==="BUTTON"||n.tagName==="ESP-BUTTON")))return;const r=this._getPreviewIndex(e);r!==null&&(this._dragPointerId=e.pointerId,this._dragSourceIndex=r,this._dragStartX=e.clientX,this._dragStartY=e.clientY,this._dragActive=!1,document.addEventListener("pointermove",this._onDocPointerMove),document.addEventListener("pointerup",this._onDocPointerUp),document.addEventListener("pointercancel",this._onDocPointerUp))},this._onDocPointerMove=e=>{if(this._dragSourceIndex===null||e.pointerId!==this._dragPointerId)return;const t=e.clientX-this._dragStartX,r=e.clientY-this._dragStartY;if(!this._dragActive){if(Math.sqrt(t*t+r*r)<_._DRAG_THRESHOLD)return;this._dragActive=!0,this._createGhost(e.clientX,e.clientY);const i=this.shadowRoot?.querySelectorAll("esp-image-preview");i&&(this._cachedPreviews=Array.from(i).map(o=>({el:o,rect:o.getBoundingClientRect()})),this._dragSourceIndex!==null&&this._dragSourceIndex<this._cachedPreviews.length&&(this._cachedPreviews[this._dragSourceIndex].el.style.opacity="0.3")),this._savedBodyUserSelect=document.body.style.userSelect,document.body.style.userSelect="none",this.previewsDiv.value&&(this.previewsDiv.value.style.touchAction="none")}this._ghostEl&&(this._ghostEl.style.transform=`translate(${e.clientX-this._ghostHalfW}px, ${e.clientY-this._ghostHalfH}px)`);const n=this._cachedPreviews;if(!n||n.length===0)return;let s=null,l=!1;for(let i=0;i<n.length;i++){const o=n[i].rect;if(e.clientX>=o.left&&e.clientX<=o.right&&e.clientY>=o.top&&e.clientY<=o.bottom){s=i,l=e.clientX>o.left+o.width/2;break}}if(s===null){let i=1/0;for(let o=0;o<n.length;o++){const a=n[o].rect,d=e.clientX-(a.left+a.width/2),h=e.clientY-(a.top+a.height/2),c=d*d+h*h;c<i&&(i=c,s=o)}if(s!==null){const o=n[s].rect;l=e.clientX>o.left+o.width/2}}s!==null&&(this._dragOverIndex=l?s+1:s,!(this._lastDropHit&&this._lastDropHit.index===s&&this._lastDropHit.after===l)&&(this._lastDropHit={index:s,after:l},this._updateDropTargets(n.map(i=>i.el),s,l)))},this._onDocPointerUp=e=>{if(e.pointerId===this._dragPointerId){if(this._dragActive&&this._dragSourceIndex!==null&&this._dragOverIndex!==null&&this._dragSourceIndex!==this._dragOverIndex&&this._dragSourceIndex+1!==this._dragOverIndex){const t=[...this.uploadedImages],[r]=t.splice(this._dragSourceIndex,1),n=this._dragOverIndex>this._dragSourceIndex?this._dragOverIndex-1:this._dragOverIndex;t.splice(n,0,r),this.uploadedImages=t,this.dispatchEvent(new CustomEvent(f.IMAGE_UPLOAD_IMAGES_REORDERED,{detail:{images:this.uploadedImages},bubbles:!0,composed:!0}))}this._clearDragState()}},this._arrowColor=null,this.filesSelected=async e=>{const t=[],r=[];for(const d of e)(this._isAccepted(d)?t:r).push(d);if(!t.length){r.length&&this._reportRejectedFiles({unsupported:r,unreadable:[]});return}const n=this._selectionGeneration,s=Math.round(E*Math.min(globalThis.devicePixelRatio||1,2)),l=new Array(t.length).fill(null),i=[];let o=0;const a=async()=>{for(;o<t.length;){const d=o++,h=t[d];try{const c=await k(h,{thumbnailHeight:s});if(n!==this._selectionGeneration){I(c);continue}this._addSelectedImage(l,d,c)}catch{i.push(h)}}};await Promise.all(Array.from({length:Math.min(U,t.length)},()=>a())),(r.length||i.length)&&n===this._selectionGeneration&&this._reportRejectedFiles({unsupported:r,unreadable:i})},this._makeCallbacks=e=>({onProgress:t=>{const r=this._uploadState.get(e);r&&(r.progress=t,r.failed=!1,this.requestUpdate())},onComplete:t=>{if(!this._uploadState.get(e))return;e.uploadedId=t,this._uploadState.delete(e),!this.uploadedImages.find(s=>!s.uploadedId)?this.uploadedImages=[...this.uploadedImages]:this.requestUpdate()},onFailed:()=>{const t=this._uploadState.get(e);t&&(t.progress=void 0,t.failed=!0,this.requestUpdate())}}),this._fullPreviewUrls=new Map,this.setExistingImages=e=>{this._selectionGeneration++,this._releaseSelectedImages(),this.uploadedImages=e.filter(t=>t.url&&t.uploadedId&&Number.isFinite(t.width)&&t.width>0&&Number.isFinite(t.height)&&t.height>0).map(t=>({source:"existing",url:t.url,urls:t.urls,height:t.height,width:t.width,uploadedId:t.uploadedId,orientation:t.width>t.height?"landscape":"portrait"})),this._uploadState=new WeakMap}}connectedCallback(){super.connectedCallback(),this._resizeObserver.observe(this)}disconnectedCallback(){super.disconnectedCallback(),this._resizeObserver.disconnect(),this._clearDragState(),clearTimeout(this._noticeTimer),this._selectionGeneration++,this._releaseSelectedImages()}_getPreviewIndex(e){const t=this.shadowRoot?.querySelectorAll("esp-image-preview");if(!t)return null;for(const r of e.composedPath())if(r instanceof HTMLElement&&r.tagName==="ESP-IMAGE-PREVIEW"){for(let n=0;n<t.length;n++)if(t[n]===r)return n}return null}_clearDragState(){document.removeEventListener("pointermove",this._onDocPointerMove),document.removeEventListener("pointerup",this._onDocPointerUp),document.removeEventListener("pointercancel",this._onDocPointerUp),this._dragActive&&(this._cachedPreviews&&this._dragSourceIndex!==null&&this._dragSourceIndex<this._cachedPreviews.length&&(this._cachedPreviews[this._dragSourceIndex].el.style.opacity=""),this._clearDropTargets(),document.body.style.userSelect=this._savedBodyUserSelect,this.previewsDiv.value&&(this.previewsDiv.value.style.touchAction="")),this._ghostEl?.remove(),this._ghostEl=null,this._cachedPreviews=null,this._lastDropHit=null,this._dragPointerId=null,this._dragSourceIndex=null,this._dragOverIndex=null,this._dragActive=!1}_createGhost(e,t){const r=this.shadowRoot?.querySelectorAll("esp-image-preview");if(!r||this._dragSourceIndex===null)return;const n=r[this._dragSourceIndex],s=n.getBoundingClientRect(),l=document.createElement("div"),i=Math.min(120/s.width,120/s.height),o=s.width*i,a=s.height*i;l.style.cssText=`
7
+ </svg>`;import{EspalierElementBase as P}from"../shared/esp-element-base.js";import{calculatePhotoLayout as M}from"../shared/justified-layout.js";import{ESP_EVENTS as f}from"../shared/events.js";const E=220,U=4,L=8e3;let p=_=class extends P{constructor(){super(...arguments),this.uploadInput=b(),this.previewsDiv=b(),this.dropAreaDiv=b(),this.draggingOver=!1,this.uploadedImages=[],this.accept="image/jpeg, image/png, image/webp",this._rejectionNotice="",this._uploadState=new WeakMap,this._selectionGeneration=0,this._chromeWidth=null,this._resizeObserver=new ResizeObserver(()=>{this._chromeWidth=null,this.requestUpdate()}),this._dragPointerId=null,this._dragSourceIndex=null,this._dragOverIndex=null,this._dragStartX=0,this._dragStartY=0,this._dragActive=!1,this._ghostEl=null,this._ghostHalfW=0,this._ghostHalfH=0,this._arrowEl=null,this._cachedPreviews=null,this._lastDropHit=null,this._savedBodyUserSelect="",this._handleDragPointerDown=e=>{if(e.button!==0||this._dragPointerId!==null||e.composedPath().some(n=>n instanceof HTMLElement&&(n.tagName==="BUTTON"||n.tagName==="ESP-BUTTON")))return;const i=this._getPreviewIndex(e);i!==null&&(this._dragPointerId=e.pointerId,this._dragSourceIndex=i,this._dragStartX=e.clientX,this._dragStartY=e.clientY,this._dragActive=!1,document.addEventListener("pointermove",this._onDocPointerMove),document.addEventListener("pointerup",this._onDocPointerUp),document.addEventListener("pointercancel",this._onDocPointerUp))},this._onDocPointerMove=e=>{if(this._dragSourceIndex===null||e.pointerId!==this._dragPointerId)return;const t=e.clientX-this._dragStartX,i=e.clientY-this._dragStartY;if(!this._dragActive){if(Math.sqrt(t*t+i*i)<_._DRAG_THRESHOLD)return;this._dragActive=!0,this._createGhost(e.clientX,e.clientY);const r=this.shadowRoot?.querySelectorAll("esp-image-preview");r&&(this._cachedPreviews=Array.from(r).map(o=>({el:o,rect:o.getBoundingClientRect()})),this._dragSourceIndex!==null&&this._dragSourceIndex<this._cachedPreviews.length&&(this._cachedPreviews[this._dragSourceIndex].el.style.opacity="0.3")),this._savedBodyUserSelect=document.body.style.userSelect,document.body.style.userSelect="none",this.previewsDiv.value&&(this.previewsDiv.value.style.touchAction="none")}this._ghostEl&&(this._ghostEl.style.transform=`translate(${e.clientX-this._ghostHalfW}px, ${e.clientY-this._ghostHalfH}px)`);const n=this._cachedPreviews;if(!n||n.length===0)return;let s=null,l=!1;for(let r=0;r<n.length;r++){const o=n[r].rect;if(e.clientX>=o.left&&e.clientX<=o.right&&e.clientY>=o.top&&e.clientY<=o.bottom){s=r,l=e.clientX>o.left+o.width/2;break}}if(s===null){let r=1/0;for(let o=0;o<n.length;o++){const a=n[o].rect,d=e.clientX-(a.left+a.width/2),h=e.clientY-(a.top+a.height/2),c=d*d+h*h;c<r&&(r=c,s=o)}if(s!==null){const o=n[s].rect;l=e.clientX>o.left+o.width/2}}s!==null&&(this._dragOverIndex=l?s+1:s,!(this._lastDropHit&&this._lastDropHit.index===s&&this._lastDropHit.after===l)&&(this._lastDropHit={index:s,after:l},this._updateDropTargets(n.map(r=>r.el),s,l)))},this._onDocPointerUp=e=>{if(e.pointerId===this._dragPointerId){if(this._dragActive&&this._dragSourceIndex!==null&&this._dragOverIndex!==null&&this._dragSourceIndex!==this._dragOverIndex&&this._dragSourceIndex+1!==this._dragOverIndex){const t=[...this.uploadedImages],[i]=t.splice(this._dragSourceIndex,1),n=this._dragOverIndex>this._dragSourceIndex?this._dragOverIndex-1:this._dragOverIndex;t.splice(n,0,i),this.uploadedImages=t,this.dispatchEvent(new CustomEvent(f.IMAGE_UPLOAD_IMAGES_REORDERED,{detail:{images:this.uploadedImages},bubbles:!0,composed:!0}))}this._clearDragState()}},this._arrowColor=null,this.filesSelected=async e=>{const t=[],i=[];for(const d of e)(this._isAccepted(d)?t:i).push(d);if(!t.length){i.length&&this._reportRejectedFiles({unsupported:i,unreadable:[]});return}const n=this._selectionGeneration,s=Math.round(E*Math.min(globalThis.devicePixelRatio||1,2)),l=new Array(t.length).fill(null),r=[];let o=0;const a=async()=>{for(;o<t.length;){const d=o++,h=t[d];try{const c=await R(h,{thumbnailHeight:s});if(n!==this._selectionGeneration){I(c);continue}this._addSelectedImage(l,d,c)}catch{r.push(h)}}};await Promise.all(Array.from({length:Math.min(U,t.length)},()=>a())),(i.length||r.length)&&n===this._selectionGeneration&&this._reportRejectedFiles({unsupported:i,unreadable:r})},this._makeCallbacks=e=>({onProgress:t=>{const i=this._uploadState.get(e);i&&(i.progress=t,i.failed=!1,this.requestUpdate())},onComplete:t=>{if(!this._uploadState.get(e))return;e.uploadedId=t,this._uploadState.delete(e),!this.uploadedImages.find(s=>!s.uploadedId)?this.uploadedImages=[...this.uploadedImages]:this.requestUpdate()},onFailed:()=>{const t=this._uploadState.get(e);t&&(t.progress=void 0,t.failed=!0,this.requestUpdate())}}),this._fullPreviewUrls=new Map,this.setExistingImages=e=>{this._selectionGeneration++,this._releaseSelectedImages(),this.uploadedImages=e.filter(t=>t.url&&t.uploadedId&&Number.isFinite(t.width)&&t.width>0&&Number.isFinite(t.height)&&t.height>0).map(t=>({source:"existing",url:t.url,urls:t.urls,height:t.height,width:t.width,uploadedId:t.uploadedId,orientation:t.width>t.height?"landscape":"portrait"})),this._uploadState=new WeakMap}}connectedCallback(){super.connectedCallback(),this._resizeObserver.observe(this)}disconnectedCallback(){super.disconnectedCallback(),this._resizeObserver.disconnect(),this._clearDragState(),clearTimeout(this._noticeTimer),this._selectionGeneration++,this._releaseSelectedImages()}_getPreviewIndex(e){const t=this.shadowRoot?.querySelectorAll("esp-image-preview");if(!t)return null;for(const i of e.composedPath())if(i instanceof HTMLElement&&i.tagName==="ESP-IMAGE-PREVIEW"){for(let n=0;n<t.length;n++)if(t[n]===i)return n}return null}_clearDragState(){document.removeEventListener("pointermove",this._onDocPointerMove),document.removeEventListener("pointerup",this._onDocPointerUp),document.removeEventListener("pointercancel",this._onDocPointerUp),this._dragActive&&(this._cachedPreviews&&this._dragSourceIndex!==null&&this._dragSourceIndex<this._cachedPreviews.length&&(this._cachedPreviews[this._dragSourceIndex].el.style.opacity=""),this._clearDropTargets(),document.body.style.userSelect=this._savedBodyUserSelect,this.previewsDiv.value&&(this.previewsDiv.value.style.touchAction="")),this._ghostEl?.remove(),this._ghostEl=null,this._cachedPreviews=null,this._lastDropHit=null,this._dragPointerId=null,this._dragSourceIndex=null,this._dragOverIndex=null,this._dragActive=!1}_createGhost(e,t){const i=this.shadowRoot?.querySelectorAll("esp-image-preview");if(!i||this._dragSourceIndex===null)return;const n=i[this._dragSourceIndex],s=n.getBoundingClientRect(),l=document.createElement("div"),r=Math.min(120/s.width,120/s.height),o=s.width*r,a=s.height*r;l.style.cssText=`
8
8
  position: fixed;
9
9
  left: 0;
10
10
  top: 0;
@@ -19,7 +19,7 @@ var g=function(v,e,t,r){var n=arguments.length,s=n<3?e:r===null?r=Object.getOwnP
19
19
  opacity: 0.85;
20
20
  will-change: transform;
21
21
  transform: translate(${e-o/2}px, ${t-a/2}px);
22
- `;const d=n.shadowRoot?.querySelector("img");d&&(l.style.backgroundImage=`url(${d.src})`),document.body.appendChild(l),this._ghostEl=l,this._ghostHalfW=o/2,this._ghostHalfH=a/2}_updateDropTargets(e,t,r){for(let d=0;d<e.length;d++){const h=e[d];d===t&&d!==this._dragSourceIndex?h.setAttribute("data-drop-side",r?"right":"left"):h.removeAttribute("data-drop-side")}if(t===this._dragSourceIndex){this._arrowEl&&(this._arrowEl.style.display="none");return}const n=this._cachedPreviews,s=n?n[t].rect:e[t].getBoundingClientRect(),l=r?M:C;if(!this._arrowEl){const d=document.createElement("div");d.style.cssText=`
22
+ `;const d=n.shadowRoot?.querySelector("img");d&&(l.style.backgroundImage=`url(${d.src})`),document.body.appendChild(l),this._ghostEl=l,this._ghostHalfW=o/2,this._ghostHalfH=a/2}_updateDropTargets(e,t,i){for(let d=0;d<e.length;d++){const h=e[d];d===t&&d!==this._dragSourceIndex?h.setAttribute("data-drop-side",i?"right":"left"):h.removeAttribute("data-drop-side")}if(t===this._dragSourceIndex){this._arrowEl&&(this._arrowEl.style.display="none");return}const n=this._cachedPreviews,s=n?n[t].rect:e[t].getBoundingClientRect(),l=i?C:k;if(!this._arrowEl){const d=document.createElement("div");d.style.cssText=`
23
23
  position: fixed;
24
24
  left: 0;
25
25
  top: 0;
@@ -30,27 +30,27 @@ var g=function(v,e,t,r){var n=arguments.length,s=n<3?e:r===null?r=Object.getOwnP
30
30
  justify-content: center;
31
31
  will-change: transform;
32
32
  filter: drop-shadow(0 1px 3px rgba(0,0,0,0.4));
33
- `,document.body.appendChild(d),this._arrowEl=d;const h=e[t],p=getComputedStyle(h).getPropertyValue("--esp-color-complementary").trim();this._arrowColor=p?`oklch(from ${p} var(--esp-l-accent) c h)`:"oklch(0.7 0.2 330)"}this._arrowEl.innerHTML=l;const i=this._arrowEl.querySelector("svg");i&&(i.style.width="40px",i.style.height="40px",i.style.color=this._arrowColor??"oklch(0.7 0.2 330)"),this._arrowEl.style.display="flex";const o=r?s.left+s.width*.75:s.left+s.width*.25,a=s.top+s.height/2;this._arrowEl.style.transform=`translate(${o-20}px, ${a-20}px)`}_clearDropTargets(){const e=this.shadowRoot?.querySelectorAll("esp-image-preview");if(e)for(const t of e)t.removeAttribute("data-drop-side");this._arrowEl?.remove(),this._arrowEl=null,this._arrowColor=null}getContainerWidth(){const e=this.parentElement??this.offsetParent,t=e?e.getBoundingClientRect().width:window.innerWidth;if(t<=0){const r=window.innerWidth;return r<640?r-32:r<1024?r*90/100-32:Math.min(r*85/100-32,1400)}if(this._chromeWidth===null){const r=this.previewsDiv.value;if(r){const n=a=>{const d=parseFloat(a);return Number.isFinite(d)?d:0},s=getComputedStyle(r),l=n(s.paddingLeft)+n(s.paddingRight),i=this.shadowRoot?.querySelector(".esp-field"),o=i?n(getComputedStyle(i).borderLeftWidth)+n(getComputedStyle(i).borderRightWidth):0;this._chromeWidth=l+o}}return Math.max(t-(this._chromeWidth??0),1)}_isAccepted(e){const t=this.accept.split(",").map(s=>s.trim().toLowerCase()).filter(Boolean);if(!t.length)return e.type.toLowerCase().startsWith("image/");const r=e.type.toLowerCase(),n=e.name.toLowerCase();return t.some(s=>s.startsWith(".")?n.endsWith(s):s.endsWith("/*")?r.startsWith(s.slice(0,-1)):r===s)}_addSelectedImage(e,t,r){e[t]=r;const n=[...this.uploadedImages];let s=-1;for(let i=t-1;i>=0&&s<0;i--){const o=e[i];if(!o)continue;const a=n.indexOf(o);a>=0&&(s=a+1)}for(let i=t+1;i<e.length&&s<0;i++){const o=e[i];if(!o)continue;const a=n.indexOf(o);a>=0&&(s=a)}s<0&&(s=n.length),n.splice(s,0,r);const l=new AbortController;this._uploadState.set(r,{progress:null,failed:!1,controller:l}),this.uploadedImages=n,this.dispatchEvent(new CustomEvent(f.IMAGE_UPLOAD_FILE_SELECTED,{detail:{image:r,signal:l.signal,...this._makeCallbacks(r)},bubbles:!0,composed:!0}))}_reportRejectedFiles(e){this.dispatchEvent(new CustomEvent(f.IMAGE_UPLOAD_FILES_REJECTED,{detail:e,bubbles:!0,composed:!0}));const t=n=>n===1?"file":"files",r=[];e.unsupported.length&&r.push(`${e.unsupported.length} unsupported ${t(e.unsupported.length)} skipped`),e.unreadable.length&&r.push(`${e.unreadable.length} ${t(e.unreadable.length)} could not be read`),this._rejectionNotice=r.join("; "),clearTimeout(this._noticeTimer),this._noticeTimer=setTimeout(()=>{this._rejectionNotice=""},L)}_previewUrlFor(e,t){if(e.source!=="selected")return this._pickExistingPreviewUrl(e,t);const r=Math.min(globalThis.devicePixelRatio||1,2),n=E*r;if(t*r<=n*_._THUMBNAIL_UPSCALE_SLACK||e.height<=n)return e.url;let i=this._fullPreviewUrls.get(e);return i||(i=URL.createObjectURL(e.file),this._fullPreviewUrls.set(e,i)),i}_pickExistingPreviewUrl(e,t){const r=e.urls;if(!r?.length)return e.url;const n=Math.min(globalThis.devicePixelRatio||1,2),s=e.height>0?e.width/e.height:1,l=t*s*n,i=[...r].sort((a,d)=>a.minWidth-d.minWidth);return(i.find(a=>a.minWidth>=l)??i[i.length-1]).url}_releaseFullPreviewUrl(e){if(e.source!=="selected")return;const t=this._fullPreviewUrls.get(e);t&&(URL.revokeObjectURL(t),this._fullPreviewUrls.delete(e))}_releaseSelectedImages(){for(const e of this.uploadedImages)e.source==="selected"&&(this._uploadState.get(e)?.controller.abort(),I(e));for(const e of this._fullPreviewUrls.values())URL.revokeObjectURL(e);this._fullPreviewUrls.clear()}render(){const{draggingOver:e,uploadedImages:t}=this,r={"dragging-over":e,"has-previews":t.length},n=this.getContainerWidth(),s=window.innerHeight>0?window.innerHeight/2:Number.POSITIVE_INFINITY,l=R(t,n,E,8,s);return m` <div
33
+ `,document.body.appendChild(d),this._arrowEl=d;const h=e[t],u=getComputedStyle(h).getPropertyValue("--esp-color-complementary").trim();this._arrowColor=u?`oklch(from ${u} var(--esp-l-accent) c h)`:"oklch(0.7 0.2 330)"}this._arrowEl.innerHTML=l;const r=this._arrowEl.querySelector("svg");r&&(r.style.width="40px",r.style.height="40px",r.style.color=this._arrowColor??"oklch(0.7 0.2 330)"),this._arrowEl.style.display="flex";const o=i?s.left+s.width*.75:s.left+s.width*.25,a=s.top+s.height/2;this._arrowEl.style.transform=`translate(${o-20}px, ${a-20}px)`}_clearDropTargets(){const e=this.shadowRoot?.querySelectorAll("esp-image-preview");if(e)for(const t of e)t.removeAttribute("data-drop-side");this._arrowEl?.remove(),this._arrowEl=null,this._arrowColor=null}getContainerWidth(){const e=this.parentElement??this.offsetParent,t=e?e.getBoundingClientRect().width:window.innerWidth;if(t<=0){const i=window.innerWidth;return i<640?i-32:i<1024?i*90/100-32:Math.min(i*85/100-32,1400)}if(this._chromeWidth===null){const i=this.previewsDiv.value;if(i){const n=a=>{const d=parseFloat(a);return Number.isFinite(d)?d:0},s=getComputedStyle(i),l=n(s.paddingLeft)+n(s.paddingRight),r=this.shadowRoot?.querySelector(".esp-field"),o=r?n(getComputedStyle(r).borderLeftWidth)+n(getComputedStyle(r).borderRightWidth):0;this._chromeWidth=l+o}}return Math.max(t-(this._chromeWidth??0),1)}_isAccepted(e){const t=this.accept.split(",").map(s=>s.trim().toLowerCase()).filter(Boolean);if(!t.length)return e.type.toLowerCase().startsWith("image/");const i=e.type.toLowerCase(),n=e.name.toLowerCase();return t.some(s=>s.startsWith(".")?n.endsWith(s):s.endsWith("/*")?i.startsWith(s.slice(0,-1)):i===s)}_addSelectedImage(e,t,i){e[t]=i;const n=[...this.uploadedImages];let s=-1;for(let r=t-1;r>=0&&s<0;r--){const o=e[r];if(!o)continue;const a=n.indexOf(o);a>=0&&(s=a+1)}for(let r=t+1;r<e.length&&s<0;r++){const o=e[r];if(!o)continue;const a=n.indexOf(o);a>=0&&(s=a)}s<0&&(s=n.length),n.splice(s,0,i);const l=new AbortController;this._uploadState.set(i,{progress:null,failed:!1,controller:l}),this.uploadedImages=n,this.dispatchEvent(new CustomEvent(f.IMAGE_UPLOAD_FILE_SELECTED,{detail:{image:i,signal:l.signal,...this._makeCallbacks(i)},bubbles:!0,composed:!0}))}_reportRejectedFiles(e){this.dispatchEvent(new CustomEvent(f.IMAGE_UPLOAD_FILES_REJECTED,{detail:e,bubbles:!0,composed:!0}));const t=n=>n===1?"file":"files",i=[];e.unsupported.length&&i.push(`${e.unsupported.length} unsupported ${t(e.unsupported.length)} skipped`),e.unreadable.length&&i.push(`${e.unreadable.length} ${t(e.unreadable.length)} could not be read`),this._rejectionNotice=i.join("; "),clearTimeout(this._noticeTimer),this._noticeTimer=setTimeout(()=>{this._rejectionNotice=""},L)}_previewUrlFor(e,t){if(e.source!=="selected")return this._pickExistingPreviewUrl(e,t);const i=Math.min(globalThis.devicePixelRatio||1,2),n=E*i;if(t*i<=n*_._THUMBNAIL_UPSCALE_SLACK||e.height<=n)return e.url;let r=this._fullPreviewUrls.get(e);return r||(r=URL.createObjectURL(e.file),this._fullPreviewUrls.set(e,r)),r}_pickExistingPreviewUrl(e,t){const i=e.urls;if(!i?.length)return e.url;const n=Math.min(globalThis.devicePixelRatio||1,2),s=e.height>0?e.width/e.height:1,l=t*s*n,r=[...i].sort((a,d)=>a.minWidth-d.minWidth);return(r.find(a=>a.minWidth>=l)??r[r.length-1]).url}_releaseFullPreviewUrl(e){if(e.source!=="selected")return;const t=this._fullPreviewUrls.get(e);t&&(URL.revokeObjectURL(t),this._fullPreviewUrls.delete(e))}_releaseSelectedImages(){for(const e of this.uploadedImages)e.source==="selected"&&(this._uploadState.get(e)?.controller.abort(),I(e));for(const e of this._fullPreviewUrls.values())URL.revokeObjectURL(e);this._fullPreviewUrls.clear()}render(){const{draggingOver:e,uploadedImages:t}=this,i={"dragging-over":e,"has-previews":t.length},n=this.getContainerWidth(),s=window.innerHeight>0?window.innerHeight/2:Number.POSITIVE_INFINITY,l=M(t,n,E,8,s,this.maxImagesPerRow);return m` <div
34
34
  class="esp-field"
35
35
  @click=${()=>{this.dropAreaDiv.value?.focus({preventScroll:!0})}}
36
- @dragover=${i=>{i.preventDefault(),this.draggingOver=!0}}
37
- @dragleave=${i=>{i.preventDefault(),this.draggingOver=!1}}
38
- @drop=${i=>{i.preventDefault(),this.draggingOver=!1;const o=i.dataTransfer?.files;o&&o.length&&this.filesSelected(o)}}
36
+ @dragover=${r=>{r.preventDefault(),this.draggingOver=!0}}
37
+ @dragleave=${r=>{r.preventDefault(),this.draggingOver=!1}}
38
+ @drop=${r=>{r.preventDefault(),this.draggingOver=!1;const o=r.dataTransfer?.files;o&&o.length&&this.filesSelected(o)}}
39
39
  >
40
40
  <div
41
41
  ${x(this.previewsDiv)}
42
- class=${S({previews:!0,...r})}
42
+ class=${y({previews:!0,...i})}
43
43
  @pointerdown=${this._handleDragPointerDown}
44
44
  >
45
- ${l.map(i=>m`<div class="photo-row" style="height: ${i.height}px;">
46
- ${i.images.map(o=>{const a=i.height,d=o.width/o.height*a,h=this._uploadState.get(o);return m`<esp-image-preview
45
+ ${l.map(r=>m`<div class="photo-row" style="height: ${r.height}px;">
46
+ ${r.images.map(o=>{const a=r.height,d=o.width/o.height*a,h=this._uploadState.get(o);return m`<esp-image-preview
47
47
  style="width: ${d}px; height: ${a}px; flex-shrink: 0;"
48
48
  .url=${this._previewUrlFor(o,a)}
49
49
  .alt=${o.source==="selected"?o.file.name:""}
50
50
  .progress=${h?.progress}
51
51
  .failed=${h?.failed??!1}
52
- @esp-internal-image-preview-remove=${()=>{const c=this.uploadedImages.indexOf(o);if(c<0)return;this._uploadState.get(o)?.controller.abort(),this._uploadState.delete(o),I(o),this._releaseFullPreviewUrl(o);const p=[...this.uploadedImages];p.splice(c,1),this.uploadedImages=p,this.dispatchEvent(new CustomEvent(f.IMAGE_UPLOAD_FILE_REMOVED,{detail:o,bubbles:!0,composed:!0}))}}
53
- @esp-internal-image-preview-retry=${()=>{if(o.source!=="selected")return;const c=new AbortController,p=this._uploadState.get(o);p?(p.failed=!1,p.progress=null,p.controller=c):this._uploadState.set(o,{progress:null,failed:!1,controller:c}),this.requestUpdate(),this.dispatchEvent(new CustomEvent(f.IMAGE_UPLOAD_RETRY,{detail:{image:o,signal:c.signal,...this._makeCallbacks(o)},bubbles:!0,composed:!0}))}}
52
+ @esp-internal-image-preview-remove=${()=>{const c=this.uploadedImages.indexOf(o);if(c<0)return;this._uploadState.get(o)?.controller.abort(),this._uploadState.delete(o),I(o),this._releaseFullPreviewUrl(o);const u=[...this.uploadedImages];u.splice(c,1),this.uploadedImages=u,this.dispatchEvent(new CustomEvent(f.IMAGE_UPLOAD_FILE_REMOVED,{detail:o,bubbles:!0,composed:!0}))}}
53
+ @esp-internal-image-preview-retry=${()=>{if(o.source!=="selected")return;const c=new AbortController,u=this._uploadState.get(o);u?(u.failed=!1,u.progress=null,u.controller=c):this._uploadState.set(o,{progress:null,failed:!1,controller:c}),this.requestUpdate(),this.dispatchEvent(new CustomEvent(f.IMAGE_UPLOAD_RETRY,{detail:{image:o,signal:c.signal,...this._makeCallbacks(o)},bubbles:!0,composed:!0}))}}
54
54
  ></esp-image-preview>`})}
55
55
  </div>`)}
56
56
  </div>
@@ -59,9 +59,9 @@ var g=function(v,e,t,r){var n=arguments.length,s=n<3?e:r===null?r=Object.getOwnP
59
59
  tabindex="0"
60
60
  role="button"
61
61
  aria-label="Add photos"
62
- class=${S({"drop-area":!0,...r})}
62
+ class=${y({"drop-area":!0,...i})}
63
63
  @click=${()=>{this.uploadInput.value?.click()}}
64
- @keydown=${i=>{(i.code==="Enter"||i.code==="Space")&&(i.preventDefault(),this.uploadInput.value?.click())}}
64
+ @keydown=${r=>{(r.code==="Enter"||r.code==="Space")&&(r.preventDefault(),this.uploadInput.value?.click())}}
65
65
  >
66
66
  <svg
67
67
  xmlns="http://www.w3.org/2000/svg"
@@ -109,9 +109,9 @@ var g=function(v,e,t,r){var n=arguments.length,s=n<3?e:r===null?r=Object.getOwnP
109
109
  multiple
110
110
  accept=${this.accept}
111
111
  hidden
112
- @change=${()=>{const i=this.uploadInput.value;!i||!i.files||!i.files.length||(this.filesSelected(i.files),i.value="")}}
112
+ @change=${()=>{const r=this.uploadInput.value;!r||!r.files||!r.files.length||(this.filesSelected(r.files),r.value="")}}
113
113
  />
114
- </div>`}};u._DRAG_THRESHOLD=8,u._THUMBNAIL_UPSCALE_SLACK=1.1,u.styles=[...y.styles,P`
114
+ </div>`}};p._DRAG_THRESHOLD=8,p._THUMBNAIL_UPSCALE_SLACK=1.1,p.styles=[...P.styles,D`
115
115
  :host {
116
116
  overflow: hidden;
117
117
  }
@@ -237,4 +237,4 @@ var g=function(v,e,t,r){var n=arguments.length,s=n<3?e:r===null?r=Object.getOwnP
237
237
  }
238
238
  }
239
239
  }
240
- `],g([w()],u.prototype,"draggingOver",void 0),g([w()],u.prototype,"uploadedImages",void 0),g([A({type:String})],u.prototype,"accept",void 0),g([w()],u.prototype,"_rejectionNotice",void 0),u=_=g([D("esp-image-upload")],u);export{u as EspalierImageUpload};
240
+ `],g([w()],p.prototype,"draggingOver",void 0),g([w()],p.prototype,"uploadedImages",void 0),g([S({type:String})],p.prototype,"accept",void 0),g([S({attribute:"max-images-per-row",type:Number})],p.prototype,"maxImagesPerRow",void 0),g([w()],p.prototype,"_rejectionNotice",void 0),p=_=g([A("esp-image-upload")],p);export{p as EspalierImageUpload};
@@ -0,0 +1 @@
1
+ class s{constructor(e){this.limit=e,this.active=0,this.pending=[]}request(e){const i={active:!1,cancelled:!1,released:!1,start:e};return this.pending.push(i),this.drain(),()=>{if(i.cancelled)return;if(i.cancelled=!0,i.active){this.release(i);return}const t=this.pending.indexOf(i);t>=0&&this.pending.splice(t,1)}}drain(){for(;this.active<this.limit&&this.pending.length>0;){const e=this.pending.shift();e.cancelled||(e.active=!0,this.active+=1,e.start(()=>this.release(e)))}}release(e){!e.active||e.released||(e.released=!0,e.active=!1,this.active-=1,this.drain())}}const a=new s(4);export{a as previewLoadQueue};
package/dist/index.d.ts CHANGED
@@ -71,7 +71,7 @@ export { requestHelp, type HelpRequest, type HelpState } from "./shared/help-eve
71
71
  export { getEspBus, type EspBusEventMap, type SchemeEvents, type ToastEvents, type FlyoutEvents, type HelpEvents, type PopoverEvents, type SizeEvents, type PageEventMap, type SeedColorRoot, } from "./shared/bus-events.js";
72
72
  export * from "./shared/events.js";
73
73
  export { getImageDetails, releasePreviewUrl, type EspalierUploadImage, type ImageDetailsOptions, type SelectedUploadImage, type ExistingUploadImage, type ExistingImage, type ResponsiveImageUrl, type UploadCallbacks, type UploadEventDetail, } from "./image-upload/image-helpers.js";
74
- export { calculatePhotoLayout, type LayoutImage, type PhotoRow, } from "./shared/justified-layout.js";
74
+ export { calculatePhotoLayout, normalizeMaxImagesPerRow, type LayoutImage, type PhotoRow, } from "./shared/justified-layout.js";
75
75
  export { type TypeaheadFetchItems } from "./pickers/types.js";
76
76
  export { type EspalierTheme, type LightnessKey, type LightnessReference, type PartialTheme, type PartialThemeContexts, type PartialThemeTones, type ThemeContext, type ThemeContexts, type ThemeTones, type ToneReference, type VariantColorSource, encodeTheme, parseTheme, mergePartials, layerThemes, validateThemePair, resolveContextTheme, isObjectRecord, isSemanticColorName, buildTaprootLightTheme, buildTaprootDarkTheme, NESTED_THEME_KEYS, } from "./shared/theme.js";
77
77
  export { auditDataPalette, describePaletteCollision, generateSequentialRamp, generateDivergingRamp, COLOR_VISION_SIMULATIONS, DATA_SERIES_KEYS, DEFAULT_DATA_PALETTE, DEFAULT_DATA_RAMP_STEPS, DEFAULT_DIVERGING_NEUTRAL, MAX_DATA_RAMP_STEPS, MIN_DATA_COLOR_DISTANCE, MIN_DATA_RAMP_LIGHTNESS_STEP, MIN_DATA_RAMP_STEPS, type ColorVisionSimulation, type DataPalette, type DataPaletteIssue, type DataRamp, type DataRamps, type DataSeriesKey, type DivergingDataRamp, type DivergingRampOptions, type PartialDataRamp, type PartialDataRamps, type SequentialDataRamp, type SequentialRampOptions, } from "./shared/data-colors.js";
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- export*from"./box/esp-box.js";export*from"./action-menu/esp-action-menu.js";export*from"./action-menu/esp-action-menu-item.js";export*from"./avatar/esp-avatar.js";export*from"./avatar/esp-profile-chip.js";export*from"./badge/esp-badge.js";export*from"./checkbox/esp-checkbox.js";export*from"./checkbox/esp-checkbox-group.js";export*from"./data-cell/esp-data-cell.js";export*from"./radio-button/esp-radio-button.js";export*from"./radio-button/esp-radio-button-group.js";export*from"./repeater/esp-repeater.js";export*from"./breadcrumbs/esp-breadcrumbs.js";export*from"./button/esp-button.js";export*from"./button/esp-button-group.js";export*from"./color-picker/esp-color-picker.js";export*from"./flyout/esp-flyout.js";export*from"./form/esp-form.js";export*from"./form-item/esp-form-item.js";export*from"./help/esp-help-button.js";export*from"./help/esp-help-provider.js";export*from"./header/index.js";export*from"./page/esp-page.js";export*from"./section/esp-section.js";export*from"./stack/esp-stack.js";export*from"./row/esp-row.js";export*from"./popover/esp-popover.js";export*from"./dialog/esp-dialog.js";export*from"./empty-state/esp-empty-state.js";export*from"./image-upload/esp-image-upload.js";export*from"./file-upload/esp-file-upload.js";export*from"./font-picker/esp-font-picker.js";export*from"./focus-picker/esp-focus-picker.js";export*from"./footer/index.js";export*from"./grid/esp-grid.js";export*from"./image/esp-image.js";export*from"./image/image-focus.js";export*from"./lightbox/esp-lightbox.js";export*from"./info/esp-info.js";export*from"./input/esp-input.js";export*from"./textarea/esp-textarea.js";export*from"./pickers/esp-pick-one.js";export*from"./pickers/esp-pick-some.js";export*from"./pickers/esp-picker-menu.js";export*from"./pickers/esp-picker-item.js";export*from"./root/esp-root.js";export*from"./menu/index.js";export*from"./burger/esp-burger.js";export*from"./date-picker/esp-date-picker.js";export*from"./details/esp-details.js";export*from"./details/esp-details-group.js";export*from"./tabs/esp-tab.js";export*from"./tabs/esp-tab-group.js";export*from"./slider/esp-slider.js";export*from"./switch/esp-switch.js";export*from"./toaster/esp-toaster.js";export*from"./tooltip/esp-tooltip.js";export*from"./progress/esp-progress.js";export*from"./search/esp-search.js";export*from"./status-indicator/esp-status-indicator.js";export*from"./tree/esp-tree.js";export*from"./tree/esp-tree-item.js";import{DEFAULT_ICON_SPRITE_URL as _o,DEFAULT_ICON_VIEW_BOX as Io,INTENT_VARIANTS as ao,normalizeIntentVariant as io,getIconHrefForHost as lo,getIconSpriteUrl as Fo,getIconSpriteReference as Ao}from"./shared/intent-values.js";import{EspalierElementBase as So}from"./shared/esp-element-base.js";import{VALIDITY_CHANGED_EVENT as Lo}from"./shared/validation.js";import{FormFieldController as Do}from"./shared/form-field-controller.js";import{traverseToClosest as go}from"./shared/utilities.js";import{showToast as ho}from"./shared/toast-events.js";import{showFlyout as Mo,closeFlyout as Oo}from"./shared/flyout-events.js";import{requestHelp as Co}from"./shared/help-events.js";import{getEspBus as Uo}from"./shared/bus-events.js";export*from"./shared/events.js";import{getImageDetails as vo,releasePreviewUrl as yo}from"./image-upload/image-helpers.js";import{calculatePhotoLayout as ko}from"./shared/justified-layout.js";import{encodeTheme as wo,parseTheme as Bo,mergePartials as Ko,layerThemes as qo,validateThemePair as zo,resolveContextTheme as Xo,isObjectRecord as Yo,isSemanticColorName as jo,buildTaprootLightTheme as Jo,buildTaprootDarkTheme as Qo,NESTED_THEME_KEYS as Zo}from"./shared/theme.js";import{auditDataPalette as oe,describePaletteCollision as ee,generateSequentialRamp as re,generateDivergingRamp as te,COLOR_VISION_SIMULATIONS as me,DATA_SERIES_KEYS as pe,DEFAULT_DATA_PALETTE as Te,DEFAULT_DATA_RAMP_STEPS as fe,DEFAULT_DIVERGING_NEUTRAL as xe,MAX_DATA_RAMP_STEPS as Ee,MIN_DATA_COLOR_DISTANCE as _e,MIN_DATA_RAMP_LIGHTNESS_STEP as Ie,MIN_DATA_RAMP_STEPS as ae}from"./shared/data-colors.js";import{WEIGHT_LABELS as le,extractWeights as Fe,normalizeWeight as Ae,bestAvailableWeight as ne,extractFamily as Se,getFallbackFont as se,googleFontStylesheetUrl as Le,isLocalFontFamily as Re,normalizeFontFaceWeight as De,removeRuntimeGoogleFontLink as Ne,removeRuntimeGoogleFontLinks as ge,syncRuntimeGoogleFontLink as ce,RUNTIME_GOOGLE_FONT_OWNER_ATTR as he}from"./shared/font-helpers.js";import{compileFontPlan as Me,fontFallbackAliases as Oe,fontFallbackBaseIndexes as He,fontFaceRequestKey as Ce}from"./shared/font-plan.js";import{getGoogleFonts as Ue}from"./font-picker/esp-font-picker.js";import{ROOT_SURFACE as de,THEME_FIT_ANCHOR_FIELDS as be,THEME_FIT_APCA_FIELDS as ve,THEME_FIT_DATA_PALETTE_LINT_FIELDS as ye,THEME_FIT_DATA_PALETTE_LINT_IDS as Ve,THEME_FIT_EXPLICIT_VALUES as ke,THEME_FIT_LINT_FIELDS as We,THEME_FIT_LINT_IDS as we,THEME_FIT_LINT_SEVERITIES as Be,THEME_FIT_REPORT_FIELDS as Ke,THEME_FIT_SUITE_FIELDS as qe,THEME_FIT_TOKEN_FIELDS as ze,themeFitReport as Xe,themeFitRows as Ye,themeFitLints as je,themeFitReportSuite as Je,printThemeFitReport as Qe}from"./shared/theme-fit-report.js";import{ICON_SPRITE as $e,ICON_SPRITE_ID as or,installIconSprite as er}from"./icons/index.js";import{deriveLightnessRamp as tr,themeFromSwatches as mr}from"./shared/theme-swatches.js";export{me as COLOR_VISION_SIMULATIONS,pe as DATA_SERIES_KEYS,Te as DEFAULT_DATA_PALETTE,fe as DEFAULT_DATA_RAMP_STEPS,xe as DEFAULT_DIVERGING_NEUTRAL,_o as DEFAULT_ICON_SPRITE_URL,Io as DEFAULT_ICON_VIEW_BOX,So as EspalierElementBase,Do as FormFieldController,$e as ICON_SPRITE,or as ICON_SPRITE_ID,ao as INTENT_VARIANTS,Ee as MAX_DATA_RAMP_STEPS,_e as MIN_DATA_COLOR_DISTANCE,Ie as MIN_DATA_RAMP_LIGHTNESS_STEP,ae as MIN_DATA_RAMP_STEPS,Zo as NESTED_THEME_KEYS,de as ROOT_SURFACE,he as RUNTIME_GOOGLE_FONT_OWNER_ATTR,be as THEME_FIT_ANCHOR_FIELDS,ve as THEME_FIT_APCA_FIELDS,ye as THEME_FIT_DATA_PALETTE_LINT_FIELDS,Ve as THEME_FIT_DATA_PALETTE_LINT_IDS,ke as THEME_FIT_EXPLICIT_VALUES,We as THEME_FIT_LINT_FIELDS,we as THEME_FIT_LINT_IDS,Be as THEME_FIT_LINT_SEVERITIES,Ke as THEME_FIT_REPORT_FIELDS,qe as THEME_FIT_SUITE_FIELDS,ze as THEME_FIT_TOKEN_FIELDS,Lo as VALIDITY_CHANGED_EVENT,le as WEIGHT_LABELS,oe as auditDataPalette,ne as bestAvailableWeight,Qo as buildTaprootDarkTheme,Jo as buildTaprootLightTheme,ko as calculatePhotoLayout,Oo as closeFlyout,Me as compileFontPlan,tr as deriveLightnessRamp,ee as describePaletteCollision,wo as encodeTheme,Se as extractFamily,Fe as extractWeights,Ce as fontFaceRequestKey,Oe as fontFallbackAliases,He as fontFallbackBaseIndexes,te as generateDivergingRamp,re as generateSequentialRamp,Uo as getEspBus,se as getFallbackFont,Ue as getGoogleFonts,lo as getIconHrefForHost,Ao as getIconSpriteReference,Fo as getIconSpriteUrl,vo as getImageDetails,Le as googleFontStylesheetUrl,er as installIconSprite,Re as isLocalFontFamily,Yo as isObjectRecord,jo as isSemanticColorName,qo as layerThemes,Ko as mergePartials,De as normalizeFontFaceWeight,io as normalizeIntentVariant,Ae as normalizeWeight,Bo as parseTheme,Qe as printThemeFitReport,yo as releasePreviewUrl,Ne as removeRuntimeGoogleFontLink,ge as removeRuntimeGoogleFontLinks,Co as requestHelp,Xo as resolveContextTheme,Mo as showFlyout,ho as showToast,ce as syncRuntimeGoogleFontLink,je as themeFitLints,Xe as themeFitReport,Je as themeFitReportSuite,Ye as themeFitRows,mr as themeFromSwatches,go as traverseToClosest,zo as validateThemePair};
1
+ export*from"./box/esp-box.js";export*from"./action-menu/esp-action-menu.js";export*from"./action-menu/esp-action-menu-item.js";export*from"./avatar/esp-avatar.js";export*from"./avatar/esp-profile-chip.js";export*from"./badge/esp-badge.js";export*from"./checkbox/esp-checkbox.js";export*from"./checkbox/esp-checkbox-group.js";export*from"./data-cell/esp-data-cell.js";export*from"./radio-button/esp-radio-button.js";export*from"./radio-button/esp-radio-button-group.js";export*from"./repeater/esp-repeater.js";export*from"./breadcrumbs/esp-breadcrumbs.js";export*from"./button/esp-button.js";export*from"./button/esp-button-group.js";export*from"./color-picker/esp-color-picker.js";export*from"./flyout/esp-flyout.js";export*from"./form/esp-form.js";export*from"./form-item/esp-form-item.js";export*from"./help/esp-help-button.js";export*from"./help/esp-help-provider.js";export*from"./header/index.js";export*from"./page/esp-page.js";export*from"./section/esp-section.js";export*from"./stack/esp-stack.js";export*from"./row/esp-row.js";export*from"./popover/esp-popover.js";export*from"./dialog/esp-dialog.js";export*from"./empty-state/esp-empty-state.js";export*from"./image-upload/esp-image-upload.js";export*from"./file-upload/esp-file-upload.js";export*from"./font-picker/esp-font-picker.js";export*from"./focus-picker/esp-focus-picker.js";export*from"./footer/index.js";export*from"./grid/esp-grid.js";export*from"./image/esp-image.js";export*from"./image/image-focus.js";export*from"./lightbox/esp-lightbox.js";export*from"./info/esp-info.js";export*from"./input/esp-input.js";export*from"./textarea/esp-textarea.js";export*from"./pickers/esp-pick-one.js";export*from"./pickers/esp-pick-some.js";export*from"./pickers/esp-picker-menu.js";export*from"./pickers/esp-picker-item.js";export*from"./root/esp-root.js";export*from"./menu/index.js";export*from"./burger/esp-burger.js";export*from"./date-picker/esp-date-picker.js";export*from"./details/esp-details.js";export*from"./details/esp-details-group.js";export*from"./tabs/esp-tab.js";export*from"./tabs/esp-tab-group.js";export*from"./slider/esp-slider.js";export*from"./switch/esp-switch.js";export*from"./toaster/esp-toaster.js";export*from"./tooltip/esp-tooltip.js";export*from"./progress/esp-progress.js";export*from"./search/esp-search.js";export*from"./status-indicator/esp-status-indicator.js";export*from"./tree/esp-tree.js";export*from"./tree/esp-tree-item.js";import{DEFAULT_ICON_SPRITE_URL as _o,DEFAULT_ICON_VIEW_BOX as Io,INTENT_VARIANTS as ao,normalizeIntentVariant as io,getIconHrefForHost as lo,getIconSpriteUrl as Fo,getIconSpriteReference as no}from"./shared/intent-values.js";import{EspalierElementBase as So}from"./shared/esp-element-base.js";import{VALIDITY_CHANGED_EVENT as Lo}from"./shared/validation.js";import{FormFieldController as Do}from"./shared/form-field-controller.js";import{traverseToClosest as go}from"./shared/utilities.js";import{showToast as co}from"./shared/toast-events.js";import{showFlyout as Mo,closeFlyout as Oo}from"./shared/flyout-events.js";import{requestHelp as Co}from"./shared/help-events.js";import{getEspBus as Uo}from"./shared/bus-events.js";export*from"./shared/events.js";import{getImageDetails as vo,releasePreviewUrl as yo}from"./image-upload/image-helpers.js";import{calculatePhotoLayout as ko,normalizeMaxImagesPerRow as Wo}from"./shared/justified-layout.js";import{encodeTheme as Bo,parseTheme as zo,mergePartials as Ko,layerThemes as qo,validateThemePair as Xo,resolveContextTheme as Yo,isObjectRecord as jo,isSemanticColorName as Jo,buildTaprootLightTheme as Qo,buildTaprootDarkTheme as Zo,NESTED_THEME_KEYS as $o}from"./shared/theme.js";import{auditDataPalette as ee,describePaletteCollision as re,generateSequentialRamp as te,generateDivergingRamp as me,COLOR_VISION_SIMULATIONS as pe,DATA_SERIES_KEYS as Te,DEFAULT_DATA_PALETTE as fe,DEFAULT_DATA_RAMP_STEPS as xe,DEFAULT_DIVERGING_NEUTRAL as Ee,MAX_DATA_RAMP_STEPS as _e,MIN_DATA_COLOR_DISTANCE as Ie,MIN_DATA_RAMP_LIGHTNESS_STEP as ae,MIN_DATA_RAMP_STEPS as ie}from"./shared/data-colors.js";import{WEIGHT_LABELS as Fe,extractWeights as ne,normalizeWeight as Ae,bestAvailableWeight as Se,extractFamily as se,getFallbackFont as Le,googleFontStylesheetUrl as Re,isLocalFontFamily as De,normalizeFontFaceWeight as Ne,removeRuntimeGoogleFontLink as ge,removeRuntimeGoogleFontLinks as Pe,syncRuntimeGoogleFontLink as ce,RUNTIME_GOOGLE_FONT_OWNER_ATTR as he}from"./shared/font-helpers.js";import{compileFontPlan as Oe,fontFallbackAliases as He,fontFallbackBaseIndexes as Ce,fontFaceRequestKey as ue}from"./shared/font-plan.js";import{getGoogleFonts as Ge}from"./font-picker/esp-font-picker.js";import{ROOT_SURFACE as be,THEME_FIT_ANCHOR_FIELDS as ve,THEME_FIT_APCA_FIELDS as ye,THEME_FIT_DATA_PALETTE_LINT_FIELDS as Ve,THEME_FIT_DATA_PALETTE_LINT_IDS as ke,THEME_FIT_EXPLICIT_VALUES as We,THEME_FIT_LINT_FIELDS as we,THEME_FIT_LINT_IDS as Be,THEME_FIT_LINT_SEVERITIES as ze,THEME_FIT_REPORT_FIELDS as Ke,THEME_FIT_SUITE_FIELDS as qe,THEME_FIT_TOKEN_FIELDS as Xe,themeFitReport as Ye,themeFitRows as je,themeFitLints as Je,themeFitReportSuite as Qe,printThemeFitReport as Ze}from"./shared/theme-fit-report.js";import{ICON_SPRITE as or,ICON_SPRITE_ID as er,installIconSprite as rr}from"./icons/index.js";import{deriveLightnessRamp as mr,themeFromSwatches as pr}from"./shared/theme-swatches.js";export{pe as COLOR_VISION_SIMULATIONS,Te as DATA_SERIES_KEYS,fe as DEFAULT_DATA_PALETTE,xe as DEFAULT_DATA_RAMP_STEPS,Ee as DEFAULT_DIVERGING_NEUTRAL,_o as DEFAULT_ICON_SPRITE_URL,Io as DEFAULT_ICON_VIEW_BOX,So as EspalierElementBase,Do as FormFieldController,or as ICON_SPRITE,er as ICON_SPRITE_ID,ao as INTENT_VARIANTS,_e as MAX_DATA_RAMP_STEPS,Ie as MIN_DATA_COLOR_DISTANCE,ae as MIN_DATA_RAMP_LIGHTNESS_STEP,ie as MIN_DATA_RAMP_STEPS,$o as NESTED_THEME_KEYS,be as ROOT_SURFACE,he as RUNTIME_GOOGLE_FONT_OWNER_ATTR,ve as THEME_FIT_ANCHOR_FIELDS,ye as THEME_FIT_APCA_FIELDS,Ve as THEME_FIT_DATA_PALETTE_LINT_FIELDS,ke as THEME_FIT_DATA_PALETTE_LINT_IDS,We as THEME_FIT_EXPLICIT_VALUES,we as THEME_FIT_LINT_FIELDS,Be as THEME_FIT_LINT_IDS,ze as THEME_FIT_LINT_SEVERITIES,Ke as THEME_FIT_REPORT_FIELDS,qe as THEME_FIT_SUITE_FIELDS,Xe as THEME_FIT_TOKEN_FIELDS,Lo as VALIDITY_CHANGED_EVENT,Fe as WEIGHT_LABELS,ee as auditDataPalette,Se as bestAvailableWeight,Zo as buildTaprootDarkTheme,Qo as buildTaprootLightTheme,ko as calculatePhotoLayout,Oo as closeFlyout,Oe as compileFontPlan,mr as deriveLightnessRamp,re as describePaletteCollision,Bo as encodeTheme,se as extractFamily,ne as extractWeights,ue as fontFaceRequestKey,He as fontFallbackAliases,Ce as fontFallbackBaseIndexes,me as generateDivergingRamp,te as generateSequentialRamp,Uo as getEspBus,Le as getFallbackFont,Ge as getGoogleFonts,lo as getIconHrefForHost,no as getIconSpriteReference,Fo as getIconSpriteUrl,vo as getImageDetails,Re as googleFontStylesheetUrl,rr as installIconSprite,De as isLocalFontFamily,jo as isObjectRecord,Jo as isSemanticColorName,qo as layerThemes,Ko as mergePartials,Ne as normalizeFontFaceWeight,io as normalizeIntentVariant,Wo as normalizeMaxImagesPerRow,Ae as normalizeWeight,zo as parseTheme,Ze as printThemeFitReport,yo as releasePreviewUrl,ge as removeRuntimeGoogleFontLink,Pe as removeRuntimeGoogleFontLinks,Co as requestHelp,Yo as resolveContextTheme,Mo as showFlyout,co as showToast,ce as syncRuntimeGoogleFontLink,Je as themeFitLints,Ye as themeFitReport,Qe as themeFitReportSuite,je as themeFitRows,pr as themeFromSwatches,go as traverseToClosest,Xo as validateThemePair};
@@ -17,6 +17,17 @@ export interface PhotoRow<T extends LayoutImage = LayoutImage> {
17
17
  images: T[];
18
18
  height: number;
19
19
  }
20
+ /**
21
+ * Resolve a consumer-provided image-row ceiling to a usable positive integer.
22
+ * Values outside `1…maximum`, including non-numbers and non-finite values,
23
+ * return `fallback`. The default fallback and maximum leave row lengths
24
+ * unlimited.
25
+ *
26
+ * @param value Consumer-provided row ceiling to validate
27
+ * @param fallback Value returned when `value` is invalid
28
+ * @param maximum Largest accepted row ceiling
29
+ */
30
+ export declare function normalizeMaxImagesPerRow(value: unknown, fallback?: number, maximum?: number): number;
20
31
  /**
21
32
  * Compute justified layout rows for a list of images.
22
33
  *
@@ -39,5 +50,8 @@ export interface PhotoRow<T extends LayoutImage = LayoutImage> {
39
50
  * @param gap Horizontal gap in px between images within a row
40
51
  * @param maxRowHeight Ceiling for row heights — keeps a stretched final row
41
52
  * (or a single image) from ballooning past e.g. half the viewport height
53
+ * @param maxImagesPerRow Optional positive-integer ceiling for the number of
54
+ * images in each row. Omit it, or pass an invalid value, for an unlimited row
55
+ * length.
42
56
  */
43
- export declare function calculatePhotoLayout<T extends LayoutImage>(images: T[], containerWidth: number, targetRowHeight?: number, gap?: number, maxRowHeight?: number): PhotoRow<T>[];
57
+ export declare function calculatePhotoLayout<T extends LayoutImage>(images: T[], containerWidth: number, targetRowHeight?: number, gap?: number, maxRowHeight?: number, maxImagesPerRow?: number): PhotoRow<T>[];
@@ -1 +1 @@
1
- function T(o,u,h=220,l=8,w=Number.POSITIVE_INFINITY){if(o.length===0)return[];const i=t=>t.reduce((e,s)=>e+s.width/s.height,0),c=t=>{const e=(t.length-1)*l;return(u-e)/i(t)},d=t=>t>h?t/h:h/t,p=[];let n=[];const r=(t,e)=>{p.push({images:[...t],height:Math.min(e,w)})};for(let t=0;t<o.length;t++){n.push(o[t]);const s=i(n)*h+(n.length-1)*l>u,a=t===o.length-1;if(s&&n.length>1){const f=c(n),I=c(n.slice(0,-1));d(f)<=d(I)?r(n,f):(n.pop(),t--,r(n,I)),n=[]}else a&&(r(n,c(n)),n=[])}return p}export{T as calculatePhotoLayout};
1
+ function w(n,I=Number.POSITIVE_INFINITY,o=Number.POSITIVE_INFINITY){return typeof n=="number"&&Number.isInteger(n)&&n>0&&n<=o?n:I}function b(n,I,o=220,i=8,m=Number.POSITIVE_INFINITY,p=Number.POSITIVE_INFINITY){if(n.length===0)return[];const d=w(p),u=e=>e.reduce((r,h)=>r+h.width/h.height,0),s=e=>{const r=(e.length-1)*i;return(I-r)/u(e)},N=e=>e>o?e/o:o/e,l=[];let t=[];const c=(e,r)=>{l.push({images:[...e],height:Math.min(r,m)})};for(let e=0;e<n.length;e++){t.push(n[e]);const h=u(t)*o+(t.length-1)*i>I,g=e===n.length-1;if(h&&t.length>1){const T=s(t),f=s(t.slice(0,-1));N(T)<=N(f)?c(t,T):(t.pop(),e--,c(t,f)),t=[]}else t.length>=d?(c(t,s(t)),t=[]):g&&(c(t,s(t)),t=[])}return l}export{b as calculatePhotoLayout,w as normalizeMaxImagesPerRow};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taprootio/espalier",
3
- "version": "4.9.0",
3
+ "version": "4.10.0",
4
4
  "packageManager": "bun@1.3.12",
5
5
  "description": "Espalier — a themeable, accessible, framework-agnostic, enterprise-grade design system built on web standards and love.",
6
6
  "customElements": "custom-elements.json",