@vectoriox/iox-builder 4.3.0 → 4.5.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.
- package/fesm2022/vectoriox-iox-builder.mjs +508 -121
- package/fesm2022/vectoriox-iox-builder.mjs.map +1 -1
- package/index.d.ts +211 -72
- package/package.json +1 -1
|
@@ -2,12 +2,12 @@ import * as i0 from '@angular/core';
|
|
|
2
2
|
import { Injectable, inject, NgZone, EventEmitter, createComponent, Output, Input, Inject, Directive, ChangeDetectionStrategy, Component, ViewChild, effect, Optional, InjectionToken, ContentChildren, NO_ERRORS_SCHEMA, NgModule } from '@angular/core';
|
|
3
3
|
import * as i2 from '@angular/common';
|
|
4
4
|
import { DOCUMENT, CommonModule } from '@angular/common';
|
|
5
|
-
import * as
|
|
5
|
+
import * as i15 from '@angular/forms';
|
|
6
6
|
import { FormsModule } from '@angular/forms';
|
|
7
7
|
import * as i4 from '@angular/cdk/drag-drop';
|
|
8
8
|
import { DragDropModule } from '@angular/cdk/drag-drop';
|
|
9
9
|
import { AccordionModule } from 'primeng/accordion';
|
|
10
|
-
import * as
|
|
10
|
+
import * as i16 from 'primeng/button';
|
|
11
11
|
import { ButtonModule } from 'primeng/button';
|
|
12
12
|
import * as i2$1 from 'primeng/popover';
|
|
13
13
|
import { PopoverModule } from 'primeng/popover';
|
|
@@ -15,8 +15,8 @@ import { InputTextModule } from 'primeng/inputtext';
|
|
|
15
15
|
import { SelectModule } from 'primeng/select';
|
|
16
16
|
import { TooltipModule } from 'primeng/tooltip';
|
|
17
17
|
import { DialogModule } from 'primeng/dialog';
|
|
18
|
-
import * as
|
|
19
|
-
import { DEFAULT_WIDTH_BY_TYPE, Breakpoint, BREAKPOINTS, countOverrides, breakpointDef, breakpointForWidth, ContextStore, ComponentInstanceRegistryService, isActionEnabled, isContextAction, contextActionEffect, resolveContextValue, DeclarationContext, planResponsiveRules, resolveStyleProps, buildTargetedStateSelector, compileDeclarations, wrapInBreakpointQuery, BreakpointQueryKind, safeSetInput, composeVirtualTraits, stateNamesIn, collectContextSlotKeys, withContextSlots, nodeUsesContext, resolvePath, applyBindingPipes, IS_PREVIEW, planDataSource, dataSourcePlanToRequest, normalizeCollectionResult, resolveRepeaterItems, getByPath, computeRelative, IoxPageModule } from '@vectoriox/iox-ui';
|
|
18
|
+
import * as i17 from '@vectoriox/iox-ui';
|
|
19
|
+
import { DEFAULT_WIDTH_BY_TYPE, Breakpoint, BREAKPOINTS, countOverrides, breakpointDef, breakpointForWidth, ContextStore, ComponentInstanceRegistryService, isActionEnabled, isContextAction, contextActionEffect, resolveContextValue, DeclarationContext, planResponsiveRules, resolveStyleProps, buildTargetedStateSelector, compileDeclarations, wrapInBreakpointQuery, BreakpointQueryKind, safeSetInput, composeVirtualTraits, stateNamesIn, setOverride, collectContextSlotKeys, withContextSlots, nodeUsesContext, resolvePath, applyBindingPipes, IS_PREVIEW, planDataSource, dataSourcePlanToRequest, normalizeCollectionResult, resolveRepeaterItems, getByPath, computeRelative, IoxPageModule } from '@vectoriox/iox-ui';
|
|
20
20
|
export { ComponentInstanceRegistryService, VIRTUAL_TRAIT_KEYS, composeVirtualTraits } from '@vectoriox/iox-ui';
|
|
21
21
|
import Lenis from 'lenis';
|
|
22
22
|
import { Subject, BehaviorSubject, of, forkJoin } from 'rxjs';
|
|
@@ -970,6 +970,11 @@ const SCREEN_WIDTH_OPTIONS = [
|
|
|
970
970
|
{ label: '2560', width: 2560 },
|
|
971
971
|
];
|
|
972
972
|
|
|
973
|
+
// Virtual trait composition now lives in @vectoriox/iox-ui so the SSR engine (which does NOT depend
|
|
974
|
+
// on iox-builder) can use the SAME logic — otherwise the engine emitted raw virtual keys
|
|
975
|
+
// (translate-x, filter-blur…) as invalid CSS. This is a re-export passthrough to keep existing
|
|
976
|
+
// iox-builder imports (`../style-compose`) working with no duplicated implementation.
|
|
977
|
+
|
|
973
978
|
/**
|
|
974
979
|
* node-path — pure ancestor-chain helpers for the builder's node tree.
|
|
975
980
|
*
|
|
@@ -1079,6 +1084,213 @@ function resolveInsertTarget(roots, selected, isContainer) {
|
|
|
1079
1084
|
return { children: siblings, index: siblings.indexOf(selected) + 1, parent };
|
|
1080
1085
|
}
|
|
1081
1086
|
|
|
1087
|
+
/**
|
|
1088
|
+
* STRUCTURE OPS (A4) — the one-click layout changes people actually make at a narrow width, as pure
|
|
1089
|
+
* "what to write" functions. Each returns a flat style map the caller stores in the node's
|
|
1090
|
+
* `responsive[bp]` layer (or in the base styles at Base) through the shared `setOverride`.
|
|
1091
|
+
*
|
|
1092
|
+
* All of it is ordinary CSS the SSR engine already renders — there is no second tree and no
|
|
1093
|
+
* per-width HTML. See responsive-breakpoints-plan.md (D5).
|
|
1094
|
+
*/
|
|
1095
|
+
/** CSS lengths that are a FIXED pixel size — the ones that stop a layout fitting a phone. */
|
|
1096
|
+
const PX_LENGTH = /^-?\d+(\.\d+)?px$/i;
|
|
1097
|
+
/**
|
|
1098
|
+
* Hide / show at a width. Hiding is `display: none` INSIDE that breakpoint's rules — the node stays
|
|
1099
|
+
* in the DOM and in the SSR output (one HTML document serves every width), so hidden content still
|
|
1100
|
+
* ships bytes and is still indexed. Showing again clears the override rather than guessing a value.
|
|
1101
|
+
*
|
|
1102
|
+
* @returns the props to write, or `null` meaning "clear the display override at this breakpoint".
|
|
1103
|
+
*/
|
|
1104
|
+
function visibilityProps(hidden) {
|
|
1105
|
+
return hidden ? { display: 'none' } : null;
|
|
1106
|
+
}
|
|
1107
|
+
/** True when this node is hidden by the resolved styles at some width. */
|
|
1108
|
+
function isHiddenAt(resolvedStyleProps) {
|
|
1109
|
+
return String(resolvedStyleProps?.['display'] ?? '') === 'none';
|
|
1110
|
+
}
|
|
1111
|
+
/**
|
|
1112
|
+
* Stack a container's children in one column — the most common mobile edit, and four separate
|
|
1113
|
+
* controls today (display, direction, wrap, columns). A grid becomes a single-column grid; anything
|
|
1114
|
+
* else becomes a column flex box.
|
|
1115
|
+
*/
|
|
1116
|
+
function stackChildrenProps(resolvedStyleProps) {
|
|
1117
|
+
const display = String(resolvedStyleProps?.['display'] ?? '');
|
|
1118
|
+
if (display === 'grid' || display === 'inline-grid') {
|
|
1119
|
+
return { gridTemplateColumns: 'repeat(1, minmax(0, 1fr))' };
|
|
1120
|
+
}
|
|
1121
|
+
return { display: 'flex', flexDirection: 'column', flexWrap: 'nowrap' };
|
|
1122
|
+
}
|
|
1123
|
+
/**
|
|
1124
|
+
* Collapse a grid to `count` columns (a flex row is stacked instead — it has no column track).
|
|
1125
|
+
* `count` is clamped to at least 1.
|
|
1126
|
+
*/
|
|
1127
|
+
function collapseColumnsProps(resolvedStyleProps, count) {
|
|
1128
|
+
const columns = Math.max(1, Math.floor(count));
|
|
1129
|
+
const display = String(resolvedStyleProps?.['display'] ?? '');
|
|
1130
|
+
if (display === 'grid' || display === 'inline-grid') {
|
|
1131
|
+
return { gridTemplateColumns: `repeat(${columns}, minmax(0, 1fr))` };
|
|
1132
|
+
}
|
|
1133
|
+
return columns === 1 ? stackChildrenProps(resolvedStyleProps) : { flexWrap: 'wrap' };
|
|
1134
|
+
}
|
|
1135
|
+
/** How many equal columns a `repeat(N, …)` track list describes, or null when it isn't one. */
|
|
1136
|
+
function columnCountOf(resolvedStyleProps) {
|
|
1137
|
+
const match = /^repeat\(\s*(\d+)\s*,/.exec(String(resolvedStyleProps?.['gridTemplateColumns'] ?? ''));
|
|
1138
|
+
return match ? Number(match[1]) : null;
|
|
1139
|
+
}
|
|
1140
|
+
/**
|
|
1141
|
+
* Make fixed pixel sizes fluid: `width: 520px` → `width: 100%` with the original kept as
|
|
1142
|
+
* `max-width`, so the element still never exceeds its designed size on a wide screen. `minWidth` in
|
|
1143
|
+
* px is dropped to 0 — it is what actually forces horizontal scrolling on a phone.
|
|
1144
|
+
*
|
|
1145
|
+
* @returns the props to write; EMPTY when there is nothing fixed to fluidise.
|
|
1146
|
+
*/
|
|
1147
|
+
function fluidiseProps(resolvedStyleProps) {
|
|
1148
|
+
const out = {};
|
|
1149
|
+
const width = String(resolvedStyleProps?.['width'] ?? '');
|
|
1150
|
+
const minWidth = String(resolvedStyleProps?.['minWidth'] ?? '');
|
|
1151
|
+
if (PX_LENGTH.test(width)) {
|
|
1152
|
+
out['width'] = '100%';
|
|
1153
|
+
const maxWidth = String(resolvedStyleProps?.['maxWidth'] ?? '');
|
|
1154
|
+
if (!maxWidth || maxWidth === 'none')
|
|
1155
|
+
out['maxWidth'] = width;
|
|
1156
|
+
}
|
|
1157
|
+
if (PX_LENGTH.test(minWidth) && parseFloat(minWidth) > 0)
|
|
1158
|
+
out['minWidth'] = '0px';
|
|
1159
|
+
return out;
|
|
1160
|
+
}
|
|
1161
|
+
/** True when `fluidiseProps` would change anything — drives the button's disabled state. */
|
|
1162
|
+
function canFluidise(resolvedStyleProps) {
|
|
1163
|
+
return Object.keys(fluidiseProps(resolvedStyleProps)).length > 0;
|
|
1164
|
+
}
|
|
1165
|
+
/**
|
|
1166
|
+
* Reorder children AT ONE WIDTH using CSS `order`, leaving the tree — and so the DOM and
|
|
1167
|
+
* screen-reader order — untouched. Returns one value per child BY CHILD INDEX: the `order` it must
|
|
1168
|
+
* take, or `null` for "no override needed".
|
|
1169
|
+
*
|
|
1170
|
+
* Either every child gets an explicit `order` or none does: CSS defaults `order` to 0, so a partial
|
|
1171
|
+
* set would mix overridden children with unordered ones and read as an arbitrary interleave.
|
|
1172
|
+
*
|
|
1173
|
+
* @param count How many children there are.
|
|
1174
|
+
* @param from Index of the child being moved.
|
|
1175
|
+
* @param to Index it should occupy.
|
|
1176
|
+
*/
|
|
1177
|
+
function reorderProps(count, from, to) {
|
|
1178
|
+
const none = Array.from({ length: Math.max(0, count) }, () => null);
|
|
1179
|
+
if (from < 0 || from >= count || to < 0 || to >= count || from === to)
|
|
1180
|
+
return none;
|
|
1181
|
+
// Visual order after the move, as a list of CHILD indices.
|
|
1182
|
+
const sequence = Array.from({ length: count }, (_, i) => i);
|
|
1183
|
+
sequence.splice(to, 0, ...sequence.splice(from, 1));
|
|
1184
|
+
const orderByChild = none.slice();
|
|
1185
|
+
sequence.forEach((childIndex, position) => { orderByChild[childIndex] = position; });
|
|
1186
|
+
return orderByChild;
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
/**
|
|
1190
|
+
* RESPONSIVE AUDIT (A5) — find what is broken at a width, from the node tree plus ONE measurement
|
|
1191
|
+
* pass. A builder that lets you author five widths must also tell you which of them are broken.
|
|
1192
|
+
*
|
|
1193
|
+
* Pure: the caller measures (one `getBoundingClientRect` per node, off the animation path) and
|
|
1194
|
+
* passes the numbers in. Every finding that can be repaired carries the exact props to write, so a
|
|
1195
|
+
* fix is one `setOverride` per key — one undo step, and it shows as an ordinary override.
|
|
1196
|
+
*/
|
|
1197
|
+
var AuditCode;
|
|
1198
|
+
(function (AuditCode) {
|
|
1199
|
+
AuditCode["Overflow"] = "overflow";
|
|
1200
|
+
AuditCode["FixedWidth"] = "fixed-width";
|
|
1201
|
+
AuditCode["TapTarget"] = "tap-target";
|
|
1202
|
+
AuditCode["TinyText"] = "tiny-text";
|
|
1203
|
+
AuditCode["UnboundedImage"] = "unbounded-image";
|
|
1204
|
+
})(AuditCode || (AuditCode = {}));
|
|
1205
|
+
var AuditSeverity;
|
|
1206
|
+
(function (AuditSeverity) {
|
|
1207
|
+
AuditSeverity["Error"] = "error";
|
|
1208
|
+
AuditSeverity["Warning"] = "warning";
|
|
1209
|
+
AuditSeverity["Info"] = "info";
|
|
1210
|
+
})(AuditSeverity || (AuditSeverity = {}));
|
|
1211
|
+
/** WCAG 2.5.8 minimum target size. */
|
|
1212
|
+
const MIN_TAP_PX = 44;
|
|
1213
|
+
/** Below this, iOS Safari zooms the page when an adjacent input is focused. */
|
|
1214
|
+
const MIN_TEXT_PX = 12;
|
|
1215
|
+
/** Types a visitor taps. */
|
|
1216
|
+
const TAPPABLE = new Set(['Button', 'ButtonBlock', 'Link', 'LinkedContainer']);
|
|
1217
|
+
const nameOf = (node) => node.label || node.type;
|
|
1218
|
+
const pxOf = (value) => {
|
|
1219
|
+
const match = /^(-?\d+(?:\.\d+)?)px$/i.exec(String(value ?? ''));
|
|
1220
|
+
return match ? Number(match[1]) : null;
|
|
1221
|
+
};
|
|
1222
|
+
/**
|
|
1223
|
+
* Audit nodes laid out at `viewportWidth`. Returns findings ordered by severity (errors first),
|
|
1224
|
+
* then by the order the nodes were given — so the worst thing is at the top of the list.
|
|
1225
|
+
*/
|
|
1226
|
+
function auditNodes(nodes, viewportWidth) {
|
|
1227
|
+
const findings = [];
|
|
1228
|
+
for (const node of nodes) {
|
|
1229
|
+
const name = nameOf(node);
|
|
1230
|
+
const styles = node.styleProps ?? {};
|
|
1231
|
+
if (String(styles['display'] ?? '') === 'none')
|
|
1232
|
+
continue; // hidden here — nothing to report
|
|
1233
|
+
// 1. Wider than the viewport: the page scrolls sideways.
|
|
1234
|
+
if (node.width > viewportWidth + 0.5) {
|
|
1235
|
+
findings.push({
|
|
1236
|
+
nodeId: node.id, nodeName: name, code: AuditCode.Overflow, severity: AuditSeverity.Error,
|
|
1237
|
+
message: `${name} is ${Math.round(node.width)}px wide in a ${viewportWidth}px viewport — the page scrolls sideways.`,
|
|
1238
|
+
fix: canFluidise(styles) ? fluidiseProps(styles) : { maxWidth: '100%' },
|
|
1239
|
+
});
|
|
1240
|
+
}
|
|
1241
|
+
else if (canFluidise(styles)) {
|
|
1242
|
+
// 2. A fixed px size that still fits, but will not on a narrower phone.
|
|
1243
|
+
const width = pxOf(styles['width']);
|
|
1244
|
+
findings.push({
|
|
1245
|
+
nodeId: node.id, nodeName: name, code: AuditCode.FixedWidth, severity: AuditSeverity.Warning,
|
|
1246
|
+
message: width !== null
|
|
1247
|
+
? `${name} has a fixed width of ${width}px — it cannot shrink on a narrower screen.`
|
|
1248
|
+
: `${name} has a fixed minimum width — it forces sideways scrolling below that size.`,
|
|
1249
|
+
fix: fluidiseProps(styles),
|
|
1250
|
+
});
|
|
1251
|
+
}
|
|
1252
|
+
// 3. Tap targets below the WCAG minimum.
|
|
1253
|
+
if (TAPPABLE.has(node.type) && node.width > 0 && node.height > 0
|
|
1254
|
+
&& (node.width < MIN_TAP_PX || node.height < MIN_TAP_PX)) {
|
|
1255
|
+
findings.push({
|
|
1256
|
+
nodeId: node.id, nodeName: name, code: AuditCode.TapTarget, severity: AuditSeverity.Warning,
|
|
1257
|
+
message: `${name} is ${Math.round(node.width)}×${Math.round(node.height)}px — below the ${MIN_TAP_PX}px minimum tap target.`,
|
|
1258
|
+
fix: { minHeight: `${MIN_TAP_PX}px`, minWidth: `${MIN_TAP_PX}px` },
|
|
1259
|
+
});
|
|
1260
|
+
}
|
|
1261
|
+
// 4. Text too small to read (and small enough to trigger iOS zoom).
|
|
1262
|
+
const fontSize = pxOf(styles['fontSize']);
|
|
1263
|
+
if (fontSize !== null && fontSize < MIN_TEXT_PX) {
|
|
1264
|
+
findings.push({
|
|
1265
|
+
nodeId: node.id, nodeName: name, code: AuditCode.TinyText, severity: AuditSeverity.Warning,
|
|
1266
|
+
message: `${name} renders at ${fontSize}px — under the ${MIN_TEXT_PX}px readable minimum.`,
|
|
1267
|
+
fix: { fontSize: `${MIN_TEXT_PX}px` },
|
|
1268
|
+
});
|
|
1269
|
+
}
|
|
1270
|
+
// 5. An image with a fixed height and no ratio crops unpredictably as the width changes.
|
|
1271
|
+
if (node.type === 'Image' && pxOf(styles['height']) !== null
|
|
1272
|
+
&& !String(styles['aspectRatio'] ?? '').trim().replace(/^auto$/, '')) {
|
|
1273
|
+
findings.push({
|
|
1274
|
+
nodeId: node.id, nodeName: name, code: AuditCode.UnboundedImage, severity: AuditSeverity.Info,
|
|
1275
|
+
message: `${name} has a fixed height and no aspect ratio — it crops unpredictably as the width changes.`,
|
|
1276
|
+
fix: { height: 'auto', aspectRatio: '16 / 9' },
|
|
1277
|
+
});
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
const rank = { [AuditSeverity.Error]: 0, [AuditSeverity.Warning]: 1, [AuditSeverity.Info]: 2 };
|
|
1281
|
+
return findings
|
|
1282
|
+
.map((finding, index) => ({ finding, index }))
|
|
1283
|
+
.sort((a, b) => rank[a.finding.severity] - rank[b.finding.severity] || a.index - b.index)
|
|
1284
|
+
.map(entry => entry.finding);
|
|
1285
|
+
}
|
|
1286
|
+
/** Counts per severity — the per-breakpoint summary strip. */
|
|
1287
|
+
function summarise(findings) {
|
|
1288
|
+
const out = { [AuditSeverity.Error]: 0, [AuditSeverity.Warning]: 0, [AuditSeverity.Info]: 0 };
|
|
1289
|
+
for (const finding of findings)
|
|
1290
|
+
out[finding.severity]++;
|
|
1291
|
+
return out;
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1082
1294
|
class ComponentRegistryService {
|
|
1083
1295
|
constructor() {
|
|
1084
1296
|
this.components = {};
|
|
@@ -3760,11 +3972,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImpo
|
|
|
3760
3972
|
args: [{ providedIn: 'root' }]
|
|
3761
3973
|
}] });
|
|
3762
3974
|
|
|
3763
|
-
// Virtual trait composition now lives in @vectoriox/iox-ui so the SSR engine (which does NOT depend
|
|
3764
|
-
// on iox-builder) can use the SAME logic — otherwise the engine emitted raw virtual keys
|
|
3765
|
-
// (translate-x, filter-blur…) as invalid CSS. This is a re-export passthrough to keep existing
|
|
3766
|
-
// iox-builder imports (`../style-compose`) working with no duplicated implementation.
|
|
3767
|
-
|
|
3768
3975
|
// Module-level counter — unique drop-list ID per Section instance across the whole app session.
|
|
3769
3976
|
let dropListCounter = 0;
|
|
3770
3977
|
/**
|
|
@@ -4773,6 +4980,9 @@ class ToolbarComponent {
|
|
|
4773
4980
|
this.activeBreakpoint = Breakpoint.Base;
|
|
4774
4981
|
/** Overrides per breakpoint — for the selection, or the page when nothing is selected. */
|
|
4775
4982
|
this.breakpointCounts = {};
|
|
4983
|
+
/** Findings at the current width — shown as a count on the audit button. */
|
|
4984
|
+
this.auditCount = 0;
|
|
4985
|
+
this.auditOpen = false;
|
|
4776
4986
|
this.activeZoom = 100;
|
|
4777
4987
|
this.activeScreenWidth = 1440;
|
|
4778
4988
|
this.canUndo = false;
|
|
@@ -4781,6 +4991,7 @@ class ToolbarComponent {
|
|
|
4781
4991
|
this.pageStatus = 'draft';
|
|
4782
4992
|
this.modeChange = new EventEmitter();
|
|
4783
4993
|
this.breakpointChange = new EventEmitter();
|
|
4994
|
+
this.auditToggle = new EventEmitter();
|
|
4784
4995
|
this.zoomChange = new EventEmitter();
|
|
4785
4996
|
this.screenWidthChange = new EventEmitter();
|
|
4786
4997
|
this.undo = new EventEmitter();
|
|
@@ -4864,17 +5075,21 @@ class ToolbarComponent {
|
|
|
4864
5075
|
this.zoomPopover.hide();
|
|
4865
5076
|
}
|
|
4866
5077
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: ToolbarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
4867
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.31", type: ToolbarComponent, isStandalone: false, selector: "app-toolbar", inputs: { activeMode: "activeMode", activeBreakpoint: "activeBreakpoint", breakpointCounts: "breakpointCounts", activeZoom: "activeZoom", activeScreenWidth: "activeScreenWidth", canUndo: "canUndo", canRedo: "canRedo", isSaving: "isSaving", pageStatus: "pageStatus" }, outputs: { modeChange: "modeChange", breakpointChange: "breakpointChange", zoomChange: "zoomChange", screenWidthChange: "screenWidthChange", undo: "undo", redo: "redo", save: "save", preview: "preview", publishToggle: "publishToggle" }, viewQueries: [{ propertyName: "modePopover", first: true, predicate: ["modePopover"], descendants: true }, { propertyName: "zoomPopover", first: true, predicate: ["zoomPopover"], descendants: true }, { propertyName: "widthPopover", first: true, predicate: ["widthPopover"], descendants: true }], ngImport: i0, template: "<div class=\"builder-toolbar\">\n <!-- \u2500\u2500 Mode switcher (stateful) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <button class=\"tool-btn stateful\"\n (click)=\"toggleModePopover($event)\"\n [title]=\"activeModeLabel\">\n <i [class]=\"activeModeIcon\"></i>\n </button>\n\n <p-popover #modePopover [appendTo]=\"'body'\" styleClass=\"toolbar-popover\">\n <div class=\"popover-strip\">\n <button class=\"popover-option\"\n [class.is-active]=\"activeMode === modes.Select\"\n (click)=\"selectMode(modes.Select)\">\n <i class=\"ph-thin ph-cursor\"></i>\n <span>Select</span>\n </button>\n <button class=\"popover-option\"\n [class.is-active]=\"activeMode === modes.Pan\"\n (click)=\"selectMode(modes.Pan)\">\n <i class=\"ph-thin ph-hand\"></i>\n <span>Pan</span>\n </button>\n <button class=\"popover-option\"\n [class.is-active]=\"activeMode === modes.Style\"\n (click)=\"selectMode(modes.Style)\">\n <i class=\"ph-thin ph-paint-brush-broad\"></i>\n <span>Style</span>\n </button>\n </div>\n </p-popover>\n\n <span class=\"divider\"></span>\n\n <!-- \u2500\u2500 Undo / Redo (action) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <button class=\"tool-btn\" title=\"Undo\" [disabled]=\"!canUndo\" (click)=\"undo.emit()\">\n <i class=\"ph-thin ph-arrow-counter-clockwise\"></i>\n </button>\n <button class=\"tool-btn\" title=\"Redo\" [disabled]=\"!canRedo\" (click)=\"redo.emit()\">\n <i class=\"ph-thin ph-arrow-clockwise\"></i>\n </button>\n\n <span class=\"divider\"></span>\n\n <!-- \u2500\u2500 Breakpoint bar \u2014 the EDITING SCOPE, not just a preview size \u2500\u2500 -->\n <div class=\"breakpoint-bar\" role=\"group\" aria-label=\"Breakpoint\">\n @for (bp of breakpointOptions; track bp.id) {\n <button type=\"button\"\n class=\"bp-btn\"\n [class.is-active]=\"activeBreakpoint === bp.id\"\n [attr.aria-pressed]=\"activeBreakpoint === bp.id\"\n [title]=\"bp.label + ' \u00B7 ' + bp.range\"\n [attr.aria-label]=\"bp.label\"\n (click)=\"selectBreakpoint(bp.id)\">\n <i [class]=\"bp.icon\" [class.bp-icon--landscape]=\"bp.landscape\" aria-hidden=\"true\"></i>\n @if (breakpointCounts[bp.id]) {\n <span class=\"bp-dot\" aria-hidden=\"true\"></span>\n }\n </button>\n }\n </div>\n\n <!-- \u2500\u2500 Screen width \u2014 Base only (any width above Laptop) \u2500\u2500 -->\n @if (activeBreakpoint === breakpointIds.Base) {\n <button class=\"tool-btn stateful width-btn\"\n (click)=\"toggleWidthPopover($event)\"\n title=\"Screen width\">\n <span class=\"width-label\">{{ activeScreenWidth }}</span>\n </button>\n }\n\n <p-popover #widthPopover [appendTo]=\"'body'\" styleClass=\"toolbar-popover\">\n <div class=\"popover-strip\">\n <button class=\"popover-option\"\n *ngFor=\"let w of screenWidthOptions\"\n [class.is-active]=\"activeScreenWidth === w.width\"\n (click)=\"selectScreenWidth(w.width)\">\n <span>{{ w.label }}</span>\n </button>\n </div>\n </p-popover>\n\n <!-- \u2500\u2500 Zoom (stateful) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <button class=\"tool-btn stateful zoom-btn\"\n (click)=\"toggleZoomPopover($event)\"\n title=\"Zoom\">\n <span class=\"zoom-label\">{{ zoomLabel }}</span>\n </button>\n\n <p-popover #zoomPopover [appendTo]=\"'body'\" styleClass=\"toolbar-popover\">\n <div class=\"zoom-popover\">\n <div class=\"zoom-slider-row\">\n <input class=\"zoom-slider\"\n type=\"range\"\n min=\"20\" max=\"100\" step=\"1\"\n [value]=\"sliderZoom\"\n (input)=\"onSliderChange($event)\">\n <span class=\"zoom-slider-value\">{{ zoomLabel }}</span>\n </div>\n <div class=\"popover-strip\">\n <button class=\"popover-option\"\n *ngFor=\"let z of zoomOptions\"\n [class.is-active]=\"z.value === activeZoom\"\n (click)=\"selectZoom(z.value)\">\n <span>{{ z.label }}</span>\n </button>\n </div>\n </div>\n </p-popover>\n\n <span class=\"divider\"></span>\n\n <!-- \u2500\u2500 Save \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <button class=\"tool-btn save-btn\"\n [class.is-saving]=\"isSaving\"\n [disabled]=\"isSaving\"\n title=\"Save\"\n (click)=\"save.emit()\">\n <i class=\"ph-thin ph-floppy-disk\"></i>\n </button>\n\n <!-- \u2500\u2500 Preview \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <button class=\"tool-btn preview-btn\"\n title=\"Preview\"\n (click)=\"preview.emit()\">\n <i class=\"ph-thin ph-eye\"></i>\n </button>\n\n <span class=\"divider\"></span>\n\n <!-- \u2500\u2500 Publish / Unpublish \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <button class=\"tool-btn publish-btn\"\n [class.is-published]=\"isPublished\"\n [title]=\"isPublished ? 'Unpublish' : 'Publish'\"\n (click)=\"publishToggle.emit()\">\n <i [class]=\"isPublished ? 'ph-thin ph-cloud-slash' : 'ph-thin ph-cloud-arrow-up'\"></i>\n <span class=\"publish-label\">{{ isPublished ? 'Unpublish' : 'Publish' }}</span>\n </button>\n</div>\n", styles: [".builder-toolbar{display:flex;align-items:center;gap:.125rem}.divider{width:1px;height:1.25rem;background:var(--p-surface-200);margin:0 .375rem;flex-shrink:0}.tool-btn{display:flex;align-items:center;justify-content:center;width:2rem;height:2rem;border-radius:6px;border:none;background:transparent;color:var(--p-text-muted-color);cursor:pointer;font-size:1rem;transition:background .15s ease-out,color .15s ease-out}.tool-btn:hover:not(:disabled){background:var(--p-surface-50);color:var(--p-text-color)}.tool-btn:disabled{opacity:.35;cursor:default}.tool-btn.stateful{position:relative}.tool-btn.stateful:after{content:\"\";position:absolute;inset-block-end:3px;width:4px;height:4px;border-radius:50%;background:#cb9090}.breakpoint-bar{display:flex;align-items:center;gap:1px;padding:2px;border-radius:8px;background:var(--p-surface-100)}.bp-btn{position:relative;display:flex;align-items:center;justify-content:center;width:1.875rem;height:1.75rem;border:1px solid transparent;border-radius:6px;background:transparent;color:var(--p-text-muted-color);font-size:.95rem;cursor:pointer;transition:background .15s ease-out,color .15s ease-out,border-color .15s ease-out}.bp-btn:hover{background:var(--p-surface-0);color:var(--p-text-color)}.bp-btn:focus-visible{outline:2px solid #cb9090;outline-offset:1px}.bp-btn.is-active{background:var(--p-surface-0);border-color:#cb9090;color:#a05c5c}.bp-icon--landscape{transform:rotate(90deg)}.bp-dot{position:absolute;inset-block-start:3px;inset-inline-end:3px;width:5px;height:5px;border-radius:50%;background:#d9924e}.zoom-btn{width:auto;border-radius:999px;padding:0 .5rem;min-width:2.5rem}.zoom-btn .zoom-label{font-size:.7rem;font-weight:500;letter-spacing:.02em}.width-btn{width:auto;border-radius:999px;padding:0 .5rem;min-width:2.8rem}.width-btn .width-label{font-size:.7rem;font-weight:500;letter-spacing:.02em}.save-btn{background:#cb9090;color:#fff}.save-btn:hover:not(:disabled){background:#b37373;color:#fff}.save-btn.is-saving{opacity:.7}.preview-btn{border:1px solid var(--p-surface-200)}.preview-btn:hover:not(:disabled){background:var(--p-surface-50);color:var(--p-text-color);border-color:var(--p-surface-100)}.publish-btn{width:auto;border-radius:999px;padding:0 .65rem;gap:.35rem;font-size:.75rem;font-weight:500;background:#22c55e;color:#fff;border:none}.publish-btn .publish-label{font-size:.72rem;letter-spacing:.02em}.publish-btn:hover:not(:disabled){background:color-mix(in srgb,#22c55e 85%,#000);color:#fff}.publish-btn.is-published{background:transparent;color:var(--p-text-muted-color);border:1px solid var(--p-surface-200)}.publish-btn.is-published:hover:not(:disabled){background:color-mix(in srgb,#ef4444 10%,transparent);color:#ef4444;border-color:color-mix(in srgb,#ef4444 40%,transparent)}::ng-deep .toolbar-popover .p-popover-content{padding:.25rem!important;background:var(--p-surface-0)!important;border:1px solid var(--p-surface-200)!important;border-radius:10px!important;box-shadow:0 4px 16px #0f172a1a!important}.popover-strip{display:flex;flex-direction:column;gap:2px;min-width:120px}.popover-option{display:flex;align-items:center;gap:.5rem;width:100%;padding:.4rem .65rem;border:none;background:transparent;color:var(--p-text-color);cursor:pointer;font-size:.8rem;border-radius:6px;transition:background .12s ease-out,color .12s ease-out;white-space:nowrap}.popover-option i{font-size:1rem;color:var(--p-text-muted-color)}.popover-option:hover{background:var(--p-surface-50)}.popover-option.is-active{background:#cb9090;color:#fff}.popover-option.is-active i{color:#fff}::ng-deep .toolbar-popover .zoom-popover{display:flex;flex-direction:column;gap:4px;padding:.15rem 0}::ng-deep .toolbar-popover .zoom-slider-row{display:flex;align-items:center;gap:.5rem;padding:.3rem .65rem .1rem}::ng-deep .toolbar-popover .zoom-slider{flex:1;-webkit-appearance:none;appearance:none;height:3px;border-radius:999px;background:var(--p-surface-200);outline:none;cursor:pointer}::ng-deep .toolbar-popover .zoom-slider::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;width:14px;height:14px;border-radius:50%;background:#cb9090;cursor:pointer;transition:transform .12s ease-out}::ng-deep .toolbar-popover .zoom-slider::-webkit-slider-thumb:hover{transform:scale(1.2)}::ng-deep .toolbar-popover .zoom-slider::-moz-range-thumb{width:14px;height:14px;border-radius:50%;border:none;background:#cb9090;cursor:pointer}::ng-deep .toolbar-popover .zoom-slider-value{font-size:.68rem;font-weight:500;color:var(--p-text-muted-color);min-width:2.4rem;text-align:end;letter-spacing:.02em}\n"], dependencies: [{ kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "component", type: i2$1.Popover, selector: "p-popover", inputs: ["ariaLabel", "ariaLabelledBy", "dismissable", "style", "styleClass", "appendTo", "autoZIndex", "ariaCloseLabel", "baseZIndex", "focusOnShow", "showTransitionOptions", "hideTransitionOptions"], outputs: ["onShow", "onHide"] }] }); }
|
|
5078
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.31", type: ToolbarComponent, isStandalone: false, selector: "app-toolbar", inputs: { activeMode: "activeMode", activeBreakpoint: "activeBreakpoint", breakpointCounts: "breakpointCounts", auditCount: "auditCount", auditOpen: "auditOpen", activeZoom: "activeZoom", activeScreenWidth: "activeScreenWidth", canUndo: "canUndo", canRedo: "canRedo", isSaving: "isSaving", pageStatus: "pageStatus" }, outputs: { modeChange: "modeChange", breakpointChange: "breakpointChange", auditToggle: "auditToggle", zoomChange: "zoomChange", screenWidthChange: "screenWidthChange", undo: "undo", redo: "redo", save: "save", preview: "preview", publishToggle: "publishToggle" }, viewQueries: [{ propertyName: "modePopover", first: true, predicate: ["modePopover"], descendants: true }, { propertyName: "zoomPopover", first: true, predicate: ["zoomPopover"], descendants: true }, { propertyName: "widthPopover", first: true, predicate: ["widthPopover"], descendants: true }], ngImport: i0, template: "<div class=\"builder-toolbar\">\n <!-- \u2500\u2500 Mode switcher (stateful) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <button class=\"tool-btn stateful\"\n (click)=\"toggleModePopover($event)\"\n [title]=\"activeModeLabel\">\n <i [class]=\"activeModeIcon\"></i>\n </button>\n\n <p-popover #modePopover [appendTo]=\"'body'\" styleClass=\"toolbar-popover\">\n <div class=\"popover-strip\">\n <button class=\"popover-option\"\n [class.is-active]=\"activeMode === modes.Select\"\n (click)=\"selectMode(modes.Select)\">\n <i class=\"ph-thin ph-cursor\"></i>\n <span>Select</span>\n </button>\n <button class=\"popover-option\"\n [class.is-active]=\"activeMode === modes.Pan\"\n (click)=\"selectMode(modes.Pan)\">\n <i class=\"ph-thin ph-hand\"></i>\n <span>Pan</span>\n </button>\n <button class=\"popover-option\"\n [class.is-active]=\"activeMode === modes.Style\"\n (click)=\"selectMode(modes.Style)\">\n <i class=\"ph-thin ph-paint-brush-broad\"></i>\n <span>Style</span>\n </button>\n </div>\n </p-popover>\n\n <span class=\"divider\"></span>\n\n <!-- \u2500\u2500 Undo / Redo (action) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <button class=\"tool-btn\" title=\"Undo\" [disabled]=\"!canUndo\" (click)=\"undo.emit()\">\n <i class=\"ph-thin ph-arrow-counter-clockwise\"></i>\n </button>\n <button class=\"tool-btn\" title=\"Redo\" [disabled]=\"!canRedo\" (click)=\"redo.emit()\">\n <i class=\"ph-thin ph-arrow-clockwise\"></i>\n </button>\n\n <span class=\"divider\"></span>\n\n <!-- \u2500\u2500 Breakpoint bar \u2014 the EDITING SCOPE, not just a preview size \u2500\u2500 -->\n <div class=\"breakpoint-bar\" role=\"group\" aria-label=\"Breakpoint\">\n @for (bp of breakpointOptions; track bp.id) {\n <button type=\"button\"\n class=\"bp-btn\"\n [class.is-active]=\"activeBreakpoint === bp.id\"\n [attr.aria-pressed]=\"activeBreakpoint === bp.id\"\n [title]=\"bp.label + ' \u00B7 ' + bp.range\"\n [attr.aria-label]=\"bp.label\"\n (click)=\"selectBreakpoint(bp.id)\">\n <i [class]=\"bp.icon\" [class.bp-icon--landscape]=\"bp.landscape\" aria-hidden=\"true\"></i>\n @if (breakpointCounts[bp.id]) {\n <span class=\"bp-dot\" aria-hidden=\"true\"></span>\n }\n </button>\n }\n </div>\n\n <!-- \u2500\u2500 Screen width \u2014 Base only (any width above Laptop) \u2500\u2500 -->\n @if (activeBreakpoint === breakpointIds.Base) {\n <button class=\"tool-btn stateful width-btn\"\n (click)=\"toggleWidthPopover($event)\"\n title=\"Screen width\">\n <span class=\"width-label\">{{ activeScreenWidth }}</span>\n </button>\n }\n\n <p-popover #widthPopover [appendTo]=\"'body'\" styleClass=\"toolbar-popover\">\n <div class=\"popover-strip\">\n <button class=\"popover-option\"\n *ngFor=\"let w of screenWidthOptions\"\n [class.is-active]=\"activeScreenWidth === w.width\"\n (click)=\"selectScreenWidth(w.width)\">\n <span>{{ w.label }}</span>\n </button>\n </div>\n </p-popover>\n\n <!-- \u2500\u2500 Zoom (stateful) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <button class=\"tool-btn stateful zoom-btn\"\n (click)=\"toggleZoomPopover($event)\"\n title=\"Zoom\">\n <span class=\"zoom-label\">{{ zoomLabel }}</span>\n </button>\n\n <p-popover #zoomPopover [appendTo]=\"'body'\" styleClass=\"toolbar-popover\">\n <div class=\"zoom-popover\">\n <div class=\"zoom-slider-row\">\n <input class=\"zoom-slider\"\n type=\"range\"\n min=\"20\" max=\"100\" step=\"1\"\n [value]=\"sliderZoom\"\n (input)=\"onSliderChange($event)\">\n <span class=\"zoom-slider-value\">{{ zoomLabel }}</span>\n </div>\n <div class=\"popover-strip\">\n <button class=\"popover-option\"\n *ngFor=\"let z of zoomOptions\"\n [class.is-active]=\"z.value === activeZoom\"\n (click)=\"selectZoom(z.value)\">\n <span>{{ z.label }}</span>\n </button>\n </div>\n </div>\n </p-popover>\n\n <!-- \u2500\u2500 Responsive audit (A5) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <button class=\"tool-btn audit-toggle\"\n [class.is-open]=\"auditOpen\"\n [class.has-findings]=\"auditCount > 0\"\n type=\"button\"\n title=\"Responsive audit \u2014 what is broken at this width\"\n aria-label=\"Responsive audit\"\n [attr.aria-pressed]=\"auditOpen\"\n (click)=\"auditToggle.emit()\">\n <i class=\"ph-thin ph-stethoscope\" aria-hidden=\"true\"></i>\n @if (auditCount) {\n <span class=\"audit-count\">{{ auditCount }}</span>\n }\n </button>\n\n <span class=\"divider\"></span>\n\n <!-- \u2500\u2500 Save \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <button class=\"tool-btn save-btn\"\n [class.is-saving]=\"isSaving\"\n [disabled]=\"isSaving\"\n title=\"Save\"\n (click)=\"save.emit()\">\n <i class=\"ph-thin ph-floppy-disk\"></i>\n </button>\n\n <!-- \u2500\u2500 Preview \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <button class=\"tool-btn preview-btn\"\n title=\"Preview\"\n (click)=\"preview.emit()\">\n <i class=\"ph-thin ph-eye\"></i>\n </button>\n\n <span class=\"divider\"></span>\n\n <!-- \u2500\u2500 Publish / Unpublish \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <button class=\"tool-btn publish-btn\"\n [class.is-published]=\"isPublished\"\n [title]=\"isPublished ? 'Unpublish' : 'Publish'\"\n (click)=\"publishToggle.emit()\">\n <i [class]=\"isPublished ? 'ph-thin ph-cloud-slash' : 'ph-thin ph-cloud-arrow-up'\"></i>\n <span class=\"publish-label\">{{ isPublished ? 'Unpublish' : 'Publish' }}</span>\n </button>\n</div>\n", styles: [".builder-toolbar{display:flex;align-items:center;gap:.125rem}.divider{width:1px;height:1.25rem;background:var(--p-surface-200);margin:0 .375rem;flex-shrink:0}.tool-btn{display:flex;align-items:center;justify-content:center;width:2rem;height:2rem;border-radius:6px;border:none;background:transparent;color:var(--p-text-muted-color);cursor:pointer;font-size:1rem;transition:background .15s ease-out,color .15s ease-out}.tool-btn:hover:not(:disabled){background:var(--p-surface-50);color:var(--p-text-color)}.tool-btn:disabled{opacity:.35;cursor:default}.tool-btn.stateful{position:relative}.tool-btn.stateful:after{content:\"\";position:absolute;inset-block-end:3px;width:4px;height:4px;border-radius:50%;background:#cb9090}.audit-toggle{position:relative}.audit-toggle.is-open{background:var(--p-surface-100);color:#a05c5c}.audit-toggle.has-findings{color:#b8702f}.audit-count{position:absolute;inset-block-start:1px;inset-inline-end:1px;min-inline-size:13px;padding:0 3px;border-radius:20px;background:#d9924e;color:#fff;font-size:8.5px;line-height:13px;font-weight:600}.breakpoint-bar{display:flex;align-items:center;gap:1px;padding:2px;border-radius:8px;background:var(--p-surface-100)}.bp-btn{position:relative;display:flex;align-items:center;justify-content:center;width:1.875rem;height:1.75rem;border:1px solid transparent;border-radius:6px;background:transparent;color:var(--p-text-muted-color);font-size:.95rem;cursor:pointer;transition:background .15s ease-out,color .15s ease-out,border-color .15s ease-out}.bp-btn:hover{background:var(--p-surface-0);color:var(--p-text-color)}.bp-btn:focus-visible{outline:2px solid #cb9090;outline-offset:1px}.bp-btn.is-active{background:var(--p-surface-0);border-color:#cb9090;color:#a05c5c}.bp-icon--landscape{transform:rotate(90deg)}.bp-dot{position:absolute;inset-block-start:3px;inset-inline-end:3px;width:5px;height:5px;border-radius:50%;background:#d9924e}.zoom-btn{width:auto;border-radius:999px;padding:0 .5rem;min-width:2.5rem}.zoom-btn .zoom-label{font-size:.7rem;font-weight:500;letter-spacing:.02em}.width-btn{width:auto;border-radius:999px;padding:0 .5rem;min-width:2.8rem}.width-btn .width-label{font-size:.7rem;font-weight:500;letter-spacing:.02em}.save-btn{background:#cb9090;color:#fff}.save-btn:hover:not(:disabled){background:#b37373;color:#fff}.save-btn.is-saving{opacity:.7}.preview-btn{border:1px solid var(--p-surface-200)}.preview-btn:hover:not(:disabled){background:var(--p-surface-50);color:var(--p-text-color);border-color:var(--p-surface-100)}.publish-btn{width:auto;border-radius:999px;padding:0 .65rem;gap:.35rem;font-size:.75rem;font-weight:500;background:#22c55e;color:#fff;border:none}.publish-btn .publish-label{font-size:.72rem;letter-spacing:.02em}.publish-btn:hover:not(:disabled){background:color-mix(in srgb,#22c55e 85%,#000);color:#fff}.publish-btn.is-published{background:transparent;color:var(--p-text-muted-color);border:1px solid var(--p-surface-200)}.publish-btn.is-published:hover:not(:disabled){background:color-mix(in srgb,#ef4444 10%,transparent);color:#ef4444;border-color:color-mix(in srgb,#ef4444 40%,transparent)}::ng-deep .toolbar-popover .p-popover-content{padding:.25rem!important;background:var(--p-surface-0)!important;border:1px solid var(--p-surface-200)!important;border-radius:10px!important;box-shadow:0 4px 16px #0f172a1a!important}.popover-strip{display:flex;flex-direction:column;gap:2px;min-width:120px}.popover-option{display:flex;align-items:center;gap:.5rem;width:100%;padding:.4rem .65rem;border:none;background:transparent;color:var(--p-text-color);cursor:pointer;font-size:.8rem;border-radius:6px;transition:background .12s ease-out,color .12s ease-out;white-space:nowrap}.popover-option i{font-size:1rem;color:var(--p-text-muted-color)}.popover-option:hover{background:var(--p-surface-50)}.popover-option.is-active{background:#cb9090;color:#fff}.popover-option.is-active i{color:#fff}::ng-deep .toolbar-popover .zoom-popover{display:flex;flex-direction:column;gap:4px;padding:.15rem 0}::ng-deep .toolbar-popover .zoom-slider-row{display:flex;align-items:center;gap:.5rem;padding:.3rem .65rem .1rem}::ng-deep .toolbar-popover .zoom-slider{flex:1;-webkit-appearance:none;appearance:none;height:3px;border-radius:999px;background:var(--p-surface-200);outline:none;cursor:pointer}::ng-deep .toolbar-popover .zoom-slider::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;width:14px;height:14px;border-radius:50%;background:#cb9090;cursor:pointer;transition:transform .12s ease-out}::ng-deep .toolbar-popover .zoom-slider::-webkit-slider-thumb:hover{transform:scale(1.2)}::ng-deep .toolbar-popover .zoom-slider::-moz-range-thumb{width:14px;height:14px;border-radius:50%;border:none;background:#cb9090;cursor:pointer}::ng-deep .toolbar-popover .zoom-slider-value{font-size:.68rem;font-weight:500;color:var(--p-text-muted-color);min-width:2.4rem;text-align:end;letter-spacing:.02em}\n"], dependencies: [{ kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "component", type: i2$1.Popover, selector: "p-popover", inputs: ["ariaLabel", "ariaLabelledBy", "dismissable", "style", "styleClass", "appendTo", "autoZIndex", "ariaCloseLabel", "baseZIndex", "focusOnShow", "showTransitionOptions", "hideTransitionOptions"], outputs: ["onShow", "onHide"] }] }); }
|
|
4868
5079
|
}
|
|
4869
5080
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: ToolbarComponent, decorators: [{
|
|
4870
5081
|
type: Component,
|
|
4871
|
-
args: [{ selector: 'app-toolbar', standalone: false, template: "<div class=\"builder-toolbar\">\n <!-- \u2500\u2500 Mode switcher (stateful) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <button class=\"tool-btn stateful\"\n (click)=\"toggleModePopover($event)\"\n [title]=\"activeModeLabel\">\n <i [class]=\"activeModeIcon\"></i>\n </button>\n\n <p-popover #modePopover [appendTo]=\"'body'\" styleClass=\"toolbar-popover\">\n <div class=\"popover-strip\">\n <button class=\"popover-option\"\n [class.is-active]=\"activeMode === modes.Select\"\n (click)=\"selectMode(modes.Select)\">\n <i class=\"ph-thin ph-cursor\"></i>\n <span>Select</span>\n </button>\n <button class=\"popover-option\"\n [class.is-active]=\"activeMode === modes.Pan\"\n (click)=\"selectMode(modes.Pan)\">\n <i class=\"ph-thin ph-hand\"></i>\n <span>Pan</span>\n </button>\n <button class=\"popover-option\"\n [class.is-active]=\"activeMode === modes.Style\"\n (click)=\"selectMode(modes.Style)\">\n <i class=\"ph-thin ph-paint-brush-broad\"></i>\n <span>Style</span>\n </button>\n </div>\n </p-popover>\n\n <span class=\"divider\"></span>\n\n <!-- \u2500\u2500 Undo / Redo (action) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <button class=\"tool-btn\" title=\"Undo\" [disabled]=\"!canUndo\" (click)=\"undo.emit()\">\n <i class=\"ph-thin ph-arrow-counter-clockwise\"></i>\n </button>\n <button class=\"tool-btn\" title=\"Redo\" [disabled]=\"!canRedo\" (click)=\"redo.emit()\">\n <i class=\"ph-thin ph-arrow-clockwise\"></i>\n </button>\n\n <span class=\"divider\"></span>\n\n <!-- \u2500\u2500 Breakpoint bar \u2014 the EDITING SCOPE, not just a preview size \u2500\u2500 -->\n <div class=\"breakpoint-bar\" role=\"group\" aria-label=\"Breakpoint\">\n @for (bp of breakpointOptions; track bp.id) {\n <button type=\"button\"\n class=\"bp-btn\"\n [class.is-active]=\"activeBreakpoint === bp.id\"\n [attr.aria-pressed]=\"activeBreakpoint === bp.id\"\n [title]=\"bp.label + ' \u00B7 ' + bp.range\"\n [attr.aria-label]=\"bp.label\"\n (click)=\"selectBreakpoint(bp.id)\">\n <i [class]=\"bp.icon\" [class.bp-icon--landscape]=\"bp.landscape\" aria-hidden=\"true\"></i>\n @if (breakpointCounts[bp.id]) {\n <span class=\"bp-dot\" aria-hidden=\"true\"></span>\n }\n </button>\n }\n </div>\n\n <!-- \u2500\u2500 Screen width \u2014 Base only (any width above Laptop) \u2500\u2500 -->\n @if (activeBreakpoint === breakpointIds.Base) {\n <button class=\"tool-btn stateful width-btn\"\n (click)=\"toggleWidthPopover($event)\"\n title=\"Screen width\">\n <span class=\"width-label\">{{ activeScreenWidth }}</span>\n </button>\n }\n\n <p-popover #widthPopover [appendTo]=\"'body'\" styleClass=\"toolbar-popover\">\n <div class=\"popover-strip\">\n <button class=\"popover-option\"\n *ngFor=\"let w of screenWidthOptions\"\n [class.is-active]=\"activeScreenWidth === w.width\"\n (click)=\"selectScreenWidth(w.width)\">\n <span>{{ w.label }}</span>\n </button>\n </div>\n </p-popover>\n\n <!-- \u2500\u2500 Zoom (stateful) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <button class=\"tool-btn stateful zoom-btn\"\n (click)=\"toggleZoomPopover($event)\"\n title=\"Zoom\">\n <span class=\"zoom-label\">{{ zoomLabel }}</span>\n </button>\n\n <p-popover #zoomPopover [appendTo]=\"'body'\" styleClass=\"toolbar-popover\">\n <div class=\"zoom-popover\">\n <div class=\"zoom-slider-row\">\n <input class=\"zoom-slider\"\n type=\"range\"\n min=\"20\" max=\"100\" step=\"1\"\n [value]=\"sliderZoom\"\n (input)=\"onSliderChange($event)\">\n <span class=\"zoom-slider-value\">{{ zoomLabel }}</span>\n </div>\n <div class=\"popover-strip\">\n <button class=\"popover-option\"\n *ngFor=\"let z of zoomOptions\"\n [class.is-active]=\"z.value === activeZoom\"\n (click)=\"selectZoom(z.value)\">\n <span>{{ z.label }}</span>\n </button>\n </div>\n </div>\n </p-popover>\n\n <span class=\"divider\"></span>\n\n <!-- \u2500\u2500 Save \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <button class=\"tool-btn save-btn\"\n [class.is-saving]=\"isSaving\"\n [disabled]=\"isSaving\"\n title=\"Save\"\n (click)=\"save.emit()\">\n <i class=\"ph-thin ph-floppy-disk\"></i>\n </button>\n\n <!-- \u2500\u2500 Preview \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <button class=\"tool-btn preview-btn\"\n title=\"Preview\"\n (click)=\"preview.emit()\">\n <i class=\"ph-thin ph-eye\"></i>\n </button>\n\n <span class=\"divider\"></span>\n\n <!-- \u2500\u2500 Publish / Unpublish \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <button class=\"tool-btn publish-btn\"\n [class.is-published]=\"isPublished\"\n [title]=\"isPublished ? 'Unpublish' : 'Publish'\"\n (click)=\"publishToggle.emit()\">\n <i [class]=\"isPublished ? 'ph-thin ph-cloud-slash' : 'ph-thin ph-cloud-arrow-up'\"></i>\n <span class=\"publish-label\">{{ isPublished ? 'Unpublish' : 'Publish' }}</span>\n </button>\n</div>\n", styles: [".builder-toolbar{display:flex;align-items:center;gap:.125rem}.divider{width:1px;height:1.25rem;background:var(--p-surface-200);margin:0 .375rem;flex-shrink:0}.tool-btn{display:flex;align-items:center;justify-content:center;width:2rem;height:2rem;border-radius:6px;border:none;background:transparent;color:var(--p-text-muted-color);cursor:pointer;font-size:1rem;transition:background .15s ease-out,color .15s ease-out}.tool-btn:hover:not(:disabled){background:var(--p-surface-50);color:var(--p-text-color)}.tool-btn:disabled{opacity:.35;cursor:default}.tool-btn.stateful{position:relative}.tool-btn.stateful:after{content:\"\";position:absolute;inset-block-end:3px;width:4px;height:4px;border-radius:50%;background:#cb9090}.breakpoint-bar{display:flex;align-items:center;gap:1px;padding:2px;border-radius:8px;background:var(--p-surface-100)}.bp-btn{position:relative;display:flex;align-items:center;justify-content:center;width:1.875rem;height:1.75rem;border:1px solid transparent;border-radius:6px;background:transparent;color:var(--p-text-muted-color);font-size:.95rem;cursor:pointer;transition:background .15s ease-out,color .15s ease-out,border-color .15s ease-out}.bp-btn:hover{background:var(--p-surface-0);color:var(--p-text-color)}.bp-btn:focus-visible{outline:2px solid #cb9090;outline-offset:1px}.bp-btn.is-active{background:var(--p-surface-0);border-color:#cb9090;color:#a05c5c}.bp-icon--landscape{transform:rotate(90deg)}.bp-dot{position:absolute;inset-block-start:3px;inset-inline-end:3px;width:5px;height:5px;border-radius:50%;background:#d9924e}.zoom-btn{width:auto;border-radius:999px;padding:0 .5rem;min-width:2.5rem}.zoom-btn .zoom-label{font-size:.7rem;font-weight:500;letter-spacing:.02em}.width-btn{width:auto;border-radius:999px;padding:0 .5rem;min-width:2.8rem}.width-btn .width-label{font-size:.7rem;font-weight:500;letter-spacing:.02em}.save-btn{background:#cb9090;color:#fff}.save-btn:hover:not(:disabled){background:#b37373;color:#fff}.save-btn.is-saving{opacity:.7}.preview-btn{border:1px solid var(--p-surface-200)}.preview-btn:hover:not(:disabled){background:var(--p-surface-50);color:var(--p-text-color);border-color:var(--p-surface-100)}.publish-btn{width:auto;border-radius:999px;padding:0 .65rem;gap:.35rem;font-size:.75rem;font-weight:500;background:#22c55e;color:#fff;border:none}.publish-btn .publish-label{font-size:.72rem;letter-spacing:.02em}.publish-btn:hover:not(:disabled){background:color-mix(in srgb,#22c55e 85%,#000);color:#fff}.publish-btn.is-published{background:transparent;color:var(--p-text-muted-color);border:1px solid var(--p-surface-200)}.publish-btn.is-published:hover:not(:disabled){background:color-mix(in srgb,#ef4444 10%,transparent);color:#ef4444;border-color:color-mix(in srgb,#ef4444 40%,transparent)}::ng-deep .toolbar-popover .p-popover-content{padding:.25rem!important;background:var(--p-surface-0)!important;border:1px solid var(--p-surface-200)!important;border-radius:10px!important;box-shadow:0 4px 16px #0f172a1a!important}.popover-strip{display:flex;flex-direction:column;gap:2px;min-width:120px}.popover-option{display:flex;align-items:center;gap:.5rem;width:100%;padding:.4rem .65rem;border:none;background:transparent;color:var(--p-text-color);cursor:pointer;font-size:.8rem;border-radius:6px;transition:background .12s ease-out,color .12s ease-out;white-space:nowrap}.popover-option i{font-size:1rem;color:var(--p-text-muted-color)}.popover-option:hover{background:var(--p-surface-50)}.popover-option.is-active{background:#cb9090;color:#fff}.popover-option.is-active i{color:#fff}::ng-deep .toolbar-popover .zoom-popover{display:flex;flex-direction:column;gap:4px;padding:.15rem 0}::ng-deep .toolbar-popover .zoom-slider-row{display:flex;align-items:center;gap:.5rem;padding:.3rem .65rem .1rem}::ng-deep .toolbar-popover .zoom-slider{flex:1;-webkit-appearance:none;appearance:none;height:3px;border-radius:999px;background:var(--p-surface-200);outline:none;cursor:pointer}::ng-deep .toolbar-popover .zoom-slider::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;width:14px;height:14px;border-radius:50%;background:#cb9090;cursor:pointer;transition:transform .12s ease-out}::ng-deep .toolbar-popover .zoom-slider::-webkit-slider-thumb:hover{transform:scale(1.2)}::ng-deep .toolbar-popover .zoom-slider::-moz-range-thumb{width:14px;height:14px;border-radius:50%;border:none;background:#cb9090;cursor:pointer}::ng-deep .toolbar-popover .zoom-slider-value{font-size:.68rem;font-weight:500;color:var(--p-text-muted-color);min-width:2.4rem;text-align:end;letter-spacing:.02em}\n"] }]
|
|
5082
|
+
args: [{ selector: 'app-toolbar', standalone: false, template: "<div class=\"builder-toolbar\">\n <!-- \u2500\u2500 Mode switcher (stateful) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <button class=\"tool-btn stateful\"\n (click)=\"toggleModePopover($event)\"\n [title]=\"activeModeLabel\">\n <i [class]=\"activeModeIcon\"></i>\n </button>\n\n <p-popover #modePopover [appendTo]=\"'body'\" styleClass=\"toolbar-popover\">\n <div class=\"popover-strip\">\n <button class=\"popover-option\"\n [class.is-active]=\"activeMode === modes.Select\"\n (click)=\"selectMode(modes.Select)\">\n <i class=\"ph-thin ph-cursor\"></i>\n <span>Select</span>\n </button>\n <button class=\"popover-option\"\n [class.is-active]=\"activeMode === modes.Pan\"\n (click)=\"selectMode(modes.Pan)\">\n <i class=\"ph-thin ph-hand\"></i>\n <span>Pan</span>\n </button>\n <button class=\"popover-option\"\n [class.is-active]=\"activeMode === modes.Style\"\n (click)=\"selectMode(modes.Style)\">\n <i class=\"ph-thin ph-paint-brush-broad\"></i>\n <span>Style</span>\n </button>\n </div>\n </p-popover>\n\n <span class=\"divider\"></span>\n\n <!-- \u2500\u2500 Undo / Redo (action) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <button class=\"tool-btn\" title=\"Undo\" [disabled]=\"!canUndo\" (click)=\"undo.emit()\">\n <i class=\"ph-thin ph-arrow-counter-clockwise\"></i>\n </button>\n <button class=\"tool-btn\" title=\"Redo\" [disabled]=\"!canRedo\" (click)=\"redo.emit()\">\n <i class=\"ph-thin ph-arrow-clockwise\"></i>\n </button>\n\n <span class=\"divider\"></span>\n\n <!-- \u2500\u2500 Breakpoint bar \u2014 the EDITING SCOPE, not just a preview size \u2500\u2500 -->\n <div class=\"breakpoint-bar\" role=\"group\" aria-label=\"Breakpoint\">\n @for (bp of breakpointOptions; track bp.id) {\n <button type=\"button\"\n class=\"bp-btn\"\n [class.is-active]=\"activeBreakpoint === bp.id\"\n [attr.aria-pressed]=\"activeBreakpoint === bp.id\"\n [title]=\"bp.label + ' \u00B7 ' + bp.range\"\n [attr.aria-label]=\"bp.label\"\n (click)=\"selectBreakpoint(bp.id)\">\n <i [class]=\"bp.icon\" [class.bp-icon--landscape]=\"bp.landscape\" aria-hidden=\"true\"></i>\n @if (breakpointCounts[bp.id]) {\n <span class=\"bp-dot\" aria-hidden=\"true\"></span>\n }\n </button>\n }\n </div>\n\n <!-- \u2500\u2500 Screen width \u2014 Base only (any width above Laptop) \u2500\u2500 -->\n @if (activeBreakpoint === breakpointIds.Base) {\n <button class=\"tool-btn stateful width-btn\"\n (click)=\"toggleWidthPopover($event)\"\n title=\"Screen width\">\n <span class=\"width-label\">{{ activeScreenWidth }}</span>\n </button>\n }\n\n <p-popover #widthPopover [appendTo]=\"'body'\" styleClass=\"toolbar-popover\">\n <div class=\"popover-strip\">\n <button class=\"popover-option\"\n *ngFor=\"let w of screenWidthOptions\"\n [class.is-active]=\"activeScreenWidth === w.width\"\n (click)=\"selectScreenWidth(w.width)\">\n <span>{{ w.label }}</span>\n </button>\n </div>\n </p-popover>\n\n <!-- \u2500\u2500 Zoom (stateful) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <button class=\"tool-btn stateful zoom-btn\"\n (click)=\"toggleZoomPopover($event)\"\n title=\"Zoom\">\n <span class=\"zoom-label\">{{ zoomLabel }}</span>\n </button>\n\n <p-popover #zoomPopover [appendTo]=\"'body'\" styleClass=\"toolbar-popover\">\n <div class=\"zoom-popover\">\n <div class=\"zoom-slider-row\">\n <input class=\"zoom-slider\"\n type=\"range\"\n min=\"20\" max=\"100\" step=\"1\"\n [value]=\"sliderZoom\"\n (input)=\"onSliderChange($event)\">\n <span class=\"zoom-slider-value\">{{ zoomLabel }}</span>\n </div>\n <div class=\"popover-strip\">\n <button class=\"popover-option\"\n *ngFor=\"let z of zoomOptions\"\n [class.is-active]=\"z.value === activeZoom\"\n (click)=\"selectZoom(z.value)\">\n <span>{{ z.label }}</span>\n </button>\n </div>\n </div>\n </p-popover>\n\n <!-- \u2500\u2500 Responsive audit (A5) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <button class=\"tool-btn audit-toggle\"\n [class.is-open]=\"auditOpen\"\n [class.has-findings]=\"auditCount > 0\"\n type=\"button\"\n title=\"Responsive audit \u2014 what is broken at this width\"\n aria-label=\"Responsive audit\"\n [attr.aria-pressed]=\"auditOpen\"\n (click)=\"auditToggle.emit()\">\n <i class=\"ph-thin ph-stethoscope\" aria-hidden=\"true\"></i>\n @if (auditCount) {\n <span class=\"audit-count\">{{ auditCount }}</span>\n }\n </button>\n\n <span class=\"divider\"></span>\n\n <!-- \u2500\u2500 Save \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <button class=\"tool-btn save-btn\"\n [class.is-saving]=\"isSaving\"\n [disabled]=\"isSaving\"\n title=\"Save\"\n (click)=\"save.emit()\">\n <i class=\"ph-thin ph-floppy-disk\"></i>\n </button>\n\n <!-- \u2500\u2500 Preview \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <button class=\"tool-btn preview-btn\"\n title=\"Preview\"\n (click)=\"preview.emit()\">\n <i class=\"ph-thin ph-eye\"></i>\n </button>\n\n <span class=\"divider\"></span>\n\n <!-- \u2500\u2500 Publish / Unpublish \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <button class=\"tool-btn publish-btn\"\n [class.is-published]=\"isPublished\"\n [title]=\"isPublished ? 'Unpublish' : 'Publish'\"\n (click)=\"publishToggle.emit()\">\n <i [class]=\"isPublished ? 'ph-thin ph-cloud-slash' : 'ph-thin ph-cloud-arrow-up'\"></i>\n <span class=\"publish-label\">{{ isPublished ? 'Unpublish' : 'Publish' }}</span>\n </button>\n</div>\n", styles: [".builder-toolbar{display:flex;align-items:center;gap:.125rem}.divider{width:1px;height:1.25rem;background:var(--p-surface-200);margin:0 .375rem;flex-shrink:0}.tool-btn{display:flex;align-items:center;justify-content:center;width:2rem;height:2rem;border-radius:6px;border:none;background:transparent;color:var(--p-text-muted-color);cursor:pointer;font-size:1rem;transition:background .15s ease-out,color .15s ease-out}.tool-btn:hover:not(:disabled){background:var(--p-surface-50);color:var(--p-text-color)}.tool-btn:disabled{opacity:.35;cursor:default}.tool-btn.stateful{position:relative}.tool-btn.stateful:after{content:\"\";position:absolute;inset-block-end:3px;width:4px;height:4px;border-radius:50%;background:#cb9090}.audit-toggle{position:relative}.audit-toggle.is-open{background:var(--p-surface-100);color:#a05c5c}.audit-toggle.has-findings{color:#b8702f}.audit-count{position:absolute;inset-block-start:1px;inset-inline-end:1px;min-inline-size:13px;padding:0 3px;border-radius:20px;background:#d9924e;color:#fff;font-size:8.5px;line-height:13px;font-weight:600}.breakpoint-bar{display:flex;align-items:center;gap:1px;padding:2px;border-radius:8px;background:var(--p-surface-100)}.bp-btn{position:relative;display:flex;align-items:center;justify-content:center;width:1.875rem;height:1.75rem;border:1px solid transparent;border-radius:6px;background:transparent;color:var(--p-text-muted-color);font-size:.95rem;cursor:pointer;transition:background .15s ease-out,color .15s ease-out,border-color .15s ease-out}.bp-btn:hover{background:var(--p-surface-0);color:var(--p-text-color)}.bp-btn:focus-visible{outline:2px solid #cb9090;outline-offset:1px}.bp-btn.is-active{background:var(--p-surface-0);border-color:#cb9090;color:#a05c5c}.bp-icon--landscape{transform:rotate(90deg)}.bp-dot{position:absolute;inset-block-start:3px;inset-inline-end:3px;width:5px;height:5px;border-radius:50%;background:#d9924e}.zoom-btn{width:auto;border-radius:999px;padding:0 .5rem;min-width:2.5rem}.zoom-btn .zoom-label{font-size:.7rem;font-weight:500;letter-spacing:.02em}.width-btn{width:auto;border-radius:999px;padding:0 .5rem;min-width:2.8rem}.width-btn .width-label{font-size:.7rem;font-weight:500;letter-spacing:.02em}.save-btn{background:#cb9090;color:#fff}.save-btn:hover:not(:disabled){background:#b37373;color:#fff}.save-btn.is-saving{opacity:.7}.preview-btn{border:1px solid var(--p-surface-200)}.preview-btn:hover:not(:disabled){background:var(--p-surface-50);color:var(--p-text-color);border-color:var(--p-surface-100)}.publish-btn{width:auto;border-radius:999px;padding:0 .65rem;gap:.35rem;font-size:.75rem;font-weight:500;background:#22c55e;color:#fff;border:none}.publish-btn .publish-label{font-size:.72rem;letter-spacing:.02em}.publish-btn:hover:not(:disabled){background:color-mix(in srgb,#22c55e 85%,#000);color:#fff}.publish-btn.is-published{background:transparent;color:var(--p-text-muted-color);border:1px solid var(--p-surface-200)}.publish-btn.is-published:hover:not(:disabled){background:color-mix(in srgb,#ef4444 10%,transparent);color:#ef4444;border-color:color-mix(in srgb,#ef4444 40%,transparent)}::ng-deep .toolbar-popover .p-popover-content{padding:.25rem!important;background:var(--p-surface-0)!important;border:1px solid var(--p-surface-200)!important;border-radius:10px!important;box-shadow:0 4px 16px #0f172a1a!important}.popover-strip{display:flex;flex-direction:column;gap:2px;min-width:120px}.popover-option{display:flex;align-items:center;gap:.5rem;width:100%;padding:.4rem .65rem;border:none;background:transparent;color:var(--p-text-color);cursor:pointer;font-size:.8rem;border-radius:6px;transition:background .12s ease-out,color .12s ease-out;white-space:nowrap}.popover-option i{font-size:1rem;color:var(--p-text-muted-color)}.popover-option:hover{background:var(--p-surface-50)}.popover-option.is-active{background:#cb9090;color:#fff}.popover-option.is-active i{color:#fff}::ng-deep .toolbar-popover .zoom-popover{display:flex;flex-direction:column;gap:4px;padding:.15rem 0}::ng-deep .toolbar-popover .zoom-slider-row{display:flex;align-items:center;gap:.5rem;padding:.3rem .65rem .1rem}::ng-deep .toolbar-popover .zoom-slider{flex:1;-webkit-appearance:none;appearance:none;height:3px;border-radius:999px;background:var(--p-surface-200);outline:none;cursor:pointer}::ng-deep .toolbar-popover .zoom-slider::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;width:14px;height:14px;border-radius:50%;background:#cb9090;cursor:pointer;transition:transform .12s ease-out}::ng-deep .toolbar-popover .zoom-slider::-webkit-slider-thumb:hover{transform:scale(1.2)}::ng-deep .toolbar-popover .zoom-slider::-moz-range-thumb{width:14px;height:14px;border-radius:50%;border:none;background:#cb9090;cursor:pointer}::ng-deep .toolbar-popover .zoom-slider-value{font-size:.68rem;font-weight:500;color:var(--p-text-muted-color);min-width:2.4rem;text-align:end;letter-spacing:.02em}\n"] }]
|
|
4872
5083
|
}], propDecorators: { activeMode: [{
|
|
4873
5084
|
type: Input
|
|
4874
5085
|
}], activeBreakpoint: [{
|
|
4875
5086
|
type: Input
|
|
4876
5087
|
}], breakpointCounts: [{
|
|
4877
5088
|
type: Input
|
|
5089
|
+
}], auditCount: [{
|
|
5090
|
+
type: Input
|
|
5091
|
+
}], auditOpen: [{
|
|
5092
|
+
type: Input
|
|
4878
5093
|
}], activeZoom: [{
|
|
4879
5094
|
type: Input
|
|
4880
5095
|
}], activeScreenWidth: [{
|
|
@@ -4891,6 +5106,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImpo
|
|
|
4891
5106
|
type: Output
|
|
4892
5107
|
}], breakpointChange: [{
|
|
4893
5108
|
type: Output
|
|
5109
|
+
}], auditToggle: [{
|
|
5110
|
+
type: Output
|
|
4894
5111
|
}], zoomChange: [{
|
|
4895
5112
|
type: Output
|
|
4896
5113
|
}], screenWidthChange: [{
|
|
@@ -5194,7 +5411,167 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImpo
|
|
|
5194
5411
|
type: Output
|
|
5195
5412
|
}] } });
|
|
5196
5413
|
|
|
5414
|
+
/**
|
|
5415
|
+
* The responsive audit list (A5) — what is broken at the width being edited, worst first.
|
|
5416
|
+
* Presentational: the builder runs the audit (it owns the tree and the canvas measurements) and
|
|
5417
|
+
* applies the fixes; this only renders and emits.
|
|
5418
|
+
*/
|
|
5419
|
+
class AuditPanelComponent {
|
|
5420
|
+
constructor() {
|
|
5421
|
+
this.findings = [];
|
|
5422
|
+
/** Name of the breakpoint being audited, for the header. */
|
|
5423
|
+
this.breakpointLabel = '';
|
|
5424
|
+
this.running = false;
|
|
5425
|
+
this.select = new EventEmitter();
|
|
5426
|
+
this.fix = new EventEmitter();
|
|
5427
|
+
this.fixAll = new EventEmitter();
|
|
5428
|
+
this.rerun = new EventEmitter();
|
|
5429
|
+
this.closePanel = new EventEmitter();
|
|
5430
|
+
this.severities = AuditSeverity;
|
|
5431
|
+
}
|
|
5432
|
+
trackFinding(_index, finding) {
|
|
5433
|
+
return `${finding.nodeId}:${finding.code}`;
|
|
5434
|
+
}
|
|
5435
|
+
/** Findings that carry a safe auto-fix — what "Fix all" would apply. */
|
|
5436
|
+
get fixableCount() {
|
|
5437
|
+
return this.findings.filter(f => !!f.fix).length;
|
|
5438
|
+
}
|
|
5439
|
+
iconFor(severity) {
|
|
5440
|
+
return severity === AuditSeverity.Info ? 'ph-thin ph-info' : 'ph-thin ph-warning';
|
|
5441
|
+
}
|
|
5442
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: AuditPanelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
5443
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.31", type: AuditPanelComponent, isStandalone: false, selector: "app-audit-panel", inputs: { findings: "findings", breakpointLabel: "breakpointLabel", running: "running" }, outputs: { select: "select", fix: "fix", fixAll: "fixAll", rerun: "rerun", closePanel: "closePanel" }, ngImport: i0, template: "<div class=\"audit-panel\" role=\"dialog\" aria-label=\"Responsive audit\">\n <header class=\"audit-head\">\n <span class=\"audit-title\">Responsive audit</span>\n <span class=\"audit-scope\">{{ breakpointLabel }}</span>\n <span class=\"audit-spacer\"></span>\n <button type=\"button\" class=\"audit-link\" [disabled]=\"running\" (click)=\"rerun.emit()\">Re-run</button>\n <button type=\"button\" class=\"audit-close\" aria-label=\"Close audit\" (click)=\"closePanel.emit()\">\n <i class=\"ph-thin ph-x\" aria-hidden=\"true\"></i>\n </button>\n </header>\n\n @if (running) {\n <p class=\"audit-empty\">Measuring\u2026</p>\n } @else if (!findings.length) {\n <p class=\"audit-empty audit-empty--clean\">\n <i class=\"ph-thin ph-check-circle\" aria-hidden=\"true\"></i>\n Nothing to fix at this width.\n </p>\n } @else {\n <div class=\"audit-actions\">\n <span>{{ findings.length }} to look at</span>\n @if (fixableCount) {\n <button type=\"button\" class=\"audit-link\" (click)=\"fixAll.emit()\">Fix {{ fixableCount }} automatically</button>\n }\n </div>\n <ul class=\"audit-list\">\n @for (finding of findings; track trackFinding($index, finding)) {\n <li class=\"audit-item\" [class.is-error]=\"finding.severity === severities.Error\">\n <i class=\"audit-icon\" [class]=\"iconFor(finding.severity)\" aria-hidden=\"true\"></i>\n <div class=\"audit-body\">\n <span class=\"audit-node\">{{ finding.nodeName }}</span>\n <span class=\"audit-message\">{{ finding.message }}</span>\n </div>\n <div class=\"audit-buttons\">\n @if (finding.fix) {\n <button type=\"button\" class=\"audit-btn audit-btn--fix\" (click)=\"fix.emit(finding)\">Fix</button>\n }\n <button type=\"button\" class=\"audit-btn\" (click)=\"select.emit(finding)\">Select</button>\n </div>\n </li>\n }\n </ul>\n }\n</div>\n", styles: [".audit-panel{display:flex;flex-direction:column;max-block-size:60vh;inline-size:340px;border:1px solid var(--p-content-border-color);border-radius:10px;background:var(--p-content-background);box-shadow:0 8px 28px #00000029;overflow:hidden}.audit-head{display:flex;align-items:center;gap:6px;padding:9px 11px;border-block-end:1px solid var(--p-content-border-color)}.audit-title{font-size:12px;font-weight:600}.audit-scope{font-size:10px;color:var(--p-text-muted-color)}.audit-spacer{flex:1}.audit-link{padding:0;border:none;background:none;font-size:10.5px;color:#a05c5c;cursor:pointer}.audit-link:hover:not(:disabled){text-decoration:underline}.audit-link:disabled{opacity:.5;pointer-events:none}.audit-link:focus-visible{outline:2px solid #cb9090;outline-offset:2px}.audit-close{display:flex;padding:2px;border:none;background:none;color:var(--p-text-muted-color);cursor:pointer}.audit-close:hover{color:var(--p-text-color)}.audit-empty{margin:0;padding:18px 12px;text-align:center;font-size:11px;color:var(--p-text-muted-color)}.audit-empty--clean{display:flex;align-items:center;justify-content:center;gap:6px;color:#4f8f6d}.audit-empty--clean i{font-size:15px}.audit-actions{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:7px 11px;background:var(--p-surface-50);font-size:10.5px;color:var(--p-text-muted-color)}.audit-list{margin:0;padding:0;list-style:none;overflow-y:auto}.audit-item{display:flex;align-items:flex-start;gap:8px;padding:9px 11px;border-block-end:1px solid var(--p-content-border-color)}.audit-item:last-child{border-block-end:none}.audit-icon{flex:none;margin-block-start:1px;font-size:14px;color:#d9924e}.is-error .audit-icon{color:#c0554f}.audit-body{flex:1;min-inline-size:0}.audit-node{display:block;font-size:11px;font-weight:600;color:var(--p-text-color)}.audit-message{display:block;margin-block-start:2px;font-size:10px;line-height:1.45;color:var(--p-text-muted-color)}.audit-buttons{display:flex;flex:none;flex-direction:column;gap:4px}.audit-btn{padding:3px 9px;border:1px solid var(--p-content-border-color);border-radius:6px;background:transparent;color:var(--p-text-muted-color);font-size:10px;cursor:pointer;white-space:nowrap}.audit-btn:hover{border-color:#cb9090;color:#a05c5c}.audit-btn:focus-visible{outline:2px solid #cb9090;outline-offset:1px}.audit-btn--fix{background:#a05c5c;border-color:#a05c5c;color:#fff}.audit-btn--fix:hover{background:#8d4f4f;color:#fff}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
5444
|
+
}
|
|
5445
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: AuditPanelComponent, decorators: [{
|
|
5446
|
+
type: Component,
|
|
5447
|
+
args: [{ selector: 'app-audit-panel', changeDetection: ChangeDetectionStrategy.OnPush, standalone: false, template: "<div class=\"audit-panel\" role=\"dialog\" aria-label=\"Responsive audit\">\n <header class=\"audit-head\">\n <span class=\"audit-title\">Responsive audit</span>\n <span class=\"audit-scope\">{{ breakpointLabel }}</span>\n <span class=\"audit-spacer\"></span>\n <button type=\"button\" class=\"audit-link\" [disabled]=\"running\" (click)=\"rerun.emit()\">Re-run</button>\n <button type=\"button\" class=\"audit-close\" aria-label=\"Close audit\" (click)=\"closePanel.emit()\">\n <i class=\"ph-thin ph-x\" aria-hidden=\"true\"></i>\n </button>\n </header>\n\n @if (running) {\n <p class=\"audit-empty\">Measuring\u2026</p>\n } @else if (!findings.length) {\n <p class=\"audit-empty audit-empty--clean\">\n <i class=\"ph-thin ph-check-circle\" aria-hidden=\"true\"></i>\n Nothing to fix at this width.\n </p>\n } @else {\n <div class=\"audit-actions\">\n <span>{{ findings.length }} to look at</span>\n @if (fixableCount) {\n <button type=\"button\" class=\"audit-link\" (click)=\"fixAll.emit()\">Fix {{ fixableCount }} automatically</button>\n }\n </div>\n <ul class=\"audit-list\">\n @for (finding of findings; track trackFinding($index, finding)) {\n <li class=\"audit-item\" [class.is-error]=\"finding.severity === severities.Error\">\n <i class=\"audit-icon\" [class]=\"iconFor(finding.severity)\" aria-hidden=\"true\"></i>\n <div class=\"audit-body\">\n <span class=\"audit-node\">{{ finding.nodeName }}</span>\n <span class=\"audit-message\">{{ finding.message }}</span>\n </div>\n <div class=\"audit-buttons\">\n @if (finding.fix) {\n <button type=\"button\" class=\"audit-btn audit-btn--fix\" (click)=\"fix.emit(finding)\">Fix</button>\n }\n <button type=\"button\" class=\"audit-btn\" (click)=\"select.emit(finding)\">Select</button>\n </div>\n </li>\n }\n </ul>\n }\n</div>\n", styles: [".audit-panel{display:flex;flex-direction:column;max-block-size:60vh;inline-size:340px;border:1px solid var(--p-content-border-color);border-radius:10px;background:var(--p-content-background);box-shadow:0 8px 28px #00000029;overflow:hidden}.audit-head{display:flex;align-items:center;gap:6px;padding:9px 11px;border-block-end:1px solid var(--p-content-border-color)}.audit-title{font-size:12px;font-weight:600}.audit-scope{font-size:10px;color:var(--p-text-muted-color)}.audit-spacer{flex:1}.audit-link{padding:0;border:none;background:none;font-size:10.5px;color:#a05c5c;cursor:pointer}.audit-link:hover:not(:disabled){text-decoration:underline}.audit-link:disabled{opacity:.5;pointer-events:none}.audit-link:focus-visible{outline:2px solid #cb9090;outline-offset:2px}.audit-close{display:flex;padding:2px;border:none;background:none;color:var(--p-text-muted-color);cursor:pointer}.audit-close:hover{color:var(--p-text-color)}.audit-empty{margin:0;padding:18px 12px;text-align:center;font-size:11px;color:var(--p-text-muted-color)}.audit-empty--clean{display:flex;align-items:center;justify-content:center;gap:6px;color:#4f8f6d}.audit-empty--clean i{font-size:15px}.audit-actions{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:7px 11px;background:var(--p-surface-50);font-size:10.5px;color:var(--p-text-muted-color)}.audit-list{margin:0;padding:0;list-style:none;overflow-y:auto}.audit-item{display:flex;align-items:flex-start;gap:8px;padding:9px 11px;border-block-end:1px solid var(--p-content-border-color)}.audit-item:last-child{border-block-end:none}.audit-icon{flex:none;margin-block-start:1px;font-size:14px;color:#d9924e}.is-error .audit-icon{color:#c0554f}.audit-body{flex:1;min-inline-size:0}.audit-node{display:block;font-size:11px;font-weight:600;color:var(--p-text-color)}.audit-message{display:block;margin-block-start:2px;font-size:10px;line-height:1.45;color:var(--p-text-muted-color)}.audit-buttons{display:flex;flex:none;flex-direction:column;gap:4px}.audit-btn{padding:3px 9px;border:1px solid var(--p-content-border-color);border-radius:6px;background:transparent;color:var(--p-text-muted-color);font-size:10px;cursor:pointer;white-space:nowrap}.audit-btn:hover{border-color:#cb9090;color:#a05c5c}.audit-btn:focus-visible{outline:2px solid #cb9090;outline-offset:1px}.audit-btn--fix{background:#a05c5c;border-color:#a05c5c;color:#fff}.audit-btn--fix:hover{background:#8d4f4f;color:#fff}\n"] }]
|
|
5448
|
+
}], propDecorators: { findings: [{
|
|
5449
|
+
type: Input
|
|
5450
|
+
}], breakpointLabel: [{
|
|
5451
|
+
type: Input
|
|
5452
|
+
}], running: [{
|
|
5453
|
+
type: Input
|
|
5454
|
+
}], select: [{
|
|
5455
|
+
type: Output
|
|
5456
|
+
}], fix: [{
|
|
5457
|
+
type: Output
|
|
5458
|
+
}], fixAll: [{
|
|
5459
|
+
type: Output
|
|
5460
|
+
}], rerun: [{
|
|
5461
|
+
type: Output
|
|
5462
|
+
}], closePanel: [{
|
|
5463
|
+
type: Output
|
|
5464
|
+
}] } });
|
|
5465
|
+
|
|
5197
5466
|
class BuilderComponent {
|
|
5467
|
+
get auditBreakpointLabel() {
|
|
5468
|
+
return breakpointDef(this.activeBreakpoint)?.label ?? '';
|
|
5469
|
+
}
|
|
5470
|
+
toggleAudit() {
|
|
5471
|
+
this.auditOpen = !this.auditOpen;
|
|
5472
|
+
if (this.auditOpen)
|
|
5473
|
+
this.runAudit();
|
|
5474
|
+
}
|
|
5475
|
+
/**
|
|
5476
|
+
* Audit every rendered node at the width being edited: resolve its styles for this breakpoint
|
|
5477
|
+
* and measure it ONCE (on demand — never on the animation path). Measurements are divided by the
|
|
5478
|
+
* canvas scale, so findings are in CSS px whatever the zoom. The rules are pure — see
|
|
5479
|
+
* `utils/responsive-audit.util.ts`.
|
|
5480
|
+
*/
|
|
5481
|
+
runAudit() {
|
|
5482
|
+
this.auditRunning = true;
|
|
5483
|
+
const scale = this.viewportService.getScale() || 1;
|
|
5484
|
+
const width = this.viewportService.getState().width;
|
|
5485
|
+
const nodes = [];
|
|
5486
|
+
const visit = (list) => {
|
|
5487
|
+
for (const node of list) {
|
|
5488
|
+
const element = this.overlayService.getNodeRef(node)?.element;
|
|
5489
|
+
if (element && node.id) {
|
|
5490
|
+
const rect = element.getBoundingClientRect();
|
|
5491
|
+
nodes.push({
|
|
5492
|
+
id: node.id,
|
|
5493
|
+
type: node.type,
|
|
5494
|
+
label: node.label,
|
|
5495
|
+
styleProps: resolveStyleProps(this.baseStyleMapOf(node), node.responsive, this.activeBreakpoint),
|
|
5496
|
+
width: rect.width / scale,
|
|
5497
|
+
height: rect.height / scale,
|
|
5498
|
+
});
|
|
5499
|
+
}
|
|
5500
|
+
if (node.children?.length)
|
|
5501
|
+
visit(node.children);
|
|
5502
|
+
}
|
|
5503
|
+
};
|
|
5504
|
+
visit(this.layout);
|
|
5505
|
+
visit(this.globalElements);
|
|
5506
|
+
this.auditFindings = auditNodes(nodes, width);
|
|
5507
|
+
this.auditRunning = false;
|
|
5508
|
+
this.cdr.detectChanges();
|
|
5509
|
+
}
|
|
5510
|
+
/** Apply one finding's fix into the current breakpoint's layer (or the base styles at Base). */
|
|
5511
|
+
applyAuditFix(finding) {
|
|
5512
|
+
const node = this.findNodeById(finding.nodeId, this.layout) ?? this.findNodeById(finding.nodeId, this.globalElements);
|
|
5513
|
+
if (!node || !finding.fix)
|
|
5514
|
+
return;
|
|
5515
|
+
if (this.activeBreakpoint === Breakpoint.Base) {
|
|
5516
|
+
for (const [name, value] of Object.entries(finding.fix)) {
|
|
5517
|
+
for (const group of node.styleTraits ?? []) {
|
|
5518
|
+
const trait = group.traits.find(t => t.name === name);
|
|
5519
|
+
if (trait)
|
|
5520
|
+
trait.default = value;
|
|
5521
|
+
}
|
|
5522
|
+
}
|
|
5523
|
+
}
|
|
5524
|
+
else {
|
|
5525
|
+
let responsive = node.responsive;
|
|
5526
|
+
for (const [name, value] of Object.entries(finding.fix)) {
|
|
5527
|
+
responsive = setOverride(responsive, this.activeBreakpoint, name, value);
|
|
5528
|
+
}
|
|
5529
|
+
node.responsive = responsive;
|
|
5530
|
+
}
|
|
5531
|
+
const cssId = node.styleId ?? node.id;
|
|
5532
|
+
if (cssId) {
|
|
5533
|
+
const base = this.baseStyleMapOf(node);
|
|
5534
|
+
this.styleRegistry.upsert(cssId, composeVirtualTraits(base));
|
|
5535
|
+
this.styleRegistry.upsertResponsive(cssId, base, node.stateStyles, node.responsive);
|
|
5536
|
+
}
|
|
5537
|
+
this.panelEventService.emit(PanelEventTypes.LAYOUT_CHANGED, this.layout);
|
|
5538
|
+
this.runAudit();
|
|
5539
|
+
}
|
|
5540
|
+
/** Apply every fixable finding, then re-measure. */
|
|
5541
|
+
applyAllAuditFixes() {
|
|
5542
|
+
for (const finding of this.auditFindings.filter(f => !!f.fix))
|
|
5543
|
+
this.applyAuditFix(finding);
|
|
5544
|
+
}
|
|
5545
|
+
/** Select the node a finding is about. */
|
|
5546
|
+
selectAuditNode(finding) {
|
|
5547
|
+
const node = this.findNodeById(finding.nodeId, this.layout) ?? this.findNodeById(finding.nodeId, this.globalElements);
|
|
5548
|
+
const ref = node && this.overlayService.getNodeRef(node);
|
|
5549
|
+
if (!node || !ref)
|
|
5550
|
+
return;
|
|
5551
|
+
this.overlayService.setSelect(ref.element, node.type, this.activeMode, node, ref.componentRef);
|
|
5552
|
+
this._emitElementSelect(node, ref.componentRef);
|
|
5553
|
+
}
|
|
5554
|
+
/** A node's full base style map, flattened from its styleTraits. */
|
|
5555
|
+
baseStyleMapOf(node) {
|
|
5556
|
+
const styles = {};
|
|
5557
|
+
for (const group of node.styleTraits ?? []) {
|
|
5558
|
+
for (const trait of group.traits) {
|
|
5559
|
+
if (trait.name !== undefined && trait.default !== undefined)
|
|
5560
|
+
styles[trait.name] = trait.default;
|
|
5561
|
+
}
|
|
5562
|
+
}
|
|
5563
|
+
return styles;
|
|
5564
|
+
}
|
|
5565
|
+
findNodeById(id, list) {
|
|
5566
|
+
for (const node of list) {
|
|
5567
|
+
if (node.id === id)
|
|
5568
|
+
return node;
|
|
5569
|
+
const deeper = node.children?.length ? this.findNodeById(id, node.children) : null;
|
|
5570
|
+
if (deeper)
|
|
5571
|
+
return deeper;
|
|
5572
|
+
}
|
|
5573
|
+
return null;
|
|
5574
|
+
}
|
|
5198
5575
|
// Type-safe event forwarding for toolbar outputs
|
|
5199
5576
|
handleToolbarModeChange(mode) {
|
|
5200
5577
|
this.onModeChange(mode);
|
|
@@ -5256,7 +5633,7 @@ class BuilderComponent {
|
|
|
5256
5633
|
get canvasMinHeight() {
|
|
5257
5634
|
return this.viewportService.getState().height;
|
|
5258
5635
|
}
|
|
5259
|
-
constructor(registry, overlayService, panelEventService, dragEngine, dsRegistry, viewportService, interactionEngine, interactionPresetRegistry, clipboard, symbolRegistry, cdr, appRef, moveableCanvas, selectoCanvas) {
|
|
5636
|
+
constructor(registry, overlayService, panelEventService, dragEngine, dsRegistry, viewportService, interactionEngine, interactionPresetRegistry, clipboard, symbolRegistry, cdr, appRef, moveableCanvas, selectoCanvas, styleRegistry) {
|
|
5260
5637
|
this.registry = registry;
|
|
5261
5638
|
this.overlayService = overlayService;
|
|
5262
5639
|
this.panelEventService = panelEventService;
|
|
@@ -5271,6 +5648,7 @@ class BuilderComponent {
|
|
|
5271
5648
|
this.appRef = appRef;
|
|
5272
5649
|
this.moveableCanvas = moveableCanvas;
|
|
5273
5650
|
this.selectoCanvas = selectoCanvas;
|
|
5651
|
+
this.styleRegistry = styleRegistry;
|
|
5274
5652
|
this.contextStore = inject(ContextStore);
|
|
5275
5653
|
/**
|
|
5276
5654
|
* Re-apply context-bound inputs on the CANVAS when a slot changes — the builder twin of the
|
|
@@ -5326,6 +5704,11 @@ class BuilderComponent {
|
|
|
5326
5704
|
this.breakpointCounts = {};
|
|
5327
5705
|
/** Deadline for swallowing the canvas click that completes an overlay action. */
|
|
5328
5706
|
this._ignoreCanvasClickUntil = 0;
|
|
5707
|
+
// ── Responsive audit (A5) ──────────────────────────────────────────────────
|
|
5708
|
+
/** Findings for the width being edited; the panel is open while `auditOpen`. */
|
|
5709
|
+
this.auditFindings = [];
|
|
5710
|
+
this.auditOpen = false;
|
|
5711
|
+
this.auditRunning = false;
|
|
5329
5712
|
this.activeZoom = 100;
|
|
5330
5713
|
this.activeScreenWidth = 1440;
|
|
5331
5714
|
this.selectedItem = null;
|
|
@@ -6264,13 +6647,13 @@ class BuilderComponent {
|
|
|
6264
6647
|
this.isPanelOpen = true;
|
|
6265
6648
|
}
|
|
6266
6649
|
}
|
|
6267
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: BuilderComponent, deps: [{ token: ComponentRegistryService }, { token: OverlayService }, { token: PanelEventService }, { token: DragEngineService }, { token: DataSourceRegistryService }, { token: ViewportService }, { token: InteractionEngineService }, { token: InteractionPresetRegistryService }, { token: BuilderClipboardService }, { token: SymbolRegistryService }, { token: i0.ChangeDetectorRef }, { token: i0.ApplicationRef }, { token: MoveableCanvasService }, { token: SelectoCanvasService }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
6268
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.31", type: BuilderComponent, isStandalone: false, selector: "app-builder", inputs: { layout: "layout", dataSources: "dataSources", orgId: "orgId", routeParams: "routeParams", pageName: "pageName", isSaving: "isSaving", pageStatus: "pageStatus", pageSettings: "pageSettings", globalElements: "globalElements", canUndo: "canUndo", canRedo: "canRedo", moveableMode: "moveableMode" }, outputs: { save: "save", undo: "undo", redo: "redo", back: "back", preview: "preview", publishToggle: "publishToggle", pageSettingsChange: "pageSettingsChange", applyAnimationToAll: "applyAnimationToAll", saveBlock: "saveBlock", globalElementsChange: "globalElementsChange" }, viewQueries: [{ propertyName: "previewCanvas", first: true, predicate: ["previewCanvas"], descendants: true }, { propertyName: "previewScrollRef", first: true, predicate: ["previewScroll"], descendants: true }, { propertyName: "lenisContentRef", first: true, predicate: ["lenisContent"], descendants: true }, { propertyName: "globalOverlayRef", first: true, predicate: ["globalOverlay"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"builder\">\n <header class=\"builder-header\">\n <div class=\"sidebar-switcher\">\n <button (click)=\"activeSidebar = 'pages'\" [class.active]=\"activeSidebar === 'pages'\" title=\"Pages\" aria-label=\"Pages\">\n <i class=\"ph-thin ph-browsers\"></i>\n </button>\n <button (click)=\"activeSidebar = 'components'\" [class.active]=\"activeSidebar === 'components'\" title=\"Components\" aria-label=\"Components\">\n <i class=\"ph-thin ph-cube\"></i>\n </button>\n <button (click)=\"activeSidebar = 'tree'\" [class.active]=\"activeSidebar === 'tree'\" title=\"Layers\" aria-label=\"Layers\">\n <i class=\"ph-thin ph-tree-structure\"></i>\n </button>\n <button (click)=\"activeSidebar = 'tokens'\" [class.active]=\"activeSidebar === 'tokens'\" title=\"Design Tokens\" aria-label=\"Design Tokens\">\n <i class=\"ph-thin ph-circles-three\"></i>\n </button>\n </div>\n\n <app-toolbar\n [activeMode]=\"activeMode\"\n [activeBreakpoint]=\"activeBreakpoint\"\n [breakpointCounts]=\"breakpointCounts\"\n [activeZoom]=\"activeZoom\"\n [activeScreenWidth]=\"activeScreenWidth\"\n [isSaving]=\"isSaving\"\n [pageStatus]=\"pageStatus\"\n [canUndo]=\"canUndo\"\n [canRedo]=\"canRedo\"\n (modeChange)=\"handleToolbarModeChange($event)\"\n (breakpointChange)=\"handleToolbarBreakpointChange($event)\"\n (zoomChange)=\"handleToolbarZoomChange($event)\"\n (screenWidthChange)=\"handleToolbarScreenWidthChange($event)\"\n (save)=\"save.emit()\"\n (preview)=\"preview.emit()\"\n (publishToggle)=\"publishToggle.emit()\"\n (undo)=\"undo.emit()\"\n (redo)=\"redo.emit()\">\n </app-toolbar>\n\n <div class=\"panel-switcher\">\n <button (click)=\"selectPanel(panelTypes.BINDINGS)\" [class.active]=\"activePanel === panelTypes.BINDINGS\" title=\"Bindings\">\n <i class=\"ph-thin ph-plugs-connected\"></i>\n </button>\n <button (click)=\"selectPanel(panelTypes.STYLES)\" [class.active]=\"activePanel === panelTypes.STYLES\" title=\"Styles\">\n <i class=\"ph-thin ph-paint-brush\"></i>\n </button>\n <button (click)=\"selectPanel(panelTypes.PAGE)\" [class.active]=\"activePanel === panelTypes.PAGE\" title=\"Page settings\" aria-label=\"Page settings\">\n <i class=\"ph-thin ph-gear-six\"></i>\n </button>\n <button (click)=\"selectPanel(panelTypes.INTERACTIONS)\" [class.active]=\"activePanel === panelTypes.INTERACTIONS\" title=\"Interactions\">\n <i class=\"ph-thin ph-lightning\"></i>\n </button>\n </div> \n </header>\n\n <app-breadcrumb-bar\n [path]=\"selectedPath\"\n (nodeSelect)=\"onCrumbSelect($event)\"\n (nodePeek)=\"onCrumbPeek($event)\">\n </app-breadcrumb-bar>\n\n <div class=\"builder-main\" [class.panel-open]=\"isPanelOpen\">\n <aside class=\"sidebar\">\n <div *ngIf=\"activeSidebar === 'pages'\" class=\"sidebar-slot\">\n <ng-content select=\"[builderPages]\"></ng-content>\n </div>\n <div *ngIf=\"activeSidebar === 'components'\" class=\"sidebar-slot\">\n <ng-content select=\"[builderComponents]\"></ng-content>\n </div>\n <div *ngIf=\"activeSidebar === 'tokens'\" class=\"sidebar-slot\">\n <ng-content select=\"[builderTokens]\"></ng-content>\n </div>\n <app-layer-tree *ngIf=\"activeSidebar === 'tree'\"\n [layout]=\"layout\"\n [globalElements]=\"globalElements\"\n (nodeSelect)=\"onTreeNodeSelect($event)\"\n (nodeAction)=\"onTreeNodeAction($event)\"\n (nodeMove)=\"onTreeNodeMove()\"></app-layer-tree>\n </aside>\n\n <main #previewCanvas class=\"preview\"\n [class.grab-mode]=\"activeMode === builderModes.Pan\"\n [class.grabbing]=\"isPanning\"\n (click)=\"handleCanvasClick()\"\n (mousedown)=\"onBoardMouseDown($event)\"\n (mousemove)=\"onBoardMouseMove($event)\"\n (mouseup)=\"onBoardMouseUp()\"\n (mouseleave)=\"onBoardMouseUp()\">\n <app-overlay (toolbarAction)=\"onToolbarAction($event)\"></app-overlay>\n\n <!-- Custom Lenis scrollbar \u2014 lives in .preview (not .preview-scroll) so it\n stays in the right gutter and never overlaps the canvas at large widths.\n No translateY counteract needed since it isn't inside the Lenis wrapper. -->\n <div class=\"canvas-scrollbar\"\n [class.visible]=\"isScrollbarVisible\">\n <div class=\"canvas-scrollbar-thumb\"\n [style.height.px]=\"scrollThumbHeight\"\n [style.top.px]=\"scrollThumbTop\">\n </div>\n </div>\n\n <!-- Global elements overlay \u2014 outside Lenis so position:fixed children\n stay pinned to the canvas viewport rather than scrolling with content.\n Shares viewportTransform so elements scale correctly at any zoom level.\n Height matches viewportHeight so position:fixed + inset:0 fills the\n simulated viewport correctly (CSS calc(100%) would give the wrong\n containing-block height and break full-screen fixed overlays). -->\n <div *ngIf=\"globalElements.length\"\n #globalOverlay\n class=\"global-elements-overlay\"\n [style.width.px]=\"viewportWidth\"\n [style.height.px]=\"canvasFrameHeight || viewportHeight\"\n [style.transform]=\"viewportTransform\">\n <ng-container *ngFor=\"let item of globalElementsBefore\">\n <ng-container [ioxRender]=\"item\"\n (onClick)=\"handleClick($event)\"\n (onMouseEnter)=\"handleMouseEnter($event)\"\n (onMouseLeave)=\"handleMouseLeave()\">\n </ng-container>\n </ng-container>\n <ng-container *ngFor=\"let item of globalElementsAfter\">\n <ng-container [ioxRender]=\"item\"\n (onClick)=\"handleClick($event)\"\n (onMouseEnter)=\"handleMouseEnter($event)\"\n (onMouseLeave)=\"handleMouseLeave()\">\n </ng-container>\n </ng-container>\n </div>\n\n <div class=\"preview-scroll\"\n #previewScroll\n [style.width.px]=\"viewportScaledWidth\"\n [class.pan-mode]=\"activeMode === builderModes.Pan\">\n\n <!-- Lenis content proxy: height = realContentHeight \u00D7 scale.\n Lenis scrolls against this value so scroll range is always in\n visual px \u2014 correct at any zoom level. -->\n <div class=\"preview-lenis-content\" #lenisContent\n [style.height.px]=\"scaledContentHeight\">\n\n <iox-page-component\n [style.width.px]=\"viewportWidth\"\n [style.height.px]=\"canvasFrameHeight || viewportHeight\"\n [style.transform]=\"viewportTransform\">\n\n <div class=\"canvas-viewport\"\n id=\"canvas-preview\"\n [style.min-height.px]=\"canvasMinHeight\">\n\n <!-- WRAPPER COLLAPSE (task 9c): top-level nodes render DIRECTLY into the\n canvas viewport \u2014 no .preview-node wrapper; iox-outer + drag move to the\n host (RenderDirective, via ioxDragSourceId). -->\n <ng-container *ngFor=\"let item of layout\"\n [ioxRender]=\"item\"\n [ioxDragSourceId]=\"'canvas-preview'\"\n (onClick)=\"handleClick($event)\"\n (onMouseEnter)=\"handleMouseEnter($event)\"\n (onMouseLeave)=\"handleMouseLeave()\">\n </ng-container>\n </div>\n </iox-page-component>\n </div>\n </div>\n </main>\n\n <ng-content select=\"[builderPanel]\"></ng-content>\n </div>\n\n <!-- Save as Reusable Block dialog -->\n @if (showSaveBlockDialog) {\n <div class=\"save-block-backdrop\" (click)=\"cancelSaveBlock()\"></div>\n <div class=\"save-block-dialog\" (click)=\"$event.stopPropagation()\">\n <h4 class=\"save-block-title\">Save as Reusable Block</h4>\n <input class=\"save-block-input\"\n type=\"text\"\n placeholder=\"Block name...\"\n [(ngModel)]=\"saveBlockName\"\n (keydown.enter)=\"confirmSaveBlock()\"\n (keydown.escape)=\"cancelSaveBlock()\"\n autofocus>\n <div class=\"save-block-actions\">\n <p-button size=\"small\" severity=\"secondary\" label=\"Cancel\" (click)=\"cancelSaveBlock()\"></p-button>\n <p-button size=\"small\" label=\"Save\" [disabled]=\"!saveBlockName.trim()\" (click)=\"confirmSaveBlock()\"></p-button>\n </div>\n </div>\n }\n</div>", styles: ["@charset \"UTF-8\";:host{display:flex;flex:1;min-height:0;overflow:hidden}.builder{display:flex;flex-direction:column;direction:ltr;width:100%;height:100%;min-height:0;border-radius:0;overflow:hidden;position:relative}.builder .builder-header{background:var(--p-surface-0);padding:.375rem .75rem;border-bottom:1px solid var(--p-surface-200);display:grid;grid-template-columns:1fr auto 1fr;align-items:center;gap:.5rem;flex-shrink:0}.builder .builder-header .header-back{display:flex;align-items:center;justify-content:center;width:28px;height:28px;border:none;background:transparent;border-radius:6px;cursor:pointer;color:var(--p-text-muted-color);font-size:16px;transition:background .15s ease-out;margin-inline-end:.25rem}.builder .builder-header .header-back:hover{background:var(--p-surface-50);color:var(--p-text-color)}.builder .builder-header .header-page-name{flex:1;text-align:center;font-size:.8rem;font-weight:500;color:var(--p-text-muted-color);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;padding-inline:1rem}.builder .sidebar-switcher,.builder .panel-switcher{display:flex;gap:.25rem}.builder .sidebar-switcher button,.builder .panel-switcher button{display:flex;align-items:center;justify-content:center;width:2rem;height:2rem;border-radius:6px;border:none;background:transparent;color:var(--p-text-muted-color);cursor:pointer;font-size:1rem;transition:background .15s ease-out,color .15s ease-out}.builder .sidebar-switcher button i,.builder .panel-switcher button i{pointer-events:none}.builder .sidebar-switcher button:hover,.builder .panel-switcher button:hover{background:var(--p-surface-50);color:var(--p-text-color)}.builder .sidebar-switcher button.active,.builder .panel-switcher button.active{background:#cb9090;color:#fff}.builder .panel-switcher{justify-self:end}.builder .builder-main{display:flex;flex:1 1 0;min-height:0;position:relative;overflow:hidden}.builder .sidebar{width:260px;min-width:260px;background:var(--p-surface-0);border-inline-end:1px solid var(--p-surface-200);overflow-y:auto;overflow-x:hidden;flex-shrink:0}.builder .preview{flex:1 1 0;min-width:0;min-height:0;position:relative;overflow:hidden}.builder .preview.grab-mode{cursor:grab}.builder .preview.grab-mode iox-page-component{pointer-events:none}.builder .preview.grabbing{cursor:grabbing}.builder .canvas-scrollbar{position:absolute;right:4px;top:20px;bottom:20px;width:6px;z-index:20;opacity:0;transition:opacity .2s ease;pointer-events:none}.builder .canvas-scrollbar.visible{opacity:1}.builder .canvas-scrollbar-thumb{position:absolute;left:0;width:100%;background:#00000059;border-radius:3px;transition:top .05s linear}.builder .preview-scroll{position:absolute;inset:20px auto;inset-inline-start:50%;transform:translate(-50%);max-width:calc(100% - 40px);height:calc(100% - 40px);overflow:hidden}.builder .preview-lenis-content{position:relative;width:100%;overflow:hidden;contain:paint}.builder iox-page-component{position:absolute!important;top:0;bottom:auto!important;right:auto!important;left:50%;transform-origin:top center;overflow:visible!important;container:iox-canvas/inline-size;box-shadow:0 1px 4px #00000014,0 4px 16px #0000000a;transition:width .25s ease,transform .25s ease}.builder iox-page-component ::ng-deep .iox-page-wrapper{height:auto!important;overflow:visible!important}.builder #canvas-preview{background:#fff}.builder .global-elements-overlay{position:absolute;top:20px;left:50%;transform-origin:top center;pointer-events:none;z-index:9;overflow:hidden;container:iox-canvas/inline-size}.builder .global-elements-overlay ::ng-deep>*{pointer-events:auto}.builder .preview-node.is-selected{cursor:grab}.builder .empty-state{min-height:240px;border:1px dashed var(--p-surface-200);border-radius:10px;display:flex;align-items:center;justify-content:center;color:var(--p-text-muted-color);text-align:center;padding:1rem}.save-block-backdrop{position:fixed;inset:0;z-index:1000;background:#00000040}.save-block-dialog{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);z-index:1001;background:#fff;border-radius:8px;box-shadow:0 8px 32px #0000002e;padding:24px;width:320px}.save-block-dialog .save-block-title{margin:0 0 16px;font-size:14px;font-weight:600;color:var(--p-text-color, #333)}.save-block-dialog .save-block-input{width:100%;padding:8px 10px;border:1px solid var(--p-surface-300, #d1d5db);border-radius:6px;font-size:13px;outline:none;box-sizing:border-box;margin-bottom:16px}.save-block-dialog .save-block-input:focus{border-color:#cb9090;box-shadow:0 0 0 2px #cb909033}.save-block-dialog .save-block-actions{display:flex;justify-content:flex-end;gap:8px}::ng-deep .moveable-control-box.iox-moveable-silent{z-index:1000!important}::ng-deep .moveable-control-box.iox-moveable-silent .moveable-line,::ng-deep .moveable-control-box.iox-moveable-silent .moveable-rotation,::ng-deep .moveable-control-box.iox-moveable-silent .moveable-rotation-line{display:none!important}::ng-deep .moveable-control-box.iox-moveable-silent .moveable-control{display:block!important;width:10px!important;height:10px!important;margin-top:-5px!important;margin-left:-5px!important;background:#fff!important;border:1.5px solid #cb9090!important;border-radius:2px!important;box-shadow:0 1px 3px #0003!important}\n"], dependencies: [{ kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i14.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i14.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i14.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: i15.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: i16.IoxPageComponent, selector: "iox-page-component", inputs: ["class"], outputs: ["scrollEvent"] }, { kind: "directive", type: RenderDirective, selector: "[ioxRender]", inputs: ["ioxRender", "ioxDragSourceId"], outputs: ["onClick", "onMouseEnter", "onMouseLeave"] }, { kind: "component", type: OverlayComponent, selector: "app-overlay", outputs: ["toolbarAction"] }, { kind: "component", type: ToolbarComponent, selector: "app-toolbar", inputs: ["activeMode", "activeBreakpoint", "breakpointCounts", "activeZoom", "activeScreenWidth", "canUndo", "canRedo", "isSaving", "pageStatus"], outputs: ["modeChange", "breakpointChange", "zoomChange", "screenWidthChange", "undo", "redo", "save", "preview", "publishToggle"] }, { kind: "component", type: LayerTreeComponent, selector: "app-layer-tree", inputs: ["layout", "globalElements"], outputs: ["nodeSelect", "nodeAction", "nodeMove"] }, { kind: "component", type: BreadcrumbBarComponent, selector: "app-breadcrumb-bar", inputs: ["path"], outputs: ["nodeSelect", "nodePeek"] }] }); }
|
|
6650
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: BuilderComponent, deps: [{ token: ComponentRegistryService }, { token: OverlayService }, { token: PanelEventService }, { token: DragEngineService }, { token: DataSourceRegistryService }, { token: ViewportService }, { token: InteractionEngineService }, { token: InteractionPresetRegistryService }, { token: BuilderClipboardService }, { token: SymbolRegistryService }, { token: i0.ChangeDetectorRef }, { token: i0.ApplicationRef }, { token: MoveableCanvasService }, { token: SelectoCanvasService }, { token: StyleRegistryService }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
6651
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.31", type: BuilderComponent, isStandalone: false, selector: "app-builder", inputs: { layout: "layout", dataSources: "dataSources", orgId: "orgId", routeParams: "routeParams", pageName: "pageName", isSaving: "isSaving", pageStatus: "pageStatus", pageSettings: "pageSettings", globalElements: "globalElements", canUndo: "canUndo", canRedo: "canRedo", moveableMode: "moveableMode" }, outputs: { save: "save", undo: "undo", redo: "redo", back: "back", preview: "preview", publishToggle: "publishToggle", pageSettingsChange: "pageSettingsChange", applyAnimationToAll: "applyAnimationToAll", saveBlock: "saveBlock", globalElementsChange: "globalElementsChange" }, viewQueries: [{ propertyName: "previewCanvas", first: true, predicate: ["previewCanvas"], descendants: true }, { propertyName: "previewScrollRef", first: true, predicate: ["previewScroll"], descendants: true }, { propertyName: "lenisContentRef", first: true, predicate: ["lenisContent"], descendants: true }, { propertyName: "globalOverlayRef", first: true, predicate: ["globalOverlay"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"builder\">\n <header class=\"builder-header\">\n <div class=\"sidebar-switcher\">\n <button (click)=\"activeSidebar = 'pages'\" [class.active]=\"activeSidebar === 'pages'\" title=\"Pages\" aria-label=\"Pages\">\n <i class=\"ph-thin ph-browsers\"></i>\n </button>\n <button (click)=\"activeSidebar = 'components'\" [class.active]=\"activeSidebar === 'components'\" title=\"Components\" aria-label=\"Components\">\n <i class=\"ph-thin ph-cube\"></i>\n </button>\n <button (click)=\"activeSidebar = 'tree'\" [class.active]=\"activeSidebar === 'tree'\" title=\"Layers\" aria-label=\"Layers\">\n <i class=\"ph-thin ph-tree-structure\"></i>\n </button>\n <button (click)=\"activeSidebar = 'tokens'\" [class.active]=\"activeSidebar === 'tokens'\" title=\"Design Tokens\" aria-label=\"Design Tokens\">\n <i class=\"ph-thin ph-circles-three\"></i>\n </button>\n </div>\n\n <app-toolbar\n [activeMode]=\"activeMode\"\n [activeBreakpoint]=\"activeBreakpoint\"\n [breakpointCounts]=\"breakpointCounts\"\n [auditCount]=\"auditFindings.length\"\n [auditOpen]=\"auditOpen\"\n [activeZoom]=\"activeZoom\"\n [activeScreenWidth]=\"activeScreenWidth\"\n [isSaving]=\"isSaving\"\n [pageStatus]=\"pageStatus\"\n [canUndo]=\"canUndo\"\n [canRedo]=\"canRedo\"\n (modeChange)=\"handleToolbarModeChange($event)\"\n (breakpointChange)=\"handleToolbarBreakpointChange($event)\"\n (auditToggle)=\"toggleAudit()\"\n (zoomChange)=\"handleToolbarZoomChange($event)\"\n (screenWidthChange)=\"handleToolbarScreenWidthChange($event)\"\n (save)=\"save.emit()\"\n (preview)=\"preview.emit()\"\n (publishToggle)=\"publishToggle.emit()\"\n (undo)=\"undo.emit()\"\n (redo)=\"redo.emit()\">\n </app-toolbar>\n\n <div class=\"panel-switcher\">\n <button (click)=\"selectPanel(panelTypes.BINDINGS)\" [class.active]=\"activePanel === panelTypes.BINDINGS\" title=\"Bindings\">\n <i class=\"ph-thin ph-plugs-connected\"></i>\n </button>\n <button (click)=\"selectPanel(panelTypes.STYLES)\" [class.active]=\"activePanel === panelTypes.STYLES\" title=\"Styles\">\n <i class=\"ph-thin ph-paint-brush\"></i>\n </button>\n <button (click)=\"selectPanel(panelTypes.PAGE)\" [class.active]=\"activePanel === panelTypes.PAGE\" title=\"Page settings\" aria-label=\"Page settings\">\n <i class=\"ph-thin ph-gear-six\"></i>\n </button>\n <button (click)=\"selectPanel(panelTypes.INTERACTIONS)\" [class.active]=\"activePanel === panelTypes.INTERACTIONS\" title=\"Interactions\">\n <i class=\"ph-thin ph-lightning\"></i>\n </button>\n </div> \n </header>\n\n <app-breadcrumb-bar\n [path]=\"selectedPath\"\n (nodeSelect)=\"onCrumbSelect($event)\"\n (nodePeek)=\"onCrumbPeek($event)\">\n </app-breadcrumb-bar>\n\n <div class=\"builder-main\" [class.panel-open]=\"isPanelOpen\">\n <aside class=\"sidebar\">\n <div *ngIf=\"activeSidebar === 'pages'\" class=\"sidebar-slot\">\n <ng-content select=\"[builderPages]\"></ng-content>\n </div>\n <div *ngIf=\"activeSidebar === 'components'\" class=\"sidebar-slot\">\n <ng-content select=\"[builderComponents]\"></ng-content>\n </div>\n <div *ngIf=\"activeSidebar === 'tokens'\" class=\"sidebar-slot\">\n <ng-content select=\"[builderTokens]\"></ng-content>\n </div>\n <app-layer-tree *ngIf=\"activeSidebar === 'tree'\"\n [layout]=\"layout\"\n [globalElements]=\"globalElements\"\n (nodeSelect)=\"onTreeNodeSelect($event)\"\n (nodeAction)=\"onTreeNodeAction($event)\"\n (nodeMove)=\"onTreeNodeMove()\"></app-layer-tree>\n </aside>\n\n <main #previewCanvas class=\"preview\"\n [class.grab-mode]=\"activeMode === builderModes.Pan\"\n [class.grabbing]=\"isPanning\"\n (click)=\"handleCanvasClick()\"\n (mousedown)=\"onBoardMouseDown($event)\"\n (mousemove)=\"onBoardMouseMove($event)\"\n (mouseup)=\"onBoardMouseUp()\"\n (mouseleave)=\"onBoardMouseUp()\">\n <app-overlay (toolbarAction)=\"onToolbarAction($event)\"></app-overlay>\n\n <!-- Responsive audit (A5) \u2014 floats over the canvas, right gutter. -->\n @if (auditOpen) {\n <div class=\"audit-dock\" (click)=\"$event.stopPropagation()\">\n <app-audit-panel\n [findings]=\"auditFindings\"\n [breakpointLabel]=\"auditBreakpointLabel\"\n [running]=\"auditRunning\"\n (select)=\"selectAuditNode($event)\"\n (fix)=\"applyAuditFix($event)\"\n (fixAll)=\"applyAllAuditFixes()\"\n (rerun)=\"runAudit()\"\n (closePanel)=\"toggleAudit()\"></app-audit-panel>\n </div>\n }\n\n <!-- Custom Lenis scrollbar \u2014 lives in .preview (not .preview-scroll) so it\n stays in the right gutter and never overlaps the canvas at large widths.\n No translateY counteract needed since it isn't inside the Lenis wrapper. -->\n <div class=\"canvas-scrollbar\"\n [class.visible]=\"isScrollbarVisible\">\n <div class=\"canvas-scrollbar-thumb\"\n [style.height.px]=\"scrollThumbHeight\"\n [style.top.px]=\"scrollThumbTop\">\n </div>\n </div>\n\n <!-- Global elements overlay \u2014 outside Lenis so position:fixed children\n stay pinned to the canvas viewport rather than scrolling with content.\n Shares viewportTransform so elements scale correctly at any zoom level.\n Height matches viewportHeight so position:fixed + inset:0 fills the\n simulated viewport correctly (CSS calc(100%) would give the wrong\n containing-block height and break full-screen fixed overlays). -->\n <div *ngIf=\"globalElements.length\"\n #globalOverlay\n class=\"global-elements-overlay\"\n [style.width.px]=\"viewportWidth\"\n [style.height.px]=\"canvasFrameHeight || viewportHeight\"\n [style.transform]=\"viewportTransform\">\n <ng-container *ngFor=\"let item of globalElementsBefore\">\n <ng-container [ioxRender]=\"item\"\n (onClick)=\"handleClick($event)\"\n (onMouseEnter)=\"handleMouseEnter($event)\"\n (onMouseLeave)=\"handleMouseLeave()\">\n </ng-container>\n </ng-container>\n <ng-container *ngFor=\"let item of globalElementsAfter\">\n <ng-container [ioxRender]=\"item\"\n (onClick)=\"handleClick($event)\"\n (onMouseEnter)=\"handleMouseEnter($event)\"\n (onMouseLeave)=\"handleMouseLeave()\">\n </ng-container>\n </ng-container>\n </div>\n\n <div class=\"preview-scroll\"\n #previewScroll\n [style.width.px]=\"viewportScaledWidth\"\n [class.pan-mode]=\"activeMode === builderModes.Pan\">\n\n <!-- Lenis content proxy: height = realContentHeight \u00D7 scale.\n Lenis scrolls against this value so scroll range is always in\n visual px \u2014 correct at any zoom level. -->\n <div class=\"preview-lenis-content\" #lenisContent\n [style.height.px]=\"scaledContentHeight\">\n\n <iox-page-component\n [style.width.px]=\"viewportWidth\"\n [style.height.px]=\"canvasFrameHeight || viewportHeight\"\n [style.transform]=\"viewportTransform\">\n\n <div class=\"canvas-viewport\"\n id=\"canvas-preview\"\n [style.min-height.px]=\"canvasMinHeight\">\n\n <!-- WRAPPER COLLAPSE (task 9c): top-level nodes render DIRECTLY into the\n canvas viewport \u2014 no .preview-node wrapper; iox-outer + drag move to the\n host (RenderDirective, via ioxDragSourceId). -->\n <ng-container *ngFor=\"let item of layout\"\n [ioxRender]=\"item\"\n [ioxDragSourceId]=\"'canvas-preview'\"\n (onClick)=\"handleClick($event)\"\n (onMouseEnter)=\"handleMouseEnter($event)\"\n (onMouseLeave)=\"handleMouseLeave()\">\n </ng-container>\n </div>\n </iox-page-component>\n </div>\n </div>\n </main>\n\n <ng-content select=\"[builderPanel]\"></ng-content>\n </div>\n\n <!-- Save as Reusable Block dialog -->\n @if (showSaveBlockDialog) {\n <div class=\"save-block-backdrop\" (click)=\"cancelSaveBlock()\"></div>\n <div class=\"save-block-dialog\" (click)=\"$event.stopPropagation()\">\n <h4 class=\"save-block-title\">Save as Reusable Block</h4>\n <input class=\"save-block-input\"\n type=\"text\"\n placeholder=\"Block name...\"\n [(ngModel)]=\"saveBlockName\"\n (keydown.enter)=\"confirmSaveBlock()\"\n (keydown.escape)=\"cancelSaveBlock()\"\n autofocus>\n <div class=\"save-block-actions\">\n <p-button size=\"small\" severity=\"secondary\" label=\"Cancel\" (click)=\"cancelSaveBlock()\"></p-button>\n <p-button size=\"small\" label=\"Save\" [disabled]=\"!saveBlockName.trim()\" (click)=\"confirmSaveBlock()\"></p-button>\n </div>\n </div>\n }\n</div>", styles: ["@charset \"UTF-8\";:host{display:flex;flex:1;min-height:0;overflow:hidden}.builder{display:flex;flex-direction:column;direction:ltr;width:100%;height:100%;min-height:0;border-radius:0;overflow:hidden;position:relative}.builder .builder-header{background:var(--p-surface-0);padding:.375rem .75rem;border-bottom:1px solid var(--p-surface-200);display:grid;grid-template-columns:1fr auto 1fr;align-items:center;gap:.5rem;flex-shrink:0}.builder .builder-header .header-back{display:flex;align-items:center;justify-content:center;width:28px;height:28px;border:none;background:transparent;border-radius:6px;cursor:pointer;color:var(--p-text-muted-color);font-size:16px;transition:background .15s ease-out;margin-inline-end:.25rem}.builder .builder-header .header-back:hover{background:var(--p-surface-50);color:var(--p-text-color)}.builder .builder-header .header-page-name{flex:1;text-align:center;font-size:.8rem;font-weight:500;color:var(--p-text-muted-color);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;padding-inline:1rem}.builder .sidebar-switcher,.builder .panel-switcher{display:flex;gap:.25rem}.builder .sidebar-switcher button,.builder .panel-switcher button{display:flex;align-items:center;justify-content:center;width:2rem;height:2rem;border-radius:6px;border:none;background:transparent;color:var(--p-text-muted-color);cursor:pointer;font-size:1rem;transition:background .15s ease-out,color .15s ease-out}.builder .sidebar-switcher button i,.builder .panel-switcher button i{pointer-events:none}.builder .sidebar-switcher button:hover,.builder .panel-switcher button:hover{background:var(--p-surface-50);color:var(--p-text-color)}.builder .sidebar-switcher button.active,.builder .panel-switcher button.active{background:#cb9090;color:#fff}.builder .panel-switcher{justify-self:end}.builder .builder-main{display:flex;flex:1 1 0;min-height:0;position:relative;overflow:hidden}.builder .sidebar{width:260px;min-width:260px;background:var(--p-surface-0);border-inline-end:1px solid var(--p-surface-200);overflow-y:auto;overflow-x:hidden;flex-shrink:0}.builder .preview{flex:1 1 0;min-width:0;min-height:0;position:relative;overflow:hidden}.builder .preview.grab-mode{cursor:grab}.builder .preview.grab-mode iox-page-component{pointer-events:none}.builder .preview.grabbing{cursor:grabbing}.builder .canvas-scrollbar{position:absolute;right:4px;top:20px;bottom:20px;width:6px;z-index:20;opacity:0;transition:opacity .2s ease;pointer-events:none}.builder .canvas-scrollbar.visible{opacity:1}.builder .canvas-scrollbar-thumb{position:absolute;left:0;width:100%;background:#00000059;border-radius:3px;transition:top .05s linear}.builder .preview-scroll{position:absolute;inset:20px auto;inset-inline-start:50%;transform:translate(-50%);max-width:calc(100% - 40px);height:calc(100% - 40px);overflow:hidden}.builder .preview-lenis-content{position:relative;width:100%;overflow:hidden;contain:paint}.builder iox-page-component{position:absolute!important;top:0;bottom:auto!important;right:auto!important;left:50%;transform-origin:top center;overflow:visible!important;container:iox-canvas/inline-size;box-shadow:0 1px 4px #00000014,0 4px 16px #0000000a;transition:width .25s ease,transform .25s ease}.builder iox-page-component ::ng-deep .iox-page-wrapper{height:auto!important;overflow:visible!important}.builder #canvas-preview{background:#fff}.builder .audit-dock{position:absolute;inset-block-start:16px;inset-inline-end:16px;z-index:20}.builder .global-elements-overlay{position:absolute;top:20px;left:50%;transform-origin:top center;pointer-events:none;z-index:9;overflow:hidden;container:iox-canvas/inline-size}.builder .global-elements-overlay ::ng-deep>*{pointer-events:auto}.builder .preview-node.is-selected{cursor:grab}.builder .empty-state{min-height:240px;border:1px dashed var(--p-surface-200);border-radius:10px;display:flex;align-items:center;justify-content:center;color:var(--p-text-muted-color);text-align:center;padding:1rem}.save-block-backdrop{position:fixed;inset:0;z-index:1000;background:#00000040}.save-block-dialog{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);z-index:1001;background:#fff;border-radius:8px;box-shadow:0 8px 32px #0000002e;padding:24px;width:320px}.save-block-dialog .save-block-title{margin:0 0 16px;font-size:14px;font-weight:600;color:var(--p-text-color, #333)}.save-block-dialog .save-block-input{width:100%;padding:8px 10px;border:1px solid var(--p-surface-300, #d1d5db);border-radius:6px;font-size:13px;outline:none;box-sizing:border-box;margin-bottom:16px}.save-block-dialog .save-block-input:focus{border-color:#cb9090;box-shadow:0 0 0 2px #cb909033}.save-block-dialog .save-block-actions{display:flex;justify-content:flex-end;gap:8px}::ng-deep .moveable-control-box.iox-moveable-silent{z-index:1000!important}::ng-deep .moveable-control-box.iox-moveable-silent .moveable-line,::ng-deep .moveable-control-box.iox-moveable-silent .moveable-rotation,::ng-deep .moveable-control-box.iox-moveable-silent .moveable-rotation-line{display:none!important}::ng-deep .moveable-control-box.iox-moveable-silent .moveable-control{display:block!important;width:10px!important;height:10px!important;margin-top:-5px!important;margin-left:-5px!important;background:#fff!important;border:1.5px solid #cb9090!important;border-radius:2px!important;box-shadow:0 1px 3px #0003!important}\n"], dependencies: [{ kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i15.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i15.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i15.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: i16.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: i17.IoxPageComponent, selector: "iox-page-component", inputs: ["class"], outputs: ["scrollEvent"] }, { kind: "directive", type: RenderDirective, selector: "[ioxRender]", inputs: ["ioxRender", "ioxDragSourceId"], outputs: ["onClick", "onMouseEnter", "onMouseLeave"] }, { kind: "component", type: OverlayComponent, selector: "app-overlay", outputs: ["toolbarAction"] }, { kind: "component", type: ToolbarComponent, selector: "app-toolbar", inputs: ["activeMode", "activeBreakpoint", "breakpointCounts", "auditCount", "auditOpen", "activeZoom", "activeScreenWidth", "canUndo", "canRedo", "isSaving", "pageStatus"], outputs: ["modeChange", "breakpointChange", "auditToggle", "zoomChange", "screenWidthChange", "undo", "redo", "save", "preview", "publishToggle"] }, { kind: "component", type: LayerTreeComponent, selector: "app-layer-tree", inputs: ["layout", "globalElements"], outputs: ["nodeSelect", "nodeAction", "nodeMove"] }, { kind: "component", type: BreadcrumbBarComponent, selector: "app-breadcrumb-bar", inputs: ["path"], outputs: ["nodeSelect", "nodePeek"] }, { kind: "component", type: AuditPanelComponent, selector: "app-audit-panel", inputs: ["findings", "breakpointLabel", "running"], outputs: ["select", "fix", "fixAll", "rerun", "closePanel"] }] }); }
|
|
6269
6652
|
}
|
|
6270
6653
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: BuilderComponent, decorators: [{
|
|
6271
6654
|
type: Component,
|
|
6272
|
-
args: [{ selector: 'app-builder', standalone: false, template: "<div class=\"builder\">\n <header class=\"builder-header\">\n <div class=\"sidebar-switcher\">\n <button (click)=\"activeSidebar = 'pages'\" [class.active]=\"activeSidebar === 'pages'\" title=\"Pages\" aria-label=\"Pages\">\n <i class=\"ph-thin ph-browsers\"></i>\n </button>\n <button (click)=\"activeSidebar = 'components'\" [class.active]=\"activeSidebar === 'components'\" title=\"Components\" aria-label=\"Components\">\n <i class=\"ph-thin ph-cube\"></i>\n </button>\n <button (click)=\"activeSidebar = 'tree'\" [class.active]=\"activeSidebar === 'tree'\" title=\"Layers\" aria-label=\"Layers\">\n <i class=\"ph-thin ph-tree-structure\"></i>\n </button>\n <button (click)=\"activeSidebar = 'tokens'\" [class.active]=\"activeSidebar === 'tokens'\" title=\"Design Tokens\" aria-label=\"Design Tokens\">\n <i class=\"ph-thin ph-circles-three\"></i>\n </button>\n </div>\n\n <app-toolbar\n [activeMode]=\"activeMode\"\n [activeBreakpoint]=\"activeBreakpoint\"\n [breakpointCounts]=\"breakpointCounts\"\n [activeZoom]=\"activeZoom\"\n [activeScreenWidth]=\"activeScreenWidth\"\n [isSaving]=\"isSaving\"\n [pageStatus]=\"pageStatus\"\n [canUndo]=\"canUndo\"\n [canRedo]=\"canRedo\"\n (modeChange)=\"handleToolbarModeChange($event)\"\n (breakpointChange)=\"handleToolbarBreakpointChange($event)\"\n (zoomChange)=\"handleToolbarZoomChange($event)\"\n (screenWidthChange)=\"handleToolbarScreenWidthChange($event)\"\n (save)=\"save.emit()\"\n (preview)=\"preview.emit()\"\n (publishToggle)=\"publishToggle.emit()\"\n (undo)=\"undo.emit()\"\n (redo)=\"redo.emit()\">\n </app-toolbar>\n\n <div class=\"panel-switcher\">\n <button (click)=\"selectPanel(panelTypes.BINDINGS)\" [class.active]=\"activePanel === panelTypes.BINDINGS\" title=\"Bindings\">\n <i class=\"ph-thin ph-plugs-connected\"></i>\n </button>\n <button (click)=\"selectPanel(panelTypes.STYLES)\" [class.active]=\"activePanel === panelTypes.STYLES\" title=\"Styles\">\n <i class=\"ph-thin ph-paint-brush\"></i>\n </button>\n <button (click)=\"selectPanel(panelTypes.PAGE)\" [class.active]=\"activePanel === panelTypes.PAGE\" title=\"Page settings\" aria-label=\"Page settings\">\n <i class=\"ph-thin ph-gear-six\"></i>\n </button>\n <button (click)=\"selectPanel(panelTypes.INTERACTIONS)\" [class.active]=\"activePanel === panelTypes.INTERACTIONS\" title=\"Interactions\">\n <i class=\"ph-thin ph-lightning\"></i>\n </button>\n </div> \n </header>\n\n <app-breadcrumb-bar\n [path]=\"selectedPath\"\n (nodeSelect)=\"onCrumbSelect($event)\"\n (nodePeek)=\"onCrumbPeek($event)\">\n </app-breadcrumb-bar>\n\n <div class=\"builder-main\" [class.panel-open]=\"isPanelOpen\">\n <aside class=\"sidebar\">\n <div *ngIf=\"activeSidebar === 'pages'\" class=\"sidebar-slot\">\n <ng-content select=\"[builderPages]\"></ng-content>\n </div>\n <div *ngIf=\"activeSidebar === 'components'\" class=\"sidebar-slot\">\n <ng-content select=\"[builderComponents]\"></ng-content>\n </div>\n <div *ngIf=\"activeSidebar === 'tokens'\" class=\"sidebar-slot\">\n <ng-content select=\"[builderTokens]\"></ng-content>\n </div>\n <app-layer-tree *ngIf=\"activeSidebar === 'tree'\"\n [layout]=\"layout\"\n [globalElements]=\"globalElements\"\n (nodeSelect)=\"onTreeNodeSelect($event)\"\n (nodeAction)=\"onTreeNodeAction($event)\"\n (nodeMove)=\"onTreeNodeMove()\"></app-layer-tree>\n </aside>\n\n <main #previewCanvas class=\"preview\"\n [class.grab-mode]=\"activeMode === builderModes.Pan\"\n [class.grabbing]=\"isPanning\"\n (click)=\"handleCanvasClick()\"\n (mousedown)=\"onBoardMouseDown($event)\"\n (mousemove)=\"onBoardMouseMove($event)\"\n (mouseup)=\"onBoardMouseUp()\"\n (mouseleave)=\"onBoardMouseUp()\">\n <app-overlay (toolbarAction)=\"onToolbarAction($event)\"></app-overlay>\n\n <!-- Custom Lenis scrollbar \u2014 lives in .preview (not .preview-scroll) so it\n stays in the right gutter and never overlaps the canvas at large widths.\n No translateY counteract needed since it isn't inside the Lenis wrapper. -->\n <div class=\"canvas-scrollbar\"\n [class.visible]=\"isScrollbarVisible\">\n <div class=\"canvas-scrollbar-thumb\"\n [style.height.px]=\"scrollThumbHeight\"\n [style.top.px]=\"scrollThumbTop\">\n </div>\n </div>\n\n <!-- Global elements overlay \u2014 outside Lenis so position:fixed children\n stay pinned to the canvas viewport rather than scrolling with content.\n Shares viewportTransform so elements scale correctly at any zoom level.\n Height matches viewportHeight so position:fixed + inset:0 fills the\n simulated viewport correctly (CSS calc(100%) would give the wrong\n containing-block height and break full-screen fixed overlays). -->\n <div *ngIf=\"globalElements.length\"\n #globalOverlay\n class=\"global-elements-overlay\"\n [style.width.px]=\"viewportWidth\"\n [style.height.px]=\"canvasFrameHeight || viewportHeight\"\n [style.transform]=\"viewportTransform\">\n <ng-container *ngFor=\"let item of globalElementsBefore\">\n <ng-container [ioxRender]=\"item\"\n (onClick)=\"handleClick($event)\"\n (onMouseEnter)=\"handleMouseEnter($event)\"\n (onMouseLeave)=\"handleMouseLeave()\">\n </ng-container>\n </ng-container>\n <ng-container *ngFor=\"let item of globalElementsAfter\">\n <ng-container [ioxRender]=\"item\"\n (onClick)=\"handleClick($event)\"\n (onMouseEnter)=\"handleMouseEnter($event)\"\n (onMouseLeave)=\"handleMouseLeave()\">\n </ng-container>\n </ng-container>\n </div>\n\n <div class=\"preview-scroll\"\n #previewScroll\n [style.width.px]=\"viewportScaledWidth\"\n [class.pan-mode]=\"activeMode === builderModes.Pan\">\n\n <!-- Lenis content proxy: height = realContentHeight \u00D7 scale.\n Lenis scrolls against this value so scroll range is always in\n visual px \u2014 correct at any zoom level. -->\n <div class=\"preview-lenis-content\" #lenisContent\n [style.height.px]=\"scaledContentHeight\">\n\n <iox-page-component\n [style.width.px]=\"viewportWidth\"\n [style.height.px]=\"canvasFrameHeight || viewportHeight\"\n [style.transform]=\"viewportTransform\">\n\n <div class=\"canvas-viewport\"\n id=\"canvas-preview\"\n [style.min-height.px]=\"canvasMinHeight\">\n\n <!-- WRAPPER COLLAPSE (task 9c): top-level nodes render DIRECTLY into the\n canvas viewport \u2014 no .preview-node wrapper; iox-outer + drag move to the\n host (RenderDirective, via ioxDragSourceId). -->\n <ng-container *ngFor=\"let item of layout\"\n [ioxRender]=\"item\"\n [ioxDragSourceId]=\"'canvas-preview'\"\n (onClick)=\"handleClick($event)\"\n (onMouseEnter)=\"handleMouseEnter($event)\"\n (onMouseLeave)=\"handleMouseLeave()\">\n </ng-container>\n </div>\n </iox-page-component>\n </div>\n </div>\n </main>\n\n <ng-content select=\"[builderPanel]\"></ng-content>\n </div>\n\n <!-- Save as Reusable Block dialog -->\n @if (showSaveBlockDialog) {\n <div class=\"save-block-backdrop\" (click)=\"cancelSaveBlock()\"></div>\n <div class=\"save-block-dialog\" (click)=\"$event.stopPropagation()\">\n <h4 class=\"save-block-title\">Save as Reusable Block</h4>\n <input class=\"save-block-input\"\n type=\"text\"\n placeholder=\"Block name...\"\n [(ngModel)]=\"saveBlockName\"\n (keydown.enter)=\"confirmSaveBlock()\"\n (keydown.escape)=\"cancelSaveBlock()\"\n autofocus>\n <div class=\"save-block-actions\">\n <p-button size=\"small\" severity=\"secondary\" label=\"Cancel\" (click)=\"cancelSaveBlock()\"></p-button>\n <p-button size=\"small\" label=\"Save\" [disabled]=\"!saveBlockName.trim()\" (click)=\"confirmSaveBlock()\"></p-button>\n </div>\n </div>\n }\n</div>", styles: ["@charset \"UTF-8\";:host{display:flex;flex:1;min-height:0;overflow:hidden}.builder{display:flex;flex-direction:column;direction:ltr;width:100%;height:100%;min-height:0;border-radius:0;overflow:hidden;position:relative}.builder .builder-header{background:var(--p-surface-0);padding:.375rem .75rem;border-bottom:1px solid var(--p-surface-200);display:grid;grid-template-columns:1fr auto 1fr;align-items:center;gap:.5rem;flex-shrink:0}.builder .builder-header .header-back{display:flex;align-items:center;justify-content:center;width:28px;height:28px;border:none;background:transparent;border-radius:6px;cursor:pointer;color:var(--p-text-muted-color);font-size:16px;transition:background .15s ease-out;margin-inline-end:.25rem}.builder .builder-header .header-back:hover{background:var(--p-surface-50);color:var(--p-text-color)}.builder .builder-header .header-page-name{flex:1;text-align:center;font-size:.8rem;font-weight:500;color:var(--p-text-muted-color);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;padding-inline:1rem}.builder .sidebar-switcher,.builder .panel-switcher{display:flex;gap:.25rem}.builder .sidebar-switcher button,.builder .panel-switcher button{display:flex;align-items:center;justify-content:center;width:2rem;height:2rem;border-radius:6px;border:none;background:transparent;color:var(--p-text-muted-color);cursor:pointer;font-size:1rem;transition:background .15s ease-out,color .15s ease-out}.builder .sidebar-switcher button i,.builder .panel-switcher button i{pointer-events:none}.builder .sidebar-switcher button:hover,.builder .panel-switcher button:hover{background:var(--p-surface-50);color:var(--p-text-color)}.builder .sidebar-switcher button.active,.builder .panel-switcher button.active{background:#cb9090;color:#fff}.builder .panel-switcher{justify-self:end}.builder .builder-main{display:flex;flex:1 1 0;min-height:0;position:relative;overflow:hidden}.builder .sidebar{width:260px;min-width:260px;background:var(--p-surface-0);border-inline-end:1px solid var(--p-surface-200);overflow-y:auto;overflow-x:hidden;flex-shrink:0}.builder .preview{flex:1 1 0;min-width:0;min-height:0;position:relative;overflow:hidden}.builder .preview.grab-mode{cursor:grab}.builder .preview.grab-mode iox-page-component{pointer-events:none}.builder .preview.grabbing{cursor:grabbing}.builder .canvas-scrollbar{position:absolute;right:4px;top:20px;bottom:20px;width:6px;z-index:20;opacity:0;transition:opacity .2s ease;pointer-events:none}.builder .canvas-scrollbar.visible{opacity:1}.builder .canvas-scrollbar-thumb{position:absolute;left:0;width:100%;background:#00000059;border-radius:3px;transition:top .05s linear}.builder .preview-scroll{position:absolute;inset:20px auto;inset-inline-start:50%;transform:translate(-50%);max-width:calc(100% - 40px);height:calc(100% - 40px);overflow:hidden}.builder .preview-lenis-content{position:relative;width:100%;overflow:hidden;contain:paint}.builder iox-page-component{position:absolute!important;top:0;bottom:auto!important;right:auto!important;left:50%;transform-origin:top center;overflow:visible!important;container:iox-canvas/inline-size;box-shadow:0 1px 4px #00000014,0 4px 16px #0000000a;transition:width .25s ease,transform .25s ease}.builder iox-page-component ::ng-deep .iox-page-wrapper{height:auto!important;overflow:visible!important}.builder #canvas-preview{background:#fff}.builder .global-elements-overlay{position:absolute;top:20px;left:50%;transform-origin:top center;pointer-events:none;z-index:9;overflow:hidden;container:iox-canvas/inline-size}.builder .global-elements-overlay ::ng-deep>*{pointer-events:auto}.builder .preview-node.is-selected{cursor:grab}.builder .empty-state{min-height:240px;border:1px dashed var(--p-surface-200);border-radius:10px;display:flex;align-items:center;justify-content:center;color:var(--p-text-muted-color);text-align:center;padding:1rem}.save-block-backdrop{position:fixed;inset:0;z-index:1000;background:#00000040}.save-block-dialog{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);z-index:1001;background:#fff;border-radius:8px;box-shadow:0 8px 32px #0000002e;padding:24px;width:320px}.save-block-dialog .save-block-title{margin:0 0 16px;font-size:14px;font-weight:600;color:var(--p-text-color, #333)}.save-block-dialog .save-block-input{width:100%;padding:8px 10px;border:1px solid var(--p-surface-300, #d1d5db);border-radius:6px;font-size:13px;outline:none;box-sizing:border-box;margin-bottom:16px}.save-block-dialog .save-block-input:focus{border-color:#cb9090;box-shadow:0 0 0 2px #cb909033}.save-block-dialog .save-block-actions{display:flex;justify-content:flex-end;gap:8px}::ng-deep .moveable-control-box.iox-moveable-silent{z-index:1000!important}::ng-deep .moveable-control-box.iox-moveable-silent .moveable-line,::ng-deep .moveable-control-box.iox-moveable-silent .moveable-rotation,::ng-deep .moveable-control-box.iox-moveable-silent .moveable-rotation-line{display:none!important}::ng-deep .moveable-control-box.iox-moveable-silent .moveable-control{display:block!important;width:10px!important;height:10px!important;margin-top:-5px!important;margin-left:-5px!important;background:#fff!important;border:1.5px solid #cb9090!important;border-radius:2px!important;box-shadow:0 1px 3px #0003!important}\n"] }]
|
|
6273
|
-
}], ctorParameters: () => [{ type: ComponentRegistryService }, { type: OverlayService }, { type: PanelEventService }, { type: DragEngineService }, { type: DataSourceRegistryService }, { type: ViewportService }, { type: InteractionEngineService }, { type: InteractionPresetRegistryService }, { type: BuilderClipboardService }, { type: SymbolRegistryService }, { type: i0.ChangeDetectorRef }, { type: i0.ApplicationRef }, { type: MoveableCanvasService }, { type: SelectoCanvasService }], propDecorators: { layout: [{
|
|
6655
|
+
args: [{ selector: 'app-builder', standalone: false, template: "<div class=\"builder\">\n <header class=\"builder-header\">\n <div class=\"sidebar-switcher\">\n <button (click)=\"activeSidebar = 'pages'\" [class.active]=\"activeSidebar === 'pages'\" title=\"Pages\" aria-label=\"Pages\">\n <i class=\"ph-thin ph-browsers\"></i>\n </button>\n <button (click)=\"activeSidebar = 'components'\" [class.active]=\"activeSidebar === 'components'\" title=\"Components\" aria-label=\"Components\">\n <i class=\"ph-thin ph-cube\"></i>\n </button>\n <button (click)=\"activeSidebar = 'tree'\" [class.active]=\"activeSidebar === 'tree'\" title=\"Layers\" aria-label=\"Layers\">\n <i class=\"ph-thin ph-tree-structure\"></i>\n </button>\n <button (click)=\"activeSidebar = 'tokens'\" [class.active]=\"activeSidebar === 'tokens'\" title=\"Design Tokens\" aria-label=\"Design Tokens\">\n <i class=\"ph-thin ph-circles-three\"></i>\n </button>\n </div>\n\n <app-toolbar\n [activeMode]=\"activeMode\"\n [activeBreakpoint]=\"activeBreakpoint\"\n [breakpointCounts]=\"breakpointCounts\"\n [auditCount]=\"auditFindings.length\"\n [auditOpen]=\"auditOpen\"\n [activeZoom]=\"activeZoom\"\n [activeScreenWidth]=\"activeScreenWidth\"\n [isSaving]=\"isSaving\"\n [pageStatus]=\"pageStatus\"\n [canUndo]=\"canUndo\"\n [canRedo]=\"canRedo\"\n (modeChange)=\"handleToolbarModeChange($event)\"\n (breakpointChange)=\"handleToolbarBreakpointChange($event)\"\n (auditToggle)=\"toggleAudit()\"\n (zoomChange)=\"handleToolbarZoomChange($event)\"\n (screenWidthChange)=\"handleToolbarScreenWidthChange($event)\"\n (save)=\"save.emit()\"\n (preview)=\"preview.emit()\"\n (publishToggle)=\"publishToggle.emit()\"\n (undo)=\"undo.emit()\"\n (redo)=\"redo.emit()\">\n </app-toolbar>\n\n <div class=\"panel-switcher\">\n <button (click)=\"selectPanel(panelTypes.BINDINGS)\" [class.active]=\"activePanel === panelTypes.BINDINGS\" title=\"Bindings\">\n <i class=\"ph-thin ph-plugs-connected\"></i>\n </button>\n <button (click)=\"selectPanel(panelTypes.STYLES)\" [class.active]=\"activePanel === panelTypes.STYLES\" title=\"Styles\">\n <i class=\"ph-thin ph-paint-brush\"></i>\n </button>\n <button (click)=\"selectPanel(panelTypes.PAGE)\" [class.active]=\"activePanel === panelTypes.PAGE\" title=\"Page settings\" aria-label=\"Page settings\">\n <i class=\"ph-thin ph-gear-six\"></i>\n </button>\n <button (click)=\"selectPanel(panelTypes.INTERACTIONS)\" [class.active]=\"activePanel === panelTypes.INTERACTIONS\" title=\"Interactions\">\n <i class=\"ph-thin ph-lightning\"></i>\n </button>\n </div> \n </header>\n\n <app-breadcrumb-bar\n [path]=\"selectedPath\"\n (nodeSelect)=\"onCrumbSelect($event)\"\n (nodePeek)=\"onCrumbPeek($event)\">\n </app-breadcrumb-bar>\n\n <div class=\"builder-main\" [class.panel-open]=\"isPanelOpen\">\n <aside class=\"sidebar\">\n <div *ngIf=\"activeSidebar === 'pages'\" class=\"sidebar-slot\">\n <ng-content select=\"[builderPages]\"></ng-content>\n </div>\n <div *ngIf=\"activeSidebar === 'components'\" class=\"sidebar-slot\">\n <ng-content select=\"[builderComponents]\"></ng-content>\n </div>\n <div *ngIf=\"activeSidebar === 'tokens'\" class=\"sidebar-slot\">\n <ng-content select=\"[builderTokens]\"></ng-content>\n </div>\n <app-layer-tree *ngIf=\"activeSidebar === 'tree'\"\n [layout]=\"layout\"\n [globalElements]=\"globalElements\"\n (nodeSelect)=\"onTreeNodeSelect($event)\"\n (nodeAction)=\"onTreeNodeAction($event)\"\n (nodeMove)=\"onTreeNodeMove()\"></app-layer-tree>\n </aside>\n\n <main #previewCanvas class=\"preview\"\n [class.grab-mode]=\"activeMode === builderModes.Pan\"\n [class.grabbing]=\"isPanning\"\n (click)=\"handleCanvasClick()\"\n (mousedown)=\"onBoardMouseDown($event)\"\n (mousemove)=\"onBoardMouseMove($event)\"\n (mouseup)=\"onBoardMouseUp()\"\n (mouseleave)=\"onBoardMouseUp()\">\n <app-overlay (toolbarAction)=\"onToolbarAction($event)\"></app-overlay>\n\n <!-- Responsive audit (A5) \u2014 floats over the canvas, right gutter. -->\n @if (auditOpen) {\n <div class=\"audit-dock\" (click)=\"$event.stopPropagation()\">\n <app-audit-panel\n [findings]=\"auditFindings\"\n [breakpointLabel]=\"auditBreakpointLabel\"\n [running]=\"auditRunning\"\n (select)=\"selectAuditNode($event)\"\n (fix)=\"applyAuditFix($event)\"\n (fixAll)=\"applyAllAuditFixes()\"\n (rerun)=\"runAudit()\"\n (closePanel)=\"toggleAudit()\"></app-audit-panel>\n </div>\n }\n\n <!-- Custom Lenis scrollbar \u2014 lives in .preview (not .preview-scroll) so it\n stays in the right gutter and never overlaps the canvas at large widths.\n No translateY counteract needed since it isn't inside the Lenis wrapper. -->\n <div class=\"canvas-scrollbar\"\n [class.visible]=\"isScrollbarVisible\">\n <div class=\"canvas-scrollbar-thumb\"\n [style.height.px]=\"scrollThumbHeight\"\n [style.top.px]=\"scrollThumbTop\">\n </div>\n </div>\n\n <!-- Global elements overlay \u2014 outside Lenis so position:fixed children\n stay pinned to the canvas viewport rather than scrolling with content.\n Shares viewportTransform so elements scale correctly at any zoom level.\n Height matches viewportHeight so position:fixed + inset:0 fills the\n simulated viewport correctly (CSS calc(100%) would give the wrong\n containing-block height and break full-screen fixed overlays). -->\n <div *ngIf=\"globalElements.length\"\n #globalOverlay\n class=\"global-elements-overlay\"\n [style.width.px]=\"viewportWidth\"\n [style.height.px]=\"canvasFrameHeight || viewportHeight\"\n [style.transform]=\"viewportTransform\">\n <ng-container *ngFor=\"let item of globalElementsBefore\">\n <ng-container [ioxRender]=\"item\"\n (onClick)=\"handleClick($event)\"\n (onMouseEnter)=\"handleMouseEnter($event)\"\n (onMouseLeave)=\"handleMouseLeave()\">\n </ng-container>\n </ng-container>\n <ng-container *ngFor=\"let item of globalElementsAfter\">\n <ng-container [ioxRender]=\"item\"\n (onClick)=\"handleClick($event)\"\n (onMouseEnter)=\"handleMouseEnter($event)\"\n (onMouseLeave)=\"handleMouseLeave()\">\n </ng-container>\n </ng-container>\n </div>\n\n <div class=\"preview-scroll\"\n #previewScroll\n [style.width.px]=\"viewportScaledWidth\"\n [class.pan-mode]=\"activeMode === builderModes.Pan\">\n\n <!-- Lenis content proxy: height = realContentHeight \u00D7 scale.\n Lenis scrolls against this value so scroll range is always in\n visual px \u2014 correct at any zoom level. -->\n <div class=\"preview-lenis-content\" #lenisContent\n [style.height.px]=\"scaledContentHeight\">\n\n <iox-page-component\n [style.width.px]=\"viewportWidth\"\n [style.height.px]=\"canvasFrameHeight || viewportHeight\"\n [style.transform]=\"viewportTransform\">\n\n <div class=\"canvas-viewport\"\n id=\"canvas-preview\"\n [style.min-height.px]=\"canvasMinHeight\">\n\n <!-- WRAPPER COLLAPSE (task 9c): top-level nodes render DIRECTLY into the\n canvas viewport \u2014 no .preview-node wrapper; iox-outer + drag move to the\n host (RenderDirective, via ioxDragSourceId). -->\n <ng-container *ngFor=\"let item of layout\"\n [ioxRender]=\"item\"\n [ioxDragSourceId]=\"'canvas-preview'\"\n (onClick)=\"handleClick($event)\"\n (onMouseEnter)=\"handleMouseEnter($event)\"\n (onMouseLeave)=\"handleMouseLeave()\">\n </ng-container>\n </div>\n </iox-page-component>\n </div>\n </div>\n </main>\n\n <ng-content select=\"[builderPanel]\"></ng-content>\n </div>\n\n <!-- Save as Reusable Block dialog -->\n @if (showSaveBlockDialog) {\n <div class=\"save-block-backdrop\" (click)=\"cancelSaveBlock()\"></div>\n <div class=\"save-block-dialog\" (click)=\"$event.stopPropagation()\">\n <h4 class=\"save-block-title\">Save as Reusable Block</h4>\n <input class=\"save-block-input\"\n type=\"text\"\n placeholder=\"Block name...\"\n [(ngModel)]=\"saveBlockName\"\n (keydown.enter)=\"confirmSaveBlock()\"\n (keydown.escape)=\"cancelSaveBlock()\"\n autofocus>\n <div class=\"save-block-actions\">\n <p-button size=\"small\" severity=\"secondary\" label=\"Cancel\" (click)=\"cancelSaveBlock()\"></p-button>\n <p-button size=\"small\" label=\"Save\" [disabled]=\"!saveBlockName.trim()\" (click)=\"confirmSaveBlock()\"></p-button>\n </div>\n </div>\n }\n</div>", styles: ["@charset \"UTF-8\";:host{display:flex;flex:1;min-height:0;overflow:hidden}.builder{display:flex;flex-direction:column;direction:ltr;width:100%;height:100%;min-height:0;border-radius:0;overflow:hidden;position:relative}.builder .builder-header{background:var(--p-surface-0);padding:.375rem .75rem;border-bottom:1px solid var(--p-surface-200);display:grid;grid-template-columns:1fr auto 1fr;align-items:center;gap:.5rem;flex-shrink:0}.builder .builder-header .header-back{display:flex;align-items:center;justify-content:center;width:28px;height:28px;border:none;background:transparent;border-radius:6px;cursor:pointer;color:var(--p-text-muted-color);font-size:16px;transition:background .15s ease-out;margin-inline-end:.25rem}.builder .builder-header .header-back:hover{background:var(--p-surface-50);color:var(--p-text-color)}.builder .builder-header .header-page-name{flex:1;text-align:center;font-size:.8rem;font-weight:500;color:var(--p-text-muted-color);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;padding-inline:1rem}.builder .sidebar-switcher,.builder .panel-switcher{display:flex;gap:.25rem}.builder .sidebar-switcher button,.builder .panel-switcher button{display:flex;align-items:center;justify-content:center;width:2rem;height:2rem;border-radius:6px;border:none;background:transparent;color:var(--p-text-muted-color);cursor:pointer;font-size:1rem;transition:background .15s ease-out,color .15s ease-out}.builder .sidebar-switcher button i,.builder .panel-switcher button i{pointer-events:none}.builder .sidebar-switcher button:hover,.builder .panel-switcher button:hover{background:var(--p-surface-50);color:var(--p-text-color)}.builder .sidebar-switcher button.active,.builder .panel-switcher button.active{background:#cb9090;color:#fff}.builder .panel-switcher{justify-self:end}.builder .builder-main{display:flex;flex:1 1 0;min-height:0;position:relative;overflow:hidden}.builder .sidebar{width:260px;min-width:260px;background:var(--p-surface-0);border-inline-end:1px solid var(--p-surface-200);overflow-y:auto;overflow-x:hidden;flex-shrink:0}.builder .preview{flex:1 1 0;min-width:0;min-height:0;position:relative;overflow:hidden}.builder .preview.grab-mode{cursor:grab}.builder .preview.grab-mode iox-page-component{pointer-events:none}.builder .preview.grabbing{cursor:grabbing}.builder .canvas-scrollbar{position:absolute;right:4px;top:20px;bottom:20px;width:6px;z-index:20;opacity:0;transition:opacity .2s ease;pointer-events:none}.builder .canvas-scrollbar.visible{opacity:1}.builder .canvas-scrollbar-thumb{position:absolute;left:0;width:100%;background:#00000059;border-radius:3px;transition:top .05s linear}.builder .preview-scroll{position:absolute;inset:20px auto;inset-inline-start:50%;transform:translate(-50%);max-width:calc(100% - 40px);height:calc(100% - 40px);overflow:hidden}.builder .preview-lenis-content{position:relative;width:100%;overflow:hidden;contain:paint}.builder iox-page-component{position:absolute!important;top:0;bottom:auto!important;right:auto!important;left:50%;transform-origin:top center;overflow:visible!important;container:iox-canvas/inline-size;box-shadow:0 1px 4px #00000014,0 4px 16px #0000000a;transition:width .25s ease,transform .25s ease}.builder iox-page-component ::ng-deep .iox-page-wrapper{height:auto!important;overflow:visible!important}.builder #canvas-preview{background:#fff}.builder .audit-dock{position:absolute;inset-block-start:16px;inset-inline-end:16px;z-index:20}.builder .global-elements-overlay{position:absolute;top:20px;left:50%;transform-origin:top center;pointer-events:none;z-index:9;overflow:hidden;container:iox-canvas/inline-size}.builder .global-elements-overlay ::ng-deep>*{pointer-events:auto}.builder .preview-node.is-selected{cursor:grab}.builder .empty-state{min-height:240px;border:1px dashed var(--p-surface-200);border-radius:10px;display:flex;align-items:center;justify-content:center;color:var(--p-text-muted-color);text-align:center;padding:1rem}.save-block-backdrop{position:fixed;inset:0;z-index:1000;background:#00000040}.save-block-dialog{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);z-index:1001;background:#fff;border-radius:8px;box-shadow:0 8px 32px #0000002e;padding:24px;width:320px}.save-block-dialog .save-block-title{margin:0 0 16px;font-size:14px;font-weight:600;color:var(--p-text-color, #333)}.save-block-dialog .save-block-input{width:100%;padding:8px 10px;border:1px solid var(--p-surface-300, #d1d5db);border-radius:6px;font-size:13px;outline:none;box-sizing:border-box;margin-bottom:16px}.save-block-dialog .save-block-input:focus{border-color:#cb9090;box-shadow:0 0 0 2px #cb909033}.save-block-dialog .save-block-actions{display:flex;justify-content:flex-end;gap:8px}::ng-deep .moveable-control-box.iox-moveable-silent{z-index:1000!important}::ng-deep .moveable-control-box.iox-moveable-silent .moveable-line,::ng-deep .moveable-control-box.iox-moveable-silent .moveable-rotation,::ng-deep .moveable-control-box.iox-moveable-silent .moveable-rotation-line{display:none!important}::ng-deep .moveable-control-box.iox-moveable-silent .moveable-control{display:block!important;width:10px!important;height:10px!important;margin-top:-5px!important;margin-left:-5px!important;background:#fff!important;border:1.5px solid #cb9090!important;border-radius:2px!important;box-shadow:0 1px 3px #0003!important}\n"] }]
|
|
6656
|
+
}], ctorParameters: () => [{ type: ComponentRegistryService }, { type: OverlayService }, { type: PanelEventService }, { type: DragEngineService }, { type: DataSourceRegistryService }, { type: ViewportService }, { type: InteractionEngineService }, { type: InteractionPresetRegistryService }, { type: BuilderClipboardService }, { type: SymbolRegistryService }, { type: i0.ChangeDetectorRef }, { type: i0.ApplicationRef }, { type: MoveableCanvasService }, { type: SelectoCanvasService }, { type: StyleRegistryService }], propDecorators: { layout: [{
|
|
6274
6657
|
type: Input
|
|
6275
6658
|
}], dataSources: [{
|
|
6276
6659
|
type: Input
|
|
@@ -8356,6 +8739,7 @@ const DECLARATIONS = [
|
|
|
8356
8739
|
PanelChildComponent,
|
|
8357
8740
|
LayerTreeComponent,
|
|
8358
8741
|
BreadcrumbBarComponent,
|
|
8742
|
+
AuditPanelComponent,
|
|
8359
8743
|
];
|
|
8360
8744
|
class IoxBuilderModule {
|
|
8361
8745
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: IoxBuilderModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
|
|
@@ -8375,7 +8759,8 @@ class IoxBuilderModule {
|
|
|
8375
8759
|
PanelComponent,
|
|
8376
8760
|
PanelChildComponent,
|
|
8377
8761
|
LayerTreeComponent,
|
|
8378
|
-
BreadcrumbBarComponent
|
|
8762
|
+
BreadcrumbBarComponent,
|
|
8763
|
+
AuditPanelComponent], imports: [CommonModule,
|
|
8379
8764
|
FormsModule,
|
|
8380
8765
|
DragDropModule,
|
|
8381
8766
|
AccordionModule,
|
|
@@ -8415,7 +8800,8 @@ class IoxBuilderModule {
|
|
|
8415
8800
|
PanelComponent,
|
|
8416
8801
|
PanelChildComponent,
|
|
8417
8802
|
LayerTreeComponent,
|
|
8418
|
-
BreadcrumbBarComponent
|
|
8803
|
+
BreadcrumbBarComponent,
|
|
8804
|
+
AuditPanelComponent] }); }
|
|
8419
8805
|
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: IoxBuilderModule, providers: [
|
|
8420
8806
|
DragEngineService,
|
|
8421
8807
|
OverlayService,
|
|
@@ -8519,6 +8905,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImpo
|
|
|
8519
8905
|
PanelChildComponent,
|
|
8520
8906
|
LayerTreeComponent,
|
|
8521
8907
|
BreadcrumbBarComponent,
|
|
8908
|
+
AuditPanelComponent,
|
|
8522
8909
|
],
|
|
8523
8910
|
schemas: [NO_ERRORS_SCHEMA],
|
|
8524
8911
|
}]
|
|
@@ -8534,108 +8921,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImpo
|
|
|
8534
8921
|
/** Optional font manager. Provide from the host app to populate the fontFamily dropdown. */
|
|
8535
8922
|
const IOX_FONT_MANAGER = new InjectionToken('IOX_FONT_MANAGER');
|
|
8536
8923
|
|
|
8537
|
-
/**
|
|
8538
|
-
* STRUCTURE OPS (A4) — the one-click layout changes people actually make at a narrow width, as pure
|
|
8539
|
-
* "what to write" functions. Each returns a flat style map the caller stores in the node's
|
|
8540
|
-
* `responsive[bp]` layer (or in the base styles at Base) through the shared `setOverride`.
|
|
8541
|
-
*
|
|
8542
|
-
* All of it is ordinary CSS the SSR engine already renders — there is no second tree and no
|
|
8543
|
-
* per-width HTML. See responsive-breakpoints-plan.md (D5).
|
|
8544
|
-
*/
|
|
8545
|
-
/** CSS lengths that are a FIXED pixel size — the ones that stop a layout fitting a phone. */
|
|
8546
|
-
const PX_LENGTH = /^-?\d+(\.\d+)?px$/i;
|
|
8547
|
-
/**
|
|
8548
|
-
* Hide / show at a width. Hiding is `display: none` INSIDE that breakpoint's rules — the node stays
|
|
8549
|
-
* in the DOM and in the SSR output (one HTML document serves every width), so hidden content still
|
|
8550
|
-
* ships bytes and is still indexed. Showing again clears the override rather than guessing a value.
|
|
8551
|
-
*
|
|
8552
|
-
* @returns the props to write, or `null` meaning "clear the display override at this breakpoint".
|
|
8553
|
-
*/
|
|
8554
|
-
function visibilityProps(hidden) {
|
|
8555
|
-
return hidden ? { display: 'none' } : null;
|
|
8556
|
-
}
|
|
8557
|
-
/** True when this node is hidden by the resolved styles at some width. */
|
|
8558
|
-
function isHiddenAt(resolvedStyleProps) {
|
|
8559
|
-
return String(resolvedStyleProps?.['display'] ?? '') === 'none';
|
|
8560
|
-
}
|
|
8561
|
-
/**
|
|
8562
|
-
* Stack a container's children in one column — the most common mobile edit, and four separate
|
|
8563
|
-
* controls today (display, direction, wrap, columns). A grid becomes a single-column grid; anything
|
|
8564
|
-
* else becomes a column flex box.
|
|
8565
|
-
*/
|
|
8566
|
-
function stackChildrenProps(resolvedStyleProps) {
|
|
8567
|
-
const display = String(resolvedStyleProps?.['display'] ?? '');
|
|
8568
|
-
if (display === 'grid' || display === 'inline-grid') {
|
|
8569
|
-
return { gridTemplateColumns: 'repeat(1, minmax(0, 1fr))' };
|
|
8570
|
-
}
|
|
8571
|
-
return { display: 'flex', flexDirection: 'column', flexWrap: 'nowrap' };
|
|
8572
|
-
}
|
|
8573
|
-
/**
|
|
8574
|
-
* Collapse a grid to `count` columns (a flex row is stacked instead — it has no column track).
|
|
8575
|
-
* `count` is clamped to at least 1.
|
|
8576
|
-
*/
|
|
8577
|
-
function collapseColumnsProps(resolvedStyleProps, count) {
|
|
8578
|
-
const columns = Math.max(1, Math.floor(count));
|
|
8579
|
-
const display = String(resolvedStyleProps?.['display'] ?? '');
|
|
8580
|
-
if (display === 'grid' || display === 'inline-grid') {
|
|
8581
|
-
return { gridTemplateColumns: `repeat(${columns}, minmax(0, 1fr))` };
|
|
8582
|
-
}
|
|
8583
|
-
return columns === 1 ? stackChildrenProps(resolvedStyleProps) : { flexWrap: 'wrap' };
|
|
8584
|
-
}
|
|
8585
|
-
/** How many equal columns a `repeat(N, …)` track list describes, or null when it isn't one. */
|
|
8586
|
-
function columnCountOf(resolvedStyleProps) {
|
|
8587
|
-
const match = /^repeat\(\s*(\d+)\s*,/.exec(String(resolvedStyleProps?.['gridTemplateColumns'] ?? ''));
|
|
8588
|
-
return match ? Number(match[1]) : null;
|
|
8589
|
-
}
|
|
8590
|
-
/**
|
|
8591
|
-
* Make fixed pixel sizes fluid: `width: 520px` → `width: 100%` with the original kept as
|
|
8592
|
-
* `max-width`, so the element still never exceeds its designed size on a wide screen. `minWidth` in
|
|
8593
|
-
* px is dropped to 0 — it is what actually forces horizontal scrolling on a phone.
|
|
8594
|
-
*
|
|
8595
|
-
* @returns the props to write; EMPTY when there is nothing fixed to fluidise.
|
|
8596
|
-
*/
|
|
8597
|
-
function fluidiseProps(resolvedStyleProps) {
|
|
8598
|
-
const out = {};
|
|
8599
|
-
const width = String(resolvedStyleProps?.['width'] ?? '');
|
|
8600
|
-
const minWidth = String(resolvedStyleProps?.['minWidth'] ?? '');
|
|
8601
|
-
if (PX_LENGTH.test(width)) {
|
|
8602
|
-
out['width'] = '100%';
|
|
8603
|
-
const maxWidth = String(resolvedStyleProps?.['maxWidth'] ?? '');
|
|
8604
|
-
if (!maxWidth || maxWidth === 'none')
|
|
8605
|
-
out['maxWidth'] = width;
|
|
8606
|
-
}
|
|
8607
|
-
if (PX_LENGTH.test(minWidth) && parseFloat(minWidth) > 0)
|
|
8608
|
-
out['minWidth'] = '0px';
|
|
8609
|
-
return out;
|
|
8610
|
-
}
|
|
8611
|
-
/** True when `fluidiseProps` would change anything — drives the button's disabled state. */
|
|
8612
|
-
function canFluidise(resolvedStyleProps) {
|
|
8613
|
-
return Object.keys(fluidiseProps(resolvedStyleProps)).length > 0;
|
|
8614
|
-
}
|
|
8615
|
-
/**
|
|
8616
|
-
* Reorder children AT ONE WIDTH using CSS `order`, leaving the tree — and so the DOM and
|
|
8617
|
-
* screen-reader order — untouched. Returns one value per child BY CHILD INDEX: the `order` it must
|
|
8618
|
-
* take, or `null` for "no override needed".
|
|
8619
|
-
*
|
|
8620
|
-
* Either every child gets an explicit `order` or none does: CSS defaults `order` to 0, so a partial
|
|
8621
|
-
* set would mix overridden children with unordered ones and read as an arbitrary interleave.
|
|
8622
|
-
*
|
|
8623
|
-
* @param count How many children there are.
|
|
8624
|
-
* @param from Index of the child being moved.
|
|
8625
|
-
* @param to Index it should occupy.
|
|
8626
|
-
*/
|
|
8627
|
-
function reorderProps(count, from, to) {
|
|
8628
|
-
const none = Array.from({ length: Math.max(0, count) }, () => null);
|
|
8629
|
-
if (from < 0 || from >= count || to < 0 || to >= count || from === to)
|
|
8630
|
-
return none;
|
|
8631
|
-
// Visual order after the move, as a list of CHILD indices.
|
|
8632
|
-
const sequence = Array.from({ length: count }, (_, i) => i);
|
|
8633
|
-
sequence.splice(to, 0, ...sequence.splice(from, 1));
|
|
8634
|
-
const orderByChild = none.slice();
|
|
8635
|
-
sequence.forEach((childIndex, position) => { orderByChild[childIndex] = position; });
|
|
8636
|
-
return orderByChild;
|
|
8637
|
-
}
|
|
8638
|
-
|
|
8639
8924
|
/**
|
|
8640
8925
|
* ESSENTIALS (B3) — the handful of style controls that matter first for each component type.
|
|
8641
8926
|
*
|
|
@@ -8685,6 +8970,108 @@ function matchesControlSearch(name, label, term) {
|
|
|
8685
8970
|
return name.toLowerCase().includes(needle) || (label ?? '').toLowerCase().includes(needle);
|
|
8686
8971
|
}
|
|
8687
8972
|
|
|
8973
|
+
const GRID = (columns) => ({ display: 'grid', gridTemplateColumns: columns, gap: '24px', width: '100%' });
|
|
8974
|
+
const ONE_COLUMN = { gridTemplateColumns: 'repeat(1, minmax(0, 1fr))' };
|
|
8975
|
+
const LAYOUT_PRESETS = Object.freeze([
|
|
8976
|
+
{
|
|
8977
|
+
id: 'stack', name: 'Stack', icon: 'ph-thin ph-rows',
|
|
8978
|
+
description: 'Vertical flow with even spacing.',
|
|
8979
|
+
container: { display: 'flex', flexDirection: 'column', gap: '24px', width: '100%' },
|
|
8980
|
+
childCount: 2,
|
|
8981
|
+
},
|
|
8982
|
+
{
|
|
8983
|
+
id: 'two-columns', name: 'Two columns', icon: 'ph-thin ph-columns',
|
|
8984
|
+
description: 'Side by side; stacks on a phone.',
|
|
8985
|
+
container: GRID('repeat(2, minmax(0, 1fr))'),
|
|
8986
|
+
containerResponsive: { [Breakpoint.Small]: ONE_COLUMN },
|
|
8987
|
+
childCount: 2,
|
|
8988
|
+
},
|
|
8989
|
+
{
|
|
8990
|
+
id: 'three-columns', name: 'Three columns', icon: 'ph-thin ph-columns',
|
|
8991
|
+
description: 'Three across; two on a tablet, one on a phone.',
|
|
8992
|
+
container: GRID('repeat(3, minmax(0, 1fr))'),
|
|
8993
|
+
containerResponsive: {
|
|
8994
|
+
[Breakpoint.Tablet]: { gridTemplateColumns: 'repeat(2, minmax(0, 1fr))' },
|
|
8995
|
+
[Breakpoint.Small]: ONE_COLUMN,
|
|
8996
|
+
},
|
|
8997
|
+
childCount: 3,
|
|
8998
|
+
},
|
|
8999
|
+
{
|
|
9000
|
+
id: 'sidebar-main', name: 'Sidebar + main', icon: 'ph-thin ph-layout',
|
|
9001
|
+
description: 'Fixed sidebar beside flexible content; the sidebar drops under it on a tablet.',
|
|
9002
|
+
container: GRID('280px minmax(0, 1fr)'),
|
|
9003
|
+
containerResponsive: { [Breakpoint.Tablet]: ONE_COLUMN },
|
|
9004
|
+
childCount: 2,
|
|
9005
|
+
},
|
|
9006
|
+
{
|
|
9007
|
+
id: 'card-grid', name: 'Card grid', icon: 'ph-thin ph-squares-four',
|
|
9008
|
+
description: 'A grid of cards: three, then two, then one.',
|
|
9009
|
+
container: GRID('repeat(3, minmax(0, 1fr))'),
|
|
9010
|
+
containerResponsive: {
|
|
9011
|
+
[Breakpoint.Tablet]: { gridTemplateColumns: 'repeat(2, minmax(0, 1fr))' },
|
|
9012
|
+
[Breakpoint.Small]: ONE_COLUMN,
|
|
9013
|
+
},
|
|
9014
|
+
childCount: 6,
|
|
9015
|
+
child: { minHeight: '160px' },
|
|
9016
|
+
},
|
|
9017
|
+
{
|
|
9018
|
+
id: 'split-hero', name: 'Split hero', icon: 'ph-thin ph-square-split-horizontal',
|
|
9019
|
+
description: 'Text beside media; on a phone the media moves above the text.',
|
|
9020
|
+
container: GRID('minmax(0, 1.2fr) minmax(0, 1fr)'),
|
|
9021
|
+
containerResponsive: { [Breakpoint.Small]: ONE_COLUMN },
|
|
9022
|
+
childCount: 2,
|
|
9023
|
+
// On a phone the MEDIA (child 2) leads — CSS order only, the DOM order never changes.
|
|
9024
|
+
childResponsive: [{ [Breakpoint.Small]: { order: '1' } }, { [Breakpoint.Small]: { order: '0' } }],
|
|
9025
|
+
},
|
|
9026
|
+
]);
|
|
9027
|
+
/** Set a style trait's value on a node built from a component config. */
|
|
9028
|
+
function setStyleTrait(node, name, value) {
|
|
9029
|
+
for (const group of node.styleTraits ?? []) {
|
|
9030
|
+
const trait = group.traits.find(t => t.name === name);
|
|
9031
|
+
if (trait) {
|
|
9032
|
+
trait.default = value;
|
|
9033
|
+
return;
|
|
9034
|
+
}
|
|
9035
|
+
}
|
|
9036
|
+
}
|
|
9037
|
+
function applyResponsive(node, layers) {
|
|
9038
|
+
if (!layers)
|
|
9039
|
+
return;
|
|
9040
|
+
let responsive = node.responsive;
|
|
9041
|
+
for (const [bp, styles] of Object.entries(layers)) {
|
|
9042
|
+
for (const [prop, value] of Object.entries(styles ?? {})) {
|
|
9043
|
+
responsive = setOverride(responsive, bp, prop, value);
|
|
9044
|
+
}
|
|
9045
|
+
}
|
|
9046
|
+
node.responsive = responsive;
|
|
9047
|
+
}
|
|
9048
|
+
/**
|
|
9049
|
+
* Build a preset's node tree. `createNode` makes an empty node of a type (the component registry in
|
|
9050
|
+
* the builder) — passed in so this stays pure and testable.
|
|
9051
|
+
*
|
|
9052
|
+
* @returns the container node, or `null` when the container type can't be created.
|
|
9053
|
+
*/
|
|
9054
|
+
function buildLayoutPreset(def, createNode) {
|
|
9055
|
+
const container = createNode('Container');
|
|
9056
|
+
if (!container)
|
|
9057
|
+
return null;
|
|
9058
|
+
for (const [prop, value] of Object.entries(def.container))
|
|
9059
|
+
setStyleTrait(container, prop, value);
|
|
9060
|
+
applyResponsive(container, def.containerResponsive);
|
|
9061
|
+
const children = [];
|
|
9062
|
+
for (let i = 0; i < def.childCount; i++) {
|
|
9063
|
+
const child = createNode('Container');
|
|
9064
|
+
if (!child)
|
|
9065
|
+
continue;
|
|
9066
|
+
for (const [prop, value] of Object.entries(def.child ?? {}))
|
|
9067
|
+
setStyleTrait(child, prop, value);
|
|
9068
|
+
applyResponsive(child, def.childResponsive?.[i]);
|
|
9069
|
+
children.push(child);
|
|
9070
|
+
}
|
|
9071
|
+
container.children = children;
|
|
9072
|
+
return container;
|
|
9073
|
+
}
|
|
9074
|
+
|
|
8688
9075
|
class BuilderButtonComponentConfig extends ComponentConfig {
|
|
8689
9076
|
constructor() {
|
|
8690
9077
|
super('Button', 'app-builder-button', 'ph-thin ph-cursor-click', 'Basic');
|
|
@@ -9238,5 +9625,5 @@ class BuilderSpacerComponentConfig extends ComponentConfig {
|
|
|
9238
9625
|
* Generated bundle index. Do not edit.
|
|
9239
9626
|
*/
|
|
9240
9627
|
|
|
9241
|
-
export { ACTION_TYPE_OPTIONS, BREAKPOINT_PRESENTATION, BUILDER_CANVAS_CONTAINER, BackgroundGroupStyleConfig, BorderGroupStyleConfig, BreadcrumbBarComponent, BuilderButtonBlockComponent, BuilderButtonComponentConfig, BuilderClipboardService, BuilderComponent, BuilderContainerComponent, BuilderContainerComponentConfig, BuilderDataSourceService, BuilderDividerComponentConfig, BuilderHeadingComponentConfig, BuilderHistoryService, BuilderIconComponentConfig, BuilderImageComponentConfig, BuilderLinkComponentConfig, BuilderLinkedContainerComponent, BuilderLinkedContainerConfig, BuilderListComponentConfig, BuilderListItemComponentConfig, BuilderMode, BuilderRepeaterComponent, BuilderSpacerComponentConfig, ButtonBlockComponentConfig, CardComponentConfig, ChildLayoutGroupStyleConfig, ComponentConfig, ComponentRegistryService, DEFAULT_ESSENTIAL_TRAITS, DRAG_HANDLE_ATTR, DataSourceRegistryService, DataSourceType, DimensionsGroupStyleConfig, DragEngineService, EASING_OPTIONS$1 as EASING_OPTIONS, ESSENTIAL_TRAITS_BY_TYPE, EffectsGroupStyleConfig, GalleryConfig, GroupStyleConfig, INTERACTION_STATES, INVERSE_ACTION, IOX_DATA_SOURCE_ENDPOINTS, IOX_FONT_MANAGER, InteractionEngineService, InteractionPresetRegistryService, IoxBuilderModule, IoxDraggableDirective, IoxDropzoneDirective, LayerTreeComponent, LayoutGroupStyleConfig, ListGroupStyleConfig, NodeAction, OverlayComponent, OverlayService, PanelChildComponent, PanelComponent, PanelEventService, PanelEventTypes, PanelTypes, PositionGroupStyleConfig, PresetRegistryService, RELATIONAL_STATES, RELATIONAL_STATE_BASE, ROUTE_ANIMATION_OPTIONS, RenderDirective, RepeaterComponentConfig, SCREEN_WIDTH_OPTIONS, STRUCTURAL_STATES, SUPPORTED_STATES, SectionComponent, SectionComponentConfig, SizeGroupStyleConfig, SliderContainerComponent, SliderContainerConfig, SliderSlideComponent, SliderSlideConfig, SpacingGroupStyleConfig, StyleCategory, StyleRegistryService, TraitConfig as StyleTraitConfig, SymbolRegistryService, TARGETED_STATES, TARGETED_STATE_BASE, TRIGGER_OPTIONS, TextBlockComponentConfig, ToolbarAction, ToolbarComponent, TraitConfig, TraitInputType, TypographyGroupStyleConfig, UNITS_ALL, UNITS_DEG, UNITS_FIXED, UNITS_NO_VW, ViewportService, ZOOM_OPTIONS, arrayItemSchemaFields, arraySchemaFields, buildFullStyleTraits, buildPresetStyleTraits, canFluidise, collapseColumnsProps, columnCountOf, defaultPageSettings, dropTargetLabel, essentialTraitsFor, findNodeLocation, findParentOf, flattenSchemaFields, fluidiseProps, generateNodeId, isHiddenAt, matchesControlSearch, nodeAncestorPath, nodeAncestorPathInEither, reorderProps, resolveInsertTarget, resolveTraitControllerType, resolveTraitOptions, stackChildrenProps, visibilityProps, wrapNode };
|
|
9628
|
+
export { ACTION_TYPE_OPTIONS, AuditCode, AuditPanelComponent, AuditSeverity, BREAKPOINT_PRESENTATION, BUILDER_CANVAS_CONTAINER, BackgroundGroupStyleConfig, BorderGroupStyleConfig, BreadcrumbBarComponent, BuilderButtonBlockComponent, BuilderButtonComponentConfig, BuilderClipboardService, BuilderComponent, BuilderContainerComponent, BuilderContainerComponentConfig, BuilderDataSourceService, BuilderDividerComponentConfig, BuilderHeadingComponentConfig, BuilderHistoryService, BuilderIconComponentConfig, BuilderImageComponentConfig, BuilderLinkComponentConfig, BuilderLinkedContainerComponent, BuilderLinkedContainerConfig, BuilderListComponentConfig, BuilderListItemComponentConfig, BuilderMode, BuilderRepeaterComponent, BuilderSpacerComponentConfig, ButtonBlockComponentConfig, CardComponentConfig, ChildLayoutGroupStyleConfig, ComponentConfig, ComponentRegistryService, DEFAULT_ESSENTIAL_TRAITS, DRAG_HANDLE_ATTR, DataSourceRegistryService, DataSourceType, DimensionsGroupStyleConfig, DragEngineService, EASING_OPTIONS$1 as EASING_OPTIONS, ESSENTIAL_TRAITS_BY_TYPE, EffectsGroupStyleConfig, GalleryConfig, GroupStyleConfig, INTERACTION_STATES, INVERSE_ACTION, IOX_DATA_SOURCE_ENDPOINTS, IOX_FONT_MANAGER, InteractionEngineService, InteractionPresetRegistryService, IoxBuilderModule, IoxDraggableDirective, IoxDropzoneDirective, LAYOUT_PRESETS, LayerTreeComponent, LayoutGroupStyleConfig, ListGroupStyleConfig, NodeAction, OverlayComponent, OverlayService, PanelChildComponent, PanelComponent, PanelEventService, PanelEventTypes, PanelTypes, PositionGroupStyleConfig, PresetRegistryService, RELATIONAL_STATES, RELATIONAL_STATE_BASE, ROUTE_ANIMATION_OPTIONS, RenderDirective, RepeaterComponentConfig, SCREEN_WIDTH_OPTIONS, STRUCTURAL_STATES, SUPPORTED_STATES, SectionComponent, SectionComponentConfig, SizeGroupStyleConfig, SliderContainerComponent, SliderContainerConfig, SliderSlideComponent, SliderSlideConfig, SpacingGroupStyleConfig, StyleCategory, StyleRegistryService, TraitConfig as StyleTraitConfig, SymbolRegistryService, TARGETED_STATES, TARGETED_STATE_BASE, TRIGGER_OPTIONS, TextBlockComponentConfig, ToolbarAction, ToolbarComponent, TraitConfig, TraitInputType, TypographyGroupStyleConfig, UNITS_ALL, UNITS_DEG, UNITS_FIXED, UNITS_NO_VW, ViewportService, ZOOM_OPTIONS, arrayItemSchemaFields, arraySchemaFields, auditNodes, buildFullStyleTraits, buildLayoutPreset, buildPresetStyleTraits, canFluidise, collapseColumnsProps, columnCountOf, defaultPageSettings, dropTargetLabel, essentialTraitsFor, findNodeLocation, findParentOf, flattenSchemaFields, fluidiseProps, generateNodeId, isHiddenAt, matchesControlSearch, nodeAncestorPath, nodeAncestorPathInEither, reorderProps, resolveInsertTarget, resolveTraitControllerType, resolveTraitOptions, setStyleTrait, stackChildrenProps, summarise, visibilityProps, wrapNode };
|
|
9242
9629
|
//# sourceMappingURL=vectoriox-iox-builder.mjs.map
|