@webikon/webentor-core 0.9.14 → 0.10.1

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 (126) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +41 -0
  3. package/core-js/_alpine.ts +6 -0
  4. package/core-js/_slider.ts +22 -11
  5. package/core-js/blocks-components/button.tsx +17 -2
  6. package/core-js/blocks-components/custom-image-sizes-panel.tsx +3 -1
  7. package/core-js/blocks-components/typography-picker-select.tsx +16 -1
  8. package/core-js/blocks-filters/_filter-core-typography.tsx +11 -1
  9. package/core-js/blocks-filters/_slider-settings.tsx +1 -1
  10. package/core-js/blocks-filters/_wrap-with-container.tsx +104 -0
  11. package/core-js/blocks-filters/responsive-settings/AGENTS.md +255 -0
  12. package/core-js/blocks-filters/responsive-settings/components/AppliedClassesViewer.tsx +189 -0
  13. package/core-js/blocks-filters/responsive-settings/components/BoxModelControl.tsx +346 -0
  14. package/core-js/blocks-filters/responsive-settings/components/BreakpointResetButton.tsx +94 -0
  15. package/core-js/blocks-filters/responsive-settings/components/DebugPanel.tsx +67 -0
  16. package/core-js/blocks-filters/responsive-settings/components/InheritedIndicator.tsx +32 -0
  17. package/core-js/blocks-filters/responsive-settings/components/LinkedValuesControl.tsx +55 -0
  18. package/core-js/blocks-filters/responsive-settings/components/ResponsiveSelectGroup.tsx +185 -0
  19. package/core-js/blocks-filters/responsive-settings/components/ResponsiveTabPanel.tsx +106 -0
  20. package/core-js/blocks-filters/responsive-settings/index.tsx +97 -148
  21. package/core-js/blocks-filters/responsive-settings/migration.ts +86 -0
  22. package/core-js/blocks-filters/responsive-settings/panels/BlockLinkPanel.tsx +38 -0
  23. package/core-js/blocks-filters/responsive-settings/panels/BorderPanel.tsx +61 -0
  24. package/core-js/blocks-filters/responsive-settings/panels/DisplayLayoutPanel.tsx +92 -0
  25. package/core-js/blocks-filters/responsive-settings/panels/SpacingPanel.tsx +63 -0
  26. package/core-js/blocks-filters/responsive-settings/panels/index.ts +4 -0
  27. package/core-js/blocks-filters/responsive-settings/registry.ts +88 -0
  28. package/core-js/blocks-filters/responsive-settings/settings/block-link/index.ts +3 -0
  29. package/core-js/blocks-filters/responsive-settings/settings/block-link/panel.tsx +6 -6
  30. package/core-js/blocks-filters/responsive-settings/settings/block-link/registration.ts +35 -0
  31. package/core-js/blocks-filters/responsive-settings/settings/border/border/properties.ts +1 -2
  32. package/core-js/blocks-filters/responsive-settings/settings/border/border/settings.tsx +21 -3
  33. package/core-js/blocks-filters/responsive-settings/settings/border/border-radius/index.tsx +2 -1
  34. package/core-js/blocks-filters/responsive-settings/settings/border/border-radius/properties.ts +6 -29
  35. package/core-js/blocks-filters/responsive-settings/settings/border/border-radius/settings.tsx +79 -6
  36. package/core-js/blocks-filters/responsive-settings/settings/border/index.ts +5 -1
  37. package/core-js/blocks-filters/responsive-settings/settings/border/panel.tsx +5 -54
  38. package/core-js/blocks-filters/responsive-settings/settings/border/registration.ts +84 -0
  39. package/core-js/blocks-filters/responsive-settings/settings/border/settings.tsx +21 -0
  40. package/core-js/blocks-filters/responsive-settings/settings/flex-item/index.ts +4 -0
  41. package/core-js/blocks-filters/responsive-settings/settings/flex-item/properties.ts +60 -0
  42. package/core-js/blocks-filters/responsive-settings/settings/flex-item/registration.ts +78 -0
  43. package/core-js/blocks-filters/responsive-settings/settings/flex-item/settings.tsx +90 -0
  44. package/core-js/blocks-filters/responsive-settings/settings/flexbox/index.ts +4 -0
  45. package/core-js/blocks-filters/responsive-settings/settings/flexbox/properties.ts +80 -0
  46. package/core-js/blocks-filters/responsive-settings/settings/flexbox/registration.ts +66 -0
  47. package/core-js/blocks-filters/responsive-settings/settings/flexbox/settings.tsx +78 -0
  48. package/core-js/blocks-filters/responsive-settings/settings/grid/index.ts +4 -0
  49. package/core-js/blocks-filters/responsive-settings/settings/grid/properties.ts +72 -0
  50. package/core-js/blocks-filters/responsive-settings/settings/grid/registration.ts +66 -0
  51. package/core-js/blocks-filters/responsive-settings/settings/grid/settings.tsx +78 -0
  52. package/core-js/blocks-filters/responsive-settings/settings/grid-item/index.ts +4 -0
  53. package/core-js/blocks-filters/responsive-settings/settings/grid-item/properties.ts +44 -0
  54. package/core-js/blocks-filters/responsive-settings/settings/grid-item/registration.ts +74 -0
  55. package/core-js/blocks-filters/responsive-settings/settings/grid-item/settings.tsx +87 -0
  56. package/core-js/blocks-filters/responsive-settings/settings/layout/index.ts +4 -0
  57. package/core-js/blocks-filters/responsive-settings/settings/layout/properties.ts +51 -0
  58. package/core-js/blocks-filters/responsive-settings/settings/layout/registration.ts +96 -0
  59. package/core-js/blocks-filters/responsive-settings/settings/layout/settings.tsx +64 -0
  60. package/core-js/blocks-filters/responsive-settings/settings/presets/index.ts +4 -0
  61. package/core-js/blocks-filters/responsive-settings/settings/presets/presets.ts +52 -0
  62. package/core-js/blocks-filters/responsive-settings/settings/presets/registration.ts +53 -0
  63. package/core-js/blocks-filters/responsive-settings/settings/presets/settings.tsx +100 -0
  64. package/core-js/blocks-filters/responsive-settings/settings/shared/gap-values.ts +16 -0
  65. package/core-js/blocks-filters/responsive-settings/settings/shared/layout-values.ts +56 -0
  66. package/core-js/blocks-filters/responsive-settings/settings/shared/tw-values.ts +107 -0
  67. package/core-js/blocks-filters/responsive-settings/settings/sizing/index.ts +4 -0
  68. package/core-js/blocks-filters/responsive-settings/settings/sizing/properties.ts +71 -0
  69. package/core-js/blocks-filters/responsive-settings/settings/sizing/registration.ts +52 -0
  70. package/core-js/blocks-filters/responsive-settings/settings/sizing/settings.tsx +96 -0
  71. package/core-js/blocks-filters/responsive-settings/settings/spacing/index.ts +7 -2
  72. package/core-js/blocks-filters/responsive-settings/settings/spacing/panel.tsx +5 -45
  73. package/core-js/blocks-filters/responsive-settings/settings/spacing/properties.ts +51 -29
  74. package/core-js/blocks-filters/responsive-settings/settings/spacing/registration.ts +53 -0
  75. package/core-js/blocks-filters/responsive-settings/settings/spacing/settings.tsx +26 -55
  76. package/core-js/blocks-filters/responsive-settings/types/index.ts +174 -28
  77. package/core-js/blocks-filters/responsive-settings/utils.ts +247 -216
  78. package/core-js/config/index.ts +6 -0
  79. package/core-js/config/webentor-config.ts +44 -2
  80. package/core-js/index.ts +8 -10
  81. package/core-js/types/index.ts +6 -0
  82. package/package.json +116 -6
  83. package/public/build/assets/_utils-CzK6Vfiv.js +2 -0
  84. package/public/build/assets/{_utils-PDaZ1Dn1.js.map → _utils-CzK6Vfiv.js.map} +1 -1
  85. package/public/build/assets/coreAppStyles-CukxHLz7.css +1 -0
  86. package/public/build/assets/coreEditorJs-DYd3ZopL.js +366 -0
  87. package/public/build/assets/coreEditorJs-DYd3ZopL.js.map +1 -0
  88. package/public/build/assets/coreEditorStyles-I9xzOGSX.css +1 -0
  89. package/public/build/assets/resources/blocks/e-table/{script-BIchbcPK.js → script-C_Z50hjm.js} +2 -2
  90. package/public/build/assets/resources/blocks/e-table/{script-BIchbcPK.js.map → script-C_Z50hjm.js.map} +1 -1
  91. package/public/build/assets/{sliderJs-Ch69_tVA.js → sliderJs-CyGnrv0Q.js} +3 -3
  92. package/public/build/assets/{sliderJs-Ch69_tVA.js.map → sliderJs-CyGnrv0Q.js.map} +1 -1
  93. package/public/build/manifest.json +10 -10
  94. package/resources/blocks/e-accordion-group/block.json +6 -4
  95. package/resources/blocks/e-gallery/block.json +2 -2
  96. package/resources/blocks/e-gallery/e-gallery.block.tsx +4 -0
  97. package/resources/blocks/e-image/e-image.block.tsx +4 -0
  98. package/resources/blocks/e-slider/block.json +3 -2
  99. package/resources/blocks/e-tab-container/block.json +2 -1
  100. package/resources/blocks/e-tabs/block.json +2 -1
  101. package/resources/blocks/l-flexible-container/block.json +3 -2
  102. package/resources/blocks/l-mobile-nav/block.json +2 -1
  103. package/resources/blocks/l-nav-menu/block.json +2 -1
  104. package/resources/blocks/l-nav-menu/l-nav-menu.block.tsx +2 -0
  105. package/resources/blocks/l-section/block.json +7 -5
  106. package/resources/blocks/l-section/l-section.block.tsx +40 -31
  107. package/resources/scripts/editor.ts +2 -0
  108. package/resources/styles/common/_editor.css +22 -0
  109. package/resources/styles/common/_utilities.css +210 -0
  110. package/core-js/blocks-filters/responsive-settings/constants.ts +0 -11
  111. package/core-js/blocks-filters/responsive-settings/settings/container/display/index.ts +0 -2
  112. package/core-js/blocks-filters/responsive-settings/settings/container/display/properties.ts +0 -167
  113. package/core-js/blocks-filters/responsive-settings/settings/container/display/settings.tsx +0 -73
  114. package/core-js/blocks-filters/responsive-settings/settings/container/flexbox/index.ts +0 -2
  115. package/core-js/blocks-filters/responsive-settings/settings/container/flexbox/properties.ts +0 -187
  116. package/core-js/blocks-filters/responsive-settings/settings/container/flexbox/settings.tsx +0 -131
  117. package/core-js/blocks-filters/responsive-settings/settings/container/grid/index.ts +0 -2
  118. package/core-js/blocks-filters/responsive-settings/settings/container/grid/properties.ts +0 -187
  119. package/core-js/blocks-filters/responsive-settings/settings/container/grid/settings.tsx +0 -132
  120. package/core-js/blocks-filters/responsive-settings/settings/container/index.ts +0 -4
  121. package/core-js/blocks-filters/responsive-settings/settings/container/panel.tsx +0 -92
  122. package/public/build/assets/_utils-PDaZ1Dn1.js +0 -2
  123. package/public/build/assets/coreAppStyles-Dp0WYk4N.css +0 -1
  124. package/public/build/assets/coreEditorJs-Cyc87wTo.js +0 -366
  125. package/public/build/assets/coreEditorJs-Cyc87wTo.js.map +0 -1
  126. package/public/build/assets/coreEditorStyles-D8-nNpQG.css +0 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,29 @@
