@webikon/webentor-core 0.9.13 → 0.10.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 (126) hide show
  1. package/CHANGELOG.md +24 -1
  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 +50 -33
  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-Bvp3emQy.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-BzlB6eA_.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
@@ -0,0 +1,346 @@
1
+ import { Icon, SelectControl, Tooltip } from '@wordpress/components';
2
+ import { __, sprintf } from '@wordpress/i18n';
3
+ import {
4
+ sidesBottom,
5
+ sidesHorizontal,
6
+ sidesLeft,
7
+ sidesRight,
8
+ sidesTop,
9
+ sidesVertical,
10
+ } from '@wordpress/icons';
11
+
12
+ import { setImmutably } from '../../../_utils';
13
+ import { SelectOption } from '../types';
14
+ import { getEffectiveValue, getInheritedFromBreakpoint } from '../utils';
15
+ import { LinkedValuesControl, LinkMode } from './LinkedValuesControl';
16
+
17
+ interface Side {
18
+ /** Attribute property name, e.g. 'margin-top' */
19
+ name: string;
20
+ label: string;
21
+ values: SelectOption[];
22
+ }
23
+
24
+ interface BoxModelControlProps {
25
+ type: 'margin' | 'padding';
26
+ sides: Side[];
27
+ attributes: Record<string, any>;
28
+ setAttributes: (attrs: Record<string, any>) => void;
29
+ /** Attribute group key, e.g. 'spacing' */
30
+ attributeKey: string;
31
+ breakpoint: string;
32
+ /** Ordered breakpoint names for cascade indicators */
33
+ breakpoints?: string[];
34
+ disabled?: boolean;
35
+ }
36
+
37
+ // ── Prefix helpers ──────────────────────────────────────────────────
38
+ // Spacing values are stored as full Tailwind classes (e.g. "mt-4").
39
+ // When linking sides we need to map the scale part to each side's prefix.
40
+
41
+ /** Extract the TW utility prefix from a side's value list (e.g. "mt" from "mt-4"). */
42
+ const getSidePrefix = (side: Side): string | null => {
43
+ const entry = side.values.find((v) => v.value !== '');
44
+ if (!entry) return null;
45
+ const match = entry.value.match(/^([a-z]+)-/);
46
+ return match ? match[1] : null;
47
+ };
48
+
49
+ /** Extract the scale/key portion from a TW class: "mt-4" → "4", "mt-auto" → "auto". */
50
+ const extractScale = (value: string): string | null => {
51
+ const match = value.match(/^[a-z]+-(.+)$/);
52
+ return match ? match[1] : null;
53
+ };
54
+
55
+ /** Build the correct TW class for a target side given a scale: ("mr", "4") → "mr-4". */
56
+ const buildValue = (prefix: string, scale: string): string =>
57
+ `${prefix}-${scale}`;
58
+
59
+ /**
60
+ * Visual box-model control for margin or padding.
61
+ * Two states: linked (H+V axis selects) or unlinked (4-corner grid).
62
+ */
63
+ export const BoxModelControl = ({
64
+ type,
65
+ sides,
66
+ attributes,
67
+ setAttributes,
68
+ attributeKey,
69
+ breakpoint,
70
+ breakpoints,
71
+ disabled = false,
72
+ }: BoxModelControlProps) => {
73
+ const linkModeKey = `_${type}LinkMode`;
74
+ const currentLinkMode: LinkMode =
75
+ (attributes[attributeKey]?.[linkModeKey]?.value?.[breakpoint] as
76
+ | LinkMode
77
+ | undefined) ?? 'unlinked';
78
+
79
+ const topSide = sides.find((s) => s.name.includes('top'));
80
+ const bottomSide = sides.find((s) => s.name.includes('bottom'));
81
+ const leftSide = sides.find((s) => s.name.includes('left'));
82
+ const rightSide = sides.find((s) => s.name.includes('right'));
83
+
84
+ const isLinked = currentLinkMode === 'linked';
85
+
86
+ const getValue = (side?: Side): string =>
87
+ side
88
+ ? (attributes[attributeKey]?.[side.name]?.value?.[breakpoint] ?? '')
89
+ : '';
90
+
91
+ // ── Mode toggle: sync pairs when switching to linked ──────────
92
+ const setLinkMode = (nextMode: LinkMode) => {
93
+ let newAttrs = setImmutably(
94
+ attributes,
95
+ [attributeKey, linkModeKey, 'value', breakpoint],
96
+ nextMode,
97
+ );
98
+
99
+ // Sync pairs when switching to linked: left→right, top→bottom
100
+ if (nextMode === 'linked') {
101
+ if (leftSide && rightSide) {
102
+ const leftVal = getValue(leftSide);
103
+ if (leftVal) {
104
+ const scale = extractScale(leftVal);
105
+ const rightPrefix = getSidePrefix(rightSide);
106
+ if (scale && rightPrefix) {
107
+ newAttrs = setImmutably(
108
+ newAttrs,
109
+ [attributeKey, rightSide.name, 'value', breakpoint],
110
+ buildValue(rightPrefix, scale),
111
+ );
112
+ }
113
+ }
114
+ }
115
+ if (topSide && bottomSide) {
116
+ const topVal = getValue(topSide);
117
+ if (topVal) {
118
+ const scale = extractScale(topVal);
119
+ const bottomPrefix = getSidePrefix(bottomSide);
120
+ if (scale && bottomPrefix) {
121
+ newAttrs = setImmutably(
122
+ newAttrs,
123
+ [attributeKey, bottomSide.name, 'value', breakpoint],
124
+ buildValue(bottomPrefix, scale),
125
+ );
126
+ }
127
+ }
128
+ }
129
+ }
130
+
131
+ setAttributes(newAttrs);
132
+ };
133
+
134
+ // ── Value change with pair propagation in linked mode ─────────
135
+ const handleChange = (sideName: string, value: string) => {
136
+ // Determine which sides to update together
137
+ let targetSides: Side[];
138
+
139
+ if (isLinked) {
140
+ const isHorizontal =
141
+ sideName.includes('left') || sideName.includes('right');
142
+ if (isHorizontal) {
143
+ targetSides = [leftSide, rightSide].filter(Boolean) as Side[];
144
+ } else {
145
+ targetSides = [topSide, bottomSide].filter(Boolean) as Side[];
146
+ }
147
+ } else {
148
+ const side = sides.find((s) => s.name === sideName);
149
+ targetSides = side ? [side] : [];
150
+ }
151
+
152
+ let newAttrs = { ...attributes };
153
+
154
+ if (!value) {
155
+ for (const side of targetSides) {
156
+ newAttrs = setImmutably(
157
+ newAttrs,
158
+ [attributeKey, side.name, 'value', breakpoint],
159
+ '',
160
+ );
161
+ }
162
+ } else {
163
+ const scale = extractScale(value);
164
+ for (const side of targetSides) {
165
+ const prefix = getSidePrefix(side);
166
+ const mapped = scale && prefix ? buildValue(prefix, scale) : value;
167
+ newAttrs = setImmutably(
168
+ newAttrs,
169
+ [attributeKey, side.name, 'value', breakpoint],
170
+ mapped,
171
+ );
172
+ }
173
+ }
174
+
175
+ setAttributes(newAttrs);
176
+ };
177
+
178
+ const resetAll = () => {
179
+ let newAttrs = { ...attributes };
180
+ for (const side of sides) {
181
+ newAttrs = setImmutably(
182
+ newAttrs,
183
+ [attributeKey, side.name, 'value', breakpoint],
184
+ '',
185
+ );
186
+ }
187
+ setAttributes(newAttrs);
188
+ };
189
+
190
+ const hasAnyValue = sides.some(
191
+ (s) => !!attributes[attributeKey]?.[s.name]?.value?.[breakpoint],
192
+ );
193
+
194
+ // ── Select renderer with cascade indicators ─────────────────────
195
+ const renderSelect = (
196
+ side: Side | undefined,
197
+ labelOverride?: string,
198
+ hideLabel?: boolean,
199
+ ) => {
200
+ if (!side) return null;
201
+
202
+ const explicitValue = getValue(side);
203
+ let options = side.values;
204
+ let inheritedClassName = '';
205
+
206
+ if (!explicitValue && breakpoints?.length) {
207
+ const inheritedFrom = getInheritedFromBreakpoint(
208
+ attributes,
209
+ attributeKey,
210
+ side.name,
211
+ breakpoint,
212
+ breakpoints,
213
+ );
214
+ if (inheritedFrom) {
215
+ const inheritedValue = getEffectiveValue(
216
+ attributes,
217
+ attributeKey,
218
+ side.name,
219
+ breakpoint,
220
+ breakpoints,
221
+ );
222
+ if (inheritedValue) {
223
+ const label =
224
+ side.values.find((o) => o.value === inheritedValue)?.label ??
225
+ inheritedValue;
226
+ const placeholderText = sprintf(
227
+ __('%s (from %s)', 'webentor'),
228
+ label,
229
+ inheritedFrom,
230
+ );
231
+ options = [
232
+ { label: placeholderText, value: '' },
233
+ ...side.values.filter((o) => o.value !== ''),
234
+ ];
235
+ inheritedClassName = 'wbtr-inherited-value';
236
+ }
237
+ }
238
+ }
239
+
240
+ return (
241
+ <SelectControl
242
+ label={labelOverride ?? side.label}
243
+ hideLabelFromVision={hideLabel}
244
+ value={explicitValue}
245
+ options={options}
246
+ disabled={disabled}
247
+ onChange={(val) => handleChange(side.name, val)}
248
+ className={inheritedClassName}
249
+ __nextHasNoMarginBottom
250
+ />
251
+ );
252
+ };
253
+
254
+ return (
255
+ <div className="wbtr:my-2 wbtr:flex wbtr:flex-col wbtr:gap-2 wbtr:border wbtr:border-editor-border wbtr:p-2">
256
+ <div className="wbtr:flex wbtr:items-center wbtr:gap-2">
257
+ <p className="wbtr:mb-0! wbtr:text-12 wbtr:uppercase">
258
+ {type === 'margin'
259
+ ? __('Margin', 'webentor')
260
+ : __('Padding', 'webentor')}
261
+ </p>
262
+
263
+ <LinkedValuesControl
264
+ mode={currentLinkMode}
265
+ onModeChange={setLinkMode}
266
+ onReset={resetAll}
267
+ resetDisabled={!hasAnyValue}
268
+ />
269
+ </div>
270
+
271
+ {isLinked ? (
272
+ <div className="wbtr:flex wbtr:flex-col wbtr:gap-1.5">
273
+ <div className="wbtr:flex wbtr:items-center wbtr:gap-2">
274
+ <Tooltip text={__('Horizontal', 'webentor')}>
275
+ <span className="wbtr:flex">
276
+ <Icon icon={sidesHorizontal} size={20} />
277
+ </span>
278
+ </Tooltip>
279
+ <div className="wbtr:flex-1">
280
+ {renderSelect(leftSide, __('Horizontal', 'webentor'), true)}
281
+ </div>
282
+ </div>
283
+ <div className="wbtr:flex wbtr:items-center wbtr:gap-2">
284
+ <Tooltip text={__('Vertical', 'webentor')}>
285
+ <span className="wbtr:flex">
286
+ <Icon icon={sidesVertical} size={20} />
287
+ </span>
288
+ </Tooltip>
289
+ <div className="wbtr:flex-1">
290
+ {renderSelect(topSide, __('Vertical', 'webentor'), true)}
291
+ </div>
292
+ </div>
293
+ </div>
294
+ ) : (
295
+ <div
296
+ style={{
297
+ display: 'grid',
298
+ gridTemplateColumns: '1fr 1fr',
299
+ gap: '4px',
300
+ }}
301
+ >
302
+ <div className="wbtr:flex wbtr:items-center wbtr:gap-1.5">
303
+ <Tooltip text={__('Top', 'webentor')}>
304
+ <span className="wbtr:flex">
305
+ <Icon icon={sidesTop} size={20} />
306
+ </span>
307
+ </Tooltip>
308
+ <div className="wbtr:flex-1">
309
+ {renderSelect(topSide, topSide?.label, true)}
310
+ </div>
311
+ </div>
312
+ <div className="wbtr:flex wbtr:items-center wbtr:gap-1.5">
313
+ <Tooltip text={__('Right', 'webentor')}>
314
+ <span className="wbtr:flex">
315
+ <Icon icon={sidesRight} size={20} />
316
+ </span>
317
+ </Tooltip>
318
+ <div className="wbtr:flex-1">
319
+ {renderSelect(rightSide, rightSide?.label, true)}
320
+ </div>
321
+ </div>
322
+ <div className="wbtr:flex wbtr:items-center wbtr:gap-1.5">
323
+ <Tooltip text={__('Bottom', 'webentor')}>
324
+ <span className="wbtr:flex">
325
+ <Icon icon={sidesBottom} size={20} />
326
+ </span>
327
+ </Tooltip>
328
+ <div className="wbtr:flex-1">
329
+ {renderSelect(bottomSide, bottomSide?.label, true)}
330
+ </div>
331
+ </div>
332
+ <div className="wbtr:flex wbtr:items-center wbtr:gap-1.5">
333
+ <Tooltip text={__('Left', 'webentor')}>
334
+ <span className="wbtr:flex">
335
+ <Icon icon={sidesLeft} size={20} />
336
+ </span>
337
+ </Tooltip>
338
+ <div className="wbtr:flex-1">
339
+ {renderSelect(leftSide, leftSide?.label, true)}
340
+ </div>
341
+ </div>
342
+ </div>
343
+ )}
344
+ </div>
345
+ );
346
+ };
@@ -0,0 +1,94 @@
1
+ /**
2
+ * BreakpointResetButton — Clears all setting values for a specific
3
+ * breakpoint within a panel group. Renders as a small button at the
4
+ * top of each breakpoint tab.
5
+ */
6
+ import { Button } from '@wordpress/components';
7
+ import { __ } from '@wordpress/i18n';
8
+
9
+ import { registry } from '../registry';
10
+ import { PanelGroup } from '../types';
11
+
12
+ interface BreakpointResetButtonProps {
13
+ panelGroup: PanelGroup;
14
+ breakpoint: string;
15
+ attributes: Record<string, any>;
16
+ setAttributes: (attrs: Record<string, any>) => void;
17
+ }
18
+
19
+ export const BreakpointResetButton = ({
20
+ panelGroup,
21
+ breakpoint,
22
+ attributes,
23
+ setAttributes,
24
+ }: BreakpointResetButtonProps) => {
25
+ const modules = registry.getByPanelGroup(panelGroup);
26
+
27
+ const hasAnyValues = modules.some((def) => {
28
+ // Check all attribute keys the module owns.
29
+ for (const key of Object.keys(def.attributeSchema)) {
30
+ const attrGroup = attributes?.[key];
31
+ if (!attrGroup || typeof attrGroup !== 'object') continue;
32
+ if (
33
+ Object.values(attrGroup).some(
34
+ (prop: any) => prop?.value?.[breakpoint] !== undefined,
35
+ )
36
+ )
37
+ return true;
38
+ }
39
+ return false;
40
+ });
41
+
42
+ if (!hasAnyValues) return null;
43
+
44
+ const handleReset = () => {
45
+ const patch: Record<string, any> = {};
46
+
47
+ for (const def of modules) {
48
+ // Iterate all attribute keys in the module's schema.
49
+ for (const key of Object.keys(def.attributeSchema)) {
50
+ const attrGroup = attributes?.[key];
51
+ if (!attrGroup || typeof attrGroup !== 'object') continue;
52
+
53
+ const updatedGroup = { ...attrGroup };
54
+ let changed = false;
55
+
56
+ for (const [propName, propData] of Object.entries(updatedGroup)) {
57
+ const prop = propData as any;
58
+ if (prop?.value?.[breakpoint] !== undefined) {
59
+ const { [breakpoint]: _, ...rest } = prop.value;
60
+ updatedGroup[propName] = { ...prop, value: rest };
61
+ changed = true;
62
+ }
63
+ }
64
+
65
+ if (changed) {
66
+ patch[key] = updatedGroup;
67
+ }
68
+ }
69
+ }
70
+
71
+ if (Object.keys(patch).length) {
72
+ setAttributes(patch);
73
+ }
74
+ };
75
+
76
+ return (
77
+ <div
78
+ style={{
79
+ display: 'flex',
80
+ justifyContent: 'flex-end',
81
+ marginBottom: '8px',
82
+ }}
83
+ >
84
+ <Button
85
+ variant="tertiary"
86
+ isDestructive
87
+ size="small"
88
+ onClick={handleReset}
89
+ >
90
+ {__('Reset breakpoint', 'webentor')}
91
+ </Button>
92
+ </div>
93
+ );
94
+ };
@@ -0,0 +1,67 @@
1
+ /**
2
+ * DebugPanel — Collapsible inspector panel showing raw responsive
3
+ * setting attributes. Gated behind window.WEBENTOR_WP_DEBUG.
4
+ */
5
+ import { PanelBody } from '@wordpress/components';
6
+ import { __ } from '@wordpress/i18n';
7
+
8
+ import { BlockPanelProps } from '../types';
9
+
10
+ declare global {
11
+ interface Window {
12
+ WEBENTOR_WP_DEBUG?: boolean;
13
+ }
14
+ }
15
+
16
+ const ATTRIBUTE_KEYS = [
17
+ 'layout',
18
+ 'sizing',
19
+ 'spacing',
20
+ 'flexbox',
21
+ 'grid',
22
+ 'flexItem',
23
+ 'gridItem',
24
+ 'border',
25
+ '_preset',
26
+ '_presetClasses',
27
+ 'blockLink',
28
+ // v1 keys are not supported anymore, kept here for debugging reference
29
+ 'display',
30
+ 'flexboxItem',
31
+ ] as const;
32
+
33
+ export const DebugPanel = ({ attributes }: BlockPanelProps) => {
34
+ if (!window.WEBENTOR_WP_DEBUG) {
35
+ return null;
36
+ }
37
+
38
+ const debugData: Record<string, any> = {};
39
+ for (const key of ATTRIBUTE_KEYS) {
40
+ if (attributes?.[key] !== undefined) {
41
+ debugData[key] = attributes[key];
42
+ }
43
+ }
44
+
45
+ return (
46
+ <PanelBody
47
+ title={__('Debug: Responsive Settings', 'webentor')}
48
+ initialOpen={false}
49
+ >
50
+ <pre
51
+ style={{
52
+ fontSize: '10px',
53
+ lineHeight: '1.4',
54
+ maxHeight: '400px',
55
+ overflow: 'auto',
56
+ background: '#f0f0f0',
57
+ padding: '8px',
58
+ borderRadius: '4px',
59
+ whiteSpace: 'pre-wrap',
60
+ wordBreak: 'break-all',
61
+ }}
62
+ >
63
+ {JSON.stringify(debugData, null, 2)}
64
+ </pre>
65
+ </PanelBody>
66
+ );
67
+ };
@@ -0,0 +1,32 @@
1
+ /**
2
+ * InheritedIndicator — shows when a setting section is visible due to
3
+ * breakpoint cascading (min-width inheritance) rather than an explicit
4
+ * value at the current breakpoint.
5
+ *
6
+ * Displays e.g. "Inherited from basic" so the user understands why
7
+ * flexbox/grid controls appear even though no display value is explicitly
8
+ * set at the active breakpoint.
9
+ */
10
+ import { __, sprintf } from '@wordpress/i18n';
11
+
12
+ interface InheritedIndicatorProps {
13
+ /** The breakpoint name the value cascades from (e.g. 'basic', 'sm') */
14
+ fromBreakpoint: string;
15
+ }
16
+
17
+ const INDICATOR_STYLE: React.CSSProperties = {
18
+ fontSize: '11px',
19
+ color: '#757575',
20
+ fontStyle: 'italic',
21
+ marginBottom: '8px',
22
+ };
23
+
24
+ export const InheritedIndicator = ({
25
+ fromBreakpoint,
26
+ }: InheritedIndicatorProps) => {
27
+ return (
28
+ <div style={INDICATOR_STYLE}>
29
+ {sprintf(__('Inherited from %s', 'webentor'), fromBreakpoint)}
30
+ </div>
31
+ );
32
+ };
@@ -0,0 +1,55 @@
1
+ import { Button } from '@wordpress/components';
2
+ import { __ } from '@wordpress/i18n';
3
+ import { link, linkOff } from '@wordpress/icons';
4
+
5
+ export type LinkMode = 'linked' | 'unlinked';
6
+
7
+ interface LinkedValuesControlProps {
8
+ mode: LinkMode;
9
+ onModeChange: (mode: LinkMode) => void;
10
+ onReset?: () => void;
11
+ resetDisabled?: boolean;
12
+ }
13
+
14
+ /**
15
+ * Toggle button for linking/unlinking spacing sides.
16
+ * Linked = horizontal+vertical pairs synced; Unlinked = all 4 independent.
17
+ */
18
+ export const LinkedValuesControl = ({
19
+ mode,
20
+ onModeChange,
21
+ onReset,
22
+ resetDisabled = false,
23
+ }: LinkedValuesControlProps) => {
24
+ const isLinked = mode === 'linked';
25
+
26
+ return (
27
+ <div className="wbtr:flex wbtr:grow-1 wbtr:items-center wbtr:justify-between">
28
+ <Button
29
+ icon={isLinked ? linkOff : link}
30
+ isPressed={isLinked}
31
+ onClick={() => onModeChange(isLinked ? 'unlinked' : 'linked')}
32
+ label={
33
+ isLinked
34
+ ? __('Unlink sides', 'webentor')
35
+ : __('Link sides', 'webentor')
36
+ }
37
+ showTooltip
38
+ size="small"
39
+ />
40
+
41
+ {onReset && (
42
+ <Button
43
+ variant="tertiary"
44
+ onClick={onReset}
45
+ disabled={resetDisabled}
46
+ label={__('Reset to defaults', 'webentor')}
47
+ showTooltip
48
+ size="small"
49
+ >
50
+ {__('Reset', 'webentor')}
51
+ </Button>
52
+ )}
53
+ </div>
54
+ );
55
+ };