@ng-prism/core 22.5.0 → 22.6.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.
Files changed (118) hide show
  1. package/README.md +2 -0
  2. package/dist/app/canvas/prism-canvas-toolbar.component.d.ts +22 -1
  3. package/dist/app/canvas/prism-canvas-toolbar.component.d.ts.map +1 -1
  4. package/dist/app/canvas/prism-canvas-toolbar.component.js +111 -38
  5. package/dist/app/canvas/state-chips.d.ts +50 -0
  6. package/dist/app/canvas/state-chips.d.ts.map +1 -0
  7. package/dist/app/canvas/state-chips.js +40 -0
  8. package/dist/app/component-head/prism-component-head.component.d.ts +1 -0
  9. package/dist/app/component-head/prism-component-head.component.d.ts.map +1 -1
  10. package/dist/app/component-head/prism-component-head.component.js +11 -6
  11. package/dist/app/component-head/prism-head-info.component.d.ts +2 -1
  12. package/dist/app/component-head/prism-head-info.component.d.ts.map +1 -1
  13. package/dist/app/component-head/prism-head-info.component.js +30 -7
  14. package/dist/app/directives/resize-value.d.ts +1 -1
  15. package/dist/app/directives/resize-value.js +1 -1
  16. package/dist/app/discovery-manifest.d.ts +2 -2
  17. package/dist/app/discovery-manifest.d.ts.map +1 -1
  18. package/dist/app/discovery-manifest.js +15 -3
  19. package/dist/app/overview/open-in-playground.d.ts +11 -0
  20. package/dist/app/overview/open-in-playground.d.ts.map +1 -0
  21. package/dist/app/overview/open-in-playground.js +12 -0
  22. package/dist/app/overview/overview-matrix.d.ts +79 -0
  23. package/dist/app/overview/overview-matrix.d.ts.map +1 -0
  24. package/dist/app/overview/overview-matrix.js +98 -0
  25. package/dist/app/overview/overview-visibility.d.ts +10 -2
  26. package/dist/app/overview/overview-visibility.d.ts.map +1 -1
  27. package/dist/app/overview/overview-visibility.js +14 -3
  28. package/dist/app/overview/prism-overview-cell.component.d.ts +23 -2
  29. package/dist/app/overview/prism-overview-cell.component.d.ts.map +1 -1
  30. package/dist/app/overview/prism-overview-cell.component.js +133 -27
  31. package/dist/app/overview/prism-overview-matrix.component.d.ts +29 -0
  32. package/dist/app/overview/prism-overview-matrix.component.d.ts.map +1 -0
  33. package/dist/app/overview/prism-overview-matrix.component.js +164 -0
  34. package/dist/app/overview/prism-overview.component.d.ts +4 -0
  35. package/dist/app/overview/prism-overview.component.d.ts.map +1 -1
  36. package/dist/app/overview/prism-overview.component.js +37 -14
  37. package/dist/app/panels/a11y/a11y-core.plugin.d.ts.map +1 -1
  38. package/dist/app/panels/a11y/a11y-core.plugin.js +0 -1
  39. package/dist/app/panels/controls/controls-plugin.d.ts.map +1 -1
  40. package/dist/app/panels/controls/controls-plugin.js +1 -2
  41. package/dist/app/panels/events/events-plugin.d.ts.map +1 -1
  42. package/dist/app/panels/events/events-plugin.js +1 -2
  43. package/dist/app/pseudo/pseudo-state-marker.d.ts +32 -10
  44. package/dist/app/pseudo/pseudo-state-marker.d.ts.map +1 -1
  45. package/dist/app/pseudo/pseudo-state-marker.js +83 -67
  46. package/dist/app/pseudo/pseudo-targets.d.ts +11 -0
  47. package/dist/app/pseudo/pseudo-targets.d.ts.map +1 -0
  48. package/dist/app/pseudo/pseudo-targets.js +46 -0
  49. package/dist/app/pseudo/state-fit.d.ts +28 -0
  50. package/dist/app/pseudo/state-fit.d.ts.map +1 -0
  51. package/dist/app/pseudo/state-fit.js +97 -0
  52. package/dist/app/renderer/input-bindings.d.ts +16 -0
  53. package/dist/app/renderer/input-bindings.d.ts.map +1 -0
  54. package/dist/app/renderer/input-bindings.js +21 -0
  55. package/dist/app/renderer/known-inputs.d.ts +8 -1
  56. package/dist/app/renderer/known-inputs.d.ts.map +1 -1
  57. package/dist/app/renderer/known-inputs.js +13 -5
  58. package/dist/app/renderer/prism-renderer.component.d.ts +10 -3
  59. package/dist/app/renderer/prism-renderer.component.d.ts.map +1 -1
  60. package/dist/app/renderer/prism-renderer.component.js +51 -29
  61. package/dist/app/renderer/projectable-content.d.ts +8 -1
  62. package/dist/app/renderer/projectable-content.d.ts.map +1 -1
  63. package/dist/app/renderer/projectable-content.js +40 -29
  64. package/dist/app/renderer/rendered-key.d.ts +20 -6
  65. package/dist/app/renderer/rendered-key.d.ts.map +1 -1
  66. package/dist/app/renderer/rendered-key.js +25 -8
  67. package/dist/app/renderer/snippet-generator.js +7 -5
  68. package/dist/app/services/prism-pseudo-state.service.d.ts +59 -14
  69. package/dist/app/services/prism-pseudo-state.service.d.ts.map +1 -1
  70. package/dist/app/services/prism-pseudo-state.service.js +86 -29
  71. package/dist/app/services/prism-renderer.service.d.ts +17 -0
  72. package/dist/app/services/prism-renderer.service.d.ts.map +1 -1
  73. package/dist/app/services/prism-renderer.service.js +37 -2
  74. package/dist/app/services/prism-url-state.service.d.ts.map +1 -1
  75. package/dist/app/services/prism-url-state.service.js +39 -8
  76. package/dist/app/theme/prism-default-theme.d.ts.map +1 -1
  77. package/dist/app/theme/prism-default-theme.js +6 -5
  78. package/dist/builder/config-loader/deprecated-options.d.ts +7 -0
  79. package/dist/builder/config-loader/deprecated-options.d.ts.map +1 -0
  80. package/dist/builder/config-loader/deprecated-options.js +23 -0
  81. package/dist/builder/manifest/runtime-manifest.generator.d.ts.map +1 -1
  82. package/dist/builder/manifest/runtime-manifest.generator.js +3 -2
  83. package/dist/builder/scanner/component.scanner.d.ts.map +1 -1
  84. package/dist/builder/scanner/component.scanner.js +20 -1
  85. package/dist/builder/scanner/entry-point-discovery.d.ts +5 -0
  86. package/dist/builder/scanner/entry-point-discovery.d.ts.map +1 -1
  87. package/dist/builder/scanner/entry-point-discovery.js +14 -0
  88. package/dist/builder/scanner/entry-point.scanner.d.ts +3 -1
  89. package/dist/builder/scanner/entry-point.scanner.d.ts.map +1 -1
  90. package/dist/builder/scanner/entry-point.scanner.js +7 -6
  91. package/dist/builder/scanner/input.extractor.d.ts.map +1 -1
  92. package/dist/builder/scanner/input.extractor.js +22 -3
  93. package/dist/builder/scanner/pseudo-states.d.ts +1 -0
  94. package/dist/builder/scanner/pseudo-states.d.ts.map +1 -1
  95. package/dist/builder/scanner/pseudo-states.js +1 -1
  96. package/dist/builder/scanner/scanner.d.ts +2 -2
  97. package/dist/builder/scanner/scanner.d.ts.map +1 -1
  98. package/dist/builder/scanner/scanner.js +16 -6
  99. package/dist/builder/scanner/showcase-diagnostics.js +9 -9
  100. package/dist/builder/scanner/showcase-states.d.ts +17 -0
  101. package/dist/builder/scanner/showcase-states.d.ts.map +1 -0
  102. package/dist/builder/scanner/showcase-states.js +58 -0
  103. package/dist/builder/scanner/source-file-cache.d.ts +26 -0
  104. package/dist/builder/scanner/source-file-cache.d.ts.map +1 -0
  105. package/dist/builder/scanner/source-file-cache.js +60 -0
  106. package/dist/builder/shared/prism-pipeline.d.ts.map +1 -1
  107. package/dist/builder/shared/prism-pipeline.js +28 -13
  108. package/dist/decorator/index.d.ts +1 -1
  109. package/dist/decorator/index.d.ts.map +1 -1
  110. package/dist/decorator/showcase.types.d.ts +23 -0
  111. package/dist/decorator/showcase.types.d.ts.map +1 -1
  112. package/dist/plugin/index.d.ts +1 -1
  113. package/dist/plugin/index.d.ts.map +1 -1
  114. package/dist/plugin/plugin.types.d.ts +44 -5
  115. package/dist/plugin/plugin.types.d.ts.map +1 -1
  116. package/package.json +1 -1
  117. package/src/builder/build/schema.json +6 -6
  118. package/src/builder/serve/schema.json +6 -6