1
1
  # Webentor Core Changelog
2
2
 
3
+ ## 0.10.1
4
+
5
+ - Fix `l-section` inner z-index
6
+
7
+ ## 0.10.0
8
+
9
+ - Refactor responsive settings and improve UX
10
+ - **BREAKING**: Run keys migrator from `Settings -> Webentor Migrator`
11
+ - **BREAKING**: Attributes keys changed, so supports in `block.json` and other usage in theme needs to be checked and renamed:
12
+ - `flexboxItem` -> `flexItem`
13
+ - `display` -> `layout` (for `display`) and `sizing` (for `width`, `height`, `minWidth`, `minHeight`, `maxWidth`, `maxHeight`)
14
+ - Added reset buttons for breakpoints
15
+ - Better values grouping
16
+ - Better cascade breakpoints conditioning
17
+ - Spacing linking
18
+ - Add `webentor/block_wrapper_class_properties` filter to exclude generated class groups such as `backgroundColor` and `textColor` from filtered `classes_by_property` and wrapper output, while keeping the raw generated map available for `block_custom_classes`
19
+ - Add Wrap with Flexible container
20
+ - Add applied classes viewer to see which responsive classes are applied
21
+ - Add Quick layout presets, customizable with filter `webentor.core.responsiveSettings.layoutPresets`
22
+ - Fix PHP path in `init.php`
23
+ - Add package module exports, you can now use `@webikon/webentor-core` instead of `@webentorCore` when importing in the theme
24
+ - Add `webentor.core.l-section.output` filter to customize the section editor preview markup
25
+
26
+
3
27
  ## 0.9.14
