@akinon/pz-theme 2.0.95-beta.1 → 2.0.95-rc.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 (60) hide show
  1. package/CHANGELOG.md +33 -15
  2. package/package.json +4 -4
  3. package/src/__tests__/auth-commerce-events.test.tsx +1 -5
  4. package/src/__tests__/auth-form-error-block.test.tsx +0 -2
  5. package/src/__tests__/auth-form-validation.test.tsx +0 -2
  6. package/src/__tests__/auth-slot-coverage.test.ts +1 -9
  7. package/src/__tests__/auth-step-title.test.tsx +0 -2
  8. package/src/__tests__/detached-submit-state.test.tsx +2 -80
  9. package/src/__tests__/forgot-password-composed.test.tsx +0 -2
  10. package/src/__tests__/otp-form-composed.test.tsx +0 -2
  11. package/src/__tests__/password-reset-step.test.tsx +0 -2
  12. package/src/__tests__/register-form-composed.test.tsx +4 -253
  13. package/src/__tests__/registry-parity.test.ts +0 -12
  14. package/src/auth/auth-field-errors.tsx +0 -31
  15. package/src/blocks/account-button-block.tsx +0 -25
  16. package/src/blocks/add-to-cart-block.tsx +0 -15
  17. package/src/blocks/auth-field-block.tsx +3 -13
  18. package/src/blocks/auth-flow-block.tsx +3 -45
  19. package/src/blocks/auth-form-block.tsx +54 -337
  20. package/src/blocks/block-renderer-registry.tsx +0 -8
  21. package/src/blocks/button-block.tsx +13 -43
  22. package/src/blocks/change-password-form-block.tsx +0 -15
  23. package/src/blocks/forgot-password-block.tsx +9 -132
  24. package/src/blocks/newsletter-form-block.tsx +0 -16
  25. package/src/blocks/otp-form-block.tsx +67 -161
  26. package/src/blocks/profile-form-block.tsx +0 -13
  27. package/src/blocks/register-form-block.tsx +110 -362
  28. package/src/chrome/__tests__/theme-config-source.test.ts +1 -2
  29. package/src/chrome/placeholder-slugs.ts +0 -3
  30. package/src/chrome/route-slug.ts +0 -11
  31. package/src/index.ts +0 -10
  32. package/src/overlays/__tests__/modal-registry.test.ts +1 -5
  33. package/src/overlays/modal-chrome-css.ts +1 -8
  34. package/src/overlays/modal-preview-host.tsx +2 -7
  35. package/src/overlays/modal-registry.ts +1 -38
  36. package/src/overlays/overlay-surface.tsx +0 -23
  37. package/src/overlays/overlay-tokens.ts +0 -12
  38. package/src/overlays/theme-modal-mount.tsx +6 -85
  39. package/src/utils/visibility-rules.ts +0 -114
  40. package/src/__tests__/auth-captcha.test.tsx +0 -261
  41. package/src/__tests__/auth-destination.test.tsx +0 -348
  42. package/src/__tests__/auth-error-reading.test.ts +0 -232
  43. package/src/__tests__/auth-flow-scope.test.tsx +0 -162
  44. package/src/__tests__/auth-form-success-block.test.tsx +0 -429
  45. package/src/__tests__/block-action-audience.test.ts +0 -154
  46. package/src/__tests__/form-state-composition.test.tsx +0 -484
  47. package/src/__tests__/modal-login-close.test.tsx +0 -366
  48. package/src/__tests__/reset-password-composed.test.tsx +0 -385
  49. package/src/auth/auth-captcha.tsx +0 -125
  50. package/src/auth/auth-destination.ts +0 -168
  51. package/src/auth/form-state-context.tsx +0 -170
  52. package/src/auth/form-success-context.tsx +0 -56
  53. package/src/auth/read-auth-error.ts +0 -332
  54. package/src/blocks/auth-success-block.tsx +0 -85
  55. package/src/blocks/captcha-block.tsx +0 -61
  56. package/src/blocks/form-state-block.tsx +0 -84
  57. package/src/blocks/reset-password-block.tsx +0 -717
  58. package/src/overlays/__tests__/modal-loading.test.tsx +0 -235
  59. package/src/overlays/modal-host-context.tsx +0 -66
  60. package/src/overlays/overlay-loading.tsx +0 -144
