|
52 | 52 | flex-direction: column; |
53 | 53 | } |
54 | 54 |
|
55 | | - /* Resize handles */ |
| 55 | + /* |
| 56 | + * Resize handles. |
| 57 | + * |
| 58 | + * These rules are one class shallower than either vendor sheet styles the |
| 59 | + * same elements — `react-resizable` uses `.react-resizable-handle`, |
| 60 | + * `react-grid-layout` uses `.react-grid-item > .react-resizable-handle.react-resizable-handle-s` |
| 61 | + * — so for as long as those sheets shipped unlayered, not one declaration |
| 62 | + * below here rendered. Both packages are now in `liebe-base.vendor` |
| 63 | + * (`src/theme/cssLayers.ts`), which puts this block in charge. |
| 64 | + * |
| 65 | + * The three resets on the base rule are what that costs, and each neutralises |
| 66 | + * a vendor declaration Liebe has no counterpart for — so without them the |
| 67 | + * vendor's value survives underneath Liebe's and the two compose into |
| 68 | + * something neither sheet describes: |
| 69 | + * |
| 70 | + * - `background-image`: react-resizable paints a grey grip SVG. Liebe draws |
| 71 | + * its own dot in `::after`, so the vendor's would sit behind it. |
| 72 | + * - `margin`: react-resizable centres edge handles with `margin-left: -10px` |
| 73 | + * / `margin-top: -10px` while Liebe centres them with a translate. Both |
| 74 | + * apply, and the handle lands half its own width PLUS ten pixels off |
| 75 | + * centre — the "40×20 bar shifted 16px left" that made demotion wait for |
| 76 | + * this reconciliation. |
| 77 | + * - `transform`: react-resizable rotates each handle to point its grip |
| 78 | + * outward (45°, 135°, 225°, 315°). Liebe overrides that on the four edge |
| 79 | + * handles because it needs the property for centring, but sets no |
| 80 | + * transform on the corners — so their rotation would survive and spin the |
| 81 | + * `::after` dot to the wrong corner of a box whose own square shape hides |
| 82 | + * that the rotation happened at all. |
| 83 | + * |
| 84 | + * `padding` goes with them for tidiness rather than necessity: it is the |
| 85 | + * vendor's inset for that grip image, and an absolutely positioned `::after` |
| 86 | + * resolves against the padding box, so it changes nothing once the image is |
| 87 | + * gone. Reset anyway, so the box this file describes is the box that renders. |
| 88 | + */ |
56 | 89 | .react-resizable-handle { |
57 | 90 | position: absolute; |
58 | 91 | width: 20px; |
59 | 92 | height: 20px; |
60 | 93 | background-color: transparent; |
| 94 | + background-image: none; |
| 95 | + margin: 0; |
| 96 | + padding: 0; |
| 97 | + transform: none; |
61 | 98 | z-index: 1; |
62 | 99 | } |
63 | 100 |
|
|
207 | 244 | bottom: 6px; |
208 | 245 | } |
209 | 246 |
|
210 | | - /* Ensure ButtonCard fills the grid item */ |
211 | | - .grid-item > * { |
| 247 | + /* |
| 248 | + * Ensure the card fills the grid item. |
| 249 | + * |
| 250 | + * `:not(.react-resizable-handle)` is load-bearing, and it is the rule that |
| 251 | + * made demoting the grid packages a real change rather than a cascade tidy. |
| 252 | + * `react-grid-layout` puts the eight resize handles in the SAME element as the |
| 253 | + * card — `.react-grid-item` and `.grid-item` are one node, and the handles are |
| 254 | + * its direct children — so an unqualified `.grid-item > *` matches every |
| 255 | + * handle too and stretches each to the full card. |
| 256 | + * |
| 257 | + * That never showed while the vendor sheets were unlayered: their |
| 258 | + * `.react-grid-item > .react-resizable-handle { width: 20px }` out-specified |
| 259 | + * this rule and held the handles at 20×20. Demotion reversed it, and because |
| 260 | + * this selector is both later in the file and no less specific than |
| 261 | + * `.react-resizable-handle`, it won — eight invisible full-card handles |
| 262 | + * stacked over the tile, the last of them (`s`) swallowing every press. The |
| 263 | + * grid still resized, from whichever handle happened to be on top, which is |
| 264 | + * why the symptom was a card that only ever changed height. |
| 265 | + */ |
| 266 | + .grid-item > *:not(.react-resizable-handle) { |
212 | 267 | width: 100%; |
213 | 268 | height: 100%; |
214 | 269 | } |
|
228 | 283 | width: 24px; |
229 | 284 | height: 24px; |
230 | 285 | } |
231 | | - |
232 | | - /* Disable hover effects on touch devices */ |
233 | | - @media (pointer: coarse) { |
234 | | - .react-grid-item:hover .react-resizable-handle::after { |
235 | | - opacity: 0; |
236 | | - } |
237 | | - |
238 | | - /* Show resize handles when in edit mode */ |
239 | | - .react-grid-item.react-draggable .react-resizable-handle::after { |
240 | | - opacity: 1; |
241 | | - } |
242 | | - } |
243 | 286 | } |
244 | 287 |
|
245 | 288 | @media (min-width: 768px) and (max-width: 1023px) { |
246 | 289 | /* Tablet adjustments */ |
247 | 290 | } |
248 | 291 |
|
249 | | - /* Touch-friendly adjustments */ |
| 292 | + /* |
| 293 | + * Touch-friendly adjustments. |
| 294 | + * |
| 295 | + * The floor [grid-layout](../../docs/specs/grid-layout/index.md#touch-first-sizing) |
| 296 | + * states as a MUST, and the reason the whole block above had to become live: |
| 297 | + * these three rules are the grid's half of the project's touch-target |
| 298 | + * principle, and for as long as the vendor sheets outranked them a coarse |
| 299 | + * pointer got the same 20×20 handles a mouse did. |
| 300 | + */ |
250 | 301 | @media (pointer: coarse) { |
251 | 302 | /* Larger touch targets for resize handles */ |
252 | 303 | .react-resizable-handle { |
|
265 | 316 | width: 60px; |
266 | 317 | height: 32px; |
267 | 318 | } |
| 319 | + |
| 320 | + /* |
| 321 | + * And never more than a share of the tile, whatever the numbers above say. |
| 322 | + * |
| 323 | + * The floor is written in pixels and a grid cell is not: at the 8-column |
| 324 | + * `tablet` breakpoint on a 480px viewport a 1×1 tile measures 33×51, and a |
| 325 | + * touch device asking for 32×32 corners with 60px edges puts eight handles |
| 326 | + * over every pixel of it — measured, 0 of 100 sampled points left uncovered. |
| 327 | + * A card whose whole surface is a resize handle cannot be dragged, tapped or |
| 328 | + * opened, so on the smallest tiles the floor and the card's own operability |
| 329 | + * are in direct conflict. |
| 330 | + * |
| 331 | + * The drag surface wins, which is the precedence |
| 332 | + * [grid-layout](../../docs/specs/grid-layout/index.md#touch-first-sizing) |
| 333 | + * states: a handle below its target is smaller than we would like, a tile |
| 334 | + * nobody can grab is broken. 40% is the largest share that leaves the tile's |
| 335 | + * centre reachable with all eight handles present, and it binds only on |
| 336 | + * tiles too small to have honoured the floor anyway — on any tile wide |
| 337 | + * enough for a 60px edge handle (150px and up) it is inert. |
| 338 | + */ |
| 339 | + .react-resizable-handle { |
| 340 | + max-width: 40%; |
| 341 | + max-height: 40%; |
| 342 | + } |
| 343 | + |
| 344 | + /* |
| 345 | + * And visible, which the size alone does not buy. `react-grid-layout` hides |
| 346 | + * every handle at `opacity: 0` and reveals it on `:hover`, an event a touch |
| 347 | + * device never sends — so enlarging the target without this ships a 32×32 |
| 348 | + * hit area nobody can see. |
| 349 | + * |
| 350 | + * This replaces a rule that tried the same thing and could not work: the |
| 351 | + * old one raised the `::after` dot's opacity while the vendor kept the |
| 352 | + * handle *containing* it at zero, and opacity composites down a subtree. It |
| 353 | + * was also nested inside the `max-width: 767px` block, so it said nothing |
| 354 | + * about a tablet. Revealing the handle itself is what the intent needed. |
| 355 | + * |
| 356 | + * No edit-mode qualifier: in view mode the grid passes no resize handles at |
| 357 | + * all (grid-layout — eight in edit mode, none in view), so a handle in the |
| 358 | + * DOM already means edit mode. |
| 359 | + */ |
| 360 | + .react-resizable-handle, |
| 361 | + .react-resizable-handle::after { |
| 362 | + opacity: 1; |
| 363 | + } |
268 | 364 | } |
269 | 365 | } |
0 commit comments