4
28
 
5
29
  - Fix Button link display
package/README.md CHANGED
@@ -21,6 +21,47 @@ pnpm install
21
21
  pnpm build
22
22
  ```
23
23
 
24
+ ## JS Package Exports
25
+
26
+ This package exports raw TypeScript source so the consumer's Vite pipeline
27
+ compiles it together with the rest of the project. Consumer projects using
28
+ Vite 7+ must enable the TypeScript-aware config loader:
29
+
30
+ ```jsonc
31
+ // package.json scripts
32
+ "dev": "vite --configLoader runner",
33
+ "build": "vite build --configLoader runner"
34
+ ```
35
+
36
+ Available subpath imports:
37
+
38
+ ```ts
39
+ import { debounce } from '@webikon/webentor-core';
40
+ import { Alpine } from '@webikon/webentor-core/_alpine';
41
+ import { SliderComponent } from '@webikon/webentor-core/_slider';
42
+ import { WebentorButton } from '@webikon/webentor-core/blocks-components';
43
+ import { initResponsiveSettings } from '@webikon/webentor-core/blocks-filters';
44
+ import { buildSafelist, webentorDefaultConfig } from '@webikon/webentor-core/config';
45
+ import type { WebentorConfig } from '@webikon/webentor-core/types';
46
+ ```
47
+
48
+ ## WP-CLI
49
+
50
+ Use the responsive attributes migration command on an existing project when you
51
+ want to scan or migrate block content without opening the WordPress admin.
52
+
53
+ ```bash
54
+ wp webentor migrate-responsive-attributes
55
+ wp webentor migrate-responsive-attributes --migrate
56
+ wp webentor migrate-responsive-attributes --cleanup
57
+ wp webentor migrate-responsive-attributes --site-id=7 --cleanup
58
+ ```
59
+
60
+ By default the command only scans and reports how many posts and blocks still
61
+ need migration or cleanup. `--migrate` writes v2 attributes while keeping legacy
62
+ keys, and `--cleanup` removes the legacy keys after backfilling the v2 shape.
63
+ On multisite, `--site-id=<id>` runs the command against a specific site.
64
+
24
65
  ## Linting
25
66
 
26
67
  Core consumes shared presets from `@webikon/webentor-configs` to keep lint behavior aligned across repositories.
@@ -1,6 +1,12 @@
1
1
  import collapse from '@alpinejs/collapse';
2
2
  import Alpine from 'alpinejs';
3
3
 
4
+ declare global {
5
+ interface Window {
6
+ Alpine?: typeof Alpine;
7
+ }
8
+ }
9
+
4
10
  // Initialize Alpine if it's not already initialized
5
11
  const AlpineInstance = window.Alpine || Alpine;
6
12
  if (!window.Alpine) {
@@ -3,18 +3,21 @@ import Swiper from 'swiper/bundle';
3
3
 
4
4
  import { debounce, throttle } from './_utils';
5
5
 
6
- type BreakpointConfig = Swiper & {
6
+ type SliderParams = Record<string, any> & {
7
+ autoplayControl: boolean;
8
+ breakpoints: Record<string, BreakpointConfig>;
9
+ on?: Record<string, (...args: any[]) => void>;
10
+ };
11
+
12
+ type BreakpointConfig = Record<string, any> & {
7
13
  enabled: boolean;
8
14
  };
9
15
 
10
16
  class SliderComponent {
11
17
  private element: HTMLElement;
12
- private params: Swiper & {
13
- breakpoints: Record<string, BreakpointConfig>;
14
- autoplayControl: boolean;
15
- };
18
+ private params: SliderParams;
16
19
  private swiper: Swiper | null;
17
- private container: HTMLElement;
20
+ private container: HTMLElement | null;
18
21
  private breakpoints: Record<string, BreakpointConfig> | null;
19
22
  private autoplayButton: HTMLElement | null;
20
23
  private timerSeconds: HTMLElement | null;
@@ -108,10 +111,14 @@ class SliderComponent {
108
111
  }
109
112
  },
110
113
  autoplayTimeLeft: (swiper, time, progress) => {
111
- if (this.autoplayControlEnabled) {
114
+ if (
115
+ this.autoplayControlEnabled &&
116
+ this.timerCircle &&
117
+ this.timerSeconds
118
+ ) {
112
119
  this.timerCircle.style.setProperty(
113
120
  '--slider-timer-progress',
114
- 1 - progress,
121
+ String(1 - progress),
115
122
  );
116
123
  this.timerSeconds.textContent = `${Math.ceil(time / 1000)}s`;
117
124
  }
@@ -125,8 +132,12 @@ class SliderComponent {
125
132
  ...this.params.on,
126
133
  },
127
134
  navigation: {
128
- nextEl: this.element.querySelector('.js-slider-button-next'),
129
- prevEl: this.element.querySelector('.js-slider-button-prev'),
135
+ nextEl: this.element.querySelector(
136
+ '.js-slider-button-next',
137
+ ) as HTMLElement | null,
138
+ prevEl: this.element.querySelector(
139
+ '.js-slider-button-prev',
140
+ ) as HTMLElement | null,
130
141
  },
131
142
  });
132
143
 
@@ -174,7 +185,7 @@ class SliderComponent {
174
185
 
175
186
  if (!wrapper) return;
176
187
 
177
- const slides = Array.from(wrapper.children);
188
+ const slides = Array.from(wrapper.children) as HTMLElement[];
178
189
 
179
190
  if (slides.length === 0) return;
180
191
 
@@ -87,7 +87,20 @@ import { __ } from '@wordpress/i18n';
87
87
  }
88
88
  */
89
89
 
90
- export const WebentorButton = (props) => {
90
+ type WebentorButtonProps = {
91
+ attributeName: string;
92
+ attributes: Record<string, any>;
93
+ buttonClassName?: string;
94
+ className?: string;
95
+ hideLink?: boolean;
96
+ hideSize?: boolean;
97
+ hideVariant?: boolean;
98
+ innerClassName?: string;
99
+ placement?: string;
100
+ setAttributes: (attributes: Record<string, any>) => void;
101
+ };
102
+
103
+ export const WebentorButton = (props: WebentorButtonProps) => {
91
104
  const {
92
105
  placement,
93
106
  className,
@@ -199,7 +212,7 @@ export const WebentorButton = (props) => {
199
212
  </button>,
200
213
  props,
201
214
  buttonClassName,
202
- );
215
+ ) as React.ReactNode;
203
216
 
204
217
  return (
205
218
  <span className={`${className ?? ''} wbtr:relative wbtr:inline-block`}>
@@ -300,6 +313,7 @@ export const WebentorButton = (props) => {
300
313
  <SelectControl
301
314
  label={__('Button HTML Element', 'webentor')}
302
315
  value={attributes[attributeName]?.htmlElement}
316
+ __nextHasNoMarginBottom
303
317
  options={[
304
318
  { label: __('Link (<a>)', 'webentor'), value: 'a' },
305
319
  { label: __('Button (<button>)', 'webentor'), value: 'button' },
@@ -378,6 +392,7 @@ export const WebentorButton = (props) => {
378
392
  <SelectControl
379
393
  label="Icon Position"
380
394
  value={attributes[attributeName]?.iconPosition}
395
+ __nextHasNoMarginBottom
381
396
  options={iconPositions}
382
397
  onChange={(value) =>
383
398
  updateObjectAttribute(
@@ -43,7 +43,9 @@ export const CustomImageSizesPanel: React.FC<Props> = (props: Props) => {
43
43
  noticeAfter,
44
44
  } = props;
45
45
 
46
- const breakpoints = applyFilters('webentor.core.twBreakpoints', ['basic']);
46
+ const breakpoints = applyFilters('webentor.core.twBreakpoints', [
47
+ 'basic',
48
+ ]) as string[];
47
49
 
48
50
  const hasSizeSettingsForBreakpoint = (attributes, breakpoint) => {
49
51
  return (
@@ -1,7 +1,22 @@
1
1
  import { CustomSelectControl } from '@wordpress/components';
2
2
  import { __, sprintf } from '@wordpress/i18n';
3
3
 
4
- export const WebentorTypographyPickerSelect = (props) => {
4
+ type TypographyOption = {
5
+ key: string;
6
+ name: string;
7
+ value: string;
8
+ };
9
+
10
+ type WebentorTypographyPickerSelectProps = {
11
+ __next40pxDefaultSize?: boolean;
12
+ onChange: (value: string) => void;
13
+ options: TypographyOption[];
14
+ value?: string;
15
+ };
16
+
17
+ export const WebentorTypographyPickerSelect = (
18
+ props: WebentorTypographyPickerSelectProps,
19
+ ) => {
5
20
  const { __next40pxDefaultSize, value, onChange, options } = props;
6
21
 
7
22
  const selectedOption = value
@@ -13,6 +13,13 @@ const BLOCKS = [
13
13
  'core/post-title',
14
14
  ];
15
15
 
16
+ type TypographyOption = {
17
+ __experimentalHint?: string;
18
+ key: string;
19
+ name: string;
20
+ value: string;
21
+ };
22
+
16
23
  /**
17
24
  * BlockEdit
18
25
  *
@@ -27,7 +34,10 @@ const BLOCKS = [
27
34
  function BlockEdit(props) {
28
35
  const { customTypography } = props.attributes;
29
36
 
30
- const options = applyFilters('webentor.core.customTypographyKeys', []);
37
+ const options = applyFilters(
38
+ 'webentor.core.customTypographyKeys',
39
+ [],
40
+ ) as TypographyOption[];
31
41
 
32
42
  return (
33
43
  <InspectorControls group="styles">
@@ -23,7 +23,7 @@ const initSliderSettings = () => {
23
23
 
24
24
  const breakpoints = applyFilters('webentor.core.twBreakpoints', [
25
25
  'basic',
26
- ]);
26
+ ]) as string[];
27
27
 
28
28
  const { attributes, setAttributes } = props;
29
29
 
@@ -0,0 +1,104 @@
1
+ import {
2
+ BlockControls,
3
+ store as blockEditorStore,
4
+ } from '@wordpress/block-editor';
5
+ import { cloneBlock, createBlock } from '@wordpress/blocks';
6
+ import { ToolbarButton, ToolbarGroup } from '@wordpress/components';
7
+ import { createHigherOrderComponent } from '@wordpress/compose';
8
+ import { select as dataSelect, useDispatch, useSelect } from '@wordpress/data';
9
+ import { Fragment } from '@wordpress/element';
10
+ import { addFilter, applyFilters } from '@wordpress/hooks';
11
+ import { __ } from '@wordpress/i18n';
12
+ import { group as groupIcon } from '@wordpress/icons';
13
+
14
+ const withWrapContainerButton = createHigherOrderComponent((BlockEdit) => {
15
+ return (props) => {
16
+ const { clientId, name } = props;
17
+
18
+ // Allow themes to exclude specific blocks via filter
19
+ const excludedBlocks: string[] = applyFilters(
20
+ 'webentor.core.wrapWithContainer.excludedBlocks',
21
+ ['webentor/l-section'],
22
+ );
23
+
24
+ if (excludedBlocks.includes(name)) {
25
+ return <BlockEdit {...props} />;
26
+ }
27
+
28
+ const { replaceBlocks } = useDispatch(blockEditorStore);
29
+
30
+ const { isMultiSelected, isFirstInMultiSelection } = useSelect(
31
+ (select) => {
32
+ const store = select(blockEditorStore);
33
+ const multiIds = store.getMultiSelectedBlockClientIds();
34
+ const isMulti = multiIds.length > 0;
35
+
36
+ return {
37
+ isMultiSelected: isMulti,
38
+ isFirstInMultiSelection: isMulti && multiIds[0] === clientId,
39
+ };
40
+ },
41
+ [clientId],
42
+ );
43
+
44
+ // During multi-selection, only render the button on the first selected block
45
+ if (isMultiSelected && !isFirstInMultiSelection) {
46
+ return <BlockEdit {...props} />;
47
+ }
48
+
49
+ const handleWrap = () => {
50
+ const store = dataSelect(blockEditorStore);
51
+ const allSelectedClientIds = store.getMultiSelectedBlockClientIds();
52
+
53
+ // Build the selected block list on demand so useSelect only returns stable values.
54
+ const allSelectedBlocks = allSelectedClientIds
55
+ .map((selectedClientId) => store.getBlock(selectedClientId))
56
+ .filter(Boolean);
57
+
58
+ if (allSelectedBlocks.length > 0) {
59
+ const clonedBlocks = allSelectedBlocks.map((b) => cloneBlock(b));
60
+ const containerBlock = createBlock(
61
+ 'webentor/l-flexible-container',
62
+ {},
63
+ clonedBlocks,
64
+ );
65
+ replaceBlocks(allSelectedClientIds, [containerBlock]);
66
+ } else {
67
+ const currentBlock = store.getBlock(clientId);
68
+
69
+ if (!currentBlock) return;
70
+
71
+ const clonedBlock = cloneBlock(currentBlock);
72
+ const containerBlock = createBlock(
73
+ 'webentor/l-flexible-container',
74
+ {},
75
+ [clonedBlock],
76
+ );
77
+ replaceBlocks(clientId, [containerBlock]);
78
+ }
79
+ };
80
+
81
+ return (
82
+ <Fragment>
83
+ <BlockControls group="other">
84
+ <ToolbarGroup>
85
+ <ToolbarButton
86
+ icon={groupIcon}
87
+ label={__('Wrap with Flexible Container', 'webentor')}
88
+ onClick={handleWrap}
89
+ />
90
+ </ToolbarGroup>
91
+ </BlockControls>
92
+
93
+ <BlockEdit {...props} />
94
+ </Fragment>
95
+ );
96
+ };
97
+ }, 'withWrapContainerButton');
98
+
99
+ // Self-register -- imported from core editor.ts, no manual init needed
100
+ addFilter(
101
+ 'editor.BlockEdit',
102
+ 'webentor/blockEdit/wrapWithContainer',
103
+ withWrapContainerButton,
104
+ );
@@ -0,0 +1,255 @@
1
+ # Responsive Settings — AI Guide
2
+
3
+ This file documents the architecture, data flow, and conventions of the
4
+ responsive settings system so AI agents can work on it without re-exploring.
5
+
6
+ ## Purpose
7
+
8
+ Provides per-breakpoint (responsive) block controls in the WordPress editor
9
+ sidebar. Users can configure spacing, display mode, sizing, flexbox, grid,
10
+ flex/grid item behaviour, borders, and presets — all with breakpoint tabs
11
+ (`basic`, `sm`, `md`, `lg`, `xl`, `2xl`). Values are stored as Tailwind
12
+ class names and output as CSS classes on the block wrapper.
13
+
14
+ ## File Structure
15
+
16
+ ```
17
+ responsive-settings/
18
+ index.tsx — Entry point: attribute filter, registerBlockExtension, BlockEdit
19
+ registry.ts — SettingsRegistry singleton (Map-based, panelGroup queries)
20
+ migration.ts — Display value helpers + display-specific cascade helpers
21
+ utils.ts — Class generation orchestrator + border preview helpers + generic breakpoint cascade utilities
22
+ constants.ts — Legacy includedBlocks map (currently empty, kept for fallback)
23
+ types/index.ts — All TypeScript interfaces (SettingDefinition, PanelGroup, BlockAttributes, etc.)
24
+
25
+ panels/ — Thin PanelBody wrappers, one per UI panel
26
+ SpacingPanel.tsx — panelGroup='spacing'
27
+ DisplayLayoutPanel.tsx — panelGroup='displayLayout' (presets rendered above tabs)
28
+ BorderPanel.tsx — panelGroup='border'
29
+ BlockLinkPanel.tsx — panelGroup='blockLink' (standalone, no breakpoint tabs)
30
+ index.ts
31
+
32
+ components/ — Shared UI components
33
+ ResponsiveTabPanel.tsx — Breakpoint tab wrapper with active-settings indicator
34
+ ResponsiveSelectGroup.tsx — Generic SelectControl list renderer (+ optgroup support)
35
+ BreakpointResetButton.tsx — Per-breakpoint "Reset" button inside tabs
36
+ DebugPanel.tsx — JSON attribute inspector (gated by window flag)
37
+ BoxModelControl.tsx — Margin/padding box-model layout with link modes
38
+ LinkedValuesControl.tsx — Link/unlink toggle + reset button
39
+ DisabledSliderInfo.tsx — Info message when slider overrides settings
40
+ InheritedIndicator.tsx — "Inherited from {breakpoint}" label for cascaded settings
41
+
42
+ settings/
43
+ presets/ — Quick layout presets (panelGroup: displayLayout, order: 0)
44
+ layout/ — Display mode: flex/grid/block/hidden (panelGroup: displayLayout, order: 10)
45
+ sizing/ — Width/height/min/max dimensions (panelGroup: displayLayout, order: 20)
46
+ flexbox/ — Flexbox container controls (panelGroup: displayLayout, order: 30)
47
+ grid/ — Grid container controls (panelGroup: displayLayout, order: 40)
48
+ flex-item/ — Flex child controls (panelGroup: displayLayout, order: 50)
49
+ grid-item/ — Grid child controls (panelGroup: displayLayout, order: 60)
50
+ spacing/ — Margin/padding (panelGroup: spacing, order: 10)
51
+ border/ — Border + border-radius (panelGroup: border, order: 10)
52
+ block-link/ — Block link (panelGroup: blockLink, order: 100)
53
+ shared/ — Shared value generators (gap-values, layout-values, tw-values)
54
+ ```
55
+
56
+ ## Architecture
57
+
58
+ ### Two-Layer Pattern: Panel Groups + Setting Modules
59
+
60
+ The UI has **4 collapsible panels** (SpacingPanel, DisplayLayoutPanel, BorderPanel, BlockLinkPanel).
61
+ Internally, the code is modular: each setting module is self-contained and
62
+ declares which `panelGroup` it belongs to.
63
+
64
+ Panel components are thin wrappers: they render a `PanelBody` with
65
+ `ResponsiveTabPanel` tabs, query the registry for all modules in their
66
+ `panelGroup` (sorted by `order`), and render each module's `SettingsComponent`.
67
+
68
+ ### SettingsRegistry (`registry.ts`)
69
+
70
+ Singleton `Map<string, SettingDefinition>`. Modules self-register via
71
+ side-effect imports in their `registration.ts` files.
72
+
73
+ Key methods:
74
+ - `register(def)` — add a setting module
75
+ - `getAll()` — all modules sorted by order
76
+ - `getByPanelGroup(group)` — modules for a specific panel
77
+ - `isSupported(supports, def)` — check if a block supports a setting
78
+
79
+ ### SettingDefinition Interface
80
+
81
+ ```typescript
82
+ {
83
+ name: string; // e.g. 'layout', 'sizing', 'flexbox'
84
+ panelGroup: PanelGroup; // 'spacing' | 'displayLayout' | 'border'
85
+ order: number; // render order within panel (lower = higher)
86
+ attributeKey: string; // WP attribute key (e.g. 'layout', 'spacing')
87
+ supportKey: string | string[]; // webentor.* support flag(s)
88
+ attributeSchema: object; // WP attribute schema
89
+ initAttributes?: Function; // custom attribute defaults (e.g. flex default)
90
+ SettingsComponent: React.FC; // renders inline within the panel
91
+ generateClasses: Function; // Tailwind class array per breakpoint
92
+ hasActiveSettings: Function; // tab indicator (breakpoint has values?)
93
+ migrateFromV1?: Function; // optional per-module migration
94
+ }
95
+ ```
96
+
97
+ ### Data Flow
98
+
99
+ 1. **Attribute injection** (`blocks.registerBlockType` filter in `index.tsx`):
100
+ - Iterates all registered modules
101
+ - Checks `supports.webentor.*` against each module's `supportKey`
102
+ - Merges attribute schemas into the block
103
+ - Runs `initAttributes` for defaults (e.g. `layout.display = 'flex'`)
104
+
105
+ 2. **Editor rendering** (`BlockEdit` in `index.tsx`):
106
+ - Renders SpacingPanel, DisplayLayoutPanel, BorderPanel, BlockLinkPanel
107
+ - Each panel queries registry and renders SettingsComponents
108
+
109
+ 3. **Class generation** (`generateClassNames` in `utils.ts`):
110
+ - Called by `registerBlockExtension` classNameGenerator hook
111
+ - Collects breakpoints from all attribute values
112
+ - Calls each module's `generateClasses(attributes, breakpoint, context)` per breakpoint
113
+ - Concatenates results
114
+
115
+ 4. **PHP class generation** (`blocks-settings.php`):
116
+ - `SettingsRegistry::generateClasses()` iterates registered handlers
117
+ - Each handler reads attribute values and generates Tailwind classes
118
+ - `prepareBlockClassesFromSettings()` orchestrates all handlers
119
+
120
+ ## Attribute Shape
121
+
122
+ All responsive values follow the same pattern:
123
+
124
+ ```
125
+ attributes.{attributeKey}.{propertyName}.value.{breakpoint} = "tailwind-class"
126
+ ```
127
+
128
+ Example:
129
+ ```json
130
+ {
131
+ "layout": { "display": { "value": { "basic": "flex", "md": "grid" } } },
132
+ "spacing": { "margin-top": { "value": { "basic": "mt-4", "lg": "mt-8" } } },
133
+ "border": { "border": { "value": { "basic": { "top": { "width": "1", "style": "solid", "color": "black" }, "linked": true } } } }
134
+ }
135
+ ```
136
+
137
+ ## Migration
138
+
139
+ - Runtime code only reads canonical v2 keys
140
+ - PHP migration lives in `app/blocks-migration.php`
141
+ - `getDisplayValue()` / `getParentDisplayValue()` are the canonical display readers for `layout.display`
142
+
143
+ ## PHP Side (`app/blocks-settings.php`)
144
+
145
+ - `SettingsRegistry` class mirrors the JS pattern
146
+ - `get_display_value_for_breakpoint()` helper for explicit display reads
147
+ - `get_effective_display_value_for_breakpoint()` cascaded display (min-width inheritance)
148
+ - `get_effective_parent_display_value_for_breakpoint()` cascaded parent display
149
+ - Handlers: `prepareLayoutBlockClassesFromSettings`, `prepareSizingBlockClassesFromSettings`,
150
+ `prepareFlexItemBlockClassesFromSettings` (new), plus unchanged handlers for spacing,
151
+ grid, gridItem, flexbox, border
152
+ - `prepareBlockClassesFromSettings()` also outputs `_presetClasses` directly
153
+
154
+ ## Presets
155
+
156
+ Defined in `settings/presets/presets.ts` as `LayoutPreset[]`. Each preset
157
+ specifies `applies` (attribute values per module) and optional `customClasses`
158
+ for edge cases that need non-Tailwind CSS (e.g. flex-wrap + gap + equal columns).
159
+
160
+ Selecting a preset fills in the individual settings (which remain editable)
161
+ and stores `_preset` (ID marker) and `_presetClasses` (custom CSS classes).
162
+
163
+ Custom CSS utilities for presets live in `resources/styles/common/_utilities.css`:
164
+ - `.w-flex-cols` — flex container with wrapping
165
+ - `.w-flex-cols-{2-6}` — sets child width via `calc()` accounting for gap
166
+ - `.w-gap-{0-12}` — gap + `--w-col-gap` CSS var
167
+
168
+ ## Block.json Support Keys
169
+
170
+ ### Supported Keys
171
+
172
+ ```json
173
+ "webentor": {
174
+ "spacing": true,
175
+ "layout": true,
176
+ "sizing": true,
177
+ "grid": true,
178
+ "gridItem": true,
179
+ "flexbox": true,
180
+ "flexItem": true,
181
+ "border": true,
182
+ "borderRadius": true,
183
+ "blockLink": true,
184
+ "presets": true
185
+ }
186
+ ```
187
+
188
+ ## JSON Schema
189
+
190
+ `schemas/webentor-block.json` defines the canonical support keys under `supports.webentor`.
191
+
192
+ ## Contextual Rendering Rules
193
+
194
+ Display checks use **breakpoint cascading** (min-width inheritance): if `display=flex`
195
+ is set at `basic`, flexbox settings are visible at `sm`, `md`, etc. even without an
196
+ explicit value, because the effective display cascades from `basic`. An
197
+ `InheritedIndicator` label is shown when the value is inherited.
198
+
199
+ - **Flexbox settings**: show when **effective** `display=flex` at the active breakpoint
200
+ - **Grid settings**: show when **effective** `display=grid` at the active breakpoint
201
+ - **Flex-item settings**: show when **parent** block's **effective** `display=flex`
202
+ - **Grid-item settings**: show when **parent** block's **effective** `display=grid`
203
+ - **Slider override**: when `slider.enabled=true` at a breakpoint, display/flexbox/spacing settings are disabled
204
+
205
+ ### Generic Cascade Functions (`utils.ts`)
206
+
207
+ | Function | Purpose |
208
+ |----------|---------|
209
+ | `getEffectiveValue(attrs, attrKey, prop, bp, bps)` | Generic cascade for any string attribute property |
210
+ | `getInheritedFromBreakpoint(attrs, attrKey, prop, bp, bps)` | Source breakpoint for inheritance (UI indicator) |
211
+ | `getEffectiveObjectValue(attrs, attrKey, prop, bp, bps)` | Cascade for object-typed values (borders, radius) |
212
+ | `getObjectInheritedFromBreakpoint(attrs, attrKey, prop, bp, bps)` | Source breakpoint for inherited object values |
213
+
214
+ ### Display Cascade Functions (`migration.ts`)
215
+
216
+ | Function | Purpose |
217
+ |----------|---------|
218
+ | `getEffectiveDisplayValue(attrs, bp, bps)` | Cascaded display mode |
219
+ | `getEffectiveParentDisplayValue(parentAttrs, bp, bps)` | Cascaded parent display |
220
+ | `getDisplayInheritedFromBreakpoint(attrs, bp, bps)` | Display-specific inheritance source |
221
+
222
+ PHP equivalents in `blocks-settings.php`:
223
+ - `get_effective_display_value_for_breakpoint($attributes, $breakpoint)`
224
+ - `get_effective_parent_display_value_for_breakpoint($parent_block, $breakpoint)`
225
+
226
+ ### Per-Property Cascade Indicators
227
+
228
+ Every property control shows inherited values from lower breakpoints:
229
+
230
+ - **`ResponsiveSelectGroup`** — when a select has no explicit value but an inherited
231
+ value exists, the placeholder changes from "None selected" to e.g. "Flex Row (from basic)"
232
+ and the select is styled with `.wbtr-inherited-value` (italic, muted color).
233
+ - **`BoxModelControl`** — same placeholder replacement per side select for spacing.
234
+ - **`BorderSettings` / `BorderRadiusSettings`** — section-level `InheritedIndicator`
235
+ label shown when border/radius objects cascade from a lower breakpoint.
236
+
237
+ The `breakpoints` prop is threaded from `SettingsComponentProps` → `ResponsiveSelectGroup` /
238
+ `BoxModelControl` to enable cascade lookups. CSS class `.wbtr-inherited-value` in
239
+ `resources/styles/common/_editor.css` provides the visual styling.
240
+
241
+ ## Adding a New Setting Module
242
+
243
+ 1. Create `settings/{name}/` with: `index.ts`, `properties.ts`, `settings.tsx`, `registration.ts`
244
+ 2. In `registration.ts`, call `registry.register({ name, panelGroup, order, ... })`
245
+ 3. Import `./settings/{name}` in `index.tsx` (side-effect import)
246
+ 4. Add a matching PHP handler in `blocks-settings.php` and register it
247
+ 5. Update `schemas/webentor-block.json` if adding a new support key
248
+
249
+ ## Common Mistakes to Avoid
250
+
251
+ - **Don't read `attributes.layout.display` directly in contextual modules** — use `getDisplayValue()` from `migration.ts`
252
+ - **Don't read parent display directly** — use `getParentDisplayValue()`
253
+ - **Don't create a new PanelBody in a SettingsComponent** — it renders inline within an existing panel
254
+ - **Don't forget both JS and PHP** — class generation runs on both sides
255
+ - **Don't change version numbers here unless the task is an explicit release change** — follow the root `AGENTS.md` release policy for manual versioning and changelog updates