@@ -2,7 +2,6 @@ export const A11Y_CORE_PANEL = {
2
2
  id: 'a11y',
3
3
  label: 'A11y',
4
4
  icon: 'accessibility',
5
- position: 'bottom',
6
5
  placement: 'addon',
7
6
  loadComponent: () => import('./a11y-panel.component.js').then((m) => m.A11yPanelComponent),
8
7
  loadOverlayComponent: () => import('./a11y-overlay-host.component.js').then((m) => m.A11yOverlayHostComponent)
@@ -1 +1 @@
1
- {"version":3,"file":"controls-plugin.d.ts","sourceRoot":"","sources":["../../../../src/app/panels/controls/controls-plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAGrE,eAAO,MAAM,eAAe,EAAE,aAW7B,CAAC"}
1
+ {"version":3,"file":"controls-plugin.d.ts","sourceRoot":"","sources":["../../../../src/app/panels/controls/controls-plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAGrE,eAAO,MAAM,eAAe,EAAE,aAU7B,CAAC"}
@@ -6,8 +6,7 @@ export const CONTROLS_PLUGIN = {
6
6
  id: 'controls',
7
7
  label: 'Controls',
8
8
  icon: 'sliders-horizontal',
9
- component: PrismControlsPanelComponent,
10
- position: 'bottom'
9
+ component: PrismControlsPanelComponent
11
10
  }
12
11
  ]
13
12
  };
@@ -1 +1 @@
1
- {"version":3,"file":"events-plugin.d.ts","sourceRoot":"","sources":["../../../../src/app/panels/events/events-plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAGrE,eAAO,MAAM,aAAa,EAAE,aAW3B,CAAC"}
1
+ {"version":3,"file":"events-plugin.d.ts","sourceRoot":"","sources":["../../../../src/app/panels/events/events-plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAGrE,eAAO,MAAM,aAAa,EAAE,aAU3B,CAAC"}
@@ -6,8 +6,7 @@ export const EVENTS_PLUGIN = {
6
6
  id: 'events',
7
7
  label: 'Events',
8
8
  icon: 'zap',
9
- component: PrismEventsPanelComponent,
10
- position: 'bottom'
9
+ component: PrismEventsPanelComponent
11
10
  }
12
11
  ]
13
12
  };
@@ -1,12 +1,18 @@
1
- import { type ForcedPseudoStates } from '../../shared/pseudo-state.type.js';
1
+ import { type ForcedPseudoStates, type PseudoState } from '../../shared/pseudo-state.type.js';
2
2
  import type { PseudoStyleSheets } from './pseudo-stylesheets.js';
3
+ import { type StateFit } from './state-fit.js';
3
4
  /** The element a marker forces states in, and the states it forces there. */
4
5
  export interface AppliedPseudoStates {
5
6
  root: Element;
6
- states: ForcedPseudoStates;
7
+ /** The variant's own states with the toggles on top, unfiltered as in 22.5.0. */
8
+ states: ForcedPseudoStates | null;
9
+ /** The active state's pseudo, `null` when no state is active. */
10
+ state: ForcedPseudoStates | null;
11
+ /** `null` when `state` is `null`. Re-evaluated on every mark. */
12
+ fit: StateFit | null;
7
13
  }
8
- /** True when `states` forces at least one state on at least one target. */
9
- export declare function forcesAnyState(states: ForcedPseudoStates | null | undefined): states is ForcedPseudoStates;
14
+ /** The pseudo-classes actually forced right now: `states`, plus `state` while it fits. */
15
+ export declare function appliedStateNames(applied: AppliedPseudoStates | null): PseudoState[];
10
16
  /**
11
17
  * Forces a variant's pseudo states on one rendered component.
12
18
  *
@@ -16,9 +22,19 @@ export declare function forcesAnyState(states: ForcedPseudoStates | null | undef
16
22
  * component's own CSS then applies, with no special case for sibling
17
23
  * combinators, pseudo-elements, `:has()` or `:not()`.
18
24
  *
25
+ * A second layer carries the component state that is active: its pseudo is
26
+ * forced only while it fits, and only on the targets a user could reach
27
+ * ({@link evaluateStateFit}). The variant's own states are forced as they are.
28
+ * Fit and reach are measured with the variant's classes and the state's on all
29
+ * of its targets at once, on every pass. The first pass, the one
30
+ * `data-prism-rendered` waits for, therefore marks what any later pass would,
31
+ * and a rule that takes away the pointer under its own `:hover` reads as not
32
+ * fitting, as the browser could not keep such an element hovered either.
33
+ *
19
34
  * The root is watched while states are forced: an element rendered later
20
35
  * (`@if`), or one whose classes or attributes change so that it starts or
21
- * stops matching a selector (`button:not([disabled])`), is marked again.
36
+ * stops matching a selector (`button:not([disabled])`), is marked again, and
37
+ * the state is checked again.
22
38
  *
23
39
  * One marker per rendered component: the Playground canvas has one, each
24
40
  * Overview cell has its own, so a state never leaks into a neighbouring cell.
@@ -28,17 +44,23 @@ export declare class PseudoStateMarker {
28
44
  private readonly _applied;
29
45
  private observer;
30
46
  /**
31
- * What the marks are in place for, `null` while nothing is forced. A
32
- * signal, so the renderer can hold back `data-prism-rendered` until the
33
- * states it reports are the ones on screen.
47
+ * What the marks are in place for, `null` while nothing is forced and no
48
+ * state is active. A signal, so the renderer can hold back
49
+ * `data-prism-rendered` until the states it reports are the ones on screen.
50
+ * A state that does not fit keeps it set, with the reason in `fit`.
34
51
  */
35
52
  readonly applied: import("@angular/core").Signal<AppliedPseudoStates | null>;
36
53
  constructor(stylesheets: PseudoStyleSheets);
37
- /** Forces `states` under `root`, replacing whatever this marker forced before. */
38
- apply(root: Element | null, states: ForcedPseudoStates | null | undefined): void;
54
+ /**
55
+ * Forces `states`, and `state` where it fits, under `root`, replacing
56
+ * whatever this marker forced before.
57
+ */
58
+ apply(root: Element | null, states: ForcedPseudoStates | null | undefined, state?: ForcedPseudoStates | null): void;
39
59
  /** Removes every mark, stops watching, and releases the stylesheets. */
40
60
  clear(): void;
41
61
  private observe;
42
62
  private mark;
63
+ /** Puts the class of `name` on exactly `targets` under `root`. */
64
+ private write;
43
65
  }
