@taprootio/espalier 2.6.0 → 2.7.1

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.
@@ -1,8 +1,18 @@
1
1
  import { type PropertyValues } from "lit";
2
2
  import { EspalierElementBase } from "../shared/esp-element-base.js";
3
+ import { type FocusPoint } from "./image-focus.js";
3
4
  import "./esp-image-option.js";
5
+ export type ImageScrim = "auto" | "none" | "flat" | "top" | "bottom" | "left" | "right" | "radial";
6
+ export type ImageScrimStrength = "soft" | "medium" | "strong";
7
+ export type ImageTexture = "none" | "dots" | "halftone" | "paper" | "grain" | "grunge" | "scanlines" | "duotone";
8
+ export type ImageTextureScale = "fine" | "medium" | "coarse";
9
+ export type ImageBannerScheme = "auto" | "light" | "dark";
10
+ export type ImageContentPosition = "bottom-start" | "bottom" | "bottom-end" | "center" | "top-start" | "top" | "top-end";
11
+ /** Resolve a supported CSS aspect-ratio string, or null when it is invalid. */
12
+ export declare function parseImageRatio(value: string): string | null;
4
13
  /**
5
- * A responsive image with a native `<picture>`.
14
+ * A responsive image with a native `<picture>` and an optional
15
+ * focal-point-aware banner mode.
6
16
  *
7
17
  * `esp-image` accepts two authoring shapes:
8
18
  *
@@ -11,23 +21,14 @@ import "./esp-image-option.js";
11
21
  * a child and `esp-image` projects it through a `<slot>`, adding only the
12
22
  * layout box, border, and lightbox wiring. Because the image lives in the
13
23
  * served HTML it is discoverable by the preload scanner and visible to
14
- * crawlers, and it is fetched exactly once (the component renders no
15
- * competing internal image).
24
+ * crawlers, and it is fetched exactly once.
16
25
  * 2. **Declare [esp-image-option](/components/image/image-option) children** —
17
26
  * the editor / fallback form. `esp-image` builds a shadow `<picture>` and
18
- * `srcset` from them. Useful for editor previews before a native `<picture>`
19
- * has been generated.
27
+ * `srcset` from them. Use `local-image` when an editor needs a blob preview.
20
28
  *
21
- * Source precedence, most specific first: `local-image` → a slotted
22
- * `<picture>` / `<img>` → `esp-image-option` children. In every mode the host
23
- * reserves the layout box up front via `aspect-ratio` (from `original-width` /
24
- * `original-height`), so there is no layout shift while the image loads.
25
- *
26
- * @slot - A light-DOM `<picture>` or `<img>` to project, or
27
- * [esp-image-option](/components/image/image-option) children describing each
28
- * available size. When projecting, `alt` lives on your `<img>`; the component's
29
- * `caption` still drives the [esp-lightbox](/components/lightbox) drawer (the
30
- * two may differ).
29
+ * Source precedence is `local-image` → a projected `<picture>` / `<img>` →
30
+ * `esp-image-option` children. In every mode the host reserves its layout box
31
+ * up front from `original-width` / `original-height`, when supplied.
31
32
  *
32
33
  * ```html
33
34
  * <!-- Projected native <picture> — published markup, crawlable + preloadable -->
@@ -57,13 +58,9 @@ import "./esp-image-option.js";
57
58
  * </esp-image>
58
59
  * ```
59
60
  *
60
- * The component picks the responsive variant for its **own rendered box**: after
61
- * it upgrades it measures its width (via a `ResizeObserver`) and sets `sizes` to
62
- * that, so a small card or gallery column does not over-fetch — no hand-authored
63
- * `sizes` required. The `sizes` attribute is only a fallback: it covers the brief
64
- * window before the first measurement and the server-rendered *initial* fetch
65
- * (the browser's preload scanner runs before the component upgrades). Set it to
66
- * the layout's server-side estimate when that initial fetch matters:
61
+ * Once upgraded, the component measures its own width and updates `sizes` so a
62
+ * card or gallery column does not over-fetch. The `sizes` attribute covers the
63
+ * server-rendered initial fetch and the brief window before measurement:
67
64
  *
68
65
  * ```html
69
66
  * <esp-image
@@ -78,21 +75,173 @@ import "./esp-image-option.js";
78
75
  * </esp-image>
79
76
  * ```
80
77
  *
81
- * @cssprop --esp-image-border - Set the border property of the image.
78
+ * Banner mode gives all three source modes the same focal position, scrim,
79
+ * texture, and semantic overlay stack. `ratio` applies at wide widths and
80
+ * `compact-ratio` below the component-relative compact threshold.
82
81
  *
83
82
  * ```html
84
- * <style>
85
- * esp-image.with-custom-border {
86
- * --esp-image-border: 5px dashed orange;
87
- * }
88
- * </style>
89
- * <esp-image class="with-custom-border" original-width="1200" original-height="800" low-res="https://picsum.photos/300/200" sizes="100vw">
90
- * <esp-image-option width="400" url="https://picsum.photos/600/400"></esp-image-option>
91
- * <esp-image-option width="900" url="https://picsum.photos/900/600"></esp-image-option>
92
- * <esp-image-option width="1200" url="https://picsum.photos/1200/800"></esp-image-option>
83
+ * <esp-image
84
+ * banner
85
+ * ratio="3/1"
86
+ * compact-ratio="3/2"
87
+ * focus="0.65 0.4"
88
+ * scrim="auto"
89
+ * scrim-strength="medium"
90
+ * content-position="bottom-start"
91
+ * >
92
+ * <img src="/assets/focus-picker-unsplash.jpg" alt="A woman standing in a sunlit field with her back to the camera" />
93
+ * <h2 slot="overlay">Grow somewhere wonderful</h2>
93
94
  * </esp-image>
94
95
  * ```
95
96
  *
97
+ * `banner-scheme` sets the banner's polarity and defaults to the active
98
+ * scheme: a light page lightens the image behind dark text, a dark page
99
+ * darkens it behind light text. Fix either polarity explicitly when a banner
100
+ * must not follow the page scheme:
101
+ *
102
+ * ```html
103
+ * <esp-image banner ratio="3/1" banner-scheme="light" focus="0.65 0.4">
104
+ * <img src="/assets/focus-picker-unsplash.jpg" alt="A woman standing in a sunlit field with her back to the camera" />
105
+ * <h2 slot="overlay">Light banner: dark ink on a lifted image</h2>
106
+ * </esp-image>
107
+ * <esp-image banner ratio="3/1" banner-scheme="dark" focus="0.65 0.4">
108
+ * <img src="/assets/focus-picker-unsplash.jpg" alt="A woman standing in a sunlit field with her back to the camera" />
109
+ * <h2 slot="overlay">Dark banner: light ink on a deepened image</h2>
110
+ * </esp-image>
111
+ * ```
112
+ *
113
+ * Every procedural preset supports `fine`, `medium`, and `coarse` scales.
114
+ *
115
+ * ### Dots
116
+ *
117
+ * A uniform tiled ink screen (shown at `fine`):
118
+ *
119
+ * ```html
120
+ * <esp-image banner ratio="3/1" texture="dots" texture-scale="fine"><img src="/assets/canvas-bg.jpg" alt="A colorful floral pattern" /></esp-image>
121
+ * ```
122
+ *
123
+ * ### Halftone
124
+ *
125
+ * The dot screen ramping toward the content edge (shown at `medium`):
126
+ *
127
+ * ```html
128
+ * <esp-image banner ratio="3/1" texture="halftone" texture-scale="medium"><img src="/assets/canvas-bg.jpg" alt="A colorful floral pattern" /></esp-image>
129
+ * ```
130
+ *
131
+ * ### Paper
132
+ *
133
+ * Soft fractal-noise mottle, like printed stock (shown at `coarse`):
134
+ *
135
+ * ```html
136
+ * <esp-image banner ratio="3/1" texture="paper" texture-scale="coarse"><img src="/assets/canvas-bg.jpg" alt="A colorful floral pattern" /></esp-image>
137
+ * ```
138
+ *
139
+ * ### Grain
140
+ *
141
+ * High-frequency film grain that also kills banding (shown at `fine`):
142
+ *
143
+ * ```html
144
+ * <esp-image banner ratio="3/1" texture="grain" texture-scale="fine"><img src="/assets/canvas-bg.jpg" alt="A colorful floral pattern" /></esp-image>
145
+ * ```
146
+ *
147
+ * ### Grunge
148
+ *
149
+ * Thresholded turbulence, a distressed ink wash (shown at `medium`):
150
+ *
151
+ * ```html
152
+ * <esp-image banner ratio="3/1" texture="grunge" texture-scale="medium"><img src="/assets/canvas-bg.jpg" alt="A colorful floral pattern" /></esp-image>
153
+ * ```
154
+ *
155
+ * ### Scanlines
156
+ *
157
+ * Hairline rules along the block axis (shown at `coarse`):
158
+ *
159
+ * ```html
160
+ * <esp-image banner ratio="3/1" texture="scanlines" texture-scale="coarse"><img src="/assets/canvas-bg.jpg" alt="A colorful floral pattern" /></esp-image>
161
+ * ```
162
+ *
163
+ * ### Duotone
164
+ *
165
+ * The photo mapped onto two theme-derived hues:
166
+ *
167
+ * ```html
168
+ * <esp-image banner ratio="3/1" texture="duotone"><img src="/assets/canvas-bg.jpg" alt="A colorful floral pattern" /></esp-image>
169
+ * ```
170
+ *
171
+ * ### Consumer texture tokens
172
+ *
173
+ * Any `background-image` value — a tile the application serves, a data URI,
174
+ * or a gradient — can be wired directly through the public consumer-image
175
+ * token path (the optional `@taprootio/espalier/textures/banner-textures.css`
176
+ * stylesheet ships `.esp-texture-paper`/`-grain`/`-grunge` classes with the
177
+ * tiles inlined as data URIs, so nothing needs asset configuration):
178
+ *
179
+ * ```html
180
+ * <esp-image
181
+ * banner
182
+ * ratio="3/1"
183
+ * 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;"
184
+ * >
185
+ * <img src="/assets/canvas-bg.jpg" alt="A colorful floral pattern" />
186
+ * </esp-image>
187
+ * ```
188
+ *
189
+ * ### Registered textures
190
+ *
191
+ * Wiring those tokens on every banner is verbose and not reusable. The
192
+ * application's texture vocabulary lives on [esp-root](/components/root):
193
+ * register the look once — `registerImageTexture(name, definition)` from
194
+ * the module, or the root's `registerTexture` static in script-only
195
+ * contexts — and any banner under the root selects it by name. Live
196
+ * banners re-render on registration, so markup order and registration
197
+ * order never race, and the root's `registeredTextures()` enumerates the
198
+ * presets for an editor's texture picker:
199
+ *
200
+ * ```html
201
+ * <esp-image id="registered-texture-banner" banner ratio="3/1" texture="pressed-paper">
202
+ * <img src="/assets/canvas-bg.jpg" alt="A colorful floral pattern" />
203
+ * </esp-image>
204
+ * <script>
205
+ * const banner = findById("registered-texture-banner");
206
+ * const root = banner.closest("esp-root");
207
+ * root.constructor.registerTexture("pressed-paper", {
208
+ * image:
209
+ * "repeating-linear-gradient(-45deg, oklch(1 0 0 / 0.3) 0 3px, transparent 3px 9px)",
210
+ * blendMode: "soft-light",
211
+ * opacity: 0.9,
212
+ * });
213
+ * </script>
214
+ * ```
215
+ *
216
+ * @slot - A projected native image, or `esp-image-option` children. When
217
+ * projecting, `alt` lives on the consumer's `<img>`; `caption` remains the
218
+ * lightbox text.
219
+ * @slot overlay - Semantic banner content above the image and decorative layers.
220
+ * @csspart frame - The stable banner/image frame.
221
+ * @csspart image - The stable media-box wrapper in every source mode.
222
+ * @csspart scrim - The decorative legibility layer.
223
+ * @csspart texture - The procedural and consumer-supplied texture layers.
224
+ * @csspart overlay - The positioned semantic overlay region.
225
+ * @cssprop --esp-image-border - Border; defaults to `2px solid var(--esp-color-border)` or none in banner mode.
226
+ * @cssprop --esp-image-border-radius - Corner radius; defaults to `var(--esp-size-border-radius)` or zero in banner mode.
227
+ * @cssprop --esp-image-object-position - Final object-position override.
228
+ * @cssprop --esp-image-compact-width - Component-width threshold; defaults to `40rem`.
229
+ * @cssprop --esp-image-scrim-color - Scrim ink; defaults to the theme background when the banner polarity matches the scheme, else a polarity-pinned derivation.
230
+ * @cssprop --esp-image-scrim-opacity - Scrim-strength opacity override.
231
+ * @cssprop --esp-image-texture-color - Procedural texture ink; defaults to a theme-derived color following `banner-scheme`.
232
+ * @cssprop --esp-image-texture-opacity - Procedural texture opacity override.
233
+ * @cssprop --esp-image-texture-scale - CSS-gradient texture pitch override.
234
+ * @cssprop --esp-image-texture-blend-mode - Texture blend-mode override.
235
+ * @cssprop --esp-image-texture-image - Consumer-supplied texture background image.
236
+ * @cssprop --esp-image-texture-image-opacity - Consumer texture opacity.
237
+ * @cssprop --esp-image-texture-repeat - Consumer texture repeat.
238
+ * @cssprop --esp-image-texture-position - Consumer texture position.
239
+ * @cssprop --esp-image-texture-size - Consumer texture size.
240
+ * @cssprop --esp-image-duotone-shadow-color - Duotone shadow stop; defaults to a dark stop derived from the theme primary.
241
+ * @cssprop --esp-image-duotone-highlight-color - Duotone highlight stop; defaults to a light stop derived from the theme complementary.
242
+ * @cssprop --esp-image-overlay-padding - Overlay content padding.
243
+ * @cssprop --esp-image-overlay-color - Overlay text color; defaults to the theme heading color when the banner polarity matches the scheme.
244
+ * @cssprop --esp-image-overlay-text-shadow - Overlay text shadow; defaults to a soft polarity-matched halo. Set to `none` to disable.
96
245
  * @docPageTitle Image
97
246
  * @docUrl /components/image
98
247
  * @menuGroup Media
@@ -101,68 +250,74 @@ import "./esp-image-option.js";
101
250
  */
