@taprootio/espalier 6.0.0 → 6.2.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,26 @@
1
+ # 6.2.0
2
+
3
+ Banners gain a soft height contract, so a banner capped to the first fold or filling the viewport never clips its copy.
4
+
5
+ - **New: `--esp-image-banner-max-block-size`**, a soft cap. A banner is the smaller of its ratio height and the cap while its copy fits, exactly as with the same `max-block-size`, and grows past the cap when the copy (with its spacing) would not fit, giving the cap back once it does.
6
+ - **New: `--esp-image-banner-block-size`**, a soft target that replaces the ratio as the banner height (viewport-fill banners), and that copy grows the banner past in the same way.
7
+ - In the compact band (`compact-placement="below"`), the soft properties size only the image box and the band adds below it, so band copy is never clipped.
8
+ - `@taprootio/espalier/image/banner-static.css` reads both properties, so server-rendered banners lay out the same before and after upgrade (where container style queries exist: Chrome and Safari 18+; elsewhere a soft height applies at upgrade).
9
+ - **Fixed: a banner nested in another banner's overlay inherited the outer banner's ratio** when it had none of its own; it now keeps its image's own size. A real `max-block-size`, `block-size`, or `min-block-size` keeps its hard meaning, alongside a soft property too, and banners that set neither soft property are unchanged.
10
+
11
+ # 6.1.0
12
+
13
+ Banner overlays gain a copy-stack layout contract (supporting copy, one action, middle positions, a compact band, and a static first-paint layout), and pickers no longer pick the option a finger lands on when it starts scrolling.
14
+
15
+ - **New: middle content positions.** `esp-image`'s `content-position` takes `middle-start` and `middle-end`, centering the copy against the inline start or end edge and mirroring in right-to-left text. An `auto` scrim follows the copy to that edge.
16
+ - **New: a banner copy stack.** Beside `slot="overlay"` (the headline), banners take `slot="overlay-supporting"` for one supporting sentence and `slot="overlay-action"` for one native link or button. Together they lay out as one stack: grouped spacing (`--esp-image-overlay-gap`), the supporting sentence on the lead type role, a line length bounded by `--esp-image-overlay-measure` (`36rem` by default), and an action with a comfortable target and a two-tone focus ring that reads over any photograph. The scrim now covers every piece of overlay copy, not just the headline. A headline on its own lays out exactly as before.
17
+ - **New: `compact-placement="below"`.** Below the compact width the copy moves into a band under the image, on the banner scheme's own surface and ink, for crops that lose their quiet region at narrow widths. The default, `overlay`, keeps today's behavior.
18
+ - **New: `@taprootio/espalier/image/banner-static.css`.** A stylesheet that lays server-rendered banners out before `esp-image` upgrades, from the same attributes plus `--esp-image-static-ratio` and `--esp-image-static-compact-ratio`, so nothing shifts at hydration. The Image banners guide lists its inputs and limits.
19
+ - **Fixed: banner copy could be clipped.** Copy taller than the ratio's frame (text enlarged to 200%, long localized labels, a narrow column) was cut off; the banner now grows to fit it, the image covers the grown frame, and words too long for a line wrap. A banner whose copy fits keeps exactly its current geometry, and an explicit, capped, or stretched block size still wins.
20
+ - **Banner copy never moves with the image.** A transform or animation on a banner's projected image or `::part(image)` stays inside the image box, and under `prefers-reduced-motion` those animations and transitions stop.
21
+ - **Fixed: swiping a picker's option list picked the first option touched.** Pickers picked on the press, before the browser could tell a tap from a scroll, so on a phone every swipe chose an option and closed the menu; `esp-pick-one` also picked a hidden option behind its input when the input was tapped after scrolling. Options are now picked by the tap that ends on them, a press keeps focus (and a typeahead query) in the input, and a tap on the input only focuses it. This applies to `esp-pick-one`, `esp-pick-some`, and `esp-font-picker`, with or without typeahead.
22
+ - **Fixed: typing a typeahead filter after scrolling a long picker list could throw** while the list shrank.
23
+
1
24
  # 6.0.0
