@akinon/pz-theme 2.0.84-rc.0 → 2.0.84

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.
@@ -6,6 +6,7 @@ import {
6
6
  escapeCssAttributeValue
7
7
  } from './css-safety';
8
8
  import {
9
+ buildInnerRootSelectors,
9
10
  buildPartSelectors,
10
11
  buildRootStateSelectors,
11
12
  splitPartStyles
@@ -1400,7 +1401,7 @@ const generateElementCSS = (
1400
1401
  * override is emitted `!important`. Without it the override would be written,
1401
1402
  * saved, shown in the panel, and then quietly do nothing.
1402
1403
  */
1403
- const generatePartCSS = (
1404
+ export const generatePartCSS = (
1404
1405
  blockSelector: string,
1405
1406
  styles: Record<string, unknown>,
1406
1407
  targetBreakpoint?: string
@@ -1449,7 +1450,15 @@ const generatePartCSS = (
1449
1450
  const generateRootStateCSS = (
1450
1451
  elementSelector: string,
1451
1452
  styles: Record<string, unknown>,
1452
- targetBreakpoint?: string
1453
+ targetBreakpoint?: string,
1454
+ /**
1455
+ * The rule is routed to the element the renderer draws rather than onto the
1456
+ * wrapper `elementSelector` names. That makes it a descendant selector, so
1457
+ * its state variants need the ownership guard for the same reason the
1458
+ * stateless rule does — and the guard has to be placed per state expression,
1459
+ * which is why the flag travels here instead of a finished selector.
1460
+ */
1461
+ targetsInnerRoot = false
1453
1462
  ): string => {
1454
1463
  const { rootStates } = splitPartStyles(styles);
1455
1464
  if (rootStates.length === 0) return '';
@@ -1457,7 +1466,9 @@ const generateRootStateCSS = (
1457
1466
  let css = '';
1458
1467
 
1459
1468
  rootStates.forEach((bucket) => {
1460
- const selectors = buildRootStateSelectors(elementSelector, bucket.state);
1469
+ const selectors = targetsInnerRoot
1470
+ ? buildInnerRootSelectors(elementSelector, bucket.state)
1471
+ : buildRootStateSelectors(elementSelector, bucket.state);
1461
1472
  if (selectors.length === 0) return;
1462
1473
 
1463
1474
  css += generateElementCSS(
@@ -1508,17 +1519,29 @@ const collectBlockCSS = (
1508
1519
  // which is what keeps published compositions rendering as before.
1509
1520
  const targetsInnerRoot =
1510
1521
  block.stylesTargetRoot ?? INNER_ROOT_BLOCK_TYPES.has(block.type ?? '');
1511
- const rootSelector = targetsInnerRoot
1512
- ? `${selector} [data-part="root"]`
1513
- : selector;
1522
+ // Guarded: `[block] [data-part="root"]` is a descendant selector, and
1523
+ // every text, link or button nested inside this one marks a root of its
1524
+ // own — see `buildInnerRootSelectors`.
1525
+ const rootSelectors = targetsInnerRoot
1526
+ ? buildInnerRootSelectors(selector)
1527
+ : [selector];
1514
1528
 
1515
1529
  if (Object.keys(root).length > 0) {
1516
- css += generateElementCSS(rootSelector, root, targetBreakpoint);
1530
+ css += generateElementCSS(
1531
+ rootSelectors.join(',\n'),
1532
+ root,
1533
+ targetBreakpoint
1534
+ );
1517
1535
  }
1518
1536
 
1519
1537
  // After the plain root rule, and against the same element: a state has to
1520
1538
  // be able to override what the stateless value painted.
1521
- css += generateRootStateCSS(rootSelector, blockStyles, targetBreakpoint);
1539
+ css += generateRootStateCSS(
1540
+ selector,
1541
+ blockStyles,
1542
+ targetBreakpoint,
1543
+ targetsInnerRoot
1544
+ );
1522
1545
 
1523
1546
  css += generatePartCSS(selector, blockStyles, targetBreakpoint);
1524
1547
  }
@@ -371,6 +371,28 @@ export const buildPartSelectors = (
371
371
  );
372
372
  };
373
373
 
374
+ /**
375
+ * Selectors for a block whose renderer draws its OWN root and marks it
376
+ * `data-part="root"` (see `INNER_ROOT_BLOCK_TYPES`).
377
+ *
378
+ * That routed rule is a descendant selector exactly like a part rule, and
379
+ * `root` is the most-shared part name in the system — every text, link and
380
+ * button marks one. So it leaked in precisely the way the guard above exists to
381
+ * prevent, and did more damage than any other part name could: a link block
382
+ * carrying an aspect-ratio also squared the text block sitting inside it, that
383
+ * square then claimed the card's whole height, and the image next to it was
384
+ * squeezed to a 4px sliver of itself.
385
+ *
386
+ * `root` is a part name like any other, so it goes through the same builder
387
+ * rather than a second, quietly different copy of the rule — including for
388
+ * states, where the guard has to be placed per expression.
389
+ */
390
+ export const buildInnerRootSelectors = (
391
+ blockSelector: string,
392
+ state: string | null = null
393
+ ): string[] =>
394
+ buildPartSelectors(blockSelector, { part: 'root', state, styles: {} });
395
+
374
396
  /**
375
397
  * Selectors for a block or section's OWN root in a state.
376
398
  *