PluginProbe ʕ •ᴥ•ʔ
Everest Forms – Contact Form, Payment Form, Quiz, Survey & Custom Form Builder with AI / 3.6.0
Everest Forms – Contact Form, Payment Form, Quiz, Survey & Custom Form Builder with AI v3.6.0
3.6.0 3.5.3 3.5.2 3.5.1 3.5.0 3.4.8 3.4.7 3.4.6 1.1.0 1.1.1 1.1.2 1.1.3 1.1.4 1.1.5 1.1.5.1 1.1.6 1.1.7 1.1.8 1.1.9 1.2.0 1.2.1 1.2.2 1.2.3 1.2.4 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.4.0 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.4.6 1.4.7 1.4.8 1.4.9 1.5.0 1.5.1 1.5.10 1.5.2 1.5.3 1.5.4 1.5.5 1.5.6 1.5.7 1.5.8 1.5.9 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 1.6.5 1.6.6 1.6.6.1 1.6.7 1.7.0 1.7.0.1 1.7.0.2 1.7.0.3 1.7.1 1.7.2 1.7.2.1 1.7.2.2 1.7.3 1.7.4 1.7.5 1.7.5.1 1.7.5.2 1.7.6 1.7.7 1.7.7.1 1.7.7.2 1.7.8 1.7.9 1.8.0 1.8.0.1 1.8.1 1.8.2 1.8.2.1 1.8.2.2 1.8.2.3 1.8.3 1.8.4 1.8.5 1.8.6 1.8.7 1.8.8 1.8.9 1.9.0 1.9.0.1 1.9.1 1.9.2 1.9.3 1.9.4 1.9.4.1 1.9.5 1.9.6 1.9.7 1.9.8 1.9.9 2.0.0 2.0.0.1 2.0.1 2.0.2 2.0.3 2.0.3.1 2.0.4 2.0.4.1 2.0.5 2.0.6 2.0.7 2.0.8 2.0.8.1 2.0.9 3.0.0 3.0.0.1 3.0.1 3.0.2 3.0.3 3.0.3.1 3.0.4 3.0.4.1 3.0.4.2 3.0.5 3.0.5.1 3.0.5.2 3.0.6 3.0.6.1 3.0.7.1 3.0.8 3.0.8.1 3.0.9 3.0.9.1 3.0.9.2 3.0.9.3 3.0.9.4 3.0.9.5 3.1.0 3.1.1 3.1.2 3.2.0 3.2.1 3.2.2 3.2.3 3.2.4 3.2.5 3.2.6 3.3.0 3.4.0 3.4.1 3.4.2 3.4.2.1 3.4.3 3.4.4 3.4.5 trunk 1.0 1.0.1 1.0.2 1.0.3
everest-forms / src / style-customizer-v2 / constants.ts
everest-forms / src / style-customizer-v2 Last commit date
AiAssistant.tsx 2 days ago App.tsx 2 days ago BuilderSync.ts 2 days ago ControlRenderer.tsx 2 days ago HoverTip.tsx 2 days ago Popover.tsx 2 days ago PreviewBridge.ts 2 days ago PreviewPane.tsx 2 days ago constants.ts 2 days ago cssVars.ts 2 days ago globals.d.ts 2 days ago index.tsx 2 days ago panes.tsx 2 days ago store.ts 2 days ago style.scss 2 days ago types.ts 2 days ago
constants.ts
239 lines
1 /**
2 * Style Customizer v2 — static UI maps.
3 *
4 * Section icons, human labels for state/variant ids, click-to-edit routing, device metadata.
5 * The token/section DATA all comes from the server; these are pure presentation.
6 */
7 import { Device } from './types';
8
9 const __ = ( window as any ).wp?.i18n?.__ || ( ( s: string ) => s );
10
11 export const clone = < T >( v: T ): T =>
12 typeof structuredClone === 'function' ? structuredClone( v ) : JSON.parse( JSON.stringify( v ) );
13
14 /**
15 * Structural value-equality for token values / device bags — order-independent for object keys
16 * (so it doesn't depend on insertion order the way a JSON.stringify compare would). Used to test
17 * whether a form's current styles EXACTLY equal a given template's (see store.appliedTemplateId).
18 */
19 export function deepEqual( a: unknown, b: unknown ): boolean {
20 if ( a === b ) {
21 return true;
22 }
23 if ( a === null || b === null || typeof a !== 'object' || typeof b !== 'object' ) {
24 return false;
25 }
26 const aArr = Array.isArray( a );
27 if ( aArr !== Array.isArray( b ) ) {
28 return false;
29 }
30 const ak = Object.keys( a as Record< string, unknown > );
31 const bk = Object.keys( b as Record< string, unknown > );
32 if ( ak.length !== bk.length ) {
33 return false;
34 }
35 return ak.every(
36 ( k ) =>
37 Object.prototype.hasOwnProperty.call( b, k ) &&
38 deepEqual( ( a as Record< string, unknown > )[ k ], ( b as Record< string, unknown > )[ k ] )
39 );
40 }
41
42 /** Section icon inner-SVG (rendered inside a 24×24 stroke viewBox). */
43 export const SECTION_ICONS: Record<string, string> = {
44 form: '<rect x="3" y="3" width="18" height="18" rx="3"/>',
45 text: '<path d="M4 7V5h16v2M9 5v14M9 19h6"/>',
46 fields: '<rect x="3" y="5" width="18" height="6" rx="2"/><rect x="3" y="14" width="12" height="6" rx="2"/>',
47 choice: '<circle cx="7" cy="12" r="3"/><rect x="14" y="9" width="6" height="6" rx="1.5"/>',
48 button: '<rect x="3" y="8" width="18" height="9" rx="4"/>',
49 messages: '<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/>',
50 pagination: '<circle cx="5" cy="12" r="2.5"/><circle cx="12" cy="12" r="2.5"/><circle cx="19" cy="12" r="2.5"/><path d="M7.5 12h2M14.5 12h2"/>',
51 };
52
53 /** One-line sub-label shown under each element row. */
54 export const SECTION_SUBTITLES: Record<string, string> = {
55 form: __( 'Background, corners, font, width', 'everest-forms' ),
56 text: __( 'Labels, sublabels, descriptions, section titles', 'everest-forms' ),
57 fields: __( 'Text boxes, dropdowns, upload area', 'everest-forms' ),
58 choice: __( 'Radio, checkbox, ratings & more', 'everest-forms' ),
59 button: __( 'Submit & navigation buttons', 'everest-forms' ),
60 messages: __( 'Success, error & validation banners', 'everest-forms' ),
61 pagination: __( 'Progress indicator & part navigation', 'everest-forms' ),
62 };
63
64 /** Human labels for the variant/state ids the schema ships. */
65 export const STATE_LABELS: Record<string, string> = {
66 label: 'Label',
67 sublabel: 'Sublabel',
68 desc: 'Description',
69 title: 'Section Title',
70 normal: 'Normal',
71 focus: 'Focus',
72 hover: 'Hover',
73 success: 'Success',
74 error: 'Error',
75 validation: 'Validation',
76 };
77
78 /** Preview force-classes the bridge toggles on the iframe wrapper for non-"normal" states. */
79 export const STATE_FORCE: Record<string, string> = {
80 focus: 'force-focus',
81 hover: 'force-hover',
82 success: 'force-msg-success',
83 error: 'force-msg-error',
84 validation: 'force-msg-validation',
85 };
86
87 export const ALL_FORCE_CLASSES = [
88 'force-focus',
89 'force-hover',
90 'force-msg-success',
91 'force-msg-error',
92 'force-msg-validation',
93 ];
94
95 export const DEVICE_LABELS: Record<Device, string> = {
96 desktop: 'Desktop',
97 tablet: 'Tablet',
98 mobile: 'Mobile',
99 };
100
101 /** Preview frame widths per device. Tablet/mobile widths sit just below the breakpoints. */
102 export const DEVICE_WIDTHS: Record<Device, number> = {
103 desktop: 760,
104 tablet: 760,
105 mobile: 390,
106 };
107
108 export const DEVICE_ICONS: Record<Device, string> = {
109 desktop: '<rect x="3" y="4" width="18" height="12" rx="2"/><path d="M8 20h8"/>',
110 tablet: '<rect x="6" y="3" width="12" height="18" rx="2"/>',
111 mobile: '<rect x="8" y="3" width="8" height="18" rx="2"/>',
112 };
113
114 export const ALIGN_ICONS: Record<string, string> = {
115 left: '<path d="M4 6h16M4 12h10M4 18h13"/>',
116 center: '<path d="M4 6h16M7 12h10M5 18h14"/>',
117 right: '<path d="M4 6h16M10 12h10M7 18h13"/>',
118 };
119
120 /** Font-style buttons: [flag, glyph-html, title]. */
121 export const FONTSTYLE_BUTTONS: Array<[ keyof import('./types').FontStyleValue, string, string ]> = [
122 [ 'bold', '<b>B</b>', 'Bold' ],
123 [ 'italic', '<i>I</i>', 'Italic' ],
124 [ 'underline', '<u>U</u>', 'Underline' ],
125 [ 'uppercase', 'TT', 'Uppercase' ],
126 ];
127
128 /** Click-to-edit resolver, ordered most-specific → least-specific; the bridge walks up from a
129 * clicked element to the first matching target. */
130 export interface PreviewTarget {
131 match: string;
132 section: string;
133 variant?: string;
134 label: string;
135 }
136
137 export const PREVIEW_TARGETS: PreviewTarget[] = [
138 {
139 match:
140 '.evf-submit-container button, .evf-submit-container input[type="submit"], .everest-forms-multi-part-actions button, .everest-forms-multi-part-actions input[type="submit"], .everest-forms-part-button, .everest-forms-submit-button',
141 section: 'button',
142 label: 'Button',
143 },
144 {
145 match: '.everest-forms-multi-part-indicator',
146 section: 'pagination',
147 label: 'Multi step',
148 },
149 {
150 match: '.everest-forms-notice--success',
151 section: 'messages',
152 variant: 'success',
153 label: 'Success message',
154 },
155 {
156 match: '.everest-forms-notice--error',
157 section: 'messages',
158 variant: 'error',
159 label: 'Error message',
160 },
161 {
162 match: '.evf-error',
163 section: 'messages',
164 variant: 'validation',
165 label: 'Validation message',
166 },
167 { match: '.evf-field-title, .evf-field-title h3', section: 'text', variant: 'title', label: 'Section title' },
168 { match: '.evf-field-description', section: 'text', variant: 'desc', label: 'Description' },
169 { match: 'label.everest-forms-field-sublabel', section: 'text', variant: 'sublabel', label: 'Sublabel' },
170 { match: 'label.evf-field-label', section: 'text', variant: 'label', label: 'Label' },
171 { match: '.everest-forms-uploader', section: 'fields', label: 'Upload area' },
172 {
173 // Choice-type fields: radio, checkbox, image-choice, likert, ratings, payment pickers, yes/no, privacy policy, subscription plan.
174 match:
175 '.evf-field-radio, .evf-field-checkbox, .evf-field-payment-multiple, .evf-field-payment-checkbox, .evf-field-privacy-policy, .evf-field-image-choice, .evf-field-likert, .evf-field-rating, .evf-field-scale-rating, .evf-field-payment-single, .evf-field-yes-no, .evf-field-payment-subscription-plan',
176 section: 'choice',
177 label: 'Choice field',
178 },
179 {
180 match:
181 'input[type="text"], input[type="email"], input[type="url"], input[type="tel"], input[type="number"], input[type="password"], input[type="date"], input[type="time"], input[type="datetime-local"], textarea, select, .StripeElement, canvas.evf-signature-canvas',
182 section: 'fields',
183 label: 'Input field',
184 },
185 { match: '.evf-field, .evf-frontend-row', section: 'fields', label: 'Field' },
186 { match: '.evf-container', section: 'form', label: 'Form container' },
187 ];
188
189 /**
190 * Best-effort #rrggbb for any CSS colour a "color" token might hold. Token values are usually
191 * plain hex, but {@see Sanitizer::sanitize_color()} also accepts (and a couple of schema defaults,
192 * e.g. `file.bg`, actually use) `rgba()` — kept for legacy v1 alpha borders/backgrounds. Neither the
193 * native `<input type="color">` swatch nor a hex textbox understands rgba, so callers use this to
194 * get something displayable instead of silently rendering black; alpha is dropped since there's no
195 * alpha control here. Returns null only for a genuinely unparseable value (a CSS keyword/var).
196 */
197 export function toDisplayHex( value: string ): string | null {
198 const v = String( value ).trim();
199 if ( /^#[0-9a-f]{3}$/i.test( v ) ) {
200 return ( '#' + v.slice( 1 ).split( '' ).map( ( c ) => c + c ).join( '' ) ).toLowerCase();
201 }
202 if ( /^#[0-9a-f]{6}$/i.test( v ) ) {
203 return v.toLowerCase();
204 }
205 if ( /^#[0-9a-f]{8}$/i.test( v ) ) {
206 return v.slice( 0, 7 ).toLowerCase();
207 }
208 const m = v.match( /^rgba?\(\s*([\d.]+%?)\s*[,\s]+\s*([\d.]+%?)\s*[,\s]+\s*([\d.]+%?)/i );
209 if ( m ) {
210 const hex = m.slice( 1, 4 ).map( ( raw ) => {
211 const num = parseFloat( raw );
212 const c = Math.max( 0, Math.min( 255, Math.round( raw.endsWith( '%' ) ? num * 2.55 : num ) ) );
213 return c.toString( 16 ).padStart( 2, '0' );
214 } );
215 return '#' + hex.join( '' );
216 }
217 return null;
218 }
219
220 /** Mix two hex colours by ratio t (0..1). Used to derive the button hover from a palette. */
221 export function mixHex( a: string, b: string, t: number ): string {
222 const parse = ( h: string ): number[] => {
223 let s = String( h ).replace( '#', '' );
224 if ( s.length === 3 ) {
225 s = s
226 .split( '' )
227 .map( ( c ) => c + c )
228 .join( '' );
229 }
230 return [ parseInt( s.slice( 0, 2 ), 16 ), parseInt( s.slice( 2, 4 ), 16 ), parseInt( s.slice( 4, 6 ), 16 ) ];
231 };
232 const A = parse( a );
233 const B = parse( b );
234 return (
235 '#' +
236 A.map( ( x, i ) => Math.round( x + ( B[ i ] - x ) * t ).toString( 16 ).padStart( 2, '0' ) ).join( '' )
237 );
238 }
239