44
66
  //# sourceMappingURL=pseudo-state-marker.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"pseudo-state-marker.d.ts","sourceRoot":"","sources":["../../../src/app/pseudo/pseudo-state-marker.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,kBAAkB,EAAoC,MAAM,mCAAmC,CAAC;AAE9G,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAEjE,6EAA6E;AAC7E,MAAM,WAAW,mBAAmB;IAChC,IAAI,EAAE,OAAO,CAAC;IACd,MAAM,EAAE,kBAAkB,CAAC;CAC9B;AAED,2EAA2E;AAC3E,wBAAgB,cAAc,CAAC,MAAM,EAAE,kBAAkB,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,IAAI,kBAAkB,CAE1G;AA8CD;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,iBAAiB;IAWd,OAAO,CAAC,QAAQ,CAAC,WAAW;IAVxC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA4C;IACrE,OAAO,CAAC,QAAQ,CAAiC;IAEjD;;;;OAIG;IACH,QAAQ,CAAC,OAAO,6DAA8B;gBAEjB,WAAW,EAAE,iBAAiB;IAE3D,kFAAkF;IAClF,KAAK,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,EAAE,MAAM,EAAE,kBAAkB,GAAG,IAAI,GAAG,SAAS,GAAG,IAAI;IAqBhF,wEAAwE;IACxE,KAAK,IAAI,IAAI;IAab,OAAO,CAAC,OAAO;IAWf,OAAO,CAAC,IAAI;CAoBf"}