2
25
 
3
26
  Espalier 6 removes `esp-video`'s unused streaming adapter, and pickers now show an item's new label when its text changes after it was selected.
@@ -0,0 +1,289 @@
1
+ /*
2
+ * Espalier esp-image banner: static layout before the component upgrades.
3
+ *
4
+ * Server-rendered `<esp-image banner>` markup paints with page styles alone
5
+ * until Espalier's module defines the element. This sheet lays the same
6
+ * markup out from its attributes in that window, matching the upgraded
7
+ * banner's geometry so nothing shifts at hydration: the frame ratio, focal
8
+ * cropping, every `content-position`, the copy stack and its measure, growth
9
+ * for long or enlarged copy, and `compact-placement="below"`.
10
+ *
11
+ * Every rule is scoped to `esp-image[banner]:not(:defined)`, so it stops
12
+ * applying the moment the element upgrades.
13
+ *
14
+ * Inputs the server sets inline (CSS cannot read a ratio from an attribute):
15
+ * --esp-image-static-ratio the `ratio` value, e.g. `3 / 1`
16
+ * --esp-image-static-compact-ratio the `compact-ratio` value, if any
17
+ * --esp-image-object-position the focal point, e.g. `65% 40%`
18
+ *
19
+ * It also reads the element's own soft height properties, exactly as the
20
+ * upgraded element does: --esp-image-banner-max-block-size (a soft cap) and
21
+ * --esp-image-banner-block-size (a soft target).
22
+ *
23
+ * Limits, all documented in the image banners guide: the compact switch uses
24
+ * the default 40rem width (a custom `--esp-image-compact-width` cannot apply
25
+ * before upgrade); no scrim or texture paints until upgrade; copy must be
26
+ * emitted in stack order (headline, supporting, action); and a custom
27
+ * `--esp-image-overlay-padding` must be a single length for the compact band.
28
+ */
29
+
30
+ esp-image[banner]:not(:defined) {
31
+ --_esp-image-static-pad: var(--esp-image-overlay-padding, var(--esp-size-padding-page, 1rem));
32
+ --_esp-image-static-gap: var(--esp-image-overlay-gap, var(--esp-size-small, 0.75rem));
33
+ --_esp-image-static-before: 1fr;
34
+ --_esp-image-static-after: 0fr;
35
+ display: grid;
36
+ grid-template-columns: minmax(0, 1fr);
37
+ /* media | space before | headline | supporting | action | space after */
38
+ grid-template-rows:
39
+ auto minmax(0, var(--_esp-image-static-before)) auto auto auto
40
+ minmax(0, var(--_esp-image-static-after));
41
+ justify-items: start;
42
+ text-align: start;
43
+ overflow: clip;
44
+ overflow-wrap: anywhere;
45
+ container-type: inline-size;
46
+ }
47
+
48
+ /* The image spans every row at the banner ratio (its own natural size when
49
+ there is none), so the banner is as tall as the ratio or the copy,
50
+ whichever is taller, and the image stretches to cover it all. An empty
51
+ spacer spans the rows too; it carries the soft height (below). */
52
+ esp-image[banner]:not(:defined)::before {
53
+ content: "";
54
+ grid-row: 1 / -1;
55
+ grid-column: 1;
56
+ align-self: start;
57
+ justify-self: stretch;
58
+ aspect-ratio: var(--esp-image-static-ratio, auto);
59
+ block-size: var(--esp-image-banner-block-size, auto);
60
+ max-block-size: var(--esp-image-banner-max-block-size, none);
61
+ }
62
+ esp-image[banner]:not(:defined) > picture,
63
+ esp-image[banner]:not(:defined) > img {
64
+ display: block;
65
+ grid-row: 1 / -1;
66
+ grid-column: 1;
67
+ align-self: stretch;
68
+ justify-self: stretch;
69
+ inline-size: 100%;
70
+ block-size: auto;
71
+ aspect-ratio: var(--esp-image-static-ratio, auto);
72
+ object-fit: cover;
73
+ object-position: var(--esp-image-object-position, center);
74
+ }
75
+ esp-image[banner]:not(:defined) > picture > img {
76
+ display: block;
77
+ inline-size: 100%;
78
+ block-size: 100%;
79
+ object-fit: cover;
80
+ object-position: var(--esp-image-object-position, center);
81
+ }
82
+ esp-image[banner]:not(:defined) > esp-image-option {
83
+ display: none;
84
+ }
85
+
86
+ /* Vertical placement: which side of the copy takes the free space. */
87
+ esp-image[banner]:not(:defined):is(
88
+ [content-position="top-start"],
89
+ [content-position="top"],
90
+ [content-position="top-end"]
91
+ ) {
92
+ --_esp-image-static-before: 0fr;
93
+ --_esp-image-static-after: 1fr;
94
+ }
95
+ esp-image[banner]:not(:defined):is(
96
+ [content-position="middle-start"],
97
+ [content-position="center"],
98
+ [content-position="middle-end"]
99
+ ) {
100
+ --_esp-image-static-before: 1fr;
101
+ --_esp-image-static-after: 1fr;
102
+ }
103
+ /* Inline placement, logical so right-to-left mirrors. */
104
+ esp-image[banner]:not(:defined):is(
105
+ [content-position="top"],
106
+ [content-position="center"],
107
+ [content-position="bottom"]
108
+ ) {
109
+ justify-items: center;
110
+ text-align: center;
111
+ }
112
+ esp-image[banner]:not(:defined):is(
113
+ [content-position="top-end"],
114
+ [content-position="middle-end"],
115
+ [content-position="bottom-end"]
116
+ ) {
117
+ justify-items: end;
118
+ text-align: end;
119
+ }
120
+
121
+ /* The copy. The upgraded overlay pads the copy as a block; here each piece of
122
+ copy carries that padding itself, as a margin, so the image stays
123
+ full-bleed. A headline on its own keeps its own margins, as it does once
124
+ upgraded, by taking the padding as padding instead. */
125
+ esp-image[banner]:not(:defined) > :is([slot="overlay"], [slot="overlay-supporting"], [slot="overlay-action"]) {
126
+ grid-column: 1;
127
+ min-inline-size: 0;
128
+ }
129
+ esp-image[banner]:not(:defined) > [slot="overlay"] {
130
+ grid-row: 3;
131
+ max-inline-size: var(--esp-image-overlay-measure, none);
132
+ padding: var(--_esp-image-static-pad);
133
+ }
134
+ /* A lone headline carries the overlay padding here; content-box keeps a
135
+ measure bounding its text, as after upgrade, where that padding belongs to
136
+ the overlay rather than the headline. */
137
+ esp-image[banner]:not(:defined):not(:has(> [slot="overlay-supporting"], > [slot="overlay-action"]))
138
+ > [slot="overlay"] {
139
+ box-sizing: content-box;
140
+ }
141
+ esp-image[banner]:not(:defined) > [slot="overlay-supporting"] {
142
+ grid-row: 4;
143
+ }
144
+ esp-image[banner]:not(:defined) > [slot="overlay-action"] {
145
+ grid-row: 5;
146
+ }
147
+
148
+ /* A copy stack: supporting copy or an action is present. */
149
+ esp-image[banner]:not(:defined):has(> [slot="overlay-supporting"], > [slot="overlay-action"])
150
+ > :is([slot="overlay"], [slot="overlay-supporting"], [slot="overlay-action"]) {
151
+ max-inline-size: var(--esp-image-overlay-measure, 36rem);
152
+ margin: var(--_esp-image-static-pad) !important;
153
+ }
154
+ esp-image[banner]:not(:defined):has(> [slot="overlay-supporting"], > [slot="overlay-action"])
155
+ > [slot="overlay"] {
156
+ padding: 0;
157
+ }
158
+ esp-image[banner]:not(:defined):has(> [slot="overlay-supporting"], > [slot="overlay-action"])
159
+ > [slot="overlay"]:has(~ [slot="overlay-supporting"], ~ [slot="overlay-action"]) {
160
+ margin-block-end: 0 !important;
161
+ }
162
+ esp-image[banner]:not(:defined) > [slot="overlay"] ~ [slot="overlay-supporting"] {
163
+ margin-block-start: var(--_esp-image-static-gap) !important;
164
+ }
165
+ esp-image[banner]:not(:defined) > [slot="overlay-supporting"]:has(~ [slot="overlay-action"]) {
166
+ margin-block-end: 0 !important;
167
+ }
168
+ esp-image[banner]:not(:defined)
169
+ > :is([slot="overlay"], [slot="overlay-supporting"])
170
+ ~ [slot="overlay-action"] {
171
+ margin-block-start: calc(2 * var(--_esp-image-static-gap)) !important;
172
+ }
173
+
174
+ /* Defaults the upgraded overlay gives its supporting copy and action. They
175
+ carry no specificity, so page styles win here exactly as they do after
176
+ upgrade. */
177
+ :where(esp-image[banner]:not(:defined)) > :where([slot="overlay-supporting"]) {
178
+ font-family: var(--esp-font-body, inherit);
179
+ font-weight: var(--esp-type-lead-font-weight, var(--esp-font-weight-body, 400));
180
+ font-size: var(--esp-type-lead-font-size, 1.25rem);
181
+ line-height: 1.4;
182
+ }
183
+ :where(esp-image[banner]:not(:defined)) > :where(a[slot="overlay-action"], button[slot="overlay-action"]) {
184
+ display: inline-flex;
185
+ align-items: center;
186
+ justify-content: center;
187
+ box-sizing: border-box;
188
+ min-block-size: 2.75rem;
189
+ padding: 0.5em 1.25em;
190
+ border: 2px solid currentColor;
191
+ border-radius: var(--esp-size-border-radius, 0.375rem);
192
+ background: transparent;
193
+ font-family: var(--esp-font-body, inherit);
194
+ font-size: var(--esp-type-normal, 1rem);
195
+ font-weight: 600;
196
+ line-height: 1.2;
197
+ text-align: center;
198
+ }
199
+
200
+ /* Compact band: below the default compact width the image keeps the compact
201
+ ratio in its own row and the copy follows on the banner scheme's surface. */
202
+ esp-image[banner][compact-placement="below"]:not(:defined) {
203
+ background-color: var(--esp-image-scrim-color, var(--esp-color-background, oklch(0.98 0 0)));
204
+ }
205
+ esp-image[banner][compact-placement="below"][banner-scheme="light"]:not(:defined) {
206
+ background-color: var(
207
+ --esp-image-scrim-color,
208
+ oklch(from var(--esp-color-background, oklch(0.98 0 0)) 0.97 c h)
209
+ );
210
+ }
211
+ esp-image[banner][compact-placement="below"][banner-scheme="dark"]:not(:defined) {
212
+ background-color: var(
213
+ --esp-image-scrim-color,
214
+ oklch(from var(--esp-color-background, oklch(0.98 0 0)) 0.13 c h)
215
+ );
216
+ }
217
+ @container (width < 40rem) {
218
+ esp-image[banner]:not(:defined) > :is(picture, img) {
219
+ aspect-ratio: var(--esp-image-static-compact-ratio, var(--esp-image-static-ratio, auto));
220
+ }
221
+ /* At compact widths a compact ratio alone is enough for a soft cap (see
222
+ the soft height rule below). */
223
+ @container (style(--esp-image-static-compact-ratio) and style(--esp-image-banner-max-block-size) and (not style(--esp-image-banner-max-block-size: none)) and (not style(--esp-image-banner-max-block-size: auto))) {
224
+ esp-image[banner]:not(:defined) > :is(picture, img) {
225
+ contain: size;
226
+ aspect-ratio: auto;
227
+ }
228
+ }
229
+ /* A pseudo-element can query its own originating element. */
230
+ esp-image[banner]:not(:defined)::before {
231
+ aspect-ratio: var(--esp-image-static-compact-ratio, var(--esp-image-static-ratio, auto));
232
+ }
233
+ /* The band: the image box takes the first row, sized like the spacer (the
234
+ soft height sizes only the image box), and the copy follows below. */
235
+ esp-image[banner][compact-placement="below"]:not(:defined)::before {
236
+ display: none;
237
+ }
238
+ esp-image[banner][compact-placement="below"]:not(:defined) > :is(picture, img) {
239
+ grid-row: 1;
240
+ contain: none;
241
+ aspect-ratio: var(--esp-image-static-compact-ratio, var(--esp-image-static-ratio, auto));
242
+ block-size: var(--esp-image-banner-block-size, auto);
243
+ max-block-size: var(--esp-image-banner-max-block-size, none);
244
+ }
245
+ esp-image[banner][compact-placement="below"]:not(:defined)
246
+ > :is([slot="overlay"], [slot="overlay-supporting"], [slot="overlay-action"]) {
247
+ color: var(--esp-image-overlay-color, var(--esp-color-headings, oklch(0.2 0 0)));
248
+ text-shadow: none;
249
+ }
250
+ esp-image[banner][compact-placement="below"][banner-scheme="light"]:not(:defined)
251
+ > :is([slot="overlay"], [slot="overlay-supporting"], [slot="overlay-action"]) {
252
+ color: var(
253
+ --esp-image-overlay-color,
254
+ oklch(from var(--esp-color-headings, oklch(0.2 0 0)) 0.2 c h)
255
+ );
256
+ }
257
+ esp-image[banner][compact-placement="below"][banner-scheme="dark"]:not(:defined)
258
+ > :is([slot="overlay"], [slot="overlay-supporting"], [slot="overlay-action"]) {
259
+ color: var(
260
+ --esp-image-overlay-color,
261
+ oklch(from var(--esp-color-headings, oklch(0.2 0 0)) 0.97 c h)
262
+ );
263
+ }
264
+ }
265
+
266
+ /* Soft height, matching the element: a soft target, or a soft cap on a
267
+ banner with a static ratio, hands sizing to the spacer, so the image gives
268
+ up its own size (size containment, no ratio) and stretches over whatever
269
+ the spacer or the copy sets. none and auto count as unset. This needs
270
+ container style queries (Chrome, Safari 18+); elsewhere the soft height
271
+ applies at upgrade, and banners without it are unaffected. It follows the
272
+ compact rules so it wins over their image ratio; the band's image rule is
273
+ more specific and keeps its own sizing. */
274
+ @container (style(--esp-image-banner-block-size) and (not style(--esp-image-banner-block-size: none)) and (not style(--esp-image-banner-block-size: auto))) or (style(--esp-image-static-ratio) and style(--esp-image-banner-max-block-size) and (not style(--esp-image-banner-max-block-size: none)) and (not style(--esp-image-banner-max-block-size: auto))) {
275
+ esp-image[banner]:not(:defined) > :is(picture, img) {
276
+ contain: size;
277
+ aspect-ratio: auto;
278
+ }
279
+ }
280
+
281
+ /* The image never moves under reduced motion, before upgrade as after. */
282
+ @media (prefers-reduced-motion: reduce) {
283
+ esp-image[banner]:not(:defined) > picture,
284
+ esp-image[banner]:not(:defined) > picture > img,
285
+ esp-image[banner]:not(:defined) > img {
286
+ animation: none !important;
287
+ transition: none !important;
288
+ }
289
+ }