package/src/index.ts CHANGED
@@ -99,16 +99,6 @@ export {
99
99
  useOpenModalIds
100
100
  } from './overlays/overlay-bus';
101
101
 
102
- /**
103
- * Which modal the composition is inside, for a brand's OWN auth surface: a
104
- * hand-written form composed into a drawer can now close the drawer the way
105
- * the built-in ones do. See `overlays/modal-host-context.tsx`.
106
- */
107
- export {
108
- useModalHostId,
109
- useHostModalCloser
110
- } from './overlays/modal-host-context';
111
-
112
102
  export {
113
103
  readThemeModals,
114
104
  MODAL_PRESENTATIONS
@@ -63,11 +63,7 @@ describe('readThemeModals', () => {
63
63
  // surface, so an unauthored padding must stay unauthored here.
64
64
  padding: undefined,
65
65
  showSheetHandle: true,
66
- stretchContent: false,
67
- // On for a modal that never mentioned it, which is safe precisely
68
- // because the layer waits: an unauthored delay leaves the surface's
69
- // own 150ms, and nothing a small composition can outrun changes.
70
- showLoading: true
66
+ stretchContent: false
71
67
  },
72
68
  autoTrigger: undefined,
73
69
  visibilityRules: undefined
@@ -41,14 +41,7 @@ export const MODAL_CHROME_PARTS = [
41
41
  'overlay',
42
42
  'close',
43
43
  'close-text',
44
- 'handle',
45
- // The layer shown while a composition is still committing, its mark, and the
46
- // authored words beside it. Chrome rather than blocks for the usual reason:
47
- // they are drawn by `overlay-loading.tsx` from inline styles and hang off no
48
- // `[data-block-id]`, so no block part rule could ever reach them.
49
- 'loading',
50
- 'loading-mark',
51
- 'loading-text'
44
+ 'handle'
52
45
  ] as const;
53
46
 
54
47
  export type ModalChromePart = (typeof MODAL_CHROME_PARTS)[number];
@@ -10,7 +10,6 @@ import {
10
10
  OverlayCloseButton,
11
11
  OverlayHandleProps
12
12
  } from './overlay-surface';
13
- import { ModalHostIdProvider } from './modal-host-context';
14
13
  import ThemeModalMount from './theme-modal-mount';
15
14
  import { useEditorModals } from './use-editor-modals';
16
15
  import { useHeldModal } from './use-modal-preview';
@@ -168,9 +167,7 @@ export const ModalPreviewHost = ({
168
167
  chromeCss={chromeCss}
169
168
  header={header}
170
169
  >
171
- <ModalHostIdProvider value={modal.id}>
172
- {composition(modal.slug)}
173
- </ModalHostIdProvider>
170
+ {composition(modal.slug)}
174
171
  </CentredModalSurface>
175
172
  ) : (
176
173
  <LayeredOverlaySurface
@@ -196,9 +193,7 @@ export const ModalPreviewHost = ({
196
193
  chromeCss={chromeCss}
197
194
  header={header}
198
195
  >
199
- <ModalHostIdProvider value={modal.id}>
200
- {composition(modal.slug)}
201
- </ModalHostIdProvider>
196
+ {composition(modal.slug)}
202
197
  </LayeredOverlaySurface>
203
198
  );
204
199
 
@@ -7,7 +7,6 @@ import type { VisibilityRules } from '../utils/visibility-rules';
7
7
  import {
8
8
  OVERLAY_BACKDROP_COLOR,
9
9
  OVERLAY_BACKDROP_OPACITY,
10
- OVERLAY_LOADING_DELAY_MAX,
11
10
  OVERLAY_WIDTH_UNITS,
12
11
  SHEET_HEIGHT_FALLBACK,
13
12
  isSheetHeightLength,
@@ -153,30 +152,6 @@ export interface ModalChrome {
153
152
  */
154
153
  closeText?: string;
155
154
  closeTextSide?: 'before' | 'after';
156
- /**
157
- * Whether the panel shows a loading mark while its composition is still
158
- * committing. Absent reads as ON, which cannot change how a published modal
159
- * looks: the layer is invisible for its first `loadingDelay` ms, and a
160
- * composition that commits in the same frame — every small one — has
161
- * revealed itself long before that. What it buys is the large ones, where
162
- * the panel's white surface is on screen for a beat with nothing in it.
163
- *
164
- * Off restores the single commit: the composition renders with the panel and
165
- * nothing is drawn over it.
166
- */
167
- showLoading?: boolean;
168
- /**
169
- * How long the panel may be empty before the mark fades in, in ms. Absent
170
- * means the historical-safe 150 — see `OVERLAY_LOADING_DELAY`.
171
- */
172
- loadingDelay?: number;
173
- /**
174
- * Words beside the mark. Absence-preserving for the same reason as
175
- * `closeText`, and for one more: this package has no dictionary, so a
176
- * default would be an English string on every storefront. Authored text is
177
- * the only text.
178
- */
179
- loadingLabel?: string;
180
155
  }
181
156
 
182
157
  /**
@@ -627,19 +602,7 @@ const readChrome = (value: unknown, name: string): ModalChrome => {
627
602
  // a close text must not come out of here claiming an empty one, and the
628
603
  // side only means something once the text exists.
629
604
  closeText: asString(raw.closeText) || undefined,
630
- closeTextSide: raw.closeTextSide === 'before' ? 'before' : undefined,
631
- // Absent reads as on: the layer is invisible until the delay has passed,
632
- // so turning it on for every published modal changes nothing a shopper
633
- // sees except on the modals that were already keeping them waiting.
634
- showLoading: asToggle(raw.showLoading, true),
635
- // Read, not defaulted, on the same grounds as `padding`: 0 is a real
636
- // answer ("show it at once"), so absence has to stay distinguishable.
637
- loadingDelay: asOptionalClamped(
638
- raw.loadingDelay,
639
- 0,
640
- OVERLAY_LOADING_DELAY_MAX
641
- ),
642
- loadingLabel: asString(raw.loadingLabel) || undefined
605
+ closeTextSide: raw.closeTextSide === 'before' ? 'before' : undefined
643
606
  };
644
607
  };
645
608
 
@@ -478,21 +478,6 @@ export interface OverlaySurfaceProps extends OverlayGeometryInput {
478
478
  chromeCss?: string;
479
479
  /** Rendered inside the panel, above the authored content. */
480
480
  header?: React.ReactNode;
481
- /**
482
- * Drawn over the panel while the composition is still committing — see
483
- * `overlay-loading.tsx` for why a large one needs it.
484
- *
485
- * A node rather than a flag because only the composed modal host has one:
486
- * the panel block passes nothing, so its markup — pinned byte for byte by
487
- * `panel-legacy-golden.json` — cannot move.
488
- */
489
- loading?: React.ReactNode;
490
- /**
491
- * Marks the dialog as still filling. The attribute is the accessible half of
492
- * `loading`: a wordless spinner tells a screen reader nothing, `aria-busy`
493
- * tells it exactly what is happening. Absent leaves no attribute at all.
494
- */
495
- panelBusy?: boolean;
496
481
  children: React.ReactNode;
497
482
  /**
498
483
  * Legacy marker attributes per node. Order matters: they are spread BEFORE
@@ -545,8 +530,6 @@ export const CentredModalSurface = ({
545
530
  topInset,
546
531
  chromeCss,
547
532
  header,
548
- loading,
549
- panelBusy,
550
533
  children,
551
534
  markers
552
535
  }: Omit<
@@ -582,7 +565,6 @@ export const CentredModalSurface = ({
582
565
  role="dialog"
583
566
  {...overlayProps}
584
567
  aria-label={ariaLabel}
585
- {...(panelBusy ? { 'aria-busy': true as const } : null)}
586
568
  // The backdrop closes the modal; clicks on the panel itself must not
587
569
  // bubble up into that handler.
588
570
  onClick={(event) => event.stopPropagation()}
@@ -611,7 +593,6 @@ export const CentredModalSurface = ({
611
593
  <style dangerouslySetInnerHTML={{ __html: chromeCss }} />
612
594
  ) : null}
613
595
  {header}
614
- {loading}
615
596
  {children}
616
597
  </div>
617
598
  </div>
@@ -645,8 +626,6 @@ export const LayeredOverlaySurface = ({
645
626
  topInset,
646
627
  chromeCss,
647
628
  header,
648
- loading,
649
- panelBusy,
650
629
  children,
651
630
  markers
652
631
  }: OverlaySurfaceProps) => {
@@ -673,7 +652,6 @@ export const LayeredOverlaySurface = ({
673
652
  role="dialog"
674
653
  {...overlayProps}
675
654
  aria-label={ariaLabel}
676
- {...(panelBusy ? { 'aria-busy': true as const } : null)}
677
655
  // Focus target of last resort, for a panel whose every control the
678
656
  // designer turned off.
679
657
  tabIndex={-1}
@@ -707,7 +685,6 @@ export const LayeredOverlaySurface = ({
707
685
  <OverlaySheetHandle markerAttrs={markers?.handle} />
708
686
  )}
709
687
  {header}
710
- {loading}
711
688
  {children}
712
689
  </div>
713
690
  );
@@ -33,18 +33,6 @@ export const OVERLAY_WIDTH_UNITS: OverlayWidthUnit[] = ['px', '%', 'vw'];
33
33
  export const OVERLAY_BACKDROP_COLOR = '#000000';
34
34
  export const OVERLAY_BACKDROP_OPACITY = 50;
35
35
 
36
- /**
37
- * How long a panel may be empty before its loading mark fades in, in ms, and
38
- * the longest delay a designer may ask for.
39
- *
40
- * Here rather than beside the layer that animates it for the reason at the top
41
- * of this file: the REGISTRY clamps the authored value, and the registry is
42
- * parsed on the server. `overlay-loading.tsx` re-exports both so client
43
- * callers keep one import path.
44
- */
45
- export const OVERLAY_LOADING_DELAY = 150;
46
- export const OVERLAY_LOADING_DELAY_MAX = 2000;
47
-
48
36
  /**
49
37
  * How tall a sheet is, and what a designer is allowed to write for it.
50
38
  *
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
 
3
- import React, { startTransition, useEffect, useRef, useState } from 'react';
3
+ import React, { useEffect, useRef } from 'react';
4
4
 
5
5
  import { useOverlay } from '../hooks/use-overlay';
6
6
  import { useVisibilityContext } from '../hooks/use-visibility-context';
@@ -18,8 +18,6 @@ import {
18
18
  LayeredOverlaySurface,
19
19
  OverlayCloseButton
20
20
  } from './overlay-surface';
21
- import { OverlayLoadingLayer } from './overlay-loading';
22
- import { ModalHostIdProvider } from './modal-host-context';
23
21
  import { useEditorKnowsModal } from './use-editor-modals';
24
22
  import { useModalPreview } from './use-modal-preview';
25
23
  import { useOverlayBreakpoint } from './use-overlay-breakpoint';
@@ -136,53 +134,6 @@ const ThemeModalMount = ({
136
134
  isDesigner ? breakpoint : undefined
137
135
  );
138
136
 
139
- /**
140
- * Whether the composition is revealed in its own commit, with a loading
141
- * layer under it until it lands.
142
- *
143
- * Never in the canvas: a designer composing a modal must see their blocks
144
- * the instant they select it, and the editor's own render is not the commit
145
- * this is about.
146
- */
147
- const showsLoading = chrome.showLoading !== false && !isEditing;
148
- const [revealed, setRevealed] = useState(!showsLoading);
149
-
150
- /**
151
- * A TRANSITION, not a timer.
152
- *
153
- * The pause this covers is the commit itself: a composed modal's content
154
- * ships with the page, so opening a large one — ninety blocks, a text block
155
- * carrying half a megabyte of pasted markup — is one long synchronous render
156
- * during which nothing at all can be scheduled, a `setTimeout` that would
157
- * show a spinner least of all. Rendering the composition at transition
158
- * priority splits it off the urgent commit that draws the panel, so the
159
- * panel and the loading layer paint first and the browser is free in
160
- * between.
161
- *
162
- * It costs the fast path one extra commit and nothing visible: the layer is
163
- * invisible for its first `loadingDelay` ms, which a small composition has
164
- * long since outrun. And because `act()` flushes transitions, a test that
165
- * opens a modal still sees its content in the same tick.
166
- *
167
- * Reset on close so a reopen starts from the cheap commit again — the bus is
168
- * module state, so a mount lives far longer than one open.
169
- */
170
- useEffect(() => {
171
- if (!showsLoading) {
172
- setRevealed(true);
173
-
174
- return;
175
- }
176
-
177
- if (!opened) {
178
- setRevealed(false);
179
-
180
- return;
181
- }
182
-
183
- startTransition(() => setRevealed(true));
184
- }, [opened, showsLoading]);
185
-
186
137
  /**
187
138
  * In the canvas the overlay is fully inert: it never steals focus into the
188
139
  * page the designer is styling, never freezes it, never claims `aria-modal`
@@ -197,17 +148,8 @@ const ThemeModalMount = ({
197
148
  // be a lie.
198
149
  modal: isCentred ? true : chrome.showBackdrop,
199
150
  lockScroll: chrome.lockScroll && (isCentred || chrome.showBackdrop),
200
- closeOnEscape: chrome.closeOnEscape
201
- /**
202
- * No `fallbackFocusRef` on purpose. It used to point at `rootRef`, which
203
- * cannot take focus (the div carries no `tabIndex={-1}`) and is detached
204
- * by the time the hand-back runs anyway — `if (!opened) return null`
205
- * removes it in the same commit that closes the modal. `useOverlay`
206
- * verifies that focus actually landed, so the branch was dead; naming a
207
- * target that cannot work only hides the real gap, which is a modal
208
- * nobody clicked open (an auto-triggered popup) having no opener to hand
209
- * focus back to.
210
- */
151
+ closeOnEscape: chrome.closeOnEscape,
152
+ fallbackFocusRef: rootRef
211
153
  });
212
154
 
213
155
  if (!opened) return null;
@@ -229,20 +171,6 @@ const ThemeModalMount = ({
229
171
 
230
172
  const ariaLabel = chrome.ariaLabel || modal.name;
231
173
 
232
- /**
233
- * The layer, and the composition it stands in for. Both are absent once the
234
- * composition has landed, so nothing is drawn over it and no attribute is
235
- * left claiming the dialog is still filling.
236
- */
237
- const loading =
238
- showsLoading && !revealed ? (
239
- <OverlayLoadingLayer
240
- label={chrome.loadingLabel}
241
- delay={chrome.loadingDelay}
242
- />
243
- ) : null;
244
- const body = revealed ? children : null;
245
-
246
174
  const surface = isCentred ? (
247
175
  <CentredModalSurface
248
176
  overlayProps={overlay.overlayProps}
@@ -258,10 +186,8 @@ const ThemeModalMount = ({
258
186
  topInset={chrome.topInset}
259
187
  chromeCss={chromeCss}
260
188
  header={header}
261
- loading={loading}
262
- panelBusy={Boolean(loading)}
263
189
  >
264
- {body}
190
+ {children}
265
191
  </CentredModalSurface>
266
192
  ) : (
267
193
  <LayeredOverlaySurface
@@ -283,10 +209,8 @@ const ThemeModalMount = ({
283
209
  panelStretch={chrome.stretchContent}
284
210
  chromeCss={chromeCss}
285
211
  header={header}
286
- loading={loading}
287
- panelBusy={Boolean(loading)}
288
212
  >
289
- {body}
213
+ {children}
290
214
  </LayeredOverlaySurface>
291
215
  );
292
216
 
@@ -300,10 +224,7 @@ const ThemeModalMount = ({
300
224
  data-theme-modal={modal.id}
301
225
  data-theme-modal-state="open"
302
226
  >
303
- {/* Tells the composition which modal it is inside, so a form that
304
- finishes can close THIS one — see `modal-host-context.tsx`. Renders
305
- no element, so the published markup is untouched. */}
306
- <ModalHostIdProvider value={modal.id}>{surface}</ModalHostIdProvider>
227
+ {surface}
307
228
  </div>
308
229
  );
309
230
  };
@@ -532,109 +532,6 @@ export const evaluateVisibilityRules = (
532
532
  const isVisibilityRuleExempt = (node: Block | Section): boolean =>
533
533
  containsCheckoutOutlet(node);
534
534
 
535
-
536
- /**
537
- * A per-breakpoint value, read the way the renderers read one.
538
- *
539
- * A local reader rather than `getResponsiveValue` from `../utils`: this module
540
- * is pure and is walked on the server read path, and that barrel pulls in the
541
- * React helpers.
542
- */
543
- const readResponsive = (value: unknown, breakpoint?: string): unknown => {
544
- if (value === null || typeof value !== 'object' || Array.isArray(value)) {
545
- return value;
546
- }
547
-
548
- const record = value as Record<string, unknown>;
549
- const chain = [
550
- breakpoint,
551
- ...(breakpoint ? BREAKPOINT_FALLBACKS[breakpoint] ?? [] : []),
552
- 'desktop'
553
- ];
554
-
555
- for (const key of chain) {
556
- if (key && record[key] !== undefined) return record[key];
557
- }
558
-
559
- return undefined;
560
- };
561
-
562
- /**
563
- * Who a block's ACTION is for.
564
- *
565
- * Visibility already answers "who is this block for"; this answers the same
566
- * question about the action wired onto it, which is not the same thing. The
567
- * account button is the case that forced it: it renders its own link — to the
568
- * account for a member, to the login page for a guest — while a designer wires
569
- * "open the login drawer" onto the block itself. Both then fire on one click,
570
- * so a signed-in shopper landed on their account page with the login drawer
571
- * open over it.
572
- *
573
- * Resolved HERE, in the pass that already knows who the shopper is, rather
574
- * than in `use-block-action`: that hook runs for every block on the page, and
575
- * making it read the session would make a SessionProvider mandatory for the
576
- * whole theme rather than for the placeholder wrapper that already has one.
577
- */
578
- export const BLOCK_ACTION_AUDIENCES = ['all', 'guest', 'authenticated'] as const;
579
-
580
- export type BlockActionAudience = (typeof BLOCK_ACTION_AUDIENCES)[number];
581
-
582
- const MODAL_ACTION_TYPES = new Set([
583
- 'open-modal',
584
- 'close-modal',
585
- 'toggle-modal'
586
- ]);
587
-
588
- /**
589
- * The audience a block's action is scoped to.
590
- *
591
- * Absent means `all`, EXCEPT for an account button opening a modal: a member
592
- * clicking their own account button never wants the login drawer, so the
593
- * default there is `guest`. That is a behaviour change on published
594
- * compositions, and a deliberate one — the alternative is the drawer covering
595
- * the account page it just navigated to. A brand that really wants it for
596
- * everyone says `all`.
597
- */
598
- const resolveActionAudience = (
599
- block: Block,
600
- currentBreakpoint?: string
601
- ): BlockActionAudience => {
602
- const authored = String(
603
- readResponsive(block.properties?.actionAudience, currentBreakpoint) ?? ''
604
- ).trim();
605
-
606
- if ((BLOCK_ACTION_AUDIENCES as readonly string[]).includes(authored)) {
607
- return authored as BlockActionAudience;
608
- }
609
-
610
- const actionType = String(
611
- readResponsive(block.properties?.actionType, currentBreakpoint) ?? ''
612
- ).trim();
613
-
614
- if (block.type === 'account-button' && MODAL_ACTION_TYPES.has(actionType)) {
615
- return 'guest';
616
- }
617
-
618
- return 'all';
619
- };
620
-
621
- /**
622
- * Whether the action runs for this shopper.
623
- *
624
- * `loading` counts as a guest: the session is unknown for the first client
625
- * render, and withholding the login drawer through it would break the common
626
- * case to fix the rare one.
627
- */
628
- const actionRunsFor = (
629
- audience: BlockActionAudience,
630
- authState: unknown
631
- ): boolean => {
632
- if (audience === 'all') return true;
633
- if (audience === 'authenticated') return authState === 'authenticated';
634
-
635
- return authState !== 'authenticated';
636
- };
637
-
638
535
  const applyBlockVisibility = (
639
536
  block: Block,
640
537
  context: VisibilityRuleContext
@@ -644,19 +541,8 @@ const applyBlockVisibility = (
644
541
  isVisibilityRuleExempt(block) ||
645
542
  evaluateVisibilityRules(block.properties?.visibilityRules, context);
646
543
 
647
- /**
648
- * An action this shopper is not the audience for is REMOVED rather than
649
- * ignored at click time, so the wrapper never claims `aria-haspopup="dialog"`
650
- * for a dialog it will not open either.
651
- */
652
- const audience = resolveActionAudience(block, context.breakpoint);
653
- const properties = actionRunsFor(audience, context.authState)
654
- ? block.properties
655
- : { ...(block.properties || {}), actionType: 'none' };
656
-
657
544
  return {
658
545
  ...block,
659
- properties,
660
546
  hidden: Boolean(block.hidden) || !isVisibleByRules,
661
547
  blocks
662
548
  };