102
251
  export declare class EspalierImage extends EspalierElementBase {
103
252
  /**
104
- * The original height of the image; used to calculate the orientation of
105
- * the image and set the element aspect ratio.
106
- * @type {number}
253
+ * Original image height, used with `originalWidth` to reserve the intrinsic
254
+ * aspect ratio and report portrait orientation.
107
255
  */
108
256
  originalHeight: number;
109
257
  /**
110
- * The original width of the image; used to calculate the orientation of
111
- * the image and set the element aspect ratio.
112
- * @type {number}
258
+ * Original image width, used with `originalHeight` to reserve the intrinsic
259
+ * aspect ratio and report portrait orientation.
113
260
  */
114
261
  originalWidth: number;
115
262
  /**
116
- * The fallback image URL. Used as the `<img src>` the variant the browser
117
- * loads when it cannot evaluate the responsive `srcset` (very old browsers),
118
- * and the SEO/no-JS source. Named `low-res` because a small, fast-loading
119
- * variant is the sensible default to point it at.
120
- * @type {string}
263
+ * Fallback image URL used as the internal `<img src>` when responsive options
264
+ * build a shadow `<picture>`.
121
265
  */
122
266
  imageUrl: string;
123
267
  /**
124
- * A local image URL (e.g. a `blob:` URL) that takes priority over responsive
125
- * selection. When set, a plain `<img>` with no responsive sources is rendered
126
- * so the local preview always wins. Used during upload to show the user's
127
- * local file immediately while CDN variants are generated in the background.
128
- * @type {string}
268
+ * Local preview URL, commonly a `blob:` URL. It takes priority over projected
269
+ * content and responsive options so an editor can show a new upload at once.
129
270
  */
130
271
  localImage: string;
131
272
  /**
132
- * A caption for the image. Drives the visible caption in the
133
- * [esp-lightbox](/components/lightbox) drawer in every mode. In the
134
- * fallback / `local-image` paths it is also applied as the internal
135
- * `<img alt>`; when projecting a light-DOM `<picture>` / `<img>`, put `alt` on
136
- * your own `<img>` (it may legitimately differ from the drawer caption).
137
- * @type {string}
273
+ * Lightbox caption in every mode, and internal image alternative text for
274
+ * local-image and responsive-option modes.
138
275
  */
139
276
  caption: string;
140
277
  /**
141
- * Fallback `sizes` hint used only until the element measures its own rendered
142
- * width (and for the server-rendered *initial* fetch, which the browser
143
- * performs before this component upgrades). Once measured, the element
144
- * advertises its actual box width instead — in both the built-from-options
145
- * and projection paths — so the browser picks the smallest variant that
146
- * covers the box (× DPR), in any layout, with no hand-authored `sizes`. Set
147
- * this when you need a specific server-side hint (e.g. the generator's
148
- * per-layout value); otherwise leave it and the component sizes itself.
149
- * @type {string}
278
+ * Initial/server-side responsive-image size hint. After upgrade the component
279
+ * replaces this with its measured rendered width.
150
280
  */
151
281
  sizes: string;
152
282
  /**
153
- * The native `loading` strategy for the **internally rendered** `<img>` (the
154
- * fallback and `local-image` paths). Use `lazy` to defer offscreen gallery
155
- * images until they approach the viewport. Defaults to `eager`. Has no effect
156
- * in projection mode, where the projected `<img>` carries its own `loading`,
157
- * `decoding`, and `fetchpriority` directly.
158
- * @type {"eager" | "lazy"}
283
+ * Native loading strategy for internally rendered images. Projected images
284
+ * retain the consumer's own loading attributes.
159
285
  */
160
286
  loading: "eager" | "lazy";
287
+ /** Enable borderless banner defaults and the decorative/content layer stack. */
288
+ banner: boolean;
289
+ /**
290
+ * Banner polarity. `dark` darkens the image behind light text; `light`
291
+ * lightens it behind dark text. `auto` (the default) follows the active
292
+ * scheme, so a light page lightens and a dark page darkens.
293
+ */
294
+ bannerScheme: ImageBannerScheme;
295
+ /** Legibility-layer anchor. `auto` follows overlay presence and position. */
296
+ scrim: ImageScrim;
297
+ /** Preset opacity for the scrim. */
298
+ scrimStrength: ImageScrimStrength;
299
+ /**
300
+ * Optional texture: a built-in procedural preset, or the name of a
301
+ * texture registered with `registerImageTexture`. Unknown names render
302
+ * as `none` (and recover if the name is registered later).
303
+ */
304
+ texture: string;
305
+ /** CSS pitch or discrete SVG-filter variant for the selected texture. */
306
+ textureScale: ImageTextureScale;
307
+ /** Logical position for semantic banner content. */
308
+ contentPosition: ImageContentPosition;
309
+ /** Normalized focal point used for cover cropping. */
310
+ focusPoint: FocusPoint;
311
+ /** Banner target aspect ratio, as a positive number or `number / number`. */
312
+ ratio: string;
313
+ /** Aspect ratio used below the component-relative compact threshold. */
314
+ compactRatio: string;
161
315
  get isPortrait(): boolean;
162
316
  connectedCallback(): void;
163
317
  disconnectedCallback(): void;
164
318
  protected willUpdate(changedProperties: PropertyValues): void;
165
- protected updated(changed: PropertyValues): void;
319
+ protected firstUpdated(changedProperties: PropertyValues): void;
320
+ protected updated(changedProperties: PropertyValues): void;
166
321
  protected render(): import("lit-html").TemplateResult<1>;
167
322
  static styles: import("lit").CSSResult[];
168
323
  }