1
+ {"version":3,"file":"pseudo-state-marker.d.ts","sourceRoot":"","sources":["../../../src/app/pseudo/pseudo-state-marker.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,kBAAkB,EAAiB,KAAK,WAAW,EAAE,MAAM,mCAAmC,CAAC;AAE7G,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAEjE,OAAO,EAAsC,KAAK,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAEnF,6EAA6E;AAC7E,MAAM,WAAW,mBAAmB;IAChC,IAAI,EAAE,OAAO,CAAC;IACd,iFAAiF;IACjF,MAAM,EAAE,kBAAkB,GAAG,IAAI,CAAC;IAClC,iEAAiE;IACjE,KAAK,EAAE,kBAAkB,GAAG,IAAI,CAAC;IACjC,iEAAiE;IACjE,GAAG,EAAE,QAAQ,GAAG,IAAI,CAAC;CACxB;AAED,0FAA0F;AAC1F,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,mBAAmB,GAAG,IAAI,GAAG,WAAW,EAAE,CAMpF;AAcD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,qBAAa,iBAAiB;IAYd,OAAO,CAAC,QAAQ,CAAC,WAAW;IAXxC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA4C;IACrE,OAAO,CAAC,QAAQ,CAAiC;IAEjD;;;;;OAKG;IACH,QAAQ,CAAC,OAAO,6DAA8B;gBAEjB,WAAW,EAAE,iBAAiB;IAE3D;;;OAGG;IACH,KAAK,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,EAAE,MAAM,EAAE,kBAAkB,GAAG,IAAI,GAAG,SAAS,EAAE,KAAK,CAAC,EAAE,kBAAkB,GAAG,IAAI,GAAG,IAAI;IAqBnH,wEAAwE;IACxE,KAAK,IAAI,IAAI;IAab,OAAO,CAAC,OAAO;IAWf,OAAO,CAAC,IAAI;IA2CZ,kEAAkE;IAClE,OAAO,CAAC,KAAK;CAYhB"}
@@ -1,49 +1,20 @@
1
1
  import { signal } from '@angular/core';
2
2
  import { PSEUDO_STATES } from '../../shared/pseudo-state.type.js';
3
3
  import { PSEUDO_CLASS_PREFIX, pseudoStateClass } from './pseudo-selector.js';
4
- /** True when `states` forces at least one state on at least one target. */
5
- export function forcesAnyState(states) {
6
- return !!states && PSEUDO_STATES.some((state) => hasTarget(states[state]));
4
+ import { forcesAnyState, hasTarget, resolveTargets } from './pseudo-targets.js';
5
+ import { evaluateStateFit, reachableTargets } from './state-fit.js';
6
+ /** The pseudo-classes actually forced right now: `states`, plus `state` while it fits. */
7
+ export function appliedStateNames(applied) {
8
+ if (!applied)
9
+ return [];
10
+ const { states, state, fit } = applied;
11
+ const stateForced = !!state && !!fit?.fits;
12
+ return PSEUDO_STATES.filter((name) => hasTarget(states?.[name]) || (stateForced && hasTarget(state[name])));
7
13
  }
8
- function hasTarget(target) {
9
- if (target === true)
10
- return true;
11
- if (typeof target === 'string')
12
- return target.trim() !== '';
13
- return Array.isArray(target) && target.some((selector) => typeof selector === 'string' && selector.trim() !== '');
14
- }
15
- const warnedSelectors = new Set();
16
- /**
17
- * The elements under `root` a target names. `root` counts: a selector can name
18
- * the component's host, and `true` includes it.
19
- */
20
- function resolveTargets(root, target) {
21
- const targets = new Set();
22
- if (target === undefined)
23
- return targets;
24
- if (target === true) {
25
- targets.add(root);
26
- for (const el of Array.from(root.querySelectorAll('*')))
27
- targets.add(el);
28
- return targets;
29
- }
30
- for (const selector of typeof target === 'string' ? [target] : target) {
31
- if (typeof selector !== 'string' || selector.trim() === '')
32
- continue;
33
- try {
34
- if (root.matches(selector))
35
- targets.add(root);
36
- for (const el of Array.from(root.querySelectorAll(selector)))
37
- targets.add(el);
38
- }
39
- catch {
40
- if (!warnedSelectors.has(selector)) {
41
- warnedSelectors.add(selector);
42
- console.warn(`[ng-prism] "${selector}" in a variant's pseudo states is not a valid selector, skipping it.`);
43
- }
44
- }
45
- }
46
- return targets;
14
+ function sameFit(a, b) {
15
+ if (a === null || b === null)
16
+ return a === b;
17
+ return a.fits ? b.fits : !b.fits && a.reason === b.reason;
47
18
  }
48
19
  /** Every element under `root`, `root` included, that carries a forced-state class. */
49
20
  function markedElements(root) {
@@ -59,9 +30,19 @@ function markedElements(root) {
59
30
  * component's own CSS then applies, with no special case for sibling
60
31
  * combinators, pseudo-elements, `:has()` or `:not()`.
61
32
  *
33
+ * A second layer carries the component state that is active: its pseudo is
34
+ * forced only while it fits, and only on the targets a user could reach
35
+ * ({@link evaluateStateFit}). The variant's own states are forced as they are.
36
+ * Fit and reach are measured with the variant's classes and the state's on all
37
+ * of its targets at once, on every pass. The first pass, the one
38
+ * `data-prism-rendered` waits for, therefore marks what any later pass would,
39
+ * and a rule that takes away the pointer under its own `:hover` reads as not
40
+ * fitting, as the browser could not keep such an element hovered either.
41
+ *
62
42
  * The root is watched while states are forced: an element rendered later
63
43
  * (`@if`), or one whose classes or attributes change so that it starts or
64
- * stops matching a selector (`button:not([disabled])`), is marked again.
44
+ * stops matching a selector (`button:not([disabled])`), is marked again, and
45
+ * the state is checked again.
65
46
  *
66
47
  * One marker per rendered component: the Playground canvas has one, each
67
48
  * Overview cell has its own, so a state never leaks into a neighbouring cell.
@@ -72,17 +53,21 @@ export class PseudoStateMarker {
72
53
  ...(ngDevMode ? [{ debugName: "_applied" }] : /* istanbul ignore next */ []));
73
54
  observer = null;
74
55
  /**
75
- * What the marks are in place for, `null` while nothing is forced. A
76
- * signal, so the renderer can hold back `data-prism-rendered` until the
77
- * states it reports are the ones on screen.
56
+ * What the marks are in place for, `null` while nothing is forced and no
57
+ * state is active. A signal, so the renderer can hold back
58
+ * `data-prism-rendered` until the states it reports are the ones on screen.
59
+ * A state that does not fit keeps it set, with the reason in `fit`.
78
60
  */
79
61
  applied = this._applied.asReadonly();
80
62
  constructor(stylesheets) {
81
63
  this.stylesheets = stylesheets;
82
64
  }
83
- /** Forces `states` under `root`, replacing whatever this marker forced before. */
84
- apply(root, states) {
85
- if (!root || !forcesAnyState(states)) {
65
+ /**
66
+ * Forces `states`, and `state` where it fits, under `root`, replacing
67
+ * whatever this marker forced before.
68
+ */
69
+ apply(root, states, state) {
70
+ if (!root || (!forcesAnyState(states) && !state)) {
86
71
  this.clear();
87
72
  return;
88
73
  }
@@ -97,7 +82,7 @@ export class PseudoStateMarker {
97
82
  // Same root, new variant: its styles may have arrived since.
98
83
  this.stylesheets.sync();
99
84
  }
100
- this._applied.set({ root, states });
85
+ this._applied.set({ root, states: states ?? null, state: state ?? null, fit: null });
101
86
  this.mark();
102
87
  }
103
88
  /** Removes every mark, stops watching, and releases the stylesheets. */
@@ -117,9 +102,9 @@ export class PseudoStateMarker {
117
102
  observe(root) {
118
103
  if (typeof MutationObserver === 'undefined')
119
104
  return;
120
- // Our own class changes come back through here too; the second pass finds
121
- // nothing to change. Inline styles alone cannot change what a selector
122
- // matches, and an animation can write them every frame.
105
+ // Inline styles alone cannot change what a selector matches, and an
106
+ // animation can write them every frame. The marker's own writes never
107
+ // get here, see the end of mark.
123
108
  this.observer = new MutationObserver((records) => {
124
109
  if (records.some((record) => record.attributeName !== 'style'))
125
110
  this.mark();
@@ -130,20 +115,51 @@ export class PseudoStateMarker {
130
115
  const applied = this._applied();
131
116
  if (!applied)
132
117
  return;
133
- const { root, states } = applied;
134
- for (const state of PSEUDO_STATES) {
135
- const cls = pseudoStateClass(state);
136
- const targets = resolveTargets(root, states[state]);
137
- // `classList.remove` writes the attribute even when the class is absent,
138
- // and the write would come back through the observer as a mutation.
139
- for (const el of markedElements(root)) {
140
- if (!targets.has(el) && el.classList.contains(cls))
141
- el.classList.remove(cls);
142
- }
143
- for (const el of targets) {
144
- if (!el.classList.contains(cls))
145
- el.classList.add(cls);
146
- }
118
+ const { root, states, state } = applied;
119
+ const forced = new Map();
120
+ const stateTargets = new Map();
121
+ // Every pass measures one DOM, whatever the previous pass left: the
122
+ // variant's classes, and the state's on every target it names, all at
123
+ // once. A target can take the pointer only under another forced class
124
+ // (a hover-reveal), and a mark must not depend on whether an unrelated
125
+ // write set off a second pass.
126
+ for (const name of PSEUDO_STATES) {
127
+ const variantTargets = resolveTargets(root, states?.[name]);
128
+ const targets = state ? resolveTargets(root, state[name]) : new Set();
129
+ forced.set(name, variantTargets);
130
+ stateTargets.set(name, targets);
131
+ this.write(root, name, new Set([...variantTargets, ...targets]));
132
+ }
133
+ const fit = state ? evaluateStateFit(root, state) : null;
134
+ // Read before the first class comes off, so no target is measured
135
+ // against a DOM another one's removal changed.
136
+ const reachable = PSEUDO_STATES.map((name) => new Set(fit?.fits ? reachableTargets(root, name, stateTargets.get(name)) : []));
137
+ // Only a change of the fit is news. The signal setting off its own
138
+ // effects is harmless, a new value on every mark is not.
139
+ if (!sameFit(applied.fit, fit))
140
+ this._applied.set({ ...applied, fit });
141
+ // The state's class comes off what it cannot reach, and off all of its
142
+ // targets when it does not fit. The variant's classes stay.
143
+ PSEUDO_STATES.forEach((name, i) => this.write(root, name, new Set([...forced.get(name), ...reachable[i]])));
144
+ // The records of the writes above are ours. Acting on them would only
145
+ // repeat this pass, and a state whose class takes away its own reach
146
+ // (`pointer-events: none`) would rewrite its class on every one. Anything
147
+ // the component changed before this pass was read by it, so nothing
148
+ // pending is lost.
149
+ this.observer?.takeRecords();
150
+ }
151
+ /** Puts the class of `name` on exactly `targets` under `root`. */
152
+ write(root, name, targets) {
153
+ const cls = pseudoStateClass(name);
154
+ // `classList.remove` writes the attribute even when the class is absent,
155
+ // and the write would come back through the observer as a mutation.
156
+ for (const el of markedElements(root)) {
157
+ if (!targets.has(el) && el.classList.contains(cls))
158
+ el.classList.remove(cls);
159
+ }
160
+ for (const el of targets) {
161
+ if (!el.classList.contains(cls))
162
+ el.classList.add(cls);
147
163
  }
148
164
  }
149
165
  }
@@ -0,0 +1,11 @@
1
+ import { type ForcedPseudoStates, type PseudoTarget } from '../../shared/pseudo-state.type.js';
2
+ /** True when `states` forces at least one state on at least one target. */
3
+ export declare function forcesAnyState(states: ForcedPseudoStates | null | undefined): states is ForcedPseudoStates;
4
+ /** True when `target` names something: `true`, or a selector that is not blank. */
5
+ export declare function hasTarget(target: PseudoTarget | undefined): boolean;
6
+ /**
7
+ * The elements under `root` a target names. `root` counts: a selector can name
8
+ * the component's host, and `true` includes it.
9
+ */
10
+ export declare function resolveTargets(root: Element, target: PseudoTarget | undefined): Set<Element>;
11
+ //# sourceMappingURL=pseudo-targets.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pseudo-targets.d.ts","sourceRoot":"","sources":["../../../src/app/pseudo/pseudo-targets.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,kBAAkB,EAAiB,KAAK,YAAY,EAAE,MAAM,mCAAmC,CAAC;AAE9G,2EAA2E;AAC3E,wBAAgB,cAAc,CAAC,MAAM,EAAE,kBAAkB,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,IAAI,kBAAkB,CAE1G;AAED,mFAAmF;AACnF,wBAAgB,SAAS,CAAC,MAAM,EAAE,YAAY,GAAG,SAAS,GAAG,OAAO,CAInE;AAID;;;GAGG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,YAAY,GAAG,SAAS,GAAG,GAAG,CAAC,OAAO,CAAC,CAuB5F"}
@@ -0,0 +1,46 @@
1
+ import { PSEUDO_STATES } from '../../shared/pseudo-state.type.js';
2
+ /** True when `states` forces at least one state on at least one target. */
3
+ export function forcesAnyState(states) {
4
+ return !!states && PSEUDO_STATES.some((state) => hasTarget(states[state]));
5
+ }
6
+ /** True when `target` names something: `true`, or a selector that is not blank. */
7
+ export function hasTarget(target) {
8
+ if (target === true)
9
+ return true;
10
+ if (typeof target === 'string')
11
+ return target.trim() !== '';
12
+ return Array.isArray(target) && target.some((selector) => typeof selector === 'string' && selector.trim() !== '');
13
+ }
14
+ const warnedSelectors = new Set();
15
+ /**
16
+ * The elements under `root` a target names. `root` counts: a selector can name
17
+ * the component's host, and `true` includes it.
18
+ */
19
+ export function resolveTargets(root, target) {
20
+ const targets = new Set();
21
+ if (target === undefined)
22
+ return targets;
23
+ if (target === true) {
24
+ targets.add(root);
25
+ for (const el of Array.from(root.querySelectorAll('*')))
26
+ targets.add(el);
27
+ return targets;
28
+ }
29
+ for (const selector of typeof target === 'string' ? [target] : target) {
30
+ if (typeof selector !== 'string' || selector.trim() === '')
31
+ continue;
32
+ try {
33
+ if (root.matches(selector))
34
+ targets.add(root);
35
+ for (const el of Array.from(root.querySelectorAll(selector)))
36
+ targets.add(el);
37
+ }
38
+ catch {
39
+ if (!warnedSelectors.has(selector)) {
40
+ warnedSelectors.add(selector);
41
+ console.warn(`[ng-prism] "${selector}" in forced pseudo states is not a valid selector, skipping it.`);
42
+ }
43
+ }
44
+ }
45
+ return targets;
46
+ }
@@ -0,0 +1,28 @@
1
+ import { type ForcedPseudoStates, type PseudoState, type PseudoTarget } from '../../shared/pseudo-state.type.js';
2
+ /** Whether a state can be forced on a rendered component, and if not, why. */
3
+ export type StateFit = {
4
+ fits: true;
5
+ } | {
6
+ fits: false;
7
+ reason: string;
8
+ };
9
+ /** `true` is the whole component, a selector list reads as its selectors joined with `, `. */
10
+ export declare function describeTarget(target: PseudoTarget): string;
11
+ /**
12
+ * The subset of `elements` the browser could put into `state`: what a user
13
+ * could focus, or hover, or press. Forcing a state on anything else would show
14
+ * something the component can never be in.
15
+ *
16
+ * `focus-within` is not filtered. Whether some descendant takes focus cannot be
17
+ * told without a browser API for focusability.
18
+ */
19
+ export declare function reachableTargets(root: Element, state: PseudoState, elements: Iterable<Element>): Element[];
20
+ /**
21
+ * Fits when every pseudo-class of `pseudo` has at least one reachable target
22
+ * under `root`. The reason names the first pseudo-class, in
23
+ * {@link PSEUDO_STATES} order, that does not. Focus on the whole component is
24
+ * decided on what could take focus ({@link fitCandidates}); which elements get
25
+ * the class is not affected.
26
+ */
27
+ export declare function evaluateStateFit(root: Element, pseudo: ForcedPseudoStates): StateFit;
28
+ //# sourceMappingURL=state-fit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state-fit.d.ts","sourceRoot":"","sources":["../../../src/app/pseudo/state-fit.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,kBAAkB,EAAiB,KAAK,WAAW,EAAE,KAAK,YAAY,EAAE,MAAM,mCAAmC,CAAC;AAGhI,8EAA8E;AAC9E,MAAM,MAAM,QAAQ,GAAG;IAAE,IAAI,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAExE,8FAA8F;AAC9F,wBAAgB,cAAc,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,CAG3D;AA4BD;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,QAAQ,CAAC,OAAO,CAAC,GAAG,OAAO,EAAE,CAc1G;AAyBD;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,kBAAkB,GAAG,QAAQ,CAkBpF"}
@@ -0,0 +1,97 @@
1
+ import { PSEUDO_STATES } from '../../shared/pseudo-state.type.js';
2
+ import { forcesAnyState, resolveTargets } from './pseudo-targets.js';
3
+ /** `true` is the whole component, a selector list reads as its selectors joined with `, `. */
4
+ export function describeTarget(target) {
5
+ if (target === true)
6
+ return 'the component';
7
+ return typeof target === 'string' ? target : target.join(', ');
8
+ }
9
+ function isDisabled(el) {
10
+ return el.matches(':disabled');
11
+ }
12
+ /** Under `[inert]` within the component: an `inert` outside `root` is not the component's doing. */
13
+ function isInert(root, el) {
14
+ const inert = el.closest('[inert]');
15
+ return inert !== null && root.contains(inert);
16
+ }
17
+ /**
18
+ * Whether `el` or something inside it receives the pointer. `pointer-events`
19
+ * inherits, so a subtree of `none` is unreachable unless a descendant sets it
20
+ * back, and then the browser hands that descendant the hover.
21
+ */
22
+ function takesPointer(el, known) {
23
+ let reachable = known.get(el);
24
+ if (reachable === undefined) {
25
+ reachable = getComputedStyle(el).pointerEvents !== 'none' || Array.from(el.children).some((child) => takesPointer(child, known));
26
+ known.set(el, reachable);
27
+ }
28
+ return reachable;
29
+ }
30
+ /**
31
+ * The subset of `elements` the browser could put into `state`: what a user
32
+ * could focus, or hover, or press. Forcing a state on anything else would show
33
+ * something the component can never be in.
34
+ *
35
+ * `focus-within` is not filtered. Whether some descendant takes focus cannot be
36
+ * told without a browser API for focusability.
37
+ */
38
+ export function reachableTargets(root, state, elements) {
39
+ switch (state) {
40
+ case 'focus':
41
+ case 'focus-visible':
42
+ return Array.from(elements).filter((el) => !isDisabled(el) && !isInert(root, el));
43
+ case 'hover':
44
+ case 'active': {
45
+ const known = new Map();
46
+ return Array.from(elements).filter((el) => takesPointer(el, known));
47
+ }
48
+ case 'focus-within':
49
+ return Array.from(elements);
50
+ }
51
+ }
52
+ /** What can take focus at all, whether or not it is disabled or inert now. */
53
+ const FOCUSABLE = 'button, input, select, textarea, a[href], area[href], summary, iframe, [tabindex], [contenteditable]:not([contenteditable="false"])';
54
+ /**
55
+ * The targets the fit of `state` is decided on. For focus on the whole
56
+ * component (`true`) that is what could take focus at all: the host is never
57
+ * `:disabled`, so a component whose only focusable element is disabled would
58
+ * otherwise fit, and show the plain default under a focus label. A component
59
+ * with nothing focusable in it keeps every element, as before.
60
+ */
61
+ function fitCandidates(state, target, targets) {
62
+ if (target !== true || (state !== 'focus' && state !== 'focus-visible'))
63
+ return targets;
64
+ const focusable = Array.from(targets).filter((el) => el.matches(FOCUSABLE));
65
+ return focusable.length > 0 ? new Set(focusable) : targets;
66
+ }
67
+ /** Why `elements`, all of them unreachable for `state`, are: the first applicable of disabled, inert and pointer-events. */
68
+ function unreachableReason(state, elements) {
69
+ if (state === 'hover' || state === 'active')
70
+ return 'has pointer-events: none';
71
+ return Array.from(elements).some(isDisabled) ? 'is disabled' : 'is inert';
72
+ }
73
+ /**
74
+ * Fits when every pseudo-class of `pseudo` has at least one reachable target
75
+ * under `root`. The reason names the first pseudo-class, in
76
+ * {@link PSEUDO_STATES} order, that does not. Focus on the whole component is
77
+ * decided on what could take focus ({@link fitCandidates}); which elements get
78
+ * the class is not affected.
79
+ */
80
+ export function evaluateStateFit(root, pseudo) {
81
+ if (!forcesAnyState(pseudo))
82
+ return { fits: false, reason: 'forces no state' };
83
+ for (const state of PSEUDO_STATES) {
84
+ const target = pseudo[state];
85
+ // Like the marker, which gives an empty selector no element to mark.
86
+ if (target === undefined || !forcesAnyState({ [state]: target }))
87
+ continue;
88
+ const targets = resolveTargets(root, target);
89
+ const label = describeTarget(target);
90
+ if (targets.size === 0)
91
+ return { fits: false, reason: `${label} matches nothing` };
92
+ const candidates = fitCandidates(state, target, targets);
93
+ if (reachableTargets(root, state, candidates).length === 0)
94
+ return { fits: false, reason: `${label} ${unreachableReason(state, candidates)}` };
95
+ }
96
+ return { fits: true };
97
+ }
@@ -0,0 +1,16 @@
1
+ import type { RuntimeComponent } from '../../plugin/plugin.types.js';
2
+ /** Synthetic input the manifest generator emits on directive wrapper classes
3
+ * to project per-variant `content` text into the host element. The directive
4
+ * itself doesn't declare it, so it isn't in `meta.inputs`, and code that
5
+ * validates / filters inputs against that list must allow this key through.
6
+ */
7
+ export declare const PRISM_CONTENT_INPUT = "__prismContent__";
8
+ /**
9
+ * The inputs a variant may set, by class member name, each mapped to the name
10
+ * `ComponentRef.setInput` takes. Angular resolves that by the public name, so a
11
+ * component's aliased input maps to its alias. A directive renders inside the
12
+ * generated `__PrismHost` wrapper, which declares the inputs under their member
13
+ * names and binds the aliases itself.
14
+ */
15
+ export declare function buildInputBindings(comp: RuntimeComponent): Map<string, string>;
16
+ //# sourceMappingURL=input-bindings.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"input-bindings.d.ts","sourceRoot":"","sources":["../../../src/app/renderer/input-bindings.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAErE;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,qBAAqB,CAAC;AAEtD;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,gBAAgB,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAQ9E"}
@@ -0,0 +1,21 @@
1
+ /** Synthetic input the manifest generator emits on directive wrapper classes
2
+ * to project per-variant `content` text into the host element. The directive
3
+ * itself doesn't declare it, so it isn't in `meta.inputs`, and code that
4
+ * validates / filters inputs against that list must allow this key through.
5
+ */
6
+ export const PRISM_CONTENT_INPUT = '__prismContent__';
7
+ /**
8
+ * The inputs a variant may set, by class member name, each mapped to the name
9
+ * `ComponentRef.setInput` takes. Angular resolves that by the public name, so a
10
+ * component's aliased input maps to its alias. A directive renders inside the
11
+ * generated `__PrismHost` wrapper, which declares the inputs under their member
12
+ * names and binds the aliases itself.
13
+ */
14
+ export function buildInputBindings(comp) {
15
+ const isDirective = comp.meta.componentMeta.isDirective;
16
+ const bindings = new Map(comp.meta.inputs.map((i) => [i.name, isDirective ? i.name : (i.alias ?? i.name)]));
17
+ if (isDirective) {
18
+ bindings.set(PRISM_CONTENT_INPUT, PRISM_CONTENT_INPUT);
19
+ }
20
+ return bindings;
21
+ }
@@ -5,5 +5,12 @@ import type { RuntimeComponent } from '../../plugin/plugin.types.js';
5
5
  * validates / filters inputs against that list must allow this key through.
6
6
  */
7
7
  export declare const PRISM_CONTENT_INPUT = "__prismContent__";
8
- export declare function buildKnownInputs(comp: RuntimeComponent): Set<string>;
8
+ /**
9
+ * The inputs a variant may set, by class member name, each mapped to the name
10
+ * `ComponentRef.setInput` takes. Angular resolves that by the public name, so a
11
+ * component's aliased input maps to its alias. A directive renders inside the
12
+ * generated `__PrismHost` wrapper, which declares the inputs under their member
13
+ * names and binds the aliases itself.
14
+ */
15
+ export declare function buildInputBindings(comp: RuntimeComponent): Map<string, string>;
9
16
  //# sourceMappingURL=known-inputs.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"known-inputs.d.ts","sourceRoot":"","sources":["../../../src/app/renderer/known-inputs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAErE;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,qBAAqB,CAAC;AAEtD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,gBAAgB,GAAG,GAAG,CAAC,MAAM,CAAC,CAOpE"}
1
+ {"version":3,"file":"known-inputs.d.ts","sourceRoot":"","sources":["../../../src/app/renderer/known-inputs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAErE;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,qBAAqB,CAAC;AAEtD;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,gBAAgB,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAQ9E"}
@@ -4,10 +4,18 @@
4
4
  * validates / filters inputs against that list must allow this key through.
5
5
  */
6
6
  export const PRISM_CONTENT_INPUT = '__prismContent__';
7
- export function buildKnownInputs(comp) {
8
- const known = new Set(comp.meta.inputs.map((i) => i.name));
9
- if (comp.meta.componentMeta.isDirective) {
10
- known.add(PRISM_CONTENT_INPUT);
7
+ /**
8
+ * The inputs a variant may set, by class member name, each mapped to the name
9
+ * `ComponentRef.setInput` takes. Angular resolves that by the public name, so a
10
+ * component's aliased input maps to its alias. A directive renders inside the
11
+ * generated `__PrismHost` wrapper, which declares the inputs under their member
12
+ * names and binds the aliases itself.
13
+ */
14
+ export function buildInputBindings(comp) {
15
+ const isDirective = comp.meta.componentMeta.isDirective;
16
+ const bindings = new Map(comp.meta.inputs.map((i) => [i.name, isDirective ? i.name : (i.alias ?? i.name)]));
17
+ if (isDirective) {
18
+ bindings.set(PRISM_CONTENT_INPUT, PRISM_CONTENT_INPUT);
11
19
  }
12
- return known;
20
+ return bindings;
13
21
  }
@@ -54,13 +54,20 @@ export declare class PrismRendererComponent {
54
54
  private readonly overlayCache;
55
55
  readonly activeOverlay: import("@angular/core").WritableSignal<Type<unknown> | null>;
56
56
  /**
57
- * Identifier of the currently rendered component/variant (for audit tooling / e2e).
58
- * Held back until the variant's forced pseudo states are in place, see {@link resolveRenderedKey}.
57
+ * Identifier of the currently rendered component/variant, and of the active state.
58
+ * Held back until the forced pseudo states and the state are in place, see {@link resolveRenderedKey}.
59
59
  */
60
60
  protected readonly renderedKey: import("@angular/core").Signal<string | null>;
61
+ /**
62
+ * Why the active state is not part of what the canvas shows, `null` when it is
63
+ * or none is active. A runner reads it next to `data-prism-rendered` and
64
+ * reports the cell as excluded rather than taking a picture of it.
65
+ */
66
+ protected readonly excludedReason: import("@angular/core").Signal<string | null>;
61
67
  /**
62
68
  * The states forced on the canvas, space-separated, `null` for none. Capture
63
- * mode keys the room it leaves for focus rings on it.
69
+ * mode keys the room it leaves for focus rings on it. The active state's
70
+ * pseudo-classes count only while it fits.
64
71
  */
65
72
  protected readonly forcedStates: import("@angular/core").Signal<string | null>;
66
73
  /** Resolved canvas layout for the active variant. Variant overrides component config; defaults to 'fit'. */