@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.
- package/CHANGELOG.md +24 -0
- package/README.md +41 -0
- package/core-js/_alpine.ts +6 -0
- package/core-js/_slider.ts +22 -11
- package/core-js/blocks-components/button.tsx +17 -2
- package/core-js/blocks-components/custom-image-sizes-panel.tsx +3 -1
- package/core-js/blocks-components/typography-picker-select.tsx +16 -1
- package/core-js/blocks-filters/_filter-core-typography.tsx +11 -1
- package/core-js/blocks-filters/_slider-settings.tsx +1 -1
- package/core-js/blocks-filters/_wrap-with-container.tsx +104 -0
- package/core-js/blocks-filters/responsive-settings/AGENTS.md +255 -0
- package/core-js/blocks-filters/responsive-settings/components/AppliedClassesViewer.tsx +189 -0
- package/core-js/blocks-filters/responsive-settings/components/BoxModelControl.tsx +346 -0
- package/core-js/blocks-filters/responsive-settings/components/BreakpointResetButton.tsx +94 -0
- package/core-js/blocks-filters/responsive-settings/components/DebugPanel.tsx +67 -0
- package/core-js/blocks-filters/responsive-settings/components/InheritedIndicator.tsx +32 -0
- package/core-js/blocks-filters/responsive-settings/components/LinkedValuesControl.tsx +55 -0
- package/core-js/blocks-filters/responsive-settings/components/ResponsiveSelectGroup.tsx +185 -0
- package/core-js/blocks-filters/responsive-settings/components/ResponsiveTabPanel.tsx +106 -0
- package/core-js/blocks-filters/responsive-settings/index.tsx +97 -148
- package/core-js/blocks-filters/responsive-settings/migration.ts +86 -0
- package/core-js/blocks-filters/responsive-settings/panels/BlockLinkPanel.tsx +38 -0
- package/core-js/blocks-filters/responsive-settings/panels/BorderPanel.tsx +61 -0
- package/core-js/blocks-filters/responsive-settings/panels/DisplayLayoutPanel.tsx +92 -0
- package/core-js/blocks-filters/responsive-settings/panels/SpacingPanel.tsx +63 -0
- package/core-js/blocks-filters/responsive-settings/panels/index.ts +4 -0
- package/core-js/blocks-filters/responsive-settings/registry.ts +88 -0
- package/core-js/blocks-filters/responsive-settings/settings/block-link/index.ts +3 -0
- package/core-js/blocks-filters/responsive-settings/settings/block-link/panel.tsx +6 -6
- package/core-js/blocks-filters/responsive-settings/settings/block-link/registration.ts +35 -0
- package/core-js/blocks-filters/responsive-settings/settings/border/border/properties.ts +1 -2
- package/core-js/blocks-filters/responsive-settings/settings/border/border/settings.tsx +21 -3
- package/core-js/blocks-filters/responsive-settings/settings/border/border-radius/index.tsx +2 -1
- package/core-js/blocks-filters/responsive-settings/settings/border/border-radius/properties.ts +6 -29
- package/core-js/blocks-filters/responsive-settings/settings/border/border-radius/settings.tsx +79 -6
- package/core-js/blocks-filters/responsive-settings/settings/border/index.ts +5 -1
- package/core-js/blocks-filters/responsive-settings/settings/border/panel.tsx +5 -54
- package/core-js/blocks-filters/responsive-settings/settings/border/registration.ts +84 -0
- package/core-js/blocks-filters/responsive-settings/settings/border/settings.tsx +21 -0
- package/core-js/blocks-filters/responsive-settings/settings/flex-item/index.ts +4 -0
- package/core-js/blocks-filters/responsive-settings/settings/flex-item/properties.ts +60 -0
- package/core-js/blocks-filters/responsive-settings/settings/flex-item/registration.ts +78 -0
- package/core-js/blocks-filters/responsive-settings/settings/flex-item/settings.tsx +90 -0
- package/core-js/blocks-filters/responsive-settings/settings/flexbox/index.ts +4 -0
- package/core-js/blocks-filters/responsive-settings/settings/flexbox/properties.ts +80 -0
- package/core-js/blocks-filters/responsive-settings/settings/flexbox/registration.ts +66 -0
- package/core-js/blocks-filters/responsive-settings/settings/flexbox/settings.tsx +78 -0
- package/core-js/blocks-filters/responsive-settings/settings/grid/index.ts +4 -0
- package/core-js/blocks-filters/responsive-settings/settings/grid/properties.ts +72 -0
- package/core-js/blocks-filters/responsive-settings/settings/grid/registration.ts +66 -0
- package/core-js/blocks-filters/responsive-settings/settings/grid/settings.tsx +78 -0
- package/core-js/blocks-filters/responsive-settings/settings/grid-item/index.ts +4 -0
- package/core-js/blocks-filters/responsive-settings/settings/grid-item/properties.ts +44 -0
- package/core-js/blocks-filters/responsive-settings/settings/grid-item/registration.ts +74 -0
- package/core-js/blocks-filters/responsive-settings/settings/grid-item/settings.tsx +87 -0
- package/core-js/blocks-filters/responsive-settings/settings/layout/index.ts +4 -0
- package/core-js/blocks-filters/responsive-settings/settings/layout/properties.ts +51 -0
- package/core-js/blocks-filters/responsive-settings/settings/layout/registration.ts +96 -0
- package/core-js/blocks-filters/responsive-settings/settings/layout/settings.tsx +64 -0
- package/core-js/blocks-filters/responsive-settings/settings/presets/index.ts +4 -0
- package/core-js/blocks-filters/responsive-settings/settings/presets/presets.ts +52 -0
- package/core-js/blocks-filters/responsive-settings/settings/presets/registration.ts +53 -0
- package/core-js/blocks-filters/responsive-settings/settings/presets/settings.tsx +100 -0
- package/core-js/blocks-filters/responsive-settings/settings/shared/gap-values.ts +16 -0
- package/core-js/blocks-filters/responsive-settings/settings/shared/layout-values.ts +56 -0
- package/core-js/blocks-filters/responsive-settings/settings/shared/tw-values.ts +107 -0
- package/core-js/blocks-filters/responsive-settings/settings/sizing/index.ts +4 -0
- package/core-js/blocks-filters/responsive-settings/settings/sizing/properties.ts +71 -0
- package/core-js/blocks-filters/responsive-settings/settings/sizing/registration.ts +52 -0
- package/core-js/blocks-filters/responsive-settings/settings/sizing/settings.tsx +96 -0
- package/core-js/blocks-filters/responsive-settings/settings/spacing/index.ts +7 -2
- package/core-js/blocks-filters/responsive-settings/settings/spacing/panel.tsx +5 -45
- package/core-js/blocks-filters/responsive-settings/settings/spacing/properties.ts +51 -29
- package/core-js/blocks-filters/responsive-settings/settings/spacing/registration.ts +53 -0
- package/core-js/blocks-filters/responsive-settings/settings/spacing/settings.tsx +26 -55
- package/core-js/blocks-filters/responsive-settings/types/index.ts +174 -28
- package/core-js/blocks-filters/responsive-settings/utils.ts +247 -216
- package/core-js/config/index.ts +6 -0
- package/core-js/config/webentor-config.ts +44 -2
- package/core-js/index.ts +8 -10
- package/core-js/types/index.ts +6 -0
- package/package.json +116 -6
- package/public/build/assets/_utils-CzK6Vfiv.js +2 -0
- package/public/build/assets/{_utils-PDaZ1Dn1.js.map → _utils-CzK6Vfiv.js.map} +1 -1
- package/public/build/assets/coreAppStyles-CukxHLz7.css +1 -0
- package/public/build/assets/coreEditorJs-DYd3ZopL.js +366 -0
- package/public/build/assets/coreEditorJs-DYd3ZopL.js.map +1 -0
- package/public/build/assets/coreEditorStyles-I9xzOGSX.css +1 -0
- package/public/build/assets/resources/blocks/e-table/{script-BIchbcPK.js → script-C_Z50hjm.js} +2 -2
- package/public/build/assets/resources/blocks/e-table/{script-BIchbcPK.js.map → script-C_Z50hjm.js.map} +1 -1
- package/public/build/assets/{sliderJs-Ch69_tVA.js → sliderJs-CyGnrv0Q.js} +3 -3
- package/public/build/assets/{sliderJs-Ch69_tVA.js.map → sliderJs-CyGnrv0Q.js.map} +1 -1
- package/public/build/manifest.json +10 -10
- package/resources/blocks/e-accordion-group/block.json +6 -4
- package/resources/blocks/e-gallery/block.json +2 -2
- package/resources/blocks/e-gallery/e-gallery.block.tsx +4 -0
- package/resources/blocks/e-image/e-image.block.tsx +4 -0
- package/resources/blocks/e-slider/block.json +3 -2
- package/resources/blocks/e-tab-container/block.json +2 -1
- package/resources/blocks/e-tabs/block.json +2 -1
- package/resources/blocks/l-flexible-container/block.json +3 -2
- package/resources/blocks/l-mobile-nav/block.json +2 -1
- package/resources/blocks/l-nav-menu/block.json +2 -1
- package/resources/blocks/l-nav-menu/l-nav-menu.block.tsx +2 -0
- package/resources/blocks/l-section/block.json +7 -5
- package/resources/blocks/l-section/l-section.block.tsx +40 -31
- package/resources/scripts/editor.ts +2 -0
- package/resources/styles/common/_editor.css +22 -0
- package/resources/styles/common/_utilities.css +210 -0
- package/core-js/blocks-filters/responsive-settings/constants.ts +0 -11
- package/core-js/blocks-filters/responsive-settings/settings/container/display/index.ts +0 -2
- package/core-js/blocks-filters/responsive-settings/settings/container/display/properties.ts +0 -167
- package/core-js/blocks-filters/responsive-settings/settings/container/display/settings.tsx +0 -73
- package/core-js/blocks-filters/responsive-settings/settings/container/flexbox/index.ts +0 -2
- package/core-js/blocks-filters/responsive-settings/settings/container/flexbox/properties.ts +0 -187
- package/core-js/blocks-filters/responsive-settings/settings/container/flexbox/settings.tsx +0 -131
- package/core-js/blocks-filters/responsive-settings/settings/container/grid/index.ts +0 -2
- package/core-js/blocks-filters/responsive-settings/settings/container/grid/properties.ts +0 -187
- package/core-js/blocks-filters/responsive-settings/settings/container/grid/settings.tsx +0 -132
- package/core-js/blocks-filters/responsive-settings/settings/container/index.ts +0 -4
- package/core-js/blocks-filters/responsive-settings/settings/container/panel.tsx +0 -92
- package/public/build/assets/_utils-PDaZ1Dn1.js +0 -2
- package/public/build/assets/coreAppStyles-Dp0WYk4N.css +0 -1
- package/public/build/assets/coreEditorJs-Cyc87wTo.js +0 -366
- package/public/build/assets/coreEditorJs-Cyc87wTo.js.map +0 -1
- package/public/build/assets/coreEditorStyles-D8-nNpQG.css +0 -1
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AppliedClassesViewer — Toolbar button (eye icon) that reveals a popover
|
|
3
|
+
* listing all Tailwind classes generated by responsive settings for the
|
|
4
|
+
* current block. Rendered inside BlockControls > ToolbarGroup in the
|
|
5
|
+
* block toolbar so it covers all panels (spacing, layout, borders).
|
|
6
|
+
*
|
|
7
|
+
* Classes are grouped by panel group (Spacing, Display & Layout, Border)
|
|
8
|
+
* for easier scanning.
|
|
9
|
+
*/
|
|
10
|
+
import { getBlockType } from '@wordpress/blocks';
|
|
11
|
+
import { Popover, ToolbarButton } from '@wordpress/components';
|
|
12
|
+
import { useMemo, useState } from '@wordpress/element';
|
|
13
|
+
import { __ } from '@wordpress/i18n';
|
|
14
|
+
import { postList } from '@wordpress/icons';
|
|
15
|
+
|
|
16
|
+
import { useBlockParent } from '../../../blocks-utils/_use-block-parent';
|
|
17
|
+
import { registry } from '../registry';
|
|
18
|
+
import { BlockPanelProps, ClassGenContext, PanelGroup } from '../types';
|
|
19
|
+
|
|
20
|
+
interface ClassGroup {
|
|
21
|
+
group: PanelGroup;
|
|
22
|
+
label: string;
|
|
23
|
+
classes: string[];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const GROUP_LABELS: Record<PanelGroup, string> = {
|
|
27
|
+
spacing: 'Spacing',
|
|
28
|
+
displayLayout: 'Display & Layout',
|
|
29
|
+
border: 'Border',
|
|
30
|
+
blockLink: 'Block Link',
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/** Panel group render order */
|
|
34
|
+
const GROUP_ORDER: PanelGroup[] = [
|
|
35
|
+
'spacing',
|
|
36
|
+
'displayLayout',
|
|
37
|
+
'border',
|
|
38
|
+
'blockLink',
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Collect responsive-settings classes grouped by panel group.
|
|
43
|
+
* Same logic as generateClassNames in utils.ts but without useBlockProps.
|
|
44
|
+
*/
|
|
45
|
+
const collectGroupedClasses = (
|
|
46
|
+
attributes: Record<string, any>,
|
|
47
|
+
blockName: string,
|
|
48
|
+
parentAttributes: Record<string, any> | undefined,
|
|
49
|
+
orderedBreakpoints: string[],
|
|
50
|
+
): ClassGroup[] => {
|
|
51
|
+
const blockSettings = getBlockType(blockName);
|
|
52
|
+
const supports = blockSettings?.supports;
|
|
53
|
+
|
|
54
|
+
const context: ClassGenContext = {
|
|
55
|
+
blockName,
|
|
56
|
+
supports,
|
|
57
|
+
parentBlockAttributes: parentAttributes,
|
|
58
|
+
breakpoints: orderedBreakpoints,
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const allSettings = registry.getAll();
|
|
62
|
+
|
|
63
|
+
// Collect all breakpoints present in any attribute
|
|
64
|
+
const breakpoints = new Set<string>();
|
|
65
|
+
for (const def of allSettings) {
|
|
66
|
+
if (!registry.isSupported(supports, def)) continue;
|
|
67
|
+
for (const attrKey of Object.keys(def.attributeSchema)) {
|
|
68
|
+
const attrGroup = attributes[attrKey];
|
|
69
|
+
if (!attrGroup || typeof attrGroup !== 'object') continue;
|
|
70
|
+
for (const prop of Object.values(attrGroup)) {
|
|
71
|
+
const propData = prop as any;
|
|
72
|
+
if (propData?.value) {
|
|
73
|
+
for (const bp of Object.keys(propData.value)) {
|
|
74
|
+
breakpoints.add(bp);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Group classes by panelGroup
|
|
82
|
+
const groupMap = new Map<PanelGroup, string[]>();
|
|
83
|
+
for (const bp of breakpoints) {
|
|
84
|
+
for (const def of allSettings) {
|
|
85
|
+
if (!registry.isSupported(supports, def)) continue;
|
|
86
|
+
const generated = def
|
|
87
|
+
.generateClasses(attributes, bp, context)
|
|
88
|
+
.filter(Boolean);
|
|
89
|
+
if (generated.length === 0) continue;
|
|
90
|
+
|
|
91
|
+
const existing = groupMap.get(def.panelGroup) ?? [];
|
|
92
|
+
existing.push(...generated);
|
|
93
|
+
groupMap.set(def.panelGroup, existing);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return GROUP_ORDER.filter((g) => groupMap.has(g)).map((g) => ({
|
|
98
|
+
group: g,
|
|
99
|
+
label: GROUP_LABELS[g],
|
|
100
|
+
classes: groupMap.get(g)!,
|
|
101
|
+
}));
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
export const AppliedClassesViewer = ({
|
|
105
|
+
attributes,
|
|
106
|
+
name,
|
|
107
|
+
breakpoints,
|
|
108
|
+
}: BlockPanelProps) => {
|
|
109
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
110
|
+
const parentBlock = useBlockParent();
|
|
111
|
+
|
|
112
|
+
const groups = useMemo(
|
|
113
|
+
() =>
|
|
114
|
+
collectGroupedClasses(
|
|
115
|
+
attributes,
|
|
116
|
+
name,
|
|
117
|
+
parentBlock?.attributes,
|
|
118
|
+
breakpoints,
|
|
119
|
+
),
|
|
120
|
+
[attributes, name, parentBlock?.attributes, breakpoints],
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
if (groups.every((g) => g.classes.length === 0)) return null;
|
|
124
|
+
|
|
125
|
+
return (
|
|
126
|
+
<>
|
|
127
|
+
<ToolbarButton
|
|
128
|
+
icon={postList}
|
|
129
|
+
label={__('View applied classes', 'webentor')}
|
|
130
|
+
onClick={() => setIsOpen((prev) => !prev)}
|
|
131
|
+
isPressed={isOpen}
|
|
132
|
+
/>
|
|
133
|
+
{isOpen && (
|
|
134
|
+
<Popover
|
|
135
|
+
placement="bottom-start"
|
|
136
|
+
onClose={() => setIsOpen(false)}
|
|
137
|
+
shift
|
|
138
|
+
>
|
|
139
|
+
<div
|
|
140
|
+
style={{
|
|
141
|
+
padding: '12px',
|
|
142
|
+
maxWidth: '320px',
|
|
143
|
+
maxHeight: '280px',
|
|
144
|
+
overflow: 'auto',
|
|
145
|
+
}}
|
|
146
|
+
>
|
|
147
|
+
{groups.map((group, gi) => (
|
|
148
|
+
<div key={group.group} style={{ marginTop: gi > 0 ? '12px' : 0 }}>
|
|
149
|
+
<p
|
|
150
|
+
style={{
|
|
151
|
+
fontSize: '11px',
|
|
152
|
+
textTransform: 'uppercase',
|
|
153
|
+
marginTop: 0,
|
|
154
|
+
marginBottom: '6px',
|
|
155
|
+
color: '#757575',
|
|
156
|
+
}}
|
|
157
|
+
>
|
|
158
|
+
{group.label}
|
|
159
|
+
</p>
|
|
160
|
+
<div
|
|
161
|
+
style={{
|
|
162
|
+
display: 'flex',
|
|
163
|
+
flexWrap: 'wrap',
|
|
164
|
+
gap: '4px',
|
|
165
|
+
}}
|
|
166
|
+
>
|
|
167
|
+
{group.classes.map((cls, i) => (
|
|
168
|
+
<code
|
|
169
|
+
key={`${cls}-${i}`}
|
|
170
|
+
style={{
|
|
171
|
+
fontSize: '11px',
|
|
172
|
+
padding: '2px 6px',
|
|
173
|
+
background: '#f0f0f0',
|
|
174
|
+
borderRadius: '3px',
|
|
175
|
+
whiteSpace: 'nowrap',
|
|
176
|
+
}}
|
|
177
|
+
>
|
|
178
|
+
{cls}
|
|
179
|
+
</code>
|
|
180
|
+
))}
|
|
181
|
+
</div>
|
|
182
|
+
</div>
|
|
183
|
+
))}
|
|
184
|
+
</div>
|
|
185
|
+
</Popover>
|
|
186
|
+
)}
|
|
187
|
+
</>
|
|
188
|
+
);
|
|
189
|
+
};
|
|
@@ -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
|
+
};
|