@elementor/editor-audits 4.3.3 → 4.3.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/dist/index.d.mts +11 -1
  2. package/dist/index.d.ts +11 -1
  3. package/dist/index.js +1103 -1146
  4. package/dist/index.js.map +1 -1
  5. package/dist/index.mjs +1075 -1108
  6. package/dist/index.mjs.map +1 -1
  7. package/package.json +17 -15
  8. package/src/api/page-context-client.ts +8 -5
  9. package/src/audits/accessibility-statement.ts +39 -0
  10. package/src/audits/accessibility-widget.ts +41 -0
  11. package/src/audits/cookie-policy.ts +1 -0
  12. package/src/audits/google-consent-mode.ts +4 -7
  13. package/src/audits/images-alt-text.ts +6 -4
  14. package/src/audits/images-inefficient-format.ts +66 -0
  15. package/src/audits/images-too-large.ts +23 -10
  16. package/src/audits/nested-boxed-containers.ts +5 -0
  17. package/src/components/audit-feedback.tsx +4 -1
  18. package/src/components/audit-panel.tsx +13 -13
  19. package/src/components/audit-toggle-icon.tsx +22 -0
  20. package/src/components/fix-violation-with-angie.tsx +18 -7
  21. package/src/components/pages/category-page.tsx +27 -3
  22. package/src/components/pages/overview-page.tsx +24 -29
  23. package/src/components/report-shell.tsx +10 -91
  24. package/src/components/rescan-button.tsx +38 -0
  25. package/src/components/severity-icons.tsx +5 -1
  26. package/src/components/severity-summary-cards.tsx +52 -0
  27. package/src/components/violation-cta-button.tsx +11 -2
  28. package/src/components/violation-icons.tsx +2 -2
  29. package/src/components/violation-row.tsx +120 -62
  30. package/src/hooks/use-audit-report.ts +5 -4
  31. package/src/hooks/use-audit-toggle-props.ts +2 -2
  32. package/src/hooks/use-single-expanded-card.ts +11 -0
  33. package/src/init.ts +2 -0
  34. package/src/register-audits.ts +6 -2
  35. package/src/runner.ts +15 -7
  36. package/src/store/selectors.ts +4 -0
  37. package/src/store/slice.ts +10 -1
  38. package/src/sync-report-staleness.ts +14 -0
  39. package/src/types.ts +11 -1
  40. package/src/utils/all-image-sources.ts +17 -0
  41. package/src/utils/alt-text-image-sources.ts +24 -0
  42. package/src/utils/atomic-background-image-sources.ts +56 -0
  43. package/src/utils/atomic-image-sources.ts +66 -0
  44. package/src/utils/audit-status-summary.ts +0 -57
  45. package/src/utils/background-image-sources.ts +32 -0
  46. package/src/utils/image-alt.ts +3 -1
  47. package/src/utils/image-like-sources.ts +5 -14
  48. package/src/utils/image-size-key.ts +8 -0
  49. package/src/utils/page-attachments.ts +7 -6
  50. package/src/utils/scan-accessibility-widgets.ts +7 -0
  51. package/src/audits/too-many-widgets.ts +0 -53
  52. package/src/components/audit-statuses-section.tsx +0 -65
  53. package/src/components/count-summary-circle.tsx +0 -66
  54. package/src/components/pages/all-audits-page.tsx +0 -65
  55. package/src/components/pages/issues-page.tsx +0 -65
  56. package/src/components/score-bar.tsx +0 -56
  57. package/src/components/score-circle.tsx +0 -60
  58. package/src/components/severity-filter-chips.tsx +0 -57
  59. package/src/utils/score-thresholds.ts +0 -23
package/dist/index.js CHANGED
@@ -37,33 +37,138 @@ __export(index_exports, {
37
37
  module.exports = __toCommonJS(index_exports);
38
38
 
39
39
  // src/init.ts
40
- var import_editor_floating_panels3 = require("@elementor/editor-floating-panels");
41
- var import_store4 = require("@elementor/store");
40
+ var import_editor_floating_panels6 = require("@elementor/editor-floating-panels");
41
+ var import_store7 = require("@elementor/store");
42
42
 
43
43
  // src/editor-app-bar.tsx
44
44
  var import_editor_app_bar = require("@elementor/editor-app-bar");
45
45
 
46
46
  // src/hooks/use-audit-toggle-props.ts
47
- var import_icons12 = require("@elementor/icons");
48
- var import_i18n24 = require("@wordpress/i18n");
47
+ var import_i18n18 = require("@wordpress/i18n");
48
+
49
+ // src/components/audit-toggle-icon.tsx
50
+ var React = __toESM(require("react"));
51
+ var import_icons = require("@elementor/icons");
52
+ var import_store2 = require("@elementor/store");
53
+ var import_ui = require("@elementor/ui");
54
+
55
+ // src/store/slice.ts
56
+ var import_store = require("@elementor/store");
57
+ var initialState = { status: "idle", report: null, error: null, isStale: false };
58
+ var slice = (0, import_store.__createSlice)({
59
+ name: "audits",
60
+ initialState,
61
+ reducers: {
62
+ runStarted(state) {
63
+ state.status = "loading";
64
+ state.error = null;
65
+ },
66
+ runSucceeded(state, action) {
67
+ state.status = "ready";
68
+ state.report = action.payload;
69
+ state.isStale = false;
70
+ },
71
+ runFailed(state, action) {
72
+ state.status = "error";
73
+ state.error = action.payload;
74
+ },
75
+ runAborted(state) {
76
+ state.status = state.report ? "ready" : "idle";
77
+ },
78
+ reportRestored(state, action) {
79
+ state.status = "ready";
80
+ state.report = action.payload;
81
+ state.error = null;
82
+ state.isStale = false;
83
+ },
84
+ reportCleared(state) {
85
+ state.status = "idle";
86
+ state.report = null;
87
+ state.error = null;
88
+ state.isStale = false;
89
+ },
90
+ reportStale(state) {
91
+ if (state.report) {
92
+ state.isStale = true;
93
+ }
94
+ }
95
+ }
96
+ });
97
+
98
+ // src/store/selectors.ts
99
+ function selectStatus(state) {
100
+ return state.audits.status;
101
+ }
102
+ function selectReport(state) {
103
+ return state.audits.report;
104
+ }
105
+ function selectError(state) {
106
+ return state.audits.error;
107
+ }
108
+ function selectIsStale(state) {
109
+ return state.audits.isStale;
110
+ }
111
+
112
+ // src/components/audit-toggle-icon.tsx
113
+ function AuditToggleIcon() {
114
+ const isStale = (0, import_store2.__useSelector)((state) => selectIsStale(state));
115
+ return /* @__PURE__ */ React.createElement(
116
+ import_ui.Badge,
117
+ {
118
+ variant: "dot",
119
+ color: "warning",
120
+ invisible: !isStale,
121
+ overlap: "circular",
122
+ slotProps: { badge: { "data-testid": "audit-stale-indicator" } }
123
+ },
124
+ /* @__PURE__ */ React.createElement(import_icons.ShieldCheckIcon, null)
125
+ );
126
+ }
49
127
 
50
128
  // src/editor-panel.tsx
51
- var import_editor_floating_panels2 = require("@elementor/editor-floating-panels");
129
+ var import_editor_floating_panels5 = require("@elementor/editor-floating-panels");
52
130
 
53
131
  // src/components/audit-panel.tsx
54
- var React25 = __toESM(require("react"));
55
- var import_editor_elements5 = require("@elementor/editor-elements");
56
- var import_editor_floating_panels = require("@elementor/editor-floating-panels");
57
- var import_ui24 = require("@elementor/ui");
58
- var import_i18n23 = require("@wordpress/i18n");
132
+ var React21 = __toESM(require("react"));
133
+ var import_editor_elements6 = require("@elementor/editor-elements");
134
+ var import_editor_floating_panels4 = require("@elementor/editor-floating-panels");
135
+ var import_ui20 = require("@elementor/ui");
136
+ var import_i18n17 = require("@wordpress/i18n");
137
+
138
+ // src/constants.ts
139
+ var import_i18n = require("@wordpress/i18n");
140
+ var AUDIT_PANEL_ID = "audit-panel";
141
+ var REPORT_STORAGE_KEY_PREFIX = "elementor/audits/report";
142
+ var CREATE_WIDGET_EVENT = "elementor/editor/create-widget";
143
+ var ANGIE_FIX_ENTRY_POINT = "audit_violation";
144
+ var FEEDBACK_EXPERIMENT_NAME = "in_editor_feedback";
145
+ var FEEDBACK_ENTRY_POINT = "audit_panel";
146
+ var FEEDBACK_CLICKED_EVENT = "audit_feedback_clicked";
147
+ var FEEDBACK_SENT_EVENT = "audit_feedback_sent";
148
+ var FEEDBACK_CANCELLED_EVENT = "audit_feedback_cancelled";
149
+ var FEEDBACK_CLOSED_EVENT = "audit_feedback_closed";
150
+ var ALL_CATEGORIES = [
151
+ "best-practices",
152
+ "seo",
153
+ "accessibility",
154
+ "performance",
155
+ "compliance"
156
+ ];
157
+ var CATEGORY_LABELS = {
158
+ "best-practices": (0, import_i18n.__)("Best Practices", "elementor"),
159
+ seo: (0, import_i18n.__)("SEO", "elementor"),
160
+ accessibility: (0, import_i18n.__)("Accessibility", "elementor"),
161
+ performance: (0, import_i18n.__)("Performance", "elementor"),
162
+ compliance: (0, import_i18n.__)("Site compliance", "elementor")
163
+ };
59
164
 
60
165
  // src/hooks/use-audit-report.ts
61
166
  var import_react = require("react");
62
- var import_editor_elements2 = require("@elementor/editor-elements");
63
- var import_store2 = require("@elementor/store");
167
+ var import_editor_elements3 = require("@elementor/editor-elements");
168
+ var import_store4 = require("@elementor/store");
64
169
 
65
170
  // src/runner.ts
66
- var import_editor_elements = require("@elementor/editor-elements");
171
+ var import_editor_elements2 = require("@elementor/editor-elements");
67
172
 
68
173
  // src/api/page-context-client.ts
69
174
  var import_http_client = require("@elementor/http-client");
@@ -115,17 +220,17 @@ async function requestFreshNonce() {
115
220
  }
116
221
 
117
222
  // src/api/page-context-client.ts
118
- async function fetchPageContext(documentId, attachmentIds) {
119
- return requestPageContext(documentId, attachmentIds, true);
223
+ async function fetchPageContext(documentId, imageSizeRequests) {
224
+ return requestPageContext(documentId, imageSizeRequests, true);
120
225
  }
121
- async function requestPageContext(documentId, attachmentIds, allowNonceRetry) {
226
+ async function requestPageContext(documentId, imageSizeRequests, allowNonceRetry) {
122
227
  const { restNamespace, nonce } = getWindowConfig();
123
228
  const url = `${restNamespace}/audits/page-context`;
124
229
  try {
125
230
  const response = await (0, import_http_client.httpService)().get(url, {
126
231
  params: {
127
232
  document_id: documentId,
128
- attachment_ids: attachmentIds
233
+ image_size_requests: imageSizeRequests
129
234
  },
130
235
  headers: { "X-WP-Nonce": nonce }
131
236
  });
@@ -135,52 +240,22 @@ async function requestPageContext(documentId, attachmentIds, allowNonceRetry) {
135
240
  throw error;
136
241
  }
137
242
  await refreshAuditsNonce();
138
- return requestPageContext(documentId, attachmentIds, false);
243
+ return requestPageContext(documentId, imageSizeRequests, false);
139
244
  }
140
245
  }
141
246
 
142
247
  // src/registry.ts
143
248
  var registry = /* @__PURE__ */ new Map();
144
- function registerAudit(audit23) {
145
- registry.set(audit23.id, audit23);
249
+ function registerAudit(audit25) {
250
+ registry.set(audit25.id, audit25);
146
251
  }
147
252
  function getRegisteredAudits() {
148
253
  return Array.from(registry.values());
149
254
  }
150
255
 
151
- // src/constants.ts
152
- var import_i18n = require("@wordpress/i18n");
153
- var AUDIT_PANEL_ID = "audit-panel";
154
- var REPORT_STORAGE_KEY_PREFIX = "elementor/audits/report";
155
- var CREATE_WIDGET_EVENT = "elementor/editor/create-widget";
156
- var ANGIE_FIX_ENTRY_POINT = "audit_violation";
157
- var FEEDBACK_EXPERIMENT_NAME = "in_editor_feedback";
158
- var FEEDBACK_ENTRY_POINT = "audit_panel";
159
- var FEEDBACK_CLICKED_EVENT = "audit_feedback_clicked";
160
- var FEEDBACK_SENT_EVENT = "audit_feedback_sent";
161
- var FEEDBACK_CANCELLED_EVENT = "audit_feedback_cancelled";
162
- var FEEDBACK_CLOSED_EVENT = "audit_feedback_closed";
163
- var ALL_CATEGORIES = [
164
- "best-practices",
165
- "seo",
166
- "accessibility",
167
- "performance",
168
- "compliance"
169
- ];
170
- var CATEGORY_LABELS = {
171
- "best-practices": (0, import_i18n.__)("Best Practices", "elementor"),
172
- seo: (0, import_i18n.__)("SEO", "elementor"),
173
- accessibility: (0, import_i18n.__)("Accessibility", "elementor"),
174
- performance: (0, import_i18n.__)("Performance", "elementor"),
175
- compliance: (0, import_i18n.__)("Site compliance", "elementor")
176
- };
177
-
178
- // src/utils/audit-status-summary.ts
179
- var import_i18n2 = require("@wordpress/i18n");
180
-
181
256
  // src/utils/is-scored-audit.ts
182
- function isScoredAudit(audit23) {
183
- return audit23.weight > 0;
257
+ function isScoredAudit(audit25) {
258
+ return audit25.weight > 0;
184
259
  }
185
260
 
186
261
  // src/utils/sort-failed-audits.ts
@@ -232,52 +307,6 @@ function partitionAuditResults(report, options = {}) {
232
307
  totalViolations
233
308
  };
234
309
  }
235
- function auditStatusDisplayCounts(report) {
236
- let pass = 0;
237
- let skipped = 0;
238
- let totalViolations = 0;
239
- for (const { audit: audit23, result } of report.auditResults) {
240
- if (!isScoredAudit(audit23)) {
241
- continue;
242
- }
243
- switch (result.status) {
244
- case "fail":
245
- totalViolations += result.violations.length;
246
- break;
247
- case "pass":
248
- pass++;
249
- break;
250
- case "skipped":
251
- skipped++;
252
- break;
253
- }
254
- }
255
- return {
256
- fail: totalViolations,
257
- pass,
258
- skipped
259
- };
260
- }
261
- function auditStatusColor(status) {
262
- switch (status) {
263
- case "fail":
264
- return "error";
265
- case "pass":
266
- return "success";
267
- case "skipped":
268
- return "default";
269
- }
270
- }
271
- function auditStatusLabel(status) {
272
- switch (status) {
273
- case "fail":
274
- return (0, import_i18n2.__)("Failed audits", "elementor");
275
- case "pass":
276
- return (0, import_i18n2.__)("Passed audits", "elementor");
277
- case "skipped":
278
- return (0, import_i18n2.__)("Skipped audits", "elementor");
279
- }
280
- }
281
310
  function getPopulatedCategories(categoryTotals, categories) {
282
311
  return categories.filter((category) => categoryTotals[category].total > 0);
283
312
  }
@@ -287,16 +316,16 @@ function computeReport(documentId, results) {
287
316
  const accumulators = Object.fromEntries(
288
317
  ALL_CATEGORIES.map((c) => [c, { totalWeight: 0, passedWeight: 0, total: 0, failed: 0 }])
289
318
  );
290
- for (const { audit: audit23, result } of results) {
319
+ for (const { audit: audit25, result } of results) {
291
320
  if (result.status === "skipped") {
292
321
  continue;
293
322
  }
294
- for (const category of audit23.categories) {
323
+ for (const category of audit25.categories) {
295
324
  const acc = accumulators[category];
296
325
  acc.total++;
297
- acc.totalWeight += audit23.weight;
326
+ acc.totalWeight += audit25.weight;
298
327
  if (result.status === "pass") {
299
- acc.passedWeight += audit23.weight;
328
+ acc.passedWeight += audit25.weight;
300
329
  } else {
301
330
  acc.failed++;
302
331
  }
@@ -320,6 +349,34 @@ function computeReport(documentId, results) {
320
349
  };
321
350
  }
322
351
 
352
+ // src/utils/fetch-rendered-html.ts
353
+ var FETCH_TIMEOUT_MS = 1e4;
354
+ async function fetchRenderedHtml(url) {
355
+ if (!url) {
356
+ return null;
357
+ }
358
+ const timeoutController = new AbortController();
359
+ const timeoutId = setTimeout(() => timeoutController.abort(), FETCH_TIMEOUT_MS);
360
+ try {
361
+ const response = await fetch(url, { credentials: "omit", signal: timeoutController.signal });
362
+ if (!response.ok) {
363
+ return null;
364
+ }
365
+ return await response.text();
366
+ } catch {
367
+ return null;
368
+ } finally {
369
+ clearTimeout(timeoutId);
370
+ }
371
+ }
372
+
373
+ // src/utils/atomic-background-image-sources.ts
374
+ var import_editor_elements = require("@elementor/editor-elements");
375
+ var import_editor_props2 = require("@elementor/editor-props");
376
+
377
+ // src/utils/atomic-image-sources.ts
378
+ var import_editor_props = require("@elementor/editor-props");
379
+
323
380
  // src/utils/walk.ts
324
381
  function walkElements(tree, visit) {
325
382
  const parents = [];
@@ -334,17 +391,93 @@ function recurse(nodes, parents, visit) {
334
391
  }
335
392
  }
336
393
 
337
- // src/utils/image-like-sources.ts
338
- var IMAGE_LIKE_WIDGETS = /* @__PURE__ */ new Set(["image", "image-box", "image-carousel", "gallery", "image-gallery"]);
339
- function hasPageImages(tree) {
340
- let found = false;
341
- walkImageLikeSources(tree, ({ media }) => {
342
- if (media.id || media.url) {
343
- found = true;
394
+ // src/utils/atomic-image-sources.ts
395
+ var ATOMIC_CONTENT_IMAGE_PROP_KEYS = ["image"];
396
+ var ATOMIC_IMAGE_PROP_KEYS = [...ATOMIC_CONTENT_IMAGE_PROP_KEYS, "poster"];
397
+ function walkAtomicImageSources(tree, visit, propKeys = ATOMIC_IMAGE_PROP_KEYS) {
398
+ walkElements(tree, (node) => {
399
+ if (node.elType !== "widget") {
400
+ return;
401
+ }
402
+ for (const key of propKeys) {
403
+ const media = extractAtomicImageMedia(node.settings[key]);
404
+ if (media) {
405
+ visit({ node, media });
406
+ }
407
+ }
408
+ });
409
+ }
410
+ function extractAtomicImageMedia(imageProp) {
411
+ const image = import_editor_props.imagePropTypeUtil.extract(imageProp);
412
+ if (!image) {
413
+ return null;
414
+ }
415
+ const src = import_editor_props.imageSrcPropTypeUtil.extract(image.src);
416
+ if (!src) {
417
+ return null;
418
+ }
419
+ const id = import_editor_props.imageAttachmentIdPropType.extract(src.id) ?? void 0;
420
+ const url = import_editor_props.urlPropTypeUtil.extract(src.url) ?? void 0;
421
+ if (!id && !url) {
422
+ return null;
423
+ }
424
+ const alt = import_editor_props.stringPropTypeUtil.extract(src.alt) ?? void 0;
425
+ const size = import_editor_props.stringPropTypeUtil.extract(image.size) ?? void 0;
426
+ return { id, url, alt, size };
427
+ }
428
+
429
+ // src/utils/atomic-background-image-sources.ts
430
+ function walkAtomicBackgroundImageSources(tree, visit) {
431
+ walkElements(tree, (node) => {
432
+ const styles = (0, import_editor_elements.getElementStyles)(node.id);
433
+ if (!styles) {
434
+ return;
435
+ }
436
+ for (const media of extractBackgroundImageMedia(styles)) {
437
+ visit({ node, media });
438
+ }
439
+ });
440
+ }
441
+ function extractBackgroundImageMedia(styles) {
442
+ const sources = [];
443
+ for (const style of Object.values(styles)) {
444
+ for (const variant of style.variants) {
445
+ const background = import_editor_props2.backgroundPropTypeUtil.extract(variant.props.background);
446
+ const overlays = import_editor_props2.backgroundOverlayPropTypeUtil.extract(background?.["background-overlay"]) ?? [];
447
+ for (const overlay of overlays) {
448
+ const imageOverlay = import_editor_props2.backgroundImageOverlayPropTypeUtil.extract(overlay);
449
+ const media = extractAtomicImageMedia(imageOverlay?.image);
450
+ if (media) {
451
+ sources.push(media);
452
+ }
453
+ }
454
+ }
455
+ }
456
+ return sources;
457
+ }
458
+
459
+ // src/utils/background-image-sources.ts
460
+ var BACKGROUND_IMAGE_SETTING_KEYS = [
461
+ "background_image",
462
+ "background_hover_image",
463
+ "background_overlay_image",
464
+ "background_overlay_hover_image",
465
+ "_background_image",
466
+ "_background_hover_image"
467
+ ];
468
+ function walkBackgroundImageSources(tree, visit) {
469
+ walkElements(tree, (node) => {
470
+ for (const key of BACKGROUND_IMAGE_SETTING_KEYS) {
471
+ const media = node.settings[key];
472
+ if (media?.id || media?.url) {
473
+ visit({ node, media });
474
+ }
344
475
  }
345
476
  });
346
- return found;
347
477
  }
478
+
479
+ // src/utils/image-like-sources.ts
480
+ var IMAGE_LIKE_WIDGETS = /* @__PURE__ */ new Set(["image", "image-box", "image-carousel", "gallery", "image-gallery"]);
348
481
  function walkImageLikeSources(tree, visit) {
349
482
  walkElements(tree, (node) => {
350
483
  if (node.elType !== "widget") {
@@ -362,28 +495,42 @@ function collectMediaFromNode(node) {
362
495
  const sources = [];
363
496
  const image = node.settings.image;
364
497
  if (image?.id || image?.url) {
365
- sources.push(image);
498
+ sources.push({ ...image, size: node.settings.image_size });
366
499
  }
367
500
  const gallery = node.settings.carousel ?? node.settings.gallery ?? node.settings.wp_gallery;
368
501
  if (Array.isArray(gallery)) {
502
+ const gallerySize = node.settings.thumbnail_size;
369
503
  for (const item of gallery) {
370
504
  if (item?.id || item?.url) {
371
- sources.push(item);
505
+ sources.push({ ...item, size: gallerySize });
372
506
  }
373
507
  }
374
508
  }
375
509
  return sources;
376
510
  }
377
511
 
512
+ // src/utils/all-image-sources.ts
513
+ function walkAllImageSources(tree, visit) {
514
+ walkImageLikeSources(tree, visit);
515
+ walkBackgroundImageSources(tree, visit);
516
+ walkAtomicImageSources(tree, visit);
517
+ walkAtomicBackgroundImageSources(tree, visit);
518
+ }
519
+
520
+ // src/utils/image-size-key.ts
521
+ function buildImageSizeKey({ id, size }) {
522
+ return `${id}:${size || "full"}`;
523
+ }
524
+
378
525
  // src/utils/page-attachments.ts
379
- function extractAttachmentIds(tree) {
380
- const ids = /* @__PURE__ */ new Set();
381
- walkImageLikeSources(tree, ({ media }) => {
526
+ function extractImageSizeRequests(tree) {
527
+ const keys = /* @__PURE__ */ new Set();
528
+ walkAllImageSources(tree, ({ media }) => {
382
529
  if (media.id) {
383
- ids.add(media.id);
530
+ keys.add(buildImageSizeKey({ id: media.id, size: media.size }));
384
531
  }
385
532
  });
386
- return Array.from(ids).sort((a, b) => a - b);
533
+ return Array.from(keys).sort();
387
534
  }
388
535
 
389
536
  // src/utils/read-kit-snapshot.ts
@@ -477,18 +624,24 @@ function toSnapshot(element) {
477
624
 
478
625
  // src/runner.ts
479
626
  async function runPageAudit(documentId) {
480
- const tree = buildSnapshotTree((0, import_editor_elements.getElements)());
481
- const attachmentIds = extractAttachmentIds(tree);
482
- const pageContext = await fetchPageContext(documentId, attachmentIds);
627
+ if (documentId !== (0, import_editor_elements2.getHostDocumentId)()) {
628
+ throw new Error("runPageAudit: documentId must match the host document.");
629
+ }
630
+ const tree = buildSnapshotTree((0, import_editor_elements2.getHostDocumentElements)());
631
+ const imageSizeRequests = extractImageSizeRequests(tree);
632
+ const pageContext = await fetchPageContext(documentId, imageSizeRequests);
483
633
  const elements = { documentId, tree };
484
- const kit = await readKitSnapshot(pageContext.kit_id);
485
- const ctx = { documentId, elements, pageContext, kit };
634
+ const [kit, renderedHtml] = await Promise.all([
635
+ readKitSnapshot(pageContext.kit_id),
636
+ fetchRenderedHtml(pageContext.frontend_url)
637
+ ]);
638
+ const ctx = { documentId, elements, pageContext, kit, renderedHtml };
486
639
  const registered = getRegisteredAudits();
487
640
  const auditResults = await Promise.all(
488
- registered.map(async (audit23) => {
489
- const { evaluate: _evaluate, ...meta } = audit23;
641
+ registered.map(async (audit25) => {
642
+ const { evaluate: _evaluate, ...meta } = audit25;
490
643
  try {
491
- const result = await audit23.evaluate(ctx);
644
+ const result = await audit25.evaluate(ctx);
492
645
  return { audit: meta, result };
493
646
  } catch (error) {
494
647
  const reason = error instanceof Error ? error.message : "unknown-error";
@@ -499,52 +652,6 @@ async function runPageAudit(documentId) {
499
652
  return computeReport(documentId, auditResults);
500
653
  }
501
654
 
502
- // src/store/slice.ts
503
- var import_store = require("@elementor/store");
504
- var initialState = { status: "idle", report: null, error: null };
505
- var slice = (0, import_store.__createSlice)({
506
- name: "audits",
507
- initialState,
508
- reducers: {
509
- runStarted(state) {
510
- state.status = "loading";
511
- state.error = null;
512
- },
513
- runSucceeded(state, action) {
514
- state.status = "ready";
515
- state.report = action.payload;
516
- },
517
- runFailed(state, action) {
518
- state.status = "error";
519
- state.error = action.payload;
520
- },
521
- runAborted(state) {
522
- state.status = state.report ? "ready" : "idle";
523
- },
524
- reportRestored(state, action) {
525
- state.status = "ready";
526
- state.report = action.payload;
527
- state.error = null;
528
- },
529
- reportCleared(state) {
530
- state.status = "idle";
531
- state.report = null;
532
- state.error = null;
533
- }
534
- }
535
- });
536
-
537
- // src/store/selectors.ts
538
- function selectStatus(state) {
539
- return state.audits.status;
540
- }
541
- function selectReport(state) {
542
- return state.audits.report;
543
- }
544
- function selectError(state) {
545
- return state.audits.error;
546
- }
547
-
548
655
  // src/utils/report-storage.ts
549
656
  var import_session = require("@elementor/session");
550
657
  function getStorageKey(documentId) {
@@ -559,51 +666,53 @@ function persistReport(documentId, report) {
559
666
 
560
667
  // src/hooks/use-audit-report.ts
561
668
  function useAuditReport() {
562
- const status = (0, import_store2.__useSelector)((state) => selectStatus(state));
563
- const report = (0, import_store2.__useSelector)((state) => selectReport(state));
564
- const error = (0, import_store2.__useSelector)((state) => selectError(state));
565
- const dispatch = (0, import_store2.__useDispatch)();
566
- const documentId = (0, import_editor_elements2.getCurrentDocumentId)() ?? 0;
669
+ const status = (0, import_store4.__useSelector)((state) => selectStatus(state));
670
+ const report = (0, import_store4.__useSelector)((state) => selectReport(state));
671
+ const error = (0, import_store4.__useSelector)((state) => selectError(state));
672
+ const isStale = (0, import_store4.__useSelector)((state) => selectIsStale(state));
673
+ const dispatch2 = (0, import_store4.__useDispatch)();
674
+ const documentId = (0, import_editor_elements3.getHostDocumentId)() ?? 0;
567
675
  (0, import_react.useEffect)(() => {
568
676
  if (!documentId || report?.documentId === documentId) {
569
677
  return;
570
678
  }
571
679
  const persisted = getPersistedReport(documentId);
572
680
  if (persisted) {
573
- dispatch(slice.actions.reportRestored(persisted));
681
+ dispatch2(slice.actions.reportRestored(persisted));
574
682
  } else if (report) {
575
- dispatch(slice.actions.reportCleared());
683
+ dispatch2(slice.actions.reportCleared());
576
684
  }
577
- }, [documentId, report, dispatch]);
685
+ }, [documentId, report, dispatch2]);
578
686
  const run = async (documentIdToRun) => {
579
- dispatch(slice.actions.runStarted());
687
+ dispatch2(slice.actions.runStarted());
580
688
  try {
581
689
  const nextReport = await runPageAudit(documentIdToRun);
582
- dispatch(slice.actions.runSucceeded(nextReport));
690
+ dispatch2(slice.actions.runSucceeded(nextReport));
583
691
  persistReport(documentIdToRun, nextReport);
584
692
  } catch (e) {
585
693
  if (e instanceof SessionExpiredError) {
586
- dispatch(slice.actions.runAborted());
694
+ dispatch2(slice.actions.runAborted());
587
695
  return;
588
696
  }
589
- dispatch(slice.actions.runFailed(e instanceof Error ? e.message : "Unknown error"));
697
+ dispatch2(slice.actions.runFailed(e instanceof Error ? e.message : "Unknown error"));
590
698
  }
591
699
  };
592
- return { status, report, error, run };
700
+ return { status, report, error, isStale, run };
593
701
  }
594
702
 
595
703
  // src/components/audit-feedback.tsx
596
- var React = __toESM(require("react"));
704
+ var React2 = __toESM(require("react"));
597
705
  var import_react2 = require("react");
706
+ var import_editor_floating_panels = require("@elementor/editor-floating-panels");
598
707
  var import_editor_notifications = require("@elementor/editor-notifications");
599
708
  var import_editor_v1_adapters = require("@elementor/editor-v1-adapters");
600
709
  var import_events = require("@elementor/events");
601
710
  var import_http_client2 = require("@elementor/http-client");
602
- var import_icons = require("@elementor/icons");
603
- var import_ui = require("@elementor/ui");
604
- var import_i18n3 = require("@wordpress/i18n");
711
+ var import_icons2 = require("@elementor/icons");
712
+ var import_ui2 = require("@elementor/ui");
713
+ var import_i18n2 = require("@wordpress/i18n");
605
714
  var FEEDBACK_SUBJECT = "Page Audit Tool";
606
- var FEEDBACK_SUBJECT_LABEL = (0, import_i18n3.__)("Page Audit Tool", "elementor");
715
+ var FEEDBACK_SUBJECT_LABEL = (0, import_i18n2.__)("Page Audit Tool", "elementor");
607
716
  var SUCCESS_NOTIFICATION_ID = "audit-feedback-success";
608
717
  var ERROR_NOTIFICATION_ID = "audit-feedback-error";
609
718
  var DIALOG_TITLE_ID = "audit-feedback-dialog-title";
@@ -612,18 +721,19 @@ var isUserConnected = () => Boolean(window.elementorCommon?.config?.library_conn
612
721
  var notifySuccess = () => (0, import_editor_notifications.notify)({
613
722
  id: SUCCESS_NOTIFICATION_ID,
614
723
  type: "success",
615
- message: (0, import_i18n3.__)("Feedback sent. Thanks for helping us out.", "elementor")
724
+ message: (0, import_i18n2.__)("Feedback sent. Thanks for helping us out.", "elementor")
616
725
  });
617
726
  var notifyError = () => (0, import_editor_notifications.notify)({
618
727
  id: ERROR_NOTIFICATION_ID,
619
728
  type: "error",
620
- message: (0, import_i18n3.__)("Something went wrong. Please try sending your feedback again.", "elementor")
729
+ message: (0, import_i18n2.__)("Something went wrong. Please try sending your feedback again.", "elementor")
621
730
  });
622
731
  function AuditFeedback() {
623
732
  const [isOpen, setIsOpen] = (0, import_react2.useState)(false);
624
733
  const [feedbackText, setFeedbackText] = (0, import_react2.useState)("");
625
734
  const [isSubmitting, setIsSubmitting] = (0, import_react2.useState)(false);
626
735
  const { dispatchEvent: trackEvent = (...args) => void args } = (0, import_events.useMixpanel)();
736
+ const panelZIndex = (0, import_editor_floating_panels.useFloatingPanelZIndex)(AUDIT_PANEL_ID);
627
737
  if (!(0, import_editor_v1_adapters.isExperimentActive)(FEEDBACK_EXPERIMENT_NAME)) {
628
738
  return null;
629
739
  }
@@ -632,7 +742,7 @@ function AuditFeedback() {
632
742
  if (!connected && !connectUrl) {
633
743
  return null;
634
744
  }
635
- const triggerLabel = (0, import_i18n3.__)("Give feedback", "elementor");
745
+ const triggerLabel = (0, import_i18n2.__)("Give feedback", "elementor");
636
746
  const handleOpen = () => {
637
747
  if (!connected) {
638
748
  window.open(connectUrl, "_blank", "noopener");
@@ -665,8 +775,8 @@ function AuditFeedback() {
665
775
  trackEvent(FEEDBACK_SENT_EVENT, { entry_point: FEEDBACK_ENTRY_POINT });
666
776
  }).catch(notifyError).finally(() => setIsSubmitting(false));
667
777
  };
668
- return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(import_ui.Tooltip, { title: triggerLabel, placement: "top" }, /* @__PURE__ */ React.createElement(import_ui.IconButton, { size: "small", "aria-label": triggerLabel, onClick: handleOpen }, /* @__PURE__ */ React.createElement(import_icons.MessageLinesIcon, { fontSize: "small" }))), /* @__PURE__ */ React.createElement(
669
- import_ui.Dialog,
778
+ return /* @__PURE__ */ React2.createElement(React2.Fragment, null, /* @__PURE__ */ React2.createElement(import_ui2.Tooltip, { title: triggerLabel, placement: "top", PopperProps: { sx: { zIndex: panelZIndex } } }, /* @__PURE__ */ React2.createElement(import_ui2.IconButton, { size: "small", "aria-label": triggerLabel, onClick: handleOpen }, /* @__PURE__ */ React2.createElement(import_icons2.MessageLinesIcon, { fontSize: "small" }))), /* @__PURE__ */ React2.createElement(
779
+ import_ui2.Dialog,
670
780
  {
671
781
  open: isOpen,
672
782
  onClose: handleClose,
@@ -674,11 +784,11 @@ function AuditFeedback() {
674
784
  "aria-describedby": DIALOG_DESCRIPTION_ID,
675
785
  PaperProps: { sx: { borderRadius: 1 } }
676
786
  },
677
- /* @__PURE__ */ React.createElement(import_ui.DialogHeader, { logo: false, onClose: handleClose }, /* @__PURE__ */ React.createElement(import_ui.DialogTitle, { id: DIALOG_TITLE_ID }, (0, import_i18n3.__)("Let us know what you think", "elementor"))),
678
- /* @__PURE__ */ React.createElement(import_ui.DialogContent, { dividers: true }, /* @__PURE__ */ React.createElement(import_ui.Stack, { direction: "column", gap: 2 }, /* @__PURE__ */ React.createElement(import_ui.TextField, { value: FEEDBACK_SUBJECT_LABEL, disabled: true, fullWidth: true, size: "small" }), /* @__PURE__ */ React.createElement(
679
- import_ui.TextField,
787
+ /* @__PURE__ */ React2.createElement(import_ui2.DialogHeader, { logo: false, onClose: handleClose }, /* @__PURE__ */ React2.createElement(import_ui2.DialogTitle, { id: DIALOG_TITLE_ID }, (0, import_i18n2.__)("Let us know what you think", "elementor"))),
788
+ /* @__PURE__ */ React2.createElement(import_ui2.DialogContent, { dividers: true }, /* @__PURE__ */ React2.createElement(import_ui2.Stack, { direction: "column", gap: 2 }, /* @__PURE__ */ React2.createElement(import_ui2.TextField, { value: FEEDBACK_SUBJECT_LABEL, disabled: true, fullWidth: true, size: "small" }), /* @__PURE__ */ React2.createElement(
789
+ import_ui2.TextField,
680
790
  {
681
- placeholder: (0, import_i18n3.__)(
791
+ placeholder: (0, import_i18n2.__)(
682
792
  "Tell us what you had in mind, what we can improve or fix to make this feature better.",
683
793
  "elementor"
684
794
  ),
@@ -689,48 +799,48 @@ function AuditFeedback() {
689
799
  onChange: (event) => setFeedbackText(event.target.value),
690
800
  value: feedbackText
691
801
  }
692
- ), /* @__PURE__ */ React.createElement(import_ui.Typography, { id: DIALOG_DESCRIPTION_ID, variant: "caption", color: "text.secondary" }, (0, import_i18n3.__)(
802
+ ), /* @__PURE__ */ React2.createElement(import_ui2.Typography, { id: DIALOG_DESCRIPTION_ID, variant: "caption", color: "text.secondary" }, (0, import_i18n2.__)(
693
803
  "We appreciate your feedback! While we review all submissions, we can't guarantee that every suggestion will result in a change or update.",
694
804
  "elementor"
695
805
  )))),
696
- /* @__PURE__ */ React.createElement(import_ui.DialogActions, null, /* @__PURE__ */ React.createElement(import_ui.Button, { variant: "text", color: "secondary", onClick: handleCancel }, (0, import_i18n3.__)("Cancel", "elementor")), /* @__PURE__ */ React.createElement(
697
- import_ui.Button,
806
+ /* @__PURE__ */ React2.createElement(import_ui2.DialogActions, null, /* @__PURE__ */ React2.createElement(import_ui2.Button, { variant: "text", color: "secondary", onClick: handleCancel }, (0, import_i18n2.__)("Cancel", "elementor")), /* @__PURE__ */ React2.createElement(
807
+ import_ui2.Button,
698
808
  {
699
809
  variant: "contained",
700
810
  color: "primary",
701
811
  disabled: isSubmitting || feedbackText.trim().length === 0,
702
812
  onClick: handleSubmit
703
813
  },
704
- (0, import_i18n3.__)("Send", "elementor")
814
+ (0, import_i18n2.__)("Send", "elementor")
705
815
  ))
706
816
  ));
707
817
  }
708
818
 
709
819
  // src/components/pages/error-page.tsx
710
- var React2 = __toESM(require("react"));
711
- var import_ui2 = require("@elementor/ui");
712
- var import_i18n4 = require("@wordpress/i18n");
820
+ var React3 = __toESM(require("react"));
821
+ var import_ui3 = require("@elementor/ui");
822
+ var import_i18n3 = require("@wordpress/i18n");
713
823
  function ErrorPage({ message, onRetry }) {
714
- return /* @__PURE__ */ React2.createElement(import_ui2.Box, { sx: { display: "flex", flexDirection: "column", alignItems: "center", gap: 1.5, p: 4 } }, /* @__PURE__ */ React2.createElement(import_ui2.Typography, { variant: "body2", color: "error", textAlign: "center" }, message), /* @__PURE__ */ React2.createElement(import_ui2.Button, { variant: "contained", size: "small", onClick: onRetry }, (0, import_i18n4.__)("Try again", "elementor")));
824
+ return /* @__PURE__ */ React3.createElement(import_ui3.Box, { sx: { display: "flex", flexDirection: "column", alignItems: "center", gap: 1.5, p: 4 } }, /* @__PURE__ */ React3.createElement(import_ui3.Typography, { variant: "body2", color: "error", textAlign: "center" }, message), /* @__PURE__ */ React3.createElement(import_ui3.Button, { variant: "contained", size: "small", onClick: onRetry }, (0, import_i18n3.__)("Try again", "elementor")));
715
825
  }
716
826
 
717
827
  // src/components/pages/loading-page.tsx
718
- var React3 = __toESM(require("react"));
719
- var import_ui3 = require("@elementor/ui");
720
- var import_i18n5 = require("@wordpress/i18n");
828
+ var React4 = __toESM(require("react"));
829
+ var import_ui4 = require("@elementor/ui");
830
+ var import_i18n4 = require("@wordpress/i18n");
721
831
  function LoadingPage() {
722
- return /* @__PURE__ */ React3.createElement(import_ui3.Box, { sx: { display: "flex", flexDirection: "column", alignItems: "center", gap: 1.5, p: 4 } }, /* @__PURE__ */ React3.createElement(import_ui3.CircularProgress, { size: 32 }), /* @__PURE__ */ React3.createElement(import_ui3.Typography, { variant: "body2" }, (0, import_i18n5.__)("Audit in progress.", "elementor")));
832
+ return /* @__PURE__ */ React4.createElement(import_ui4.Box, { sx: { display: "flex", flexDirection: "column", alignItems: "center", gap: 1.5, p: 4 } }, /* @__PURE__ */ React4.createElement(import_ui4.CircularProgress, { size: 32 }), /* @__PURE__ */ React4.createElement(import_ui4.Typography, { variant: "body2" }, (0, import_i18n4.__)("Audit in progress.", "elementor")));
723
833
  }
724
834
 
725
835
  // src/components/pages/welcome-page.tsx
726
- var React4 = __toESM(require("react"));
727
- var import_ui4 = require("@elementor/ui");
728
- var import_i18n6 = require("@wordpress/i18n");
836
+ var React5 = __toESM(require("react"));
837
+ var import_ui5 = require("@elementor/ui");
838
+ var import_i18n5 = require("@wordpress/i18n");
729
839
  var INFORMATION_IMAGE_PATH = "images/information.svg";
730
840
  function WelcomePage() {
731
841
  const assetsUrl = window.elementorCommon?.config?.urls?.assets ?? "";
732
- return /* @__PURE__ */ React4.createElement(
733
- import_ui4.Box,
842
+ return /* @__PURE__ */ React5.createElement(
843
+ import_ui5.Box,
734
844
  {
735
845
  sx: {
736
846
  display: "flex",
@@ -741,17 +851,17 @@ function WelcomePage() {
741
851
  p: 3
742
852
  }
743
853
  },
744
- /* @__PURE__ */ React4.createElement(
854
+ /* @__PURE__ */ React5.createElement(
745
855
  "img",
746
856
  {
747
857
  className: "elementor-nerd-box-icon",
748
858
  src: `${assetsUrl}${INFORMATION_IMAGE_PATH}`,
749
859
  loading: "lazy",
750
- alt: (0, import_i18n6.__)("Elementor", "elementor")
860
+ alt: (0, import_i18n5.__)("Elementor", "elementor")
751
861
  }
752
862
  ),
753
- /* @__PURE__ */ React4.createElement(import_ui4.Typography, { variant: "subtitle1", component: "h3", sx: { marginBlockStart: 2 } }, (0, import_i18n6.__)("Audit your page!", "elementor")),
754
- /* @__PURE__ */ React4.createElement(import_ui4.Typography, { variant: "caption", textAlign: "center" }, (0, import_i18n6.__)(
863
+ /* @__PURE__ */ React5.createElement(import_ui5.Typography, { variant: "subtitle1", component: "h3", sx: { marginBlockStart: 2 } }, (0, import_i18n5.__)("Audit your page!", "elementor")),
864
+ /* @__PURE__ */ React5.createElement(import_ui5.Typography, { variant: "caption", textAlign: "center" }, (0, import_i18n5.__)(
755
865
  'Check SEO, accessibility, performance, and best practices. Click "Run page audit" to begin.',
756
866
  "elementor"
757
867
  ))
@@ -759,41 +869,60 @@ function WelcomePage() {
759
869
  }
760
870
 
761
871
  // src/components/report-shell.tsx
762
- var React24 = __toESM(require("react"));
763
- var import_react6 = require("react");
764
- var import_ui23 = require("@elementor/ui");
765
- var import_i18n22 = require("@wordpress/i18n");
872
+ var React19 = __toESM(require("react"));
873
+ var import_react5 = require("react");
874
+ var import_ui18 = require("@elementor/ui");
766
875
 
767
- // src/components/pages/all-audits-page.tsx
768
- var React12 = __toESM(require("react"));
769
- var import_ui11 = require("@elementor/ui");
770
- var import_i18n12 = require("@wordpress/i18n");
876
+ // src/components/pages/category-page.tsx
877
+ var React13 = __toESM(require("react"));
878
+ var import_ui12 = require("@elementor/ui");
879
+ var import_i18n11 = require("@wordpress/i18n");
771
880
 
772
- // src/components/status-section.tsx
773
- var React5 = __toESM(require("react"));
881
+ // src/hooks/use-single-expanded-card.ts
774
882
  var import_react3 = require("react");
775
- var import_icons2 = require("@elementor/icons");
776
- var import_ui5 = require("@elementor/ui");
777
- var import_i18n7 = require("@wordpress/i18n");
883
+ function useSingleExpandedCard(initialId = null) {
884
+ const [expandedId, setExpandedId] = (0, import_react3.useState)(initialId);
885
+ const toggle = (id) => {
886
+ setExpandedId((current) => current === id ? null : id);
887
+ };
888
+ return { expandedId, toggle };
889
+ }
890
+
891
+ // src/components/category-icons.ts
892
+ var import_icons3 = require("@elementor/icons");
893
+ var CATEGORY_ICONS = {
894
+ "best-practices": import_icons3.Settings2Icon,
895
+ seo: import_icons3.FileSearchIcon,
896
+ accessibility: import_icons3.ElementorAccessibilityIcon,
897
+ performance: import_icons3.BoltIcon,
898
+ compliance: import_icons3.ShieldCheckIcon
899
+ };
900
+
901
+ // src/components/status-section.tsx
902
+ var React6 = __toESM(require("react"));
903
+ var import_react4 = require("react");
904
+ var import_icons4 = require("@elementor/icons");
905
+ var import_ui6 = require("@elementor/ui");
906
+ var import_i18n6 = require("@wordpress/i18n");
778
907
  function StatusSection({ label, count, color = "default", defaultExpanded = false, children }) {
779
- const [expanded, setExpanded] = (0, import_react3.useState)(defaultExpanded);
780
- if (React5.Children.count(children) === 0) {
908
+ const [expanded, setExpanded] = (0, import_react4.useState)(defaultExpanded);
909
+ if (React6.Children.count(children) === 0) {
781
910
  return null;
782
911
  }
783
- return /* @__PURE__ */ React5.createElement(import_ui5.Box, { sx: { paddingBlock: 1 } }, /* @__PURE__ */ React5.createElement(
784
- import_ui5.Box,
912
+ return /* @__PURE__ */ React6.createElement(import_ui6.Box, { sx: { paddingBlock: 1 } }, /* @__PURE__ */ React6.createElement(
913
+ import_ui6.Box,
785
914
  {
786
915
  sx: { display: "flex", alignItems: "center", gap: 0.5, cursor: "pointer" },
787
916
  onClick: () => setExpanded((v) => !v)
788
917
  },
789
- /* @__PURE__ */ React5.createElement(
790
- import_ui5.IconButton,
918
+ /* @__PURE__ */ React6.createElement(
919
+ import_ui6.IconButton,
791
920
  {
792
921
  size: "small",
793
- "aria-label": expanded ? (0, import_i18n7.__)("Collapse", "elementor") : (0, import_i18n7.__)("Expand", "elementor")
922
+ "aria-label": expanded ? (0, import_i18n6.__)("Collapse", "elementor") : (0, import_i18n6.__)("Expand", "elementor")
794
923
  },
795
- /* @__PURE__ */ React5.createElement(
796
- import_icons2.ChevronDownIcon,
924
+ /* @__PURE__ */ React6.createElement(
925
+ import_icons4.ChevronDownIcon,
797
926
  {
798
927
  fontSize: "small",
799
928
  sx: {
@@ -803,37 +932,37 @@ function StatusSection({ label, count, color = "default", defaultExpanded = fals
803
932
  }
804
933
  )
805
934
  ),
806
- /* @__PURE__ */ React5.createElement(import_ui5.Chip, { label: `${label} (${count})`, size: "small", color, variant: "standard" })
807
- ), /* @__PURE__ */ React5.createElement(import_ui5.Collapse, { in: expanded }, /* @__PURE__ */ React5.createElement(import_ui5.Box, { sx: { paddingInlineStart: 4.25, paddingBlock: 2 } }, children)));
935
+ /* @__PURE__ */ React6.createElement(import_ui6.Chip, { label: `${label} (${count})`, size: "small", color, variant: "standard" })
936
+ ), /* @__PURE__ */ React6.createElement(import_ui6.Collapse, { in: expanded }, /* @__PURE__ */ React6.createElement(import_ui6.Box, { sx: { paddingInlineStart: 4.25, paddingBlock: 2 } }, children)));
808
937
  }
809
938
 
810
939
  // src/components/subpage-header.tsx
811
- var React6 = __toESM(require("react"));
812
- var import_icons3 = require("@elementor/icons");
813
- var import_ui6 = require("@elementor/ui");
814
- var import_i18n8 = require("@wordpress/i18n");
940
+ var React7 = __toESM(require("react"));
941
+ var import_icons5 = require("@elementor/icons");
942
+ var import_ui7 = require("@elementor/ui");
943
+ var import_i18n7 = require("@wordpress/i18n");
815
944
  function SubpageHeader({ title, onBack, backLabel, icon }) {
816
- const isRtl = "rtl" === (0, import_ui6.useTheme)().direction;
817
- return /* @__PURE__ */ React6.createElement(import_ui6.Box, { sx: { display: "flex", alignItems: "center", gap: 0.5, p: 1 } }, /* @__PURE__ */ React6.createElement(import_ui6.IconButton, { size: "small", onClick: onBack, "aria-label": backLabel ?? (0, import_i18n8.__)("Back", "elementor") }, /* @__PURE__ */ React6.createElement(import_ui6.Rotate, { in: isRtl }, /* @__PURE__ */ React6.createElement(import_icons3.ArrowLeftIcon, { fontSize: "small" }))), icon, /* @__PURE__ */ React6.createElement(import_ui6.Typography, { variant: "subtitle2", fontWeight: "bold" }, title));
945
+ const isRtl = "rtl" === (0, import_ui7.useTheme)().direction;
946
+ return /* @__PURE__ */ React7.createElement(import_ui7.Box, { sx: { display: "flex", alignItems: "center", gap: 0.5, p: 1 } }, /* @__PURE__ */ React7.createElement(import_ui7.IconButton, { size: "small", onClick: onBack, "aria-label": backLabel ?? (0, import_i18n7.__)("Back", "elementor") }, /* @__PURE__ */ React7.createElement(import_ui7.Rotate, { in: isRtl }, /* @__PURE__ */ React7.createElement(import_icons5.ArrowLeftIcon, { fontSize: "small" }))), icon, /* @__PURE__ */ React7.createElement(import_ui7.Typography, { variant: "subtitle2", fontWeight: "bold" }, title));
818
947
  }
819
948
 
820
949
  // src/components/violation-row.tsx
821
- var React11 = __toESM(require("react"));
822
- var import_react4 = require("react");
823
- var import_editor_elements4 = require("@elementor/editor-elements");
824
- var import_icons7 = require("@elementor/icons");
825
- var import_ui10 = require("@elementor/ui");
826
- var import_i18n11 = require("@wordpress/i18n");
950
+ var React12 = __toESM(require("react"));
951
+ var import_editor_elements5 = require("@elementor/editor-elements");
952
+ var import_editor_floating_panels2 = require("@elementor/editor-floating-panels");
953
+ var import_icons10 = require("@elementor/icons");
954
+ var import_ui11 = require("@elementor/ui");
955
+ var import_i18n10 = require("@wordpress/i18n");
827
956
 
828
957
  // src/hooks/focus-violation.ts
829
- var import_editor_elements3 = require("@elementor/editor-elements");
958
+ var import_editor_elements4 = require("@elementor/editor-elements");
830
959
  var import_editor_v1_adapters2 = require("@elementor/editor-v1-adapters");
831
960
  var SCROLL_TO_VIEW_DELAY_MS = 200;
832
961
  function focusViolation(violation) {
833
962
  if (violation.elementId) {
834
- const container = (0, import_editor_elements3.getContainer)(violation.elementId);
963
+ const container = (0, import_editor_elements4.getContainer)(violation.elementId);
835
964
  if (container) {
836
- (0, import_editor_elements3.selectElement)(violation.elementId);
965
+ (0, import_editor_elements4.selectElement)(violation.elementId);
837
966
  const domElement = container.view?.getDomElement?.();
838
967
  if (domElement) {
839
968
  window.elementor?.helpers?.scrollToView?.(domElement, SCROLL_TO_VIEW_DELAY_MS);
@@ -864,11 +993,11 @@ function focusViolation(violation) {
864
993
  }
865
994
 
866
995
  // src/utils/build-angie-prompt.ts
867
- var import_i18n9 = require("@wordpress/i18n");
996
+ var import_i18n8 = require("@wordpress/i18n");
868
997
  function buildAngiePrompt(violationText) {
869
- return (0, import_i18n9.sprintf)(
998
+ return (0, import_i18n8.sprintf)(
870
999
  /* translators: %s: audit violation description. */
871
- (0, import_i18n9.__)("Help me fix: %s", "elementor"),
1000
+ (0, import_i18n8.__)("Help me fix: %s", "elementor"),
872
1001
  violationText
873
1002
  );
874
1003
  }
@@ -884,13 +1013,13 @@ function onKeyboardClick(callback) {
884
1013
  }
885
1014
 
886
1015
  // src/components/fix-violation-with-angie.tsx
887
- var React7 = __toESM(require("react"));
1016
+ var React8 = __toESM(require("react"));
888
1017
  var import_editor_mcp = require("@elementor/editor-mcp");
889
- var import_icons4 = require("@elementor/icons");
890
- var import_ui7 = require("@elementor/ui");
891
- var import_i18n10 = require("@wordpress/i18n");
892
- function FixViolationWithAngie({ prompt }) {
893
- const label = (0, import_i18n10.__)("Fix with Angie", "elementor");
1018
+ var import_icons6 = require("@elementor/icons");
1019
+ var import_ui8 = require("@elementor/ui");
1020
+ var import_i18n9 = require("@wordpress/i18n");
1021
+ function FixViolationWithAngie({ prompt, panelZIndex, variant = "icon" }) {
1022
+ const label = (0, import_i18n9.__)("Fix with Angie", "elementor");
894
1023
  const href = `#angie-prompt=${encodeURIComponent(prompt)}`;
895
1024
  const handleClick = (event) => {
896
1025
  event.stopPropagation();
@@ -908,53 +1037,70 @@ function FixViolationWithAngie({ prompt }) {
908
1037
  })
909
1038
  );
910
1039
  };
911
- return /* @__PURE__ */ React7.createElement(import_ui7.Tooltip, { title: label }, /* @__PURE__ */ React7.createElement(
912
- import_ui7.IconButton,
913
- {
914
- className: "violation-hover-icon",
915
- component: "a",
916
- href,
917
- size: "small",
918
- "aria-label": label,
919
- onClick: handleClick
920
- },
921
- /* @__PURE__ */ React7.createElement(import_icons4.AngieIcon, { fontSize: "tiny" })
922
- ));
1040
+ if (variant === "button") {
1041
+ return /* @__PURE__ */ React8.createElement(
1042
+ import_ui8.Button,
1043
+ {
1044
+ component: "a",
1045
+ href,
1046
+ size: "small",
1047
+ color: "secondary",
1048
+ variant: "outlined",
1049
+ startIcon: /* @__PURE__ */ React8.createElement(import_icons6.AngieIcon, { fontSize: "tiny", "aria-hidden": true }),
1050
+ onClick: handleClick
1051
+ },
1052
+ label
1053
+ );
1054
+ }
1055
+ return /* @__PURE__ */ React8.createElement(import_ui8.Tooltip, { title: label, placement: "top", PopperProps: { sx: { zIndex: panelZIndex } } }, /* @__PURE__ */ React8.createElement(import_ui8.IconButton, { component: "a", href, size: "small", "aria-label": label, onClick: handleClick }, /* @__PURE__ */ React8.createElement(import_icons6.AngieIcon, { fontSize: "tiny" })));
923
1056
  }
924
1057
 
925
1058
  // src/components/severity-icons.tsx
926
- var React8 = __toESM(require("react"));
927
- var import_icons5 = require("@elementor/icons");
1059
+ var React9 = __toESM(require("react"));
1060
+ var import_icons7 = require("@elementor/icons");
928
1061
  var SEVERITY_CONFIG = {
929
- error: { Icon: import_icons5.AlertTriangleFilledIcon, color: "error" },
930
- warning: { Icon: import_icons5.AlertCircleIcon, color: "warning" },
931
- info: { Icon: import_icons5.BulbIcon, color: "info" }
1062
+ error: { Icon: import_icons7.AlertTriangleFilledIcon, color: "error" },
1063
+ warning: { Icon: import_icons7.AlertCircleIcon, color: "warning" },
1064
+ info: { Icon: import_icons7.BulbIcon, color: "info" }
932
1065
  };
1066
+ var INFO_ICON_VERTICAL_NUDGE = "translateY(-2px)";
933
1067
  function SeverityIcon({ severity }) {
934
1068
  const { Icon, color } = SEVERITY_CONFIG[severity];
935
- return /* @__PURE__ */ React8.createElement(Icon, { fontSize: "small", color });
1069
+ const sx = "info" === severity ? { transform: INFO_ICON_VERTICAL_NUDGE } : void 0;
1070
+ return /* @__PURE__ */ React9.createElement(Icon, { fontSize: "small", color, sx });
936
1071
  }
937
1072
 
938
1073
  // src/components/violation-cta-button.tsx
939
- var React9 = __toESM(require("react"));
940
- var import_ui8 = require("@elementor/ui");
941
- function ViolationCtaButton({ ctaLabel, externalUrl }) {
1074
+ var React10 = __toESM(require("react"));
1075
+ var import_icons8 = require("@elementor/icons");
1076
+ var import_ui9 = require("@elementor/ui");
1077
+ function ViolationCtaButton({ ctaLabel, externalUrl, variant = "outlined", withIcon = false }) {
942
1078
  const handleClick = (event) => {
943
1079
  event.stopPropagation();
944
1080
  event.preventDefault();
945
1081
  window.open(externalUrl, "_blank", "noopener");
946
1082
  };
947
- return /* @__PURE__ */ React9.createElement(import_ui8.Button, { variant: "outlined", color: "secondary", size: "small", onClick: handleClick }, ctaLabel);
1083
+ return /* @__PURE__ */ React10.createElement(
1084
+ import_ui9.Button,
1085
+ {
1086
+ variant,
1087
+ color: "secondary",
1088
+ size: "small",
1089
+ startIcon: withIcon ? /* @__PURE__ */ React10.createElement(import_icons8.WandIcon, { fontSize: "tiny", "aria-hidden": true }) : void 0,
1090
+ onClick: handleClick
1091
+ },
1092
+ ctaLabel
1093
+ );
948
1094
  }
949
1095
 
950
1096
  // src/components/violation-icons.tsx
951
- var React10 = __toESM(require("react"));
952
- var import_icons6 = require("@elementor/icons");
953
- var import_ui9 = require("@elementor/ui");
1097
+ var React11 = __toESM(require("react"));
1098
+ var import_icons9 = require("@elementor/icons");
1099
+ var import_ui10 = require("@elementor/ui");
954
1100
  function ViolationIcon({ violation, widgetIcon }) {
955
1101
  if (widgetIcon) {
956
- return /* @__PURE__ */ React10.createElement(
957
- import_ui9.Box,
1102
+ return /* @__PURE__ */ React11.createElement(
1103
+ import_ui10.Box,
958
1104
  {
959
1105
  component: "i",
960
1106
  className: widgetIcon,
@@ -964,29 +1110,74 @@ function ViolationIcon({ violation, widgetIcon }) {
964
1110
  );
965
1111
  }
966
1112
  if (violation.targetHint === "page-settings") {
967
- return /* @__PURE__ */ React10.createElement(import_icons6.FileSettingsIcon, { fontSize: "inherit", "aria-hidden": true });
1113
+ return /* @__PURE__ */ React11.createElement(import_icons9.FileSettingsIcon, { fontSize: "inherit", "aria-hidden": true });
968
1114
  }
969
1115
  if (violation.targetHint === "site-settings" || violation.targetHint === "site-identity-settings") {
970
- return /* @__PURE__ */ React10.createElement(import_icons6.SettingsIcon, { fontSize: "inherit", "aria-hidden": true });
1116
+ return /* @__PURE__ */ React11.createElement(import_icons9.SettingsIcon, { fontSize: "inherit", "aria-hidden": true });
971
1117
  }
972
- return /* @__PURE__ */ React10.createElement(import_icons6.ShieldCheckIcon, { fontSize: "inherit", "aria-hidden": true });
1118
+ return /* @__PURE__ */ React11.createElement(import_icons9.SettingsIcon, { fontSize: "inherit", "aria-hidden": true });
973
1119
  }
974
1120
 
975
1121
  // src/components/violation-row.tsx
976
1122
  function SkipReasonTooltip({ reason }) {
977
- return /* @__PURE__ */ React11.createElement(import_ui10.Tooltip, { title: reason, placement: "top" }, /* @__PURE__ */ React11.createElement(import_ui10.Box, { "aria-label": reason, component: "span", sx: { display: "inline-flex", alignItems: "center" } }, /* @__PURE__ */ React11.createElement(import_icons7.HelpIcon, { fontSize: "small", color: "action" })));
1123
+ return /* @__PURE__ */ React12.createElement(import_ui11.Tooltip, { title: reason, placement: "top" }, /* @__PURE__ */ React12.createElement(import_ui11.Box, { "aria-label": reason, component: "span", sx: { display: "inline-flex", alignItems: "center" } }, /* @__PURE__ */ React12.createElement(import_icons10.HelpIcon, { fontSize: "small", color: "action" })));
978
1124
  }
979
- function StatusIndicator({ audit: audit23, violations }) {
1125
+ function StatusIndicator({ audit: audit25, violations }) {
980
1126
  if (violations) {
981
- return /* @__PURE__ */ React11.createElement(React11.Fragment, null, isScoredAudit(audit23) && /* @__PURE__ */ React11.createElement(import_ui10.Typography, { variant: "caption", color: "text.secondary", fontWeight: "bold" }, violations.length), /* @__PURE__ */ React11.createElement(SeverityIcon, { severity: audit23.severity }));
1127
+ return /* @__PURE__ */ React12.createElement(React12.Fragment, null, /* @__PURE__ */ React12.createElement(SeverityIcon, { severity: audit25.severity }), isScoredAudit(audit25) && /* @__PURE__ */ React12.createElement(import_ui11.Typography, { variant: "caption", color: "text.secondary", fontWeight: "bold" }, violations.length));
1128
+ }
1129
+ return /* @__PURE__ */ React12.createElement(import_icons10.CheckIcon, { fontSize: "small", color: "success" });
1130
+ }
1131
+ function GuidanceAction({
1132
+ violation,
1133
+ sharedAngiePrompt,
1134
+ panelZIndex
1135
+ }) {
1136
+ const hasPrimaryCta = !!(violation.ctaLabel && violation.externalUrl);
1137
+ if (!hasPrimaryCta && !sharedAngiePrompt) {
1138
+ return null;
1139
+ }
1140
+ return /* @__PURE__ */ React12.createElement(import_ui11.Box, { display: "flex", alignItems: "center", gap: 1, mt: 1 }, hasPrimaryCta && /* @__PURE__ */ React12.createElement(
1141
+ ViolationCtaButton,
1142
+ {
1143
+ ctaLabel: violation.ctaLabel,
1144
+ externalUrl: violation.externalUrl,
1145
+ withIcon: true
1146
+ }
1147
+ ), hasPrimaryCta && violation.secondaryCtaLabel && violation.secondaryCtaUrl && /* @__PURE__ */ React12.createElement(
1148
+ ViolationCtaButton,
1149
+ {
1150
+ ctaLabel: violation.secondaryCtaLabel,
1151
+ externalUrl: violation.secondaryCtaUrl,
1152
+ variant: "text"
1153
+ }
1154
+ ), sharedAngiePrompt && /* @__PURE__ */ React12.createElement(FixViolationWithAngie, { prompt: sharedAngiePrompt, panelZIndex, variant: "button" }));
1155
+ }
1156
+ function getSharedAngiePrompt(violations) {
1157
+ if (!violations || violations.length < 2) {
1158
+ return void 0;
1159
+ }
1160
+ const [first, ...rest] = violations;
1161
+ if (!first.angieFix || !first.angiePrompt) {
1162
+ return void 0;
982
1163
  }
983
- return /* @__PURE__ */ React11.createElement(import_icons7.CheckIcon, { fontSize: "small", color: "success" });
1164
+ const allShareTheSameFix = rest.every(
1165
+ (violation) => violation.angieFix && violation.angiePrompt === first.angiePrompt
1166
+ );
1167
+ return allShareTheSameFix ? first.angiePrompt : void 0;
984
1168
  }
985
- function ViolationRow({ audit: audit23, skipReason, violations }) {
986
- const [expanded, setExpanded] = (0, import_react4.useState)(false);
987
- const toggleExpanded = () => setExpanded((value) => !value);
988
- return /* @__PURE__ */ React11.createElement(import_ui10.Box, { sx: { borderBottom: 1, borderColor: "divider", paddingBlock: 0.5 } }, /* @__PURE__ */ React11.createElement(import_ui10.Box, { sx: { display: "flex", alignItems: "center", gap: 0.5 } }, /* @__PURE__ */ React11.createElement(
989
- import_ui10.Box,
1169
+ function ViolationRow({ audit: audit25, expanded, onToggleExpand, skipReason, violations }) {
1170
+ const primaryViolation = violations?.[0];
1171
+ const isGuidanceFocusable = !!primaryViolation;
1172
+ const panelZIndex = (0, import_editor_floating_panels2.useFloatingPanelZIndex)(AUDIT_PANEL_ID);
1173
+ const sharedAngiePrompt = getSharedAngiePrompt(violations);
1174
+ const handleGuidanceClick = () => {
1175
+ if (isGuidanceFocusable && primaryViolation) {
1176
+ focusViolation(primaryViolation);
1177
+ }
1178
+ };
1179
+ return /* @__PURE__ */ React12.createElement(import_ui11.Box, { sx: { borderBottom: 1, borderColor: "divider", paddingBlock: 0.5 } }, /* @__PURE__ */ React12.createElement(import_ui11.Box, { sx: { display: "flex", alignItems: "center", gap: 0.5 } }, /* @__PURE__ */ React12.createElement(
1180
+ import_ui11.Box,
990
1181
  {
991
1182
  sx: {
992
1183
  alignItems: "center",
@@ -996,19 +1187,19 @@ function ViolationRow({ audit: audit23, skipReason, violations }) {
996
1187
  gap: 0.5,
997
1188
  minWidth: 0
998
1189
  },
999
- onClick: toggleExpanded
1190
+ onClick: onToggleExpand
1000
1191
  },
1001
- /* @__PURE__ */ React11.createElement(import_ui10.Typography, { variant: "body2", sx: { flex: 1 } }, audit23.title),
1002
- !skipReason && /* @__PURE__ */ React11.createElement(StatusIndicator, { audit: audit23, violations })
1003
- ), skipReason && /* @__PURE__ */ React11.createElement(SkipReasonTooltip, { reason: skipReason }), /* @__PURE__ */ React11.createElement(
1004
- import_ui10.IconButton,
1192
+ /* @__PURE__ */ React12.createElement(import_ui11.Typography, { variant: "subtitle2", color: "text.secondary", sx: { flex: 1 } }, audit25.title),
1193
+ !skipReason && /* @__PURE__ */ React12.createElement(StatusIndicator, { audit: audit25, violations })
1194
+ ), skipReason && /* @__PURE__ */ React12.createElement(SkipReasonTooltip, { reason: skipReason }), /* @__PURE__ */ React12.createElement(
1195
+ import_ui11.IconButton,
1005
1196
  {
1006
1197
  size: "small",
1007
- "aria-label": expanded ? (0, import_i18n11.__)("Collapse", "elementor") : (0, import_i18n11.__)("Expand", "elementor"),
1008
- onClick: toggleExpanded
1198
+ "aria-label": expanded ? (0, import_i18n10.__)("Collapse", "elementor") : (0, import_i18n10.__)("Expand", "elementor"),
1199
+ onClick: onToggleExpand
1009
1200
  },
1010
- /* @__PURE__ */ React11.createElement(
1011
- import_icons7.ChevronDownIcon,
1201
+ /* @__PURE__ */ React12.createElement(
1202
+ import_icons10.ChevronDownIcon,
1012
1203
  {
1013
1204
  fontSize: "small",
1014
1205
  sx: {
@@ -1017,30 +1208,34 @@ function ViolationRow({ audit: audit23, skipReason, violations }) {
1017
1208
  }
1018
1209
  }
1019
1210
  )
1020
- )), /* @__PURE__ */ React11.createElement(import_ui10.Collapse, { in: expanded }, /* @__PURE__ */ React11.createElement(import_ui10.Box, { sx: { display: "flex", flexDirection: "column", gap: 1, paddingBlock: 1 } }, /* @__PURE__ */ React11.createElement(
1021
- import_ui10.Alert,
1211
+ )), /* @__PURE__ */ React12.createElement(import_ui11.Collapse, { in: expanded }, /* @__PURE__ */ React12.createElement(import_ui11.Box, { paddingBlock: 1 }, /* @__PURE__ */ React12.createElement(import_ui11.Tooltip, { title: audit25.fixHint, placement: "top" }, /* @__PURE__ */ React12.createElement(
1212
+ import_ui11.Alert,
1022
1213
  {
1023
1214
  severity: "secondary",
1024
- sx: { p: 1 },
1025
- icon: /* @__PURE__ */ React11.createElement(import_icons7.AlertCircleIcon, { fontSize: "small", color: "secondary", "aria-hidden": true })
1026
- },
1027
- /* @__PURE__ */ React11.createElement(import_ui10.AlertTitle, null, /* @__PURE__ */ React11.createElement(import_ui10.Typography, { variant: "caption", component: "p", color: "text.primary", fontWeight: "bold" }, (0, import_i18n11.__)("What's the issue", "elementor"))),
1028
- /* @__PURE__ */ React11.createElement(import_ui10.Typography, { variant: "caption", component: "p", color: "text.secondary" }, audit23.description)
1029
- ), /* @__PURE__ */ React11.createElement(
1030
- import_ui10.Alert,
1031
- {
1032
- severity: "info",
1033
- sx: { p: 1 },
1034
- icon: /* @__PURE__ */ React11.createElement(import_icons7.BulbIcon, { fontSize: "small", color: "info", "aria-hidden": true })
1215
+ sx: { p: 1, cursor: isGuidanceFocusable ? "pointer" : void 0 },
1216
+ icon: /* @__PURE__ */ React12.createElement(import_icons10.InfoCircleIcon, { fontSize: "small", "aria-hidden": true }),
1217
+ role: isGuidanceFocusable ? "button" : void 0,
1218
+ tabIndex: isGuidanceFocusable ? 0 : void 0,
1219
+ onClick: isGuidanceFocusable ? handleGuidanceClick : void 0,
1220
+ onKeyDown: isGuidanceFocusable ? onKeyboardClick(handleGuidanceClick) : void 0
1035
1221
  },
1036
- /* @__PURE__ */ React11.createElement(import_ui10.AlertTitle, null, /* @__PURE__ */ React11.createElement(import_ui10.Typography, { variant: "caption", component: "p", color: "text.primary", fontWeight: "bold" }, (0, import_i18n11.__)("How to resolve", "elementor"))),
1037
- /* @__PURE__ */ React11.createElement(import_ui10.Typography, { variant: "caption", component: "p", color: "text.secondary" }, audit23.fixHint)
1038
- )), violations && violations.length > 0 && /* @__PURE__ */ React11.createElement(import_ui10.Box, { role: "list", sx: { paddingBlockEnd: 1, paddingInlineStart: 2 } }, violations.map((violation, idx) => {
1039
- const widgetIcon = violation.elementId ? (0, import_editor_elements4.getElementIcon)(violation.elementId) : null;
1040
- const elementTitle = violation.elementId ? (0, import_editor_elements4.getElementTitle)(violation.elementId) : null;
1041
- const rowLabel = elementTitle ? `${elementTitle} - ${violation.label}` : violation.label;
1042
- return /* @__PURE__ */ React11.createElement(
1043
- import_ui10.Box,
1222
+ /* @__PURE__ */ React12.createElement(import_ui11.Typography, { variant: "caption", component: "p", color: "text.secondary" }, audit25.description),
1223
+ primaryViolation && /* @__PURE__ */ React12.createElement(
1224
+ GuidanceAction,
1225
+ {
1226
+ violation: primaryViolation,
1227
+ sharedAngiePrompt,
1228
+ panelZIndex
1229
+ }
1230
+ )
1231
+ ))), violations && violations.length > 0 && /* @__PURE__ */ React12.createElement(import_ui11.Box, { role: "list", sx: { paddingBlockEnd: 1 } }, violations.map((violation, idx) => {
1232
+ const widgetIcon = violation.elementId ? (0, import_editor_elements5.getElementIcon)(violation.elementId) : null;
1233
+ const elementTitle = violation.elementId ? (0, import_editor_elements5.getElementTitle)(violation.elementId) : null;
1234
+ const displayLabel = elementTitle ?? violation.label;
1235
+ const angiePromptContext = elementTitle ? `${elementTitle} - ${violation.label}` : violation.label;
1236
+ const showsOwnFixAction = !!violation.angieFix && !sharedAngiePrompt;
1237
+ return /* @__PURE__ */ React12.createElement(
1238
+ import_ui11.Box,
1044
1239
  {
1045
1240
  key: idx,
1046
1241
  role: "button",
@@ -1052,258 +1247,130 @@ function ViolationRow({ audit: audit23, skipReason, violations }) {
1052
1247
  alignItems: "center",
1053
1248
  gap: 1,
1054
1249
  paddingBlock: 0.5,
1055
- paddingInline: 2,
1250
+ paddingInline: 1,
1056
1251
  borderRadius: 1,
1057
1252
  cursor: "pointer",
1058
- "& .violation-hover-icon": { opacity: 0, transition: "opacity .15s" },
1059
1253
  "&:hover": { bgcolor: "action.hover" },
1060
- "&:hover .violation-hover-icon, &:focus-visible .violation-hover-icon": {
1254
+ "&:hover .violation-row-fix-action, &:focus-visible .violation-row-fix-action": {
1061
1255
  opacity: 1
1062
1256
  }
1063
1257
  }
1064
1258
  },
1065
- /* @__PURE__ */ React11.createElement(ViolationIcon, { violation, widgetIcon }),
1066
- /* @__PURE__ */ React11.createElement(import_ui10.Box, { sx: { flex: 1 } }, /* @__PURE__ */ React11.createElement(import_ui10.Typography, { variant: "caption" }, rowLabel), violation.detail && /* @__PURE__ */ React11.createElement(import_ui10.Typography, { variant: "caption", color: "text.secondary" }, violation.detail)),
1067
- violation.angieFix && /* @__PURE__ */ React11.createElement(
1068
- FixViolationWithAngie,
1069
- {
1070
- prompt: violation.angiePrompt ?? buildAngiePrompt(rowLabel)
1071
- }
1072
- ),
1073
- violation.ctaLabel && violation.externalUrl ? /* @__PURE__ */ React11.createElement(
1074
- ViolationCtaButton,
1075
- {
1076
- ctaLabel: violation.ctaLabel,
1077
- externalUrl: violation.externalUrl
1078
- }
1079
- ) : /* @__PURE__ */ React11.createElement(
1080
- import_icons7.EyeIcon,
1259
+ /* @__PURE__ */ React12.createElement(ViolationIcon, { violation, widgetIcon }),
1260
+ /* @__PURE__ */ React12.createElement(import_ui11.Box, { sx: { flex: 1 } }, /* @__PURE__ */ React12.createElement(import_ui11.Typography, { variant: "caption" }, displayLabel), violation.detail && /* @__PURE__ */ React12.createElement(import_ui11.Typography, { variant: "caption", color: "text.secondary" }, violation.detail)),
1261
+ showsOwnFixAction && /* @__PURE__ */ React12.createElement(
1262
+ import_ui11.Box,
1081
1263
  {
1082
- className: "violation-hover-icon",
1083
- fontSize: "tiny",
1084
- "aria-hidden": true
1085
- }
1264
+ className: "violation-row-fix-action",
1265
+ sx: { opacity: 0, transition: "opacity .15s" }
1266
+ },
1267
+ /* @__PURE__ */ React12.createElement(
1268
+ FixViolationWithAngie,
1269
+ {
1270
+ prompt: violation.angiePrompt ?? buildAngiePrompt(angiePromptContext),
1271
+ panelZIndex
1272
+ }
1273
+ )
1086
1274
  )
1087
1275
  );
1088
1276
  }))));
1089
1277
  }
1090
1278
 
1091
- // src/components/pages/all-audits-page.tsx
1092
- function AllAuditsPage({ initialExpandedStatus, onBack, report }) {
1093
- const { failed, passed, skipped, totalViolations } = partitionAuditResults(report);
1094
- const expandFail = !initialExpandedStatus || initialExpandedStatus === "fail";
1095
- const expandPass = initialExpandedStatus === "pass";
1096
- const expandSkipped = initialExpandedStatus === "skipped";
1097
- return /* @__PURE__ */ React12.createElement(import_ui11.Box, { key: initialExpandedStatus ?? "default" }, /* @__PURE__ */ React12.createElement(SubpageHeader, { title: (0, import_i18n12.__)("All audits", "elementor"), onBack }), /* @__PURE__ */ React12.createElement(import_ui11.Box, { sx: { p: 1 } }, /* @__PURE__ */ React12.createElement(
1098
- StatusSection,
1099
- {
1100
- label: auditStatusLabel("fail"),
1101
- count: totalViolations,
1102
- color: auditStatusColor("fail"),
1103
- defaultExpanded: expandFail
1104
- },
1105
- failed.map((r) => /* @__PURE__ */ React12.createElement(ViolationRow, { key: r.audit.id, audit: r.audit, violations: r.result.violations }))
1106
- ), /* @__PURE__ */ React12.createElement(
1107
- StatusSection,
1108
- {
1109
- label: auditStatusLabel("pass"),
1110
- count: passed.length,
1111
- color: auditStatusColor("pass"),
1112
- defaultExpanded: expandPass
1113
- },
1114
- passed.map((r) => /* @__PURE__ */ React12.createElement(ViolationRow, { key: r.audit.id, audit: r.audit }))
1115
- ), /* @__PURE__ */ React12.createElement(
1116
- StatusSection,
1117
- {
1118
- label: auditStatusLabel("skipped"),
1119
- count: skipped.length,
1120
- color: auditStatusColor("skipped"),
1121
- defaultExpanded: expandSkipped
1122
- },
1123
- skipped.map((r) => /* @__PURE__ */ React12.createElement(ViolationRow, { key: r.audit.id, audit: r.audit, skipReason: r.result.reason }))
1124
- )));
1125
- }
1126
-
1127
- // src/components/pages/category-page.tsx
1128
- var React13 = __toESM(require("react"));
1129
- var import_ui12 = require("@elementor/ui");
1130
- var import_i18n13 = require("@wordpress/i18n");
1131
-
1132
- // src/components/category-icons.ts
1133
- var import_icons8 = require("@elementor/icons");
1134
- var CATEGORY_ICONS = {
1135
- "best-practices": import_icons8.Settings2Icon,
1136
- seo: import_icons8.FileSearchIcon,
1137
- accessibility: import_icons8.ElementorAccessibilityIcon,
1138
- performance: import_icons8.BoltIcon,
1139
- compliance: import_icons8.ShieldCheckIcon
1140
- };
1141
-
1142
1279
  // src/components/pages/category-page.tsx
1143
1280
  function CategoryPage({ category, report, onBack }) {
1144
1281
  const Icon = CATEGORY_ICONS[category];
1145
- const { failed, passed, totalViolations } = partitionAuditResults(report, { category });
1282
+ const { failed, passed, skipped, totalViolations } = partitionAuditResults(report, { category });
1283
+ const { expandedId, toggle } = useSingleExpandedCard();
1146
1284
  return /* @__PURE__ */ React13.createElement(React13.Fragment, null, /* @__PURE__ */ React13.createElement(
1147
1285
  SubpageHeader,
1148
1286
  {
1149
1287
  title: CATEGORY_LABELS[category],
1150
1288
  onBack,
1151
- backLabel: (0, import_i18n13.__)("Back to all issues", "elementor"),
1289
+ backLabel: (0, import_i18n11.__)("Back to all issues", "elementor"),
1152
1290
  icon: /* @__PURE__ */ React13.createElement(Icon, { fontSize: "small", color: "action" })
1153
1291
  }
1154
1292
  ), /* @__PURE__ */ React13.createElement(import_ui12.Box, { sx: { p: 1 } }, /* @__PURE__ */ React13.createElement(
1155
1293
  StatusSection,
1156
1294
  {
1157
- label: (0, import_i18n13.__)("Failed audits", "elementor"),
1295
+ label: (0, import_i18n11.__)("Failed audits", "elementor"),
1158
1296
  count: totalViolations,
1159
1297
  color: "error",
1160
1298
  defaultExpanded: true
1161
1299
  },
1162
- failed.map((r) => /* @__PURE__ */ React13.createElement(ViolationRow, { key: r.audit.id, audit: r.audit, violations: r.result.violations }))
1163
- ), /* @__PURE__ */ React13.createElement(StatusSection, { label: (0, import_i18n13.__)("Passed audits", "elementor"), count: passed.length, color: "success" }, passed.map((r) => /* @__PURE__ */ React13.createElement(ViolationRow, { key: r.audit.id, audit: r.audit })))));
1300
+ failed.map((r) => /* @__PURE__ */ React13.createElement(
1301
+ ViolationRow,
1302
+ {
1303
+ key: r.audit.id,
1304
+ audit: r.audit,
1305
+ violations: r.result.violations,
1306
+ expanded: expandedId === r.audit.id,
1307
+ onToggleExpand: () => toggle(r.audit.id)
1308
+ }
1309
+ ))
1310
+ ), /* @__PURE__ */ React13.createElement(StatusSection, { label: (0, import_i18n11.__)("Passed audits", "elementor"), count: passed.length, color: "success" }, passed.map((r) => /* @__PURE__ */ React13.createElement(
1311
+ ViolationRow,
1312
+ {
1313
+ key: r.audit.id,
1314
+ audit: r.audit,
1315
+ expanded: expandedId === r.audit.id,
1316
+ onToggleExpand: () => toggle(r.audit.id)
1317
+ }
1318
+ ))), /* @__PURE__ */ React13.createElement(StatusSection, { label: (0, import_i18n11.__)("Skipped audits", "elementor"), count: skipped.length, color: "default" }, skipped.map((r) => /* @__PURE__ */ React13.createElement(
1319
+ ViolationRow,
1320
+ {
1321
+ key: r.audit.id,
1322
+ audit: r.audit,
1323
+ skipReason: r.result.reason,
1324
+ expanded: expandedId === r.audit.id,
1325
+ onToggleExpand: () => toggle(r.audit.id)
1326
+ }
1327
+ )))));
1164
1328
  }
1165
1329
 
1166
- // src/components/pages/issues-page.tsx
1330
+ // src/components/pages/overview-page.tsx
1167
1331
  var React18 = __toESM(require("react"));
1168
- var import_react5 = require("react");
1169
1332
  var import_ui17 = require("@elementor/ui");
1170
- var import_i18n17 = require("@wordpress/i18n");
1333
+ var import_i18n15 = require("@wordpress/i18n");
1171
1334
 
1172
1335
  // src/utils/severity-counts.ts
1173
- var import_i18n14 = require("@wordpress/i18n");
1336
+ var import_i18n12 = require("@wordpress/i18n");
1174
1337
  var ALL_SEVERITIES = ["error", "warning", "info"];
1175
1338
  function countSeverities(report, category) {
1176
1339
  const counts = { error: 0, warning: 0, info: 0 };
1177
- for (const { audit: audit23, result } of report.auditResults) {
1340
+ for (const { audit: audit25, result } of report.auditResults) {
1178
1341
  if (result.status !== "fail") {
1179
1342
  continue;
1180
1343
  }
1181
- if (category && !audit23.categories.includes(category)) {
1344
+ if (category && !audit25.categories.includes(category)) {
1182
1345
  continue;
1183
1346
  }
1184
- if (!isScoredAudit(audit23)) {
1347
+ if (!isScoredAudit(audit25)) {
1185
1348
  continue;
1186
1349
  }
1187
- counts[audit23.severity] += result.violations.length;
1350
+ counts[audit25.severity] += result.violations.length;
1188
1351
  }
1189
1352
  return counts;
1190
1353
  }
1191
1354
  function severityPluralLabel(severity) {
1192
1355
  switch (severity) {
1193
1356
  case "error":
1194
- return (0, import_i18n14.__)("Errors", "elementor");
1357
+ return (0, import_i18n12.__)("Errors", "elementor");
1195
1358
  case "warning":
1196
- return (0, import_i18n14.__)("Warnings", "elementor");
1359
+ return (0, import_i18n12.__)("Warnings", "elementor");
1197
1360
  case "info":
1198
- return (0, import_i18n14.__)("Suggestions", "elementor");
1361
+ return (0, import_i18n12.__)("Suggestions", "elementor");
1199
1362
  }
1200
1363
  }
1201
1364
 
1202
- // src/components/audit-statuses-section.tsx
1203
- var React15 = __toESM(require("react"));
1204
- var import_ui14 = require("@elementor/ui");
1205
- var import_i18n15 = require("@wordpress/i18n");
1206
-
1207
- // src/components/count-summary-circle.tsx
1365
+ // src/components/issues-category-row.tsx
1208
1366
  var React14 = __toESM(require("react"));
1367
+ var import_icons11 = require("@elementor/icons");
1209
1368
  var import_ui13 = require("@elementor/ui");
1210
- var COUNT_SUMMARY_CIRCLE_SIZE = 64;
1211
- var COUNT_SUMMARY_BORDER_WIDTH = 4;
1212
- function chipBorderColor(theme, color) {
1213
- if (color === "default" || !color) {
1214
- return theme.palette.text.secondary;
1215
- }
1216
- return theme.palette[color].main;
1217
- }
1218
- function CountSummaryCircle({ ariaLabel, color, count, label, onClick }) {
1219
- return /* @__PURE__ */ React14.createElement(
1220
- import_ui13.Box,
1221
- {
1222
- "aria-label": ariaLabel,
1223
- component: "button",
1224
- onClick,
1225
- type: "button",
1226
- sx: {
1227
- alignItems: "center",
1228
- background: "none",
1229
- border: "none",
1230
- cursor: "pointer",
1231
- display: "flex",
1232
- flex: 1,
1233
- flexDirection: "column",
1234
- gap: 0.5,
1235
- padding: 0
1236
- }
1237
- },
1238
- /* @__PURE__ */ React14.createElement(
1239
- import_ui13.Chip,
1240
- {
1241
- color,
1242
- label: count,
1243
- size: "small",
1244
- variant: "standard",
1245
- sx: (theme) => ({
1246
- border: `${COUNT_SUMMARY_BORDER_WIDTH}px solid ${chipBorderColor(theme, color)}`,
1247
- borderRadius: "50%",
1248
- height: COUNT_SUMMARY_CIRCLE_SIZE,
1249
- maxWidth: COUNT_SUMMARY_CIRCLE_SIZE,
1250
- width: COUNT_SUMMARY_CIRCLE_SIZE,
1251
- "& .MuiChip-label": {
1252
- fontSize: theme.typography.body2.fontSize,
1253
- fontWeight: 700,
1254
- lineHeight: 1,
1255
- padding: 0
1256
- }
1257
- })
1258
- }
1259
- ),
1260
- /* @__PURE__ */ React14.createElement(import_ui13.Typography, { color: "text.secondary", sx: { textAlign: "center" }, variant: "caption" }, label)
1261
- );
1262
- }
1263
-
1264
- // src/components/audit-statuses-section.tsx
1265
- var STATUS_ARIA_LABELS = {
1266
- pass: (count) => (0, import_i18n15.sprintf)(
1267
- /* translators: %d: number of passed audits. */
1268
- (0, import_i18n15.__)("%d passed audits, view all", "elementor"),
1269
- count
1270
- ),
1271
- fail: (count) => (0, import_i18n15.sprintf)(
1272
- /* translators: %d: number of failed audit violations. */
1273
- (0, import_i18n15.__)("%d failed audits, view all", "elementor"),
1274
- count
1275
- ),
1276
- skipped: (count) => (0, import_i18n15.sprintf)(
1277
- /* translators: %d: number of skipped audits. */
1278
- (0, import_i18n15.__)("%d skipped audits, view all", "elementor"),
1279
- count
1280
- )
1281
- };
1282
- var STATUS_GROUPS = ["fail", "pass", "skipped"];
1283
- function AuditStatusesSection({ report, onStatusClick }) {
1284
- const statusCounts = auditStatusDisplayCounts(report);
1285
- return /* @__PURE__ */ React15.createElement(React15.Fragment, null, /* @__PURE__ */ React15.createElement(import_ui14.Typography, { variant: "subtitle1", fontWeight: "bold" }, (0, import_i18n15.__)("Audit statuses", "elementor")), /* @__PURE__ */ React15.createElement(import_ui14.Box, { sx: { display: "flex", justifyContent: "space-around", gap: 2 } }, STATUS_GROUPS.map((status) => /* @__PURE__ */ React15.createElement(
1286
- CountSummaryCircle,
1287
- {
1288
- key: status,
1289
- ariaLabel: STATUS_ARIA_LABELS[status](statusCounts[status]),
1290
- color: auditStatusColor(status),
1291
- count: statusCounts[status],
1292
- label: auditStatusLabel(status),
1293
- onClick: () => onStatusClick(status)
1294
- }
1295
- ))), /* @__PURE__ */ React15.createElement(import_ui14.Divider, null));
1296
- }
1297
-
1298
- // src/components/issues-category-row.tsx
1299
- var React16 = __toESM(require("react"));
1300
- var import_icons9 = require("@elementor/icons");
1301
- var import_ui15 = require("@elementor/ui");
1302
1369
  function IssuesCategoryRow({ category, label, counts, onClick }) {
1303
- const isRtl = "rtl" === (0, import_ui15.useTheme)().direction;
1370
+ const isRtl = "rtl" === (0, import_ui13.useTheme)().direction;
1304
1371
  const Icon = CATEGORY_ICONS[category];
1305
- return /* @__PURE__ */ React16.createElement(
1306
- import_ui15.Box,
1372
+ return /* @__PURE__ */ React14.createElement(
1373
+ import_ui13.Box,
1307
1374
  {
1308
1375
  role: "button",
1309
1376
  tabIndex: 0,
@@ -1324,120 +1391,29 @@ function IssuesCategoryRow({ category, label, counts, onClick }) {
1324
1391
  "&:focus-visible": { outline: "2px solid", outlineColor: "primary.main" }
1325
1392
  }
1326
1393
  },
1327
- /* @__PURE__ */ React16.createElement(Icon, { fontSize: "small", color: "action" }),
1328
- /* @__PURE__ */ React16.createElement(import_ui15.Typography, { variant: "body2", fontWeight: "bold", sx: { flex: 1 } }, label),
1329
- /* @__PURE__ */ React16.createElement(import_ui15.Box, { sx: { display: "flex", alignItems: "center", gap: 0.5 } }, ALL_SEVERITIES.filter((s) => counts[s] > 0).map((severity) => /* @__PURE__ */ React16.createElement(import_ui15.Box, { key: severity, sx: { display: "flex", alignItems: "center", gap: 0.25 } }, /* @__PURE__ */ React16.createElement(SeverityIcon, { severity }), /* @__PURE__ */ React16.createElement(import_ui15.Typography, { variant: "caption", color: "text.primary", fontWeight: "bold" }, counts[severity])))),
1330
- /* @__PURE__ */ React16.createElement(import_ui15.Rotate, { in: isRtl }, /* @__PURE__ */ React16.createElement(import_icons9.ChevronRightIcon, { fontSize: "small", color: "action" }))
1394
+ /* @__PURE__ */ React14.createElement(Icon, { fontSize: "small", color: "action" }),
1395
+ /* @__PURE__ */ React14.createElement(import_ui13.Typography, { variant: "body2", fontWeight: "bold", sx: { flex: 1 } }, label),
1396
+ /* @__PURE__ */ React14.createElement(import_ui13.Box, { sx: { display: "flex", alignItems: "center", gap: 0.5 } }, ALL_SEVERITIES.filter((s) => counts[s] > 0).map((severity) => /* @__PURE__ */ React14.createElement(import_ui13.Box, { key: severity, sx: { display: "flex", alignItems: "center", gap: 0.25 } }, /* @__PURE__ */ React14.createElement(SeverityIcon, { severity }), /* @__PURE__ */ React14.createElement(import_ui13.Typography, { variant: "caption", color: "text.primary", fontWeight: "bold" }, counts[severity])))),
1397
+ /* @__PURE__ */ React14.createElement(import_ui13.Rotate, { in: isRtl }, /* @__PURE__ */ React14.createElement(import_icons11.ChevronRightIcon, { fontSize: "small", color: "action" }))
1331
1398
  );
1332
1399
  }
1333
1400
 
1334
- // src/components/severity-filter-chips.tsx
1335
- var React17 = __toESM(require("react"));
1336
- var import_ui16 = require("@elementor/ui");
1337
- var import_i18n16 = require("@wordpress/i18n");
1338
- function SeverityFilterChips({ selected, onChange }) {
1339
- return /* @__PURE__ */ React17.createElement(import_ui16.Box, { sx: { display: "flex", alignItems: "center", gap: 1 } }, /* @__PURE__ */ React17.createElement(
1340
- FilterChip,
1341
- {
1342
- label: (0, import_i18n16.__)("All", "elementor"),
1343
- isSelected: selected === "all",
1344
- onClick: () => onChange("all")
1345
- }
1346
- ), ALL_SEVERITIES.map((severity) => /* @__PURE__ */ React17.createElement(
1347
- FilterChip,
1348
- {
1349
- key: severity,
1350
- label: severityPluralLabel(severity),
1351
- icon: /* @__PURE__ */ React17.createElement(SeverityIcon, { severity }),
1352
- isSelected: selected === severity,
1353
- onClick: () => onChange(severity)
1354
- }
1355
- )));
1356
- }
1357
- function FilterChip({ label, icon, isSelected, onClick }) {
1358
- return /* @__PURE__ */ React17.createElement(
1359
- import_ui16.Chip,
1360
- {
1361
- label,
1362
- icon,
1363
- size: "small",
1364
- variant: "filled",
1365
- color: "default",
1366
- onClick,
1367
- "aria-pressed": isSelected,
1368
- sx: { bgcolor: isSelected ? "action.selected" : "transparent" }
1369
- }
1370
- );
1371
- }
1372
-
1373
- // src/components/pages/issues-page.tsx
1374
- function filterCounts(counts, filter) {
1375
- if (filter === "all") {
1376
- return counts;
1377
- }
1378
- return { error: 0, warning: 0, info: 0, [filter]: counts[filter] };
1379
- }
1380
- function IssuesPage({ report, onCategoryClick, onAllAuditsClick, onStatusClick }) {
1381
- const [filter, setFilter] = (0, import_react5.useState)("all");
1382
- const populatedCategories = getPopulatedCategories(report.categories, ALL_CATEGORIES);
1383
- const categoryRows = populatedCategories.map((category) => ({ category, counts: filterCounts(countSeverities(report, category), filter) })).filter(({ counts }) => Object.values(counts).some((count) => count > 0));
1384
- return /* @__PURE__ */ React18.createElement(import_ui17.Box, { sx: { display: "flex", flexDirection: "column", gap: 4, p: 2 } }, /* @__PURE__ */ React18.createElement(AuditStatusesSection, { report, onStatusClick }), /* @__PURE__ */ React18.createElement(
1385
- import_ui17.Link,
1386
- {
1387
- component: "button",
1388
- underline: "none",
1389
- color: "inherit",
1390
- onClick: onAllAuditsClick,
1391
- sx: { textAlign: "start" }
1392
- },
1393
- /* @__PURE__ */ React18.createElement(import_ui17.Typography, { variant: "subtitle1", component: "h2" }, (0, import_i18n17.__)("All issues", "elementor"))
1394
- ), /* @__PURE__ */ React18.createElement(SeverityFilterChips, { selected: filter, onChange: setFilter }), /* @__PURE__ */ React18.createElement(import_ui17.Box, { sx: { display: "flex", flexDirection: "column", gap: 1 } }, categoryRows.map(({ category, counts }) => /* @__PURE__ */ React18.createElement(
1395
- IssuesCategoryRow,
1396
- {
1397
- key: category,
1398
- category,
1399
- label: CATEGORY_LABELS[category],
1400
- counts,
1401
- onClick: () => onCategoryClick(category)
1402
- }
1403
- ))));
1404
- }
1405
-
1406
- // src/components/pages/overview-page.tsx
1407
- var React23 = __toESM(require("react"));
1408
- var import_ui22 = require("@elementor/ui");
1409
- var import_i18n21 = require("@wordpress/i18n");
1410
-
1411
- // src/utils/score-thresholds.ts
1412
- var import_i18n18 = require("@wordpress/i18n");
1413
- var GOOD_THRESHOLD = 90;
1414
- var OK_THRESHOLD = 50;
1415
- function getScoreTier(score) {
1416
- if (score >= GOOD_THRESHOLD) {
1417
- return { color: "success", label: (0, import_i18n18.__)("Good", "elementor") };
1418
- }
1419
- if (score >= OK_THRESHOLD) {
1420
- return { color: "warning", label: (0, import_i18n18.__)("Needs work", "elementor") };
1421
- }
1422
- return { color: "error", label: (0, import_i18n18.__)("At risk", "elementor") };
1423
- }
1424
-
1425
1401
  // src/components/promotions.tsx
1426
- var React20 = __toESM(require("react"));
1427
- var import_ui19 = require("@elementor/ui");
1428
- var import_i18n20 = require("@wordpress/i18n");
1402
+ var React16 = __toESM(require("react"));
1403
+ var import_ui15 = require("@elementor/ui");
1404
+ var import_i18n14 = require("@wordpress/i18n");
1429
1405
 
1430
1406
  // src/register-promotions.ts
1431
- var import_icons10 = require("@elementor/icons");
1432
- var import_i18n19 = require("@wordpress/i18n");
1407
+ var import_icons12 = require("@elementor/icons");
1408
+ var import_i18n13 = require("@wordpress/i18n");
1433
1409
  function firstFailExternalUrl(run) {
1434
1410
  return run.result.status === "fail" ? run.result.violations[0]?.externalUrl : void 0;
1435
1411
  }
1436
1412
  var PROMOTIONS = [
1437
1413
  {
1438
1414
  auditId: "audits/images-alt-text",
1439
- icon: import_icons10.PhotoIcon,
1440
- ctaLabel: (0, import_i18n19.__)("Fix with Ally", "elementor"),
1415
+ icon: import_icons12.PhotoIcon,
1416
+ ctaLabel: (0, import_i18n13.__)("Fix with Ally", "elementor"),
1441
1417
  formatSubtitle: (run) => {
1442
1418
  if (run.result.status !== "fail") {
1443
1419
  return null;
@@ -1446,9 +1422,9 @@ var PROMOTIONS = [
1446
1422
  if (count === 0) {
1447
1423
  return null;
1448
1424
  }
1449
- return (0, import_i18n19.sprintf)(
1425
+ return (0, import_i18n13.sprintf)(
1450
1426
  /* translators: %d: number of images missing alt text. */
1451
- (0, import_i18n19._n)("%d image", "%d images", count, "elementor"),
1427
+ (0, import_i18n13._n)("%d image", "%d images", count, "elementor"),
1452
1428
  count
1453
1429
  );
1454
1430
  },
@@ -1456,22 +1432,22 @@ var PROMOTIONS = [
1456
1432
  },
1457
1433
  {
1458
1434
  auditId: "audits/cookie-policy",
1459
- icon: import_icons10.ElementorCookieIcon,
1460
- ctaLabel: (0, import_i18n19.__)("Fix with Cookiez", "elementor"),
1461
- formatSubtitle: (run) => run.result.status === "fail" ? (0, import_i18n19.__)("Generate cookie policy", "elementor") : null,
1435
+ icon: import_icons12.ElementorCookieIcon,
1436
+ ctaLabel: (0, import_i18n13.__)("Fix with Cookiez", "elementor"),
1437
+ formatSubtitle: (run) => run.result.status === "fail" ? (0, import_i18n13.__)("Generate cookie policy", "elementor") : null,
1462
1438
  getCtaUrl: firstFailExternalUrl
1463
1439
  },
1464
1440
  {
1465
1441
  auditId: "audits/scan-for-cookies",
1466
- icon: import_icons10.ElementorCookieIcon,
1467
- ctaLabel: (0, import_i18n19.__)("Scan", "elementor"),
1468
- formatSubtitle: (run) => run.result.status === "fail" ? (0, import_i18n19.__)("Scan this page for cookies", "elementor") : null,
1442
+ icon: import_icons12.ElementorCookieIcon,
1443
+ ctaLabel: (0, import_i18n13.__)("Scan", "elementor"),
1444
+ formatSubtitle: (run) => run.result.status === "fail" ? (0, import_i18n13.__)("Scan this page for cookies", "elementor") : null,
1469
1445
  getCtaUrl: firstFailExternalUrl
1470
1446
  },
1471
1447
  {
1472
1448
  auditId: "audits/images-too-large",
1473
- icon: import_icons10.ShieldHalfFilledIcon,
1474
- ctaLabel: (0, import_i18n19.__)("Optimize all", "elementor"),
1449
+ icon: import_icons12.ShieldHalfFilledIcon,
1450
+ ctaLabel: (0, import_i18n13.__)("Optimize all", "elementor"),
1475
1451
  formatSubtitle: (run) => {
1476
1452
  if (run.result.status !== "fail") {
1477
1453
  return null;
@@ -1480,9 +1456,9 @@ var PROMOTIONS = [
1480
1456
  if (count === 0) {
1481
1457
  return null;
1482
1458
  }
1483
- return (0, import_i18n19.sprintf)(
1459
+ return (0, import_i18n13.sprintf)(
1484
1460
  /* translators: %d: number of oversized images. */
1485
- (0, import_i18n19._n)("%d image", "%d images", count, "elementor"),
1461
+ (0, import_i18n13._n)("%d image", "%d images", count, "elementor"),
1486
1462
  count
1487
1463
  );
1488
1464
  },
@@ -1496,11 +1472,11 @@ function findAuditRun(report, auditId) {
1496
1472
  }
1497
1473
 
1498
1474
  // src/components/promotion-card.tsx
1499
- var React19 = __toESM(require("react"));
1500
- var import_ui18 = require("@elementor/ui");
1475
+ var React15 = __toESM(require("react"));
1476
+ var import_ui14 = require("@elementor/ui");
1501
1477
  function PromotionCard({ ctaDisabled, ctaLabel, icon: Icon, onCtaClick, subtitle, title }) {
1502
- return /* @__PURE__ */ React19.createElement(
1503
- import_ui18.Box,
1478
+ return /* @__PURE__ */ React15.createElement(
1479
+ import_ui14.Box,
1504
1480
  {
1505
1481
  sx: {
1506
1482
  alignItems: "center",
@@ -1513,9 +1489,9 @@ function PromotionCard({ ctaDisabled, ctaLabel, icon: Icon, onCtaClick, subtitle
1513
1489
  py: 1.5
1514
1490
  }
1515
1491
  },
1516
- /* @__PURE__ */ React19.createElement(Icon, { fontSize: "small", color: "action" }),
1517
- /* @__PURE__ */ React19.createElement(import_ui18.Box, { sx: { display: "flex", flex: 1, flexDirection: "column", gap: 0.25, minWidth: 0 } }, /* @__PURE__ */ React19.createElement(import_ui18.Typography, { variant: "body2", fontWeight: "bold" }, title), /* @__PURE__ */ React19.createElement(import_ui18.Typography, { variant: "caption", color: "text.secondary" }, subtitle)),
1518
- /* @__PURE__ */ React19.createElement(import_ui18.Button, { variant: "outlined", color: "secondary", size: "small", disabled: ctaDisabled, onClick: onCtaClick }, ctaLabel)
1492
+ /* @__PURE__ */ React15.createElement(Icon, { fontSize: "small", color: "action" }),
1493
+ /* @__PURE__ */ React15.createElement(import_ui14.Box, { sx: { display: "flex", flex: 1, flexDirection: "column", gap: 0.25, minWidth: 0 } }, /* @__PURE__ */ React15.createElement(import_ui14.Typography, { variant: "body2", fontWeight: "bold" }, title), /* @__PURE__ */ React15.createElement(import_ui14.Typography, { variant: "caption", color: "text.secondary" }, subtitle)),
1494
+ /* @__PURE__ */ React15.createElement(import_ui14.Button, { variant: "outlined", color: "secondary", size: "small", disabled: ctaDisabled, onClick: onCtaClick }, ctaLabel)
1519
1495
  );
1520
1496
  }
1521
1497
 
@@ -1544,7 +1520,7 @@ function Promotions({ report }) {
1544
1520
  if (cards.length === 0) {
1545
1521
  return null;
1546
1522
  }
1547
- return /* @__PURE__ */ React20.createElement(React20.Fragment, null, /* @__PURE__ */ React20.createElement(import_ui19.Divider, null), /* @__PURE__ */ React20.createElement(import_ui19.Typography, { variant: "subtitle1", fontWeight: "bold" }, (0, import_i18n20.__)("Quick actions", "elementor")), /* @__PURE__ */ React20.createElement(import_ui19.Box, { sx: { display: "flex", flexDirection: "column", gap: 1 } }, cards.map(({ config, ctaUrl, key, run, subtitle }) => /* @__PURE__ */ React20.createElement(
1523
+ return /* @__PURE__ */ React16.createElement(React16.Fragment, null, /* @__PURE__ */ React16.createElement(import_ui15.Divider, null), /* @__PURE__ */ React16.createElement(import_ui15.Typography, { variant: "subtitle1", fontWeight: "bold" }, (0, import_i18n14.__)("Quick actions", "elementor")), /* @__PURE__ */ React16.createElement(import_ui15.Box, { sx: { display: "flex", flexDirection: "column", gap: 1 } }, cards.map(({ config, ctaUrl, key, run, subtitle }) => /* @__PURE__ */ React16.createElement(
1548
1524
  PromotionCard,
1549
1525
  {
1550
1526
  key,
@@ -1562,246 +1538,123 @@ function Promotions({ report }) {
1562
1538
  ))));
1563
1539
  }
1564
1540
 
1565
- // src/components/score-bar.tsx
1566
- var React21 = __toESM(require("react"));
1567
- var import_icons11 = require("@elementor/icons");
1568
- var import_ui20 = require("@elementor/ui");
1569
- function ScoreBar({ label, score, onClick }) {
1570
- const isRtl = "rtl" === (0, import_ui20.useTheme)().direction;
1571
- return /* @__PURE__ */ React21.createElement(
1572
- import_ui20.Box,
1573
- {
1574
- role: onClick ? "button" : void 0,
1575
- tabIndex: onClick ? 0 : void 0,
1576
- onClick,
1577
- onKeyDown: onClick ? onKeyboardClick(onClick) : void 0,
1578
- sx: {
1579
- display: "flex",
1580
- alignItems: "center",
1581
- gap: 1,
1582
- cursor: onClick ? "pointer" : "default",
1583
- outline: "none",
1584
- "&:focus-visible": { outline: "2px solid", outlineColor: "primary.main", borderRadius: 1 },
1585
- py: 0.5
1586
- }
1587
- },
1588
- /* @__PURE__ */ React21.createElement(import_ui20.Typography, { variant: "body2", sx: { minWidth: 96 } }, label),
1589
- /* @__PURE__ */ React21.createElement(
1590
- import_ui20.LinearProgress,
1591
- {
1592
- variant: "determinate",
1593
- value: score,
1594
- color: getScoreTier(score).color,
1595
- sx: { flex: 1, height: 6, borderRadius: 4, bgcolor: "action.disabledBackground" }
1596
- }
1597
- ),
1598
- /* @__PURE__ */ React21.createElement(
1599
- import_ui20.Typography,
1600
- {
1601
- variant: "body2",
1602
- color: "text.primary",
1603
- sx: { minWidth: 24, textAlign: "right", fontWeight: 900 }
1604
- },
1605
- score
1606
- ),
1607
- onClick && /* @__PURE__ */ React21.createElement(import_ui20.Rotate, { in: isRtl }, /* @__PURE__ */ React21.createElement(import_icons11.ChevronRightIcon, { fontSize: "small", color: "action" }))
1608
- );
1541
+ // src/components/severity-summary-cards.tsx
1542
+ var React17 = __toESM(require("react"));
1543
+ var import_ui16 = require("@elementor/ui");
1544
+ function SeveritySummaryCards({ report }) {
1545
+ const counts = countSeverities(report);
1546
+ return /* @__PURE__ */ React17.createElement(import_ui16.Box, { display: "flex", gap: 1.5 }, ALL_SEVERITIES.map((severity) => /* @__PURE__ */ React17.createElement(SeverityCard, { key: severity, severity, count: counts[severity] })));
1609
1547
  }
1610
-
1611
- // src/components/score-circle.tsx
1612
- var React22 = __toESM(require("react"));
1613
- var import_ui21 = require("@elementor/ui");
1614
- var SCORE_CIRCLE_SIZE = 88;
1615
- var SCORE_CIRCLE_THICKNESS = 3;
1616
- function ScoreCircle({ color, score }) {
1617
- const progressColor = color ?? getScoreTier(score).color;
1618
- return /* @__PURE__ */ React22.createElement(
1619
- import_ui21.Box,
1548
+ function SeverityCard({ severity, count }) {
1549
+ return /* @__PURE__ */ React17.createElement(
1550
+ import_ui16.Box,
1620
1551
  {
1621
- role: "progressbar",
1622
- "aria-valuenow": score,
1623
- "aria-valuemin": 0,
1624
- "aria-valuemax": 100,
1625
- sx: { position: "relative", display: "inline-flex" }
1552
+ display: "flex",
1553
+ flex: 1,
1554
+ flexDirection: "column",
1555
+ gap: 1,
1556
+ bgcolor: "action.hover",
1557
+ borderRadius: 1.5,
1558
+ px: 1.5,
1559
+ py: 1
1626
1560
  },
1627
- /* @__PURE__ */ React22.createElement(
1628
- import_ui21.CircularProgress,
1629
- {
1630
- variant: "determinate",
1631
- value: 100,
1632
- size: SCORE_CIRCLE_SIZE,
1633
- thickness: SCORE_CIRCLE_THICKNESS,
1634
- sx: { color: "action.disabledBackground" }
1635
- }
1636
- ),
1637
- /* @__PURE__ */ React22.createElement(
1638
- import_ui21.CircularProgress,
1639
- {
1640
- variant: "determinate",
1641
- value: score,
1642
- size: SCORE_CIRCLE_SIZE,
1643
- thickness: SCORE_CIRCLE_THICKNESS,
1644
- color: progressColor,
1645
- sx: { position: "absolute", left: 0 }
1646
- }
1647
- ),
1648
- /* @__PURE__ */ React22.createElement(
1649
- import_ui21.Box,
1650
- {
1651
- sx: {
1652
- position: "absolute",
1653
- inset: 0,
1654
- display: "flex",
1655
- alignItems: "center",
1656
- justifyContent: "center"
1657
- }
1658
- },
1659
- /* @__PURE__ */ React22.createElement(
1660
- import_ui21.Typography,
1661
- {
1662
- variant: "h4",
1663
- component: "span",
1664
- color: "text.primary",
1665
- sx: { fontWeight: 700, lineHeight: 1 }
1666
- },
1667
- score
1668
- )
1669
- )
1561
+ /* @__PURE__ */ React17.createElement(SeverityIcon, { severity }),
1562
+ /* @__PURE__ */ React17.createElement(import_ui16.Box, { display: "flex", flexDirection: "column" }, /* @__PURE__ */ React17.createElement(import_ui16.Typography, { variant: "h5", fontWeight: 700, component: "span" }, count), /* @__PURE__ */ React17.createElement(import_ui16.Typography, { variant: "caption", color: "text.secondary" }, severityPluralLabel(severity)))
1670
1563
  );
1671
1564
  }
1672
1565
 
1673
1566
  // src/components/pages/overview-page.tsx
1674
1567
  function OverviewPage({ onCategoryClick, report }) {
1675
1568
  const populatedCategories = getPopulatedCategories(report.categories, ALL_CATEGORIES);
1676
- const overallScore = getScoreTier(report.overall);
1677
- return /* @__PURE__ */ React23.createElement(import_ui22.Box, { sx: { display: "flex", flexDirection: "column", gap: 4, p: 2 } }, /* @__PURE__ */ React23.createElement(import_ui22.Box, { sx: { display: "flex", alignItems: "center", gap: 2 } }, /* @__PURE__ */ React23.createElement(ScoreCircle, { color: overallScore.color, score: report.overall }), /* @__PURE__ */ React23.createElement(import_ui22.Box, { sx: { display: "flex", flexDirection: "column", gap: 0.5 } }, /* @__PURE__ */ React23.createElement(
1678
- import_ui22.Chip,
1679
- {
1680
- label: overallScore.label,
1681
- color: overallScore.color,
1682
- variant: "standard",
1683
- size: "small",
1684
- sx: { fontWeight: 600, alignSelf: "flex-start" }
1685
- }
1686
- ), /* @__PURE__ */ React23.createElement(import_ui22.Typography, { variant: "body2", color: "text.secondary" }, (0, import_i18n21.__)("Overall score", "elementor")))), /* @__PURE__ */ React23.createElement(import_ui22.Box, { sx: { display: "flex", flexDirection: "column", gap: 2 } }, populatedCategories.map((category) => /* @__PURE__ */ React23.createElement(
1687
- ScoreBar,
1569
+ const categoryRows = populatedCategories.map((category) => ({ category, counts: countSeverities(report, category) })).filter(({ counts }) => Object.values(counts).some((count) => count > 0));
1570
+ return /* @__PURE__ */ React18.createElement(import_ui17.Box, { display: "flex", flexDirection: "column", gap: 3, p: 2 }, /* @__PURE__ */ React18.createElement(SeveritySummaryCards, { report }), /* @__PURE__ */ React18.createElement(import_ui17.Box, { display: "flex", flexDirection: "column", gap: 1 }, /* @__PURE__ */ React18.createElement(import_ui17.Typography, { variant: "subtitle1", component: "h2" }, (0, import_i18n15.__)("All issues", "elementor")), /* @__PURE__ */ React18.createElement(import_ui17.Box, { display: "flex", flexDirection: "column", gap: 1 }, categoryRows.map(({ category, counts }) => /* @__PURE__ */ React18.createElement(
1571
+ IssuesCategoryRow,
1688
1572
  {
1689
1573
  key: category,
1574
+ category,
1690
1575
  label: CATEGORY_LABELS[category],
1691
- score: report.categories[category].score,
1576
+ counts,
1692
1577
  onClick: () => onCategoryClick(category)
1693
1578
  }
1694
- ))), /* @__PURE__ */ React23.createElement(Promotions, { report }));
1579
+ )))), /* @__PURE__ */ React18.createElement(Promotions, { report }));
1695
1580
  }
1696
1581
 
1697
1582
  // src/components/report-shell.tsx
1698
- function activeTab(page) {
1699
- if (page === "overview") {
1700
- return "overview";
1701
- }
1702
- if (page === "issues") {
1703
- return "issues";
1704
- }
1705
- return page.backTo;
1706
- }
1707
- function isAllAuditsPage(page) {
1708
- return typeof page === "object" && "page" in page && page.page === "all-audits";
1709
- }
1710
1583
  function isCategoryPage(page) {
1711
1584
  return typeof page === "object" && "category" in page;
1712
1585
  }
1713
1586
  function ReportShell({ report }) {
1714
- const [activePage, setActivePage] = (0, import_react6.useState)("overview");
1715
- const currentTab = activeTab(activePage);
1716
- const handleTabChange = (_, value) => {
1717
- setActivePage(value);
1718
- };
1719
- const openCategory = (category, backTo) => {
1720
- setActivePage({ category, backTo });
1721
- };
1722
- const openAllAudits = (expand, backTo = "issues") => {
1723
- setActivePage({ page: "all-audits", expand, backTo });
1724
- };
1725
- const backFromSubPage = () => {
1726
- if (isAllAuditsPage(activePage) || isCategoryPage(activePage)) {
1727
- setActivePage(activePage.backTo);
1728
- }
1729
- };
1730
- return /* @__PURE__ */ React24.createElement(import_ui23.Box, { sx: { display: "flex", flexDirection: "column", flex: 1, minHeight: 0 } }, /* @__PURE__ */ React24.createElement(import_ui23.Box, { sx: { flexShrink: 0 } }, /* @__PURE__ */ React24.createElement(
1731
- import_ui23.Tabs,
1587
+ const [activePage, setActivePage] = (0, import_react5.useState)("overview");
1588
+ const openCategory = (category) => setActivePage({ category });
1589
+ const backToOverview = () => setActivePage("overview");
1590
+ return /* @__PURE__ */ React19.createElement(import_ui18.Box, { sx: { flex: 1, minHeight: 0, overflowY: "auto" } }, activePage === "overview" && /* @__PURE__ */ React19.createElement(OverviewPage, { report, onCategoryClick: openCategory }), isCategoryPage(activePage) && /* @__PURE__ */ React19.createElement(CategoryPage, { category: activePage.category, report, onBack: backToOverview }));
1591
+ }
1592
+
1593
+ // src/components/rescan-button.tsx
1594
+ var React20 = __toESM(require("react"));
1595
+ var import_editor_floating_panels3 = require("@elementor/editor-floating-panels");
1596
+ var import_icons13 = require("@elementor/icons");
1597
+ var import_ui19 = require("@elementor/ui");
1598
+ var import_i18n16 = require("@wordpress/i18n");
1599
+ function RescanButton({ hasScanned, isStale, disabled, onClick }) {
1600
+ const panelZIndex = (0, import_editor_floating_panels3.useFloatingPanelZIndex)(AUDIT_PANEL_ID);
1601
+ return /* @__PURE__ */ React20.createElement(
1602
+ import_ui19.Tooltip,
1732
1603
  {
1733
- "aria-label": (0, import_i18n22.__)("Audit navigation", "elementor"),
1734
- value: currentTab,
1735
- onChange: handleTabChange,
1736
- textColor: "secondary",
1737
- indicatorColor: "secondary",
1738
- size: "small",
1739
- centered: true,
1740
- variant: "fullWidth"
1604
+ title: (0, import_i18n16.__)("Page content changed - rescan advised", "elementor"),
1605
+ disableHoverListener: !isStale,
1606
+ PopperProps: { sx: { zIndex: panelZIndex } }
1741
1607
  },
1742
- /* @__PURE__ */ React24.createElement(import_ui23.Tab, { value: "overview", label: (0, import_i18n22.__)("Overview", "elementor") }),
1743
- /* @__PURE__ */ React24.createElement(import_ui23.Tab, { value: "issues", label: (0, import_i18n22.__)("Issues", "elementor") })
1744
- ), /* @__PURE__ */ React24.createElement(import_ui23.Divider, null)), /* @__PURE__ */ React24.createElement(import_ui23.Box, { sx: { flex: 1, minHeight: 0, overflowY: "auto" } }, activePage === "overview" && /* @__PURE__ */ React24.createElement(
1745
- OverviewPage,
1746
- {
1747
- report,
1748
- onCategoryClick: (category) => openCategory(category, "overview")
1749
- }
1750
- ), activePage === "issues" && /* @__PURE__ */ React24.createElement(
1751
- IssuesPage,
1752
- {
1753
- report,
1754
- onCategoryClick: (category) => openCategory(category, "issues"),
1755
- onAllAuditsClick: () => openAllAudits(),
1756
- onStatusClick: (status) => openAllAudits(status)
1757
- }
1758
- ), isAllAuditsPage(activePage) && /* @__PURE__ */ React24.createElement(
1759
- AllAuditsPage,
1760
- {
1761
- report,
1762
- initialExpandedStatus: activePage.expand,
1763
- onBack: backFromSubPage
1764
- }
1765
- ), isCategoryPage(activePage) && /* @__PURE__ */ React24.createElement(CategoryPage, { category: activePage.category, report, onBack: backFromSubPage })));
1608
+ /* @__PURE__ */ React20.createElement(import_ui19.Badge, { variant: "dot", color: "warning", invisible: !isStale, overlap: "rectangular" }, /* @__PURE__ */ React20.createElement(
1609
+ import_ui19.Button,
1610
+ {
1611
+ variant: "contained",
1612
+ size: "small",
1613
+ startIcon: hasScanned ? /* @__PURE__ */ React20.createElement(import_icons13.ReloadIcon, { fontSize: "small" }) : void 0,
1614
+ onClick,
1615
+ disabled
1616
+ },
1617
+ hasScanned ? (0, import_i18n16.__)("Rescan", "elementor") : (0, import_i18n16.__)("Run page audit", "elementor")
1618
+ ))
1619
+ );
1766
1620
  }
1767
1621
 
1768
1622
  // src/components/audit-panel.tsx
1769
1623
  function AuditPanel() {
1770
- const { status, report, error, run } = useAuditReport();
1771
- const currentDocumentId = (0, import_editor_elements5.getCurrentDocumentId)() ?? 0;
1772
- const onRun = () => run(currentDocumentId);
1624
+ const { status, report, error, isStale, run } = useAuditReport();
1625
+ const hostDocumentId = (0, import_editor_elements6.getHostDocumentId)() ?? 0;
1626
+ const onRun = () => run(hostDocumentId);
1773
1627
  const lastScanLabel = report ? new Date(report.runAt).toLocaleTimeString() : null;
1774
- return /* @__PURE__ */ React25.createElement(React25.Fragment, null, /* @__PURE__ */ React25.createElement(
1775
- import_editor_floating_panels.FloatingPanelHeader,
1628
+ return /* @__PURE__ */ React21.createElement(React21.Fragment, null, /* @__PURE__ */ React21.createElement(
1629
+ import_editor_floating_panels4.FloatingPanelHeader,
1776
1630
  {
1777
- panelId: "audit-panel",
1778
- title: (0, import_i18n23.__)("Page Audit", "elementor"),
1779
- badge: (0, import_i18n23.__)("Beta", "elementor"),
1631
+ panelId: AUDIT_PANEL_ID,
1632
+ title: (0, import_i18n17.__)("Page Audit", "elementor"),
1633
+ badge: (0, import_i18n17.__)("Beta", "elementor"),
1780
1634
  titleVariant: "subtitle2"
1781
1635
  }
1782
- ), /* @__PURE__ */ React25.createElement(
1783
- import_editor_floating_panels.FloatingPanelBody,
1636
+ ), /* @__PURE__ */ React21.createElement(
1637
+ import_editor_floating_panels4.FloatingPanelBody,
1784
1638
  {
1785
1639
  sx: status === "ready" ? { display: "flex", flexDirection: "column", overflow: "hidden", minHeight: 0 } : void 0
1786
1640
  },
1787
- status === "idle" && /* @__PURE__ */ React25.createElement(WelcomePage, null),
1788
- status === "loading" && /* @__PURE__ */ React25.createElement(LoadingPage, null),
1789
- status === "error" && /* @__PURE__ */ React25.createElement(ErrorPage, { message: error ?? "", onRetry: onRun }),
1790
- status === "ready" && report && /* @__PURE__ */ React25.createElement(ReportShell, { report })
1791
- ), /* @__PURE__ */ React25.createElement(import_editor_floating_panels.FloatingPanelFooter, null, lastScanLabel ? /* @__PURE__ */ React25.createElement(import_ui24.Typography, { variant: "caption", sx: { flex: 1 } }, (0, import_i18n23.__)("Last scan:", "elementor"), " ", lastScanLabel) : /* @__PURE__ */ React25.createElement(import_ui24.Box, { sx: { flex: 1 } }), lastScanLabel && /* @__PURE__ */ React25.createElement(AuditFeedback, null), /* @__PURE__ */ React25.createElement(
1792
- import_ui24.Button,
1641
+ status === "idle" && /* @__PURE__ */ React21.createElement(WelcomePage, null),
1642
+ status === "loading" && /* @__PURE__ */ React21.createElement(LoadingPage, null),
1643
+ status === "error" && /* @__PURE__ */ React21.createElement(ErrorPage, { message: error ?? "", onRetry: onRun }),
1644
+ status === "ready" && report && /* @__PURE__ */ React21.createElement(ReportShell, { report })
1645
+ ), /* @__PURE__ */ React21.createElement(import_editor_floating_panels4.FloatingPanelFooter, null, lastScanLabel ? /* @__PURE__ */ React21.createElement(import_ui20.Typography, { variant: "caption", sx: { flex: 1 } }, (0, import_i18n17.__)("Last scan:", "elementor"), " ", lastScanLabel) : /* @__PURE__ */ React21.createElement(import_ui20.Box, { sx: { flex: 1 } }), lastScanLabel && /* @__PURE__ */ React21.createElement(AuditFeedback, null), /* @__PURE__ */ React21.createElement(
1646
+ RescanButton,
1793
1647
  {
1794
- variant: "contained",
1795
- size: "small",
1796
- onClick: onRun,
1797
- disabled: status === "loading" || currentDocumentId === 0
1798
- },
1799
- lastScanLabel ? (0, import_i18n23.__)("Re-scan", "elementor") : (0, import_i18n23.__)("Run page audit", "elementor")
1648
+ hasScanned: Boolean(lastScanLabel),
1649
+ isStale,
1650
+ disabled: status === "loading" || hostDocumentId === 0,
1651
+ onClick: onRun
1652
+ }
1800
1653
  )));
1801
1654
  }
1802
1655
 
1803
1656
  // src/editor-panel.tsx
1804
- var auditPanel = (0, import_editor_floating_panels2.createFloatingPanel)({
1657
+ var auditPanel = (0, import_editor_floating_panels5.createFloatingPanel)({
1805
1658
  id: AUDIT_PANEL_ID,
1806
1659
  title: "Audit",
1807
1660
  icon: () => null,
@@ -1821,8 +1674,8 @@ function useAuditToggleProps() {
1821
1674
  const { isOpen } = auditPanel.useFloatingPanelStatus();
1822
1675
  const { toggle } = auditPanel.useFloatingPanelActions();
1823
1676
  return {
1824
- title: (0, import_i18n24.__)("Audit Page", "elementor"),
1825
- icon: import_icons12.ShieldCheckIcon,
1677
+ title: (0, import_i18n18.__)("Audit Page", "elementor"),
1678
+ icon: AuditToggleIcon,
1826
1679
  selected: isOpen,
1827
1680
  onClick: () => toggle()
1828
1681
  };
@@ -1838,30 +1691,107 @@ function registerAppBarAuditsToggle() {
1838
1691
  });
1839
1692
  }
1840
1693
 
1841
- // src/audits/accessibility-policy.ts
1842
- var import_i18n25 = require("@wordpress/i18n");
1843
- var audit = {
1844
- id: "audits/accessibility-policy",
1845
- title: (0, import_i18n25.__)("Accessibility policy", "elementor"),
1846
- description: (0, import_i18n25.__)(
1847
- "An accessibility policy demonstrates your commitment to digital inclusion and may be legally required in some regions.",
1848
- "elementor"
1849
- ),
1850
- fixHint: (0, import_i18n25.__)("Install the Ally plugin to add an accessibility policy to your website.", "elementor"),
1851
- categories: ["compliance"],
1694
+ // src/audits/accessibility-policy.ts
1695
+ var import_i18n19 = require("@wordpress/i18n");
1696
+ var audit = {
1697
+ id: "audits/accessibility-policy",
1698
+ title: (0, import_i18n19.__)("Accessibility policy", "elementor"),
1699
+ description: (0, import_i18n19.__)(
1700
+ "An accessibility policy demonstrates your commitment to digital inclusion and may be legally required in some regions.",
1701
+ "elementor"
1702
+ ),
1703
+ fixHint: (0, import_i18n19.__)("Install the Ally plugin to add an accessibility policy to your website.", "elementor"),
1704
+ categories: ["compliance"],
1705
+ severity: "info",
1706
+ weight: 1,
1707
+ evaluate: (ctx) => {
1708
+ if (ctx.pageContext.ally_plugin_active) {
1709
+ return { status: "pass" };
1710
+ }
1711
+ return {
1712
+ status: "fail",
1713
+ violations: [
1714
+ {
1715
+ auditId: audit.id,
1716
+ label: (0, import_i18n19.__)("Ally plugin is not installed or active.", "elementor"),
1717
+ externalUrl: ctx.pageContext.ally_plugin_url
1718
+ }
1719
+ ]
1720
+ };
1721
+ }
1722
+ };
1723
+
1724
+ // src/audits/accessibility-statement.ts
1725
+ var import_i18n20 = require("@wordpress/i18n");
1726
+ var audit2 = {
1727
+ id: "audits/accessibility-statement",
1728
+ title: (0, import_i18n20.__)("Create an accessibility statement", "elementor"),
1729
+ description: (0, import_i18n20.__)(
1730
+ "An accessibility statement showcases your efforts to create an inclusive online space, highlighting helpful features and a commitment to accessibility.",
1731
+ "elementor"
1732
+ ),
1733
+ fixHint: (0, import_i18n20.__)("Use Ally to create and publish an accessibility statement for your site.", "elementor"),
1734
+ categories: ["accessibility"],
1735
+ severity: "info",
1736
+ weight: 1,
1737
+ evaluate: (ctx) => {
1738
+ const isReady = ctx.pageContext.ally_plugin_active;
1739
+ if (isReady && ctx.pageContext.ally_accessibility_statement_created) {
1740
+ return { status: "pass" };
1741
+ }
1742
+ return {
1743
+ status: "fail",
1744
+ violations: [
1745
+ {
1746
+ auditId: audit2.id,
1747
+ label: (0, import_i18n20.__)("No accessibility statement has been created for this site.", "elementor"),
1748
+ externalUrl: isReady ? ctx.pageContext.ally_accessibility_statement_url : ctx.pageContext.ally_plugin_url,
1749
+ ctaLabel: (0, import_i18n20.__)("Create", "elementor"),
1750
+ secondaryCtaLabel: (0, import_i18n20.__)("Learn more", "elementor"),
1751
+ secondaryCtaUrl: "https://elementor.com/help/start-using-ally/#statement"
1752
+ }
1753
+ ]
1754
+ };
1755
+ }
1756
+ };
1757
+
1758
+ // src/audits/accessibility-widget.ts
1759
+ var import_i18n21 = require("@wordpress/i18n");
1760
+
1761
+ // src/utils/scan-accessibility-widgets.ts
1762
+ var ACCESSIBILITY_WIDGET_SCRIPT_SOURCES = ["cdn.elementor.com/a11y/widget.js"];
1763
+ function hasAccessibilityWidgetScript(html) {
1764
+ return ACCESSIBILITY_WIDGET_SCRIPT_SOURCES.some((src) => html.includes(src));
1765
+ }
1766
+
1767
+ // src/audits/accessibility-widget.ts
1768
+ var audit3 = {
1769
+ id: "audits/accessibility-widget",
1770
+ title: (0, import_i18n21.__)("Add an accessibility widget", "elementor"),
1771
+ description: (0, import_i18n21.__)("Make your site more inclusive by adding an accessibility widget.", "elementor"),
1772
+ fixHint: (0, import_i18n21.__)("Activate Ally\u2019s accessibility widget to give visitors on-page accessibility tools.", "elementor"),
1773
+ categories: ["accessibility"],
1852
1774
  severity: "info",
1853
1775
  weight: 1,
1854
1776
  evaluate: (ctx) => {
1855
- if (ctx.pageContext.ally_plugin_active) {
1777
+ if (!ctx.pageContext.frontend_url) {
1778
+ return { status: "skipped", reason: (0, import_i18n21.__)("Page is not published.", "elementor") };
1779
+ }
1780
+ if (!ctx.renderedHtml) {
1781
+ return { status: "skipped", reason: (0, import_i18n21.__)("Could not fetch the published page.", "elementor") };
1782
+ }
1783
+ if (hasAccessibilityWidgetScript(ctx.renderedHtml)) {
1856
1784
  return { status: "pass" };
1857
1785
  }
1786
+ const isReady = ctx.pageContext.ally_plugin_active;
1858
1787
  return {
1859
1788
  status: "fail",
1860
1789
  violations: [
1861
1790
  {
1862
- auditId: audit.id,
1863
- label: (0, import_i18n25.__)("Ally plugin is not installed or active.", "elementor"),
1864
- externalUrl: ctx.pageContext.ally_plugin_url
1791
+ auditId: audit3.id,
1792
+ label: (0, import_i18n21.__)("No accessibility widget is active on this site.", "elementor"),
1793
+ externalUrl: isReady ? ctx.pageContext.ally_widget_settings_url : ctx.pageContext.ally_plugin_url,
1794
+ ctaLabel: (0, import_i18n21.__)("Activate", "elementor")
1865
1795
  }
1866
1796
  ]
1867
1797
  };
@@ -1869,15 +1799,15 @@ var audit = {
1869
1799
  };
1870
1800
 
1871
1801
  // src/audits/cookie-policy.ts
1872
- var import_i18n26 = require("@wordpress/i18n");
1873
- var audit2 = {
1802
+ var import_i18n22 = require("@wordpress/i18n");
1803
+ var audit4 = {
1874
1804
  id: "audits/cookie-policy",
1875
- title: (0, import_i18n26.__)("Cookie policy", "elementor"),
1876
- description: (0, import_i18n26.__)(
1805
+ title: (0, import_i18n22.__)("Cookie policy", "elementor"),
1806
+ description: (0, import_i18n22.__)(
1877
1807
  "A cookie policy is required by privacy regulations such as GDPR (EU) or CCPA (US) to inform visitors about tracking and data collection.",
1878
1808
  "elementor"
1879
1809
  ),
1880
- fixHint: (0, import_i18n26.__)("Install the Cookiez plugin to add a cookie policy to your website.", "elementor"),
1810
+ fixHint: (0, import_i18n22.__)("Install the Cookiez plugin to add a cookie policy to your website.", "elementor"),
1881
1811
  categories: ["compliance"],
1882
1812
  severity: "info",
1883
1813
  weight: 1,
@@ -1889,9 +1819,10 @@ var audit2 = {
1889
1819
  status: "fail",
1890
1820
  violations: [
1891
1821
  {
1892
- auditId: audit2.id,
1893
- label: (0, import_i18n26.__)("Cookiez plugin is not installed or active.", "elementor"),
1894
- externalUrl: ctx.pageContext.cookiez_plugin_url
1822
+ auditId: audit4.id,
1823
+ label: (0, import_i18n22.__)("Cookiez plugin is not installed or active.", "elementor"),
1824
+ externalUrl: ctx.pageContext.cookiez_plugin_url,
1825
+ ctaLabel: (0, import_i18n22.__)("Set up cookie consent", "elementor")
1895
1826
  }
1896
1827
  ]
1897
1828
  };
@@ -1899,8 +1830,8 @@ var audit2 = {
1899
1830
  };
1900
1831
 
1901
1832
  // src/audits/deep-nesting.ts
1902
- var import_editor_elements6 = require("@elementor/editor-elements");
1903
- var import_i18n27 = require("@wordpress/i18n");
1833
+ var import_editor_elements7 = require("@elementor/editor-elements");
1834
+ var import_i18n23 = require("@wordpress/i18n");
1904
1835
  var MAX_NESTING_DEPTH = 6;
1905
1836
  function isNestingElement(node, widgetsCache) {
1906
1837
  if (node.elType === "container") {
@@ -1908,22 +1839,22 @@ function isNestingElement(node, widgetsCache) {
1908
1839
  }
1909
1840
  return !!widgetsCache?.[node.elType]?.meta?.is_container;
1910
1841
  }
1911
- var audit3 = {
1842
+ var audit5 = {
1912
1843
  id: "audits/deep-nesting",
1913
- title: (0, import_i18n27.__)("Deep container nesting", "elementor"),
1914
- description: (0, import_i18n27.__)(
1844
+ title: (0, import_i18n23.__)("Deep container nesting", "elementor"),
1845
+ description: (0, import_i18n23.__)(
1915
1846
  "Container nesting exceeds the recommended depth. Deep DOM trees hurt rendering performance and readability.",
1916
1847
  "elementor"
1917
1848
  ),
1918
- fixHint: (0, import_i18n27.__)("Flatten the layout by removing unnecessary wrapper containers.", "elementor"),
1849
+ fixHint: (0, import_i18n23.__)("Flatten the layout by removing unnecessary wrapper containers.", "elementor"),
1919
1850
  categories: ["performance", "best-practices"],
1920
1851
  severity: "warning",
1921
1852
  weight: 5,
1922
1853
  evaluate: (ctx) => {
1923
1854
  if (ctx.elements.tree.length === 0) {
1924
- return { status: "skipped", reason: (0, import_i18n27.__)("No elements", "elementor") };
1855
+ return { status: "skipped", reason: (0, import_i18n23.__)("No elements", "elementor") };
1925
1856
  }
1926
- const widgetsCache = (0, import_editor_elements6.getWidgetsCache)();
1857
+ const widgetsCache = (0, import_editor_elements7.getWidgetsCache)();
1927
1858
  const violations = [];
1928
1859
  walkElements(ctx.elements.tree, (node, parents) => {
1929
1860
  if (!isNestingElement(node, widgetsCache)) {
@@ -1932,10 +1863,10 @@ var audit3 = {
1932
1863
  const depth = parents.filter((parent) => isNestingElement(parent, widgetsCache)).length + 1;
1933
1864
  if (depth > MAX_NESTING_DEPTH) {
1934
1865
  violations.push({
1935
- auditId: audit3.id,
1866
+ auditId: audit5.id,
1936
1867
  elementId: node.id,
1937
1868
  targetHint: "element-settings",
1938
- label: (0, import_i18n27.__)("Container nesting is too deep.", "elementor")
1869
+ label: (0, import_i18n23.__)("Container nesting is too deep.", "elementor")
1939
1870
  });
1940
1871
  }
1941
1872
  });
@@ -1944,15 +1875,15 @@ var audit3 = {
1944
1875
  };
1945
1876
 
1946
1877
  // src/audits/default-design-system.ts
1947
- var import_i18n28 = require("@wordpress/i18n");
1948
- var audit4 = {
1878
+ var import_i18n24 = require("@wordpress/i18n");
1879
+ var audit6 = {
1949
1880
  id: "audits/default-design-system",
1950
- title: (0, import_i18n28.__)("Default website kit", "elementor"),
1951
- description: (0, import_i18n28.__)(
1881
+ title: (0, import_i18n24.__)("Default website kit", "elementor"),
1882
+ description: (0, import_i18n24.__)(
1952
1883
  "Your website is using the default design system colors and fonts. Custom branding makes the website feel uniquely yours.",
1953
1884
  "elementor"
1954
1885
  ),
1955
- fixHint: (0, import_i18n28.__)("Open Site Settings and customize your kit (colors, fonts, layout).", "elementor"),
1886
+ fixHint: (0, import_i18n24.__)("Open Site Settings and customize your kit (colors, fonts, layout).", "elementor"),
1956
1887
  categories: ["best-practices"],
1957
1888
  severity: "info",
1958
1889
  weight: 1,
@@ -1964,8 +1895,8 @@ var audit4 = {
1964
1895
  status: "fail",
1965
1896
  violations: [
1966
1897
  {
1967
- auditId: audit4.id,
1968
- label: (0, import_i18n28.__)("Kit appears unchanged from default.", "elementor"),
1898
+ auditId: audit6.id,
1899
+ label: (0, import_i18n24.__)("Kit appears unchanged from default.", "elementor"),
1969
1900
  targetHint: "site-settings"
1970
1901
  }
1971
1902
  ]
@@ -1974,22 +1905,22 @@ var audit4 = {
1974
1905
  };
1975
1906
 
1976
1907
  // src/audits/deprecated-widgets.ts
1977
- var import_editor_elements7 = require("@elementor/editor-elements");
1978
- var import_i18n29 = require("@wordpress/i18n");
1908
+ var import_editor_elements8 = require("@elementor/editor-elements");
1909
+ var import_i18n25 = require("@wordpress/i18n");
1979
1910
  var DEPRECATION_CONTROL_NAME = "deprecation_message";
1980
- var audit5 = {
1911
+ var audit7 = {
1981
1912
  id: "audits/deprecated-widgets",
1982
- title: (0, import_i18n29.__)("Deprecated widgets", "elementor"),
1983
- description: (0, import_i18n29.__)(
1913
+ title: (0, import_i18n25.__)("Deprecated widgets", "elementor"),
1914
+ description: (0, import_i18n25.__)(
1984
1915
  "Deprecated widgets should not be used. For better capabilities use the recommended replacement.",
1985
1916
  "elementor"
1986
1917
  ),
1987
- fixHint: (0, import_i18n29.__)("Replace each deprecated widget with the recommended new widget shown in its panel.", "elementor"),
1918
+ fixHint: (0, import_i18n25.__)("Replace each deprecated widget with the recommended new widget shown in its panel.", "elementor"),
1988
1919
  categories: ["best-practices", "performance"],
1989
1920
  severity: "warning",
1990
1921
  weight: 7,
1991
1922
  evaluate: (ctx) => {
1992
- const widgetsCache = (0, import_editor_elements7.getWidgetsCache)();
1923
+ const widgetsCache = (0, import_editor_elements8.getWidgetsCache)();
1993
1924
  if (!widgetsCache) {
1994
1925
  return { status: "skipped", reason: "widgets-cache-unavailable" };
1995
1926
  }
@@ -2008,10 +1939,10 @@ var audit5 = {
2008
1939
  return;
2009
1940
  }
2010
1941
  violations.push({
2011
- auditId: audit5.id,
1942
+ auditId: audit7.id,
2012
1943
  elementId: node.id,
2013
1944
  targetHint: "element-settings",
2014
- label: (0, import_i18n29.__)("Using deprecated widget.", "elementor")
1945
+ label: (0, import_i18n25.__)("Using deprecated widget.", "elementor")
2015
1946
  });
2016
1947
  });
2017
1948
  return violations.length === 0 ? { status: "pass" } : { status: "fail", violations };
@@ -2019,28 +1950,7 @@ var audit5 = {
2019
1950
  };
2020
1951
 
2021
1952
  // src/audits/google-consent-mode.ts
2022
- var import_i18n30 = require("@wordpress/i18n");
2023
-
2024
- // src/utils/fetch-rendered-html.ts
2025
- var FETCH_TIMEOUT_MS = 1e4;
2026
- async function fetchRenderedHtml(url) {
2027
- if (!url) {
2028
- return null;
2029
- }
2030
- const timeoutController = new AbortController();
2031
- const timeoutId = setTimeout(() => timeoutController.abort(), FETCH_TIMEOUT_MS);
2032
- try {
2033
- const response = await fetch(url, { credentials: "omit", signal: timeoutController.signal });
2034
- if (!response.ok) {
2035
- return null;
2036
- }
2037
- return await response.text();
2038
- } catch {
2039
- return null;
2040
- } finally {
2041
- clearTimeout(timeoutId);
2042
- }
2043
- }
1953
+ var import_i18n26 = require("@wordpress/i18n");
2044
1954
 
2045
1955
  // src/utils/scan-consent-signals.ts
2046
1956
  var GOOGLE_TRACKING_PATTERNS = [
@@ -2061,44 +1971,43 @@ function hasConsentDefaultCall(html) {
2061
1971
  }
2062
1972
 
2063
1973
  // src/audits/google-consent-mode.ts
2064
- var audit6 = {
1974
+ var audit8 = {
2065
1975
  id: "audits/google-consent-mode",
2066
- title: (0, import_i18n30.__)("Google Consent Mode", "elementor"),
2067
- description: (0, import_i18n30.__)(
1976
+ title: (0, import_i18n26.__)("Google Consent Mode", "elementor"),
1977
+ description: (0, import_i18n26.__)(
2068
1978
  "Google Analytics, Ads, or Tag Manager are running on this site and may not receive a consent signal, which risks losing conversion/analytics data and compliance exposure.",
2069
1979
  "elementor"
2070
1980
  ),
2071
- fixHint: (0, import_i18n30.__)(
1981
+ fixHint: (0, import_i18n26.__)(
2072
1982
  "Turn on Google Consent Mode in your cookie consent plugin so Google tags respect visitor consent choices.",
2073
1983
  "elementor"
2074
1984
  ),
2075
1985
  categories: ["compliance"],
2076
1986
  severity: "warning",
2077
1987
  weight: 1,
2078
- evaluate: async (ctx) => {
1988
+ evaluate: (ctx) => {
2079
1989
  if (!ctx.pageContext.frontend_url) {
2080
- return { status: "skipped", reason: (0, import_i18n30.__)("Page is not published.", "elementor") };
1990
+ return { status: "skipped", reason: (0, import_i18n26.__)("Page is not published.", "elementor") };
2081
1991
  }
2082
- const html = await fetchRenderedHtml(ctx.pageContext.frontend_url);
2083
- if (!html) {
2084
- return { status: "skipped", reason: (0, import_i18n30.__)("Could not fetch the published page.", "elementor") };
1992
+ if (!ctx.renderedHtml) {
1993
+ return { status: "skipped", reason: (0, import_i18n26.__)("Could not fetch the published page.", "elementor") };
2085
1994
  }
2086
- if (!hasGoogleTracking(html)) {
2087
- return { status: "skipped", reason: (0, import_i18n30.__)("No Google tracking product detected.", "elementor") };
1995
+ if (!hasGoogleTracking(ctx.renderedHtml)) {
1996
+ return { status: "skipped", reason: (0, import_i18n26.__)("No Google tracking product detected.", "elementor") };
2088
1997
  }
2089
- if (hasConsentDefaultCall(html)) {
1998
+ if (hasConsentDefaultCall(ctx.renderedHtml)) {
2090
1999
  return { status: "pass" };
2091
2000
  }
2092
- const label = (0, import_i18n30.__)("No Google Consent Mode signal was found on the page.", "elementor");
2001
+ const label = (0, import_i18n26.__)("No Google Consent Mode signal was found on the page.", "elementor");
2093
2002
  const isCookiezReady = ctx.pageContext.cookiez_plugin_installed && ctx.pageContext.cookiez_plugin_active;
2094
2003
  if (isCookiezReady) {
2095
2004
  return {
2096
2005
  status: "fail",
2097
2006
  violations: [
2098
2007
  {
2099
- auditId: audit6.id,
2008
+ auditId: audit8.id,
2100
2009
  label,
2101
- ctaLabel: (0, import_i18n30.__)("Enable", "elementor"),
2010
+ ctaLabel: (0, import_i18n26.__)("Enable", "elementor"),
2102
2011
  externalUrl: ctx.pageContext.cookiez_consent_mode_settings_url
2103
2012
  }
2104
2013
  ]
@@ -2108,7 +2017,7 @@ var audit6 = {
2108
2017
  status: "fail",
2109
2018
  violations: [
2110
2019
  {
2111
- auditId: audit6.id,
2020
+ auditId: audit8.id,
2112
2021
  label,
2113
2022
  externalUrl: ctx.pageContext.cookiez_plugin_action_url
2114
2023
  }
@@ -2118,7 +2027,7 @@ var audit6 = {
2118
2027
  };
2119
2028
 
2120
2029
  // src/audits/heading-structure.ts
2121
- var import_i18n31 = require("@wordpress/i18n");
2030
+ var import_i18n27 = require("@wordpress/i18n");
2122
2031
  var HEADING_LEVELS = ["h1", "h2", "h3", "h4", "h5", "h6"];
2123
2032
  var HEADING_CONTROLS_REGISTRY = {
2124
2033
  // Core
@@ -2294,47 +2203,47 @@ function extractPageHeadings(tree) {
2294
2203
  });
2295
2204
  return headings;
2296
2205
  }
2297
- var audit7 = {
2206
+ var audit9 = {
2298
2207
  id: "audits/heading-structure",
2299
- title: (0, import_i18n31.__)("Heading structure", "elementor"),
2300
- description: (0, import_i18n31.__)("Pages should have exactly one H1 and a non-skipping heading order.", "elementor"),
2301
- fixHint: (0, import_i18n31.__)("Ensure your page has one H1 and that heading levels do not skip (no H2 \u2192 H4).", "elementor"),
2208
+ title: (0, import_i18n27.__)("Heading structure", "elementor"),
2209
+ description: (0, import_i18n27.__)("Pages should have exactly one H1 and a non-skipping heading order.", "elementor"),
2210
+ fixHint: (0, import_i18n27.__)("Ensure your page has one H1 and that heading levels do not skip (no H2 \u2192 H4).", "elementor"),
2302
2211
  categories: ["seo", "accessibility"],
2303
2212
  severity: "error",
2304
2213
  weight: 10,
2305
2214
  evaluate: (ctx) => {
2306
2215
  if (ctx.elements.tree.length === 0) {
2307
- return { status: "skipped", reason: (0, import_i18n31.__)("No elements", "elementor") };
2216
+ return { status: "skipped", reason: (0, import_i18n27.__)("No elements", "elementor") };
2308
2217
  }
2309
2218
  const headings = extractPageHeadings(ctx.elements.tree);
2310
2219
  const violations = [];
2311
2220
  if (headings.length === 0) {
2312
2221
  violations.push({
2313
- auditId: audit7.id,
2314
- label: (0, import_i18n31.__)("No headings found on the page.", "elementor")
2222
+ auditId: audit9.id,
2223
+ label: (0, import_i18n27.__)("No headings found on the page.", "elementor")
2315
2224
  });
2316
2225
  } else {
2317
2226
  const h1Count = headings.filter((h) => h.level === 1).length;
2318
2227
  if (h1Count === 0) {
2319
- violations.push({ auditId: audit7.id, label: (0, import_i18n31.__)("No H1 on the page.", "elementor") });
2228
+ violations.push({ auditId: audit9.id, label: (0, import_i18n27.__)("No H1 on the page.", "elementor") });
2320
2229
  }
2321
2230
  if (h1Count > 1) {
2322
2231
  headings.filter((h) => h.level === 1).slice(1).forEach(
2323
2232
  (h) => violations.push({
2324
- auditId: audit7.id,
2233
+ auditId: audit9.id,
2325
2234
  elementId: h.elementId,
2326
2235
  targetHint: "element-settings",
2327
- label: (0, import_i18n31.__)("More than one H1 on the page.", "elementor")
2236
+ label: (0, import_i18n27.__)("More than one H1 on the page.", "elementor")
2328
2237
  })
2329
2238
  );
2330
2239
  }
2331
2240
  for (let i = 1; i < headings.length; i++) {
2332
2241
  if (headings[i].level - headings[i - 1].level > 1) {
2333
2242
  violations.push({
2334
- auditId: audit7.id,
2243
+ auditId: audit9.id,
2335
2244
  elementId: headings[i].elementId,
2336
2245
  targetHint: "element-settings",
2337
- label: (0, import_i18n31.__)("Heading level skipped.", "elementor")
2246
+ label: (0, import_i18n27.__)("Heading level skipped.", "elementor")
2338
2247
  });
2339
2248
  }
2340
2249
  }
@@ -2344,33 +2253,33 @@ var audit7 = {
2344
2253
  };
2345
2254
 
2346
2255
  // src/audits/hidden-elements.ts
2347
- var import_i18n32 = require("@wordpress/i18n");
2256
+ var import_i18n28 = require("@wordpress/i18n");
2348
2257
  function isHiddenOnAllDevices(settings) {
2349
2258
  return Boolean(settings.hide_desktop) && Boolean(settings.hide_tablet) && Boolean(settings.hide_mobile);
2350
2259
  }
2351
- var audit8 = {
2260
+ var audit10 = {
2352
2261
  id: "audits/hidden-elements",
2353
- title: (0, import_i18n32.__)("Hidden elements", "elementor"),
2354
- description: (0, import_i18n32.__)(
2262
+ title: (0, import_i18n28.__)("Hidden elements", "elementor"),
2263
+ description: (0, import_i18n28.__)(
2355
2264
  "Elements hidden on all devices simultaneously are effectively dead code in the page tree.",
2356
2265
  "elementor"
2357
2266
  ),
2358
- fixHint: (0, import_i18n32.__)("Remove the element or make it visible on at least one device breakpoint.", "elementor"),
2267
+ fixHint: (0, import_i18n28.__)("Remove the element or make it visible on at least one device breakpoint.", "elementor"),
2359
2268
  categories: ["best-practices", "performance"],
2360
2269
  severity: "info",
2361
2270
  weight: 3,
2362
2271
  evaluate: (ctx) => {
2363
2272
  if (ctx.elements.tree.length === 0) {
2364
- return { status: "skipped", reason: (0, import_i18n32.__)("No elements", "elementor") };
2273
+ return { status: "skipped", reason: (0, import_i18n28.__)("No elements", "elementor") };
2365
2274
  }
2366
2275
  const violations = [];
2367
2276
  walkElements(ctx.elements.tree, (node) => {
2368
2277
  if (isHiddenOnAllDevices(node.settings)) {
2369
2278
  violations.push({
2370
- auditId: audit8.id,
2279
+ auditId: audit10.id,
2371
2280
  elementId: node.id,
2372
2281
  targetHint: "element-settings",
2373
- label: (0, import_i18n32.__)("Element is hidden on all devices.", "elementor")
2282
+ label: (0, import_i18n28.__)("Element is hidden on all devices.", "elementor")
2374
2283
  });
2375
2284
  }
2376
2285
  });
@@ -2379,7 +2288,22 @@ var audit8 = {
2379
2288
  };
2380
2289
 
2381
2290
  // src/audits/images-alt-text.ts
2382
- var import_i18n33 = require("@wordpress/i18n");
2291
+ var import_i18n29 = require("@wordpress/i18n");
2292
+
2293
+ // src/utils/alt-text-image-sources.ts
2294
+ function walkAltTextImageSources(tree, visit) {
2295
+ walkImageLikeSources(tree, visit);
2296
+ walkAtomicImageSources(tree, visit, ATOMIC_CONTENT_IMAGE_PROP_KEYS);
2297
+ }
2298
+ function hasAltTextImages(tree) {
2299
+ let found = false;
2300
+ walkAltTextImageSources(tree, ({ media }) => {
2301
+ if (media.id || media.url) {
2302
+ found = true;
2303
+ }
2304
+ });
2305
+ return found;
2306
+ }
2383
2307
 
2384
2308
  // src/utils/image-alt.ts
2385
2309
  function hasMeaningfulAlt(media, pageContext) {
@@ -2387,7 +2311,8 @@ function hasMeaningfulAlt(media, pageContext) {
2387
2311
  return true;
2388
2312
  }
2389
2313
  if (media.id) {
2390
- const alt = pageContext.image_sizes[media.id]?.alt ?? "";
2314
+ const key = buildImageSizeKey({ id: media.id, size: media.size });
2315
+ const alt = pageContext.image_sizes[key]?.alt ?? "";
2391
2316
  return alt.trim().length > 0;
2392
2317
  }
2393
2318
  return (media.alt ?? "").trim().length > 0;
@@ -2397,22 +2322,23 @@ function isImageSourcePresent(media) {
2397
2322
  }
2398
2323
 
2399
2324
  // src/audits/images-alt-text.ts
2400
- var audit9 = {
2325
+ var audit11 = {
2401
2326
  id: "audits/images-alt-text",
2402
- title: (0, import_i18n33.__)("Images alt text", "elementor"),
2403
- description: (0, import_i18n33.__)("Every image needs a meaningful alt attribute for screen readers and image-search.", "elementor"),
2404
- fixHint: (0, import_i18n33.__)("Open the image's settings and add an Alt Text describing the image.", "elementor"),
2327
+ title: (0, import_i18n29.__)("Images alt text", "elementor"),
2328
+ description: (0, import_i18n29.__)("Every image needs a meaningful alt attribute for screen readers and image-search.", "elementor"),
2329
+ fixHint: (0, import_i18n29.__)("Open the image's settings and add an Alt Text describing the image.", "elementor"),
2405
2330
  categories: ["seo", "accessibility"],
2406
2331
  severity: "error",
2407
2332
  weight: 10,
2408
2333
  evaluate: (ctx) => {
2409
- if (!hasPageImages(ctx.elements.tree)) {
2410
- return { status: "skipped", reason: (0, import_i18n33.__)("No images", "elementor") };
2334
+ if (!hasAltTextImages(ctx.elements.tree)) {
2335
+ return { status: "skipped", reason: (0, import_i18n29.__)("No images", "elementor") };
2411
2336
  }
2337
+ const isReady = ctx.pageContext.ally_plugin_active;
2412
2338
  const violations = [];
2413
2339
  const failedWidgetIds = /* @__PURE__ */ new Set();
2414
2340
  let missingAltImageCount = 0;
2415
- walkImageLikeSources(ctx.elements.tree, ({ node, media }) => {
2341
+ walkAltTextImageSources(ctx.elements.tree, ({ node, media }) => {
2416
2342
  if (!isImageSourcePresent(media) || hasMeaningfulAlt(media, ctx.pageContext)) {
2417
2343
  return;
2418
2344
  }
@@ -2420,11 +2346,12 @@ var audit9 = {
2420
2346
  if (!failedWidgetIds.has(node.id)) {
2421
2347
  failedWidgetIds.add(node.id);
2422
2348
  violations.push({
2423
- auditId: audit9.id,
2349
+ auditId: audit11.id,
2424
2350
  elementId: node.id,
2425
2351
  targetHint: "element-settings",
2426
- label: (0, import_i18n33.__)("Image is missing alt text.", "elementor"),
2427
- externalUrl: ctx.pageContext.ally_plugin_url
2352
+ label: (0, import_i18n29.__)("Image is missing alt text.", "elementor"),
2353
+ externalUrl: isReady ? ctx.pageContext.ally_scan_url : ctx.pageContext.ally_plugin_url,
2354
+ ctaLabel: (0, import_i18n29.__)("Fix with Ally", "elementor")
2428
2355
  });
2429
2356
  }
2430
2357
  });
@@ -2432,30 +2359,79 @@ var audit9 = {
2432
2359
  }
2433
2360
  };
2434
2361
 
2362
+ // src/audits/images-inefficient-format.ts
2363
+ var import_i18n30 = require("@wordpress/i18n");
2364
+ var LEGACY_RASTER_MIME_TYPES = /* @__PURE__ */ new Set(["image/jpeg", "image/png", "image/gif", "image/bmp", "image/tiff"]);
2365
+ var audit12 = {
2366
+ id: "audits/images-inefficient-format",
2367
+ title: (0, import_i18n30.__)("Inefficient image format", "elementor"),
2368
+ description: (0, import_i18n30.__)("Image isn't served in a modern, efficient format like WebP or AVIF.", "elementor"),
2369
+ fixHint: (0, import_i18n30.__)("Convert the image to a modern format like WebP or AVIF, or enable image optimization.", "elementor"),
2370
+ categories: ["performance"],
2371
+ severity: "info",
2372
+ weight: 1,
2373
+ evaluate: (ctx) => {
2374
+ const isReady = ctx.pageContext.image_optimization_plugin_active;
2375
+ const offendingElementIds = /* @__PURE__ */ new Set();
2376
+ let hasAnyImage = false;
2377
+ let inefficientImageFormatCount = 0;
2378
+ const evaluateSource = (node, media) => {
2379
+ if (media.id || media.url) {
2380
+ hasAnyImage = true;
2381
+ }
2382
+ const id = media.id;
2383
+ if (!id) {
2384
+ return;
2385
+ }
2386
+ const mime = ctx.pageContext.image_sizes[buildImageSizeKey({ id, size: media.size })]?.mime;
2387
+ if (!mime || !LEGACY_RASTER_MIME_TYPES.has(mime)) {
2388
+ return;
2389
+ }
2390
+ inefficientImageFormatCount++;
2391
+ offendingElementIds.add(node.id);
2392
+ };
2393
+ walkAllImageSources(ctx.elements.tree, ({ node, media }) => evaluateSource(node, media));
2394
+ if (!hasAnyImage) {
2395
+ return { status: "skipped", reason: (0, import_i18n30.__)("No images", "elementor") };
2396
+ }
2397
+ const violations = Array.from(offendingElementIds).map((elementId) => ({
2398
+ auditId: audit12.id,
2399
+ elementId,
2400
+ targetHint: "element-settings",
2401
+ label: (0, import_i18n30.__)("Image isn't served in a modern format like WebP or AVIF.", "elementor"),
2402
+ externalUrl: isReady ? ctx.pageContext.image_optimization_settings_url : ctx.pageContext.image_optimization_plugin_url,
2403
+ ctaLabel: (0, import_i18n30.__)("Optimize all", "elementor")
2404
+ }));
2405
+ return violations.length === 0 ? { status: "pass" } : { status: "fail", violations, metadata: { inefficientImageFormatCount } };
2406
+ }
2407
+ };
2408
+
2435
2409
  // src/audits/images-too-large.ts
2436
- var import_i18n34 = require("@wordpress/i18n");
2410
+ var import_i18n31 = require("@wordpress/i18n");
2437
2411
  var SIZE_THRESHOLD_BYTES = 500 * 1024;
2438
2412
  var BYTES_PER_KB = 1024;
2439
- var audit10 = {
2413
+ var audit13 = {
2440
2414
  id: "audits/images-too-large",
2441
- title: (0, import_i18n34.__)("Oversized images", "elementor"),
2442
- description: (0, import_i18n34.__)("Large image files slow down the page.", "elementor"),
2443
- fixHint: (0, import_i18n34.__)("Replace the image with a smaller version or enable image optimization.", "elementor"),
2415
+ title: (0, import_i18n31.__)("Oversized images", "elementor"),
2416
+ description: (0, import_i18n31.__)("Large image files slow down the page.", "elementor"),
2417
+ fixHint: (0, import_i18n31.__)("Replace the image with a smaller version or enable image optimization.", "elementor"),
2444
2418
  categories: ["performance"],
2445
2419
  severity: "warning",
2446
2420
  weight: 7,
2447
2421
  evaluate: (ctx) => {
2448
- if (!hasPageImages(ctx.elements.tree)) {
2449
- return { status: "skipped", reason: (0, import_i18n34.__)("No images", "elementor") };
2450
- }
2422
+ const isReady = ctx.pageContext.image_optimization_plugin_active;
2451
2423
  const widgetMaxKb = /* @__PURE__ */ new Map();
2452
2424
  let oversizedImageCount = 0;
2453
- walkImageLikeSources(ctx.elements.tree, ({ node, media }) => {
2425
+ let hasAnyImage = false;
2426
+ const evaluateSource = (node, media) => {
2427
+ if (media.id || media.url) {
2428
+ hasAnyImage = true;
2429
+ }
2454
2430
  const id = media.id;
2455
2431
  if (!id) {
2456
2432
  return;
2457
2433
  }
2458
- const size = ctx.pageContext.image_sizes[id];
2434
+ const size = ctx.pageContext.image_sizes[buildImageSizeKey({ id, size: media.size })];
2459
2435
  if (!size || size.filesize_bytes <= SIZE_THRESHOLD_BYTES) {
2460
2436
  return;
2461
2437
  }
@@ -2463,38 +2439,43 @@ var audit10 = {
2463
2439
  const kb = Math.round(size.filesize_bytes / BYTES_PER_KB);
2464
2440
  const currentMax = widgetMaxKb.get(node.id) ?? 0;
2465
2441
  widgetMaxKb.set(node.id, Math.max(currentMax, kb));
2466
- });
2442
+ };
2443
+ walkAllImageSources(ctx.elements.tree, ({ node, media }) => evaluateSource(node, media));
2444
+ if (!hasAnyImage) {
2445
+ return { status: "skipped", reason: (0, import_i18n31.__)("No images", "elementor") };
2446
+ }
2467
2447
  const violations = Array.from(widgetMaxKb.entries()).map(([elementId, kb]) => ({
2468
- auditId: audit10.id,
2448
+ auditId: audit13.id,
2469
2449
  elementId,
2470
2450
  targetHint: "element-settings",
2471
- label: (0, import_i18n34.sprintf)(
2451
+ label: (0, import_i18n31.sprintf)(
2472
2452
  /* translators: %d is the image file size in kilobytes. */
2473
- (0, import_i18n34.__)("Image is %d KB (over 500 KB).", "elementor"),
2453
+ (0, import_i18n31.__)("Image is %d KB (over 500 KB).", "elementor"),
2474
2454
  kb
2475
2455
  ),
2476
- externalUrl: ctx.pageContext.image_optimization_plugin_url
2456
+ externalUrl: isReady ? ctx.pageContext.image_optimization_settings_url : ctx.pageContext.image_optimization_plugin_url,
2457
+ ctaLabel: (0, import_i18n31.__)("Optimize all", "elementor")
2477
2458
  }));
2478
2459
  return violations.length === 0 ? { status: "pass" } : { status: "fail", violations, metadata: { oversizedImageCount } };
2479
2460
  }
2480
2461
  };
2481
2462
 
2482
2463
  // src/audits/nested-boxed-containers.ts
2483
- var import_i18n35 = require("@wordpress/i18n");
2464
+ var import_i18n32 = require("@wordpress/i18n");
2484
2465
  function isBoxed(settings) {
2485
2466
  return settings.content_width === "boxed";
2486
2467
  }
2487
- var audit11 = {
2468
+ var audit14 = {
2488
2469
  id: "audits/nested-boxed-containers",
2489
- title: (0, import_i18n35.__)("Boxed container nested inside a boxed parent", "elementor"),
2490
- description: (0, import_i18n35.__)("An inner container does not need to be boxed when its parent already is.", "elementor"),
2491
- fixHint: (0, import_i18n35.__)("Change the inner container's content width to Full Width.", "elementor"),
2470
+ title: (0, import_i18n32.__)("Boxed container nested inside a boxed parent", "elementor"),
2471
+ description: (0, import_i18n32.__)("An inner container does not need to be boxed when its parent already is.", "elementor"),
2472
+ fixHint: (0, import_i18n32.__)("Change the inner container's content width to Full Width.", "elementor"),
2492
2473
  categories: ["best-practices", "performance"],
2493
2474
  severity: "info",
2494
2475
  weight: 5,
2495
2476
  evaluate: (ctx) => {
2496
2477
  if (ctx.elements.tree.length === 0) {
2497
- return { status: "skipped", reason: (0, import_i18n35.__)("No elements", "elementor") };
2478
+ return { status: "skipped", reason: (0, import_i18n32.__)("No elements", "elementor") };
2498
2479
  }
2499
2480
  const violations = [];
2500
2481
  walkElements(ctx.elements.tree, (node, parents) => {
@@ -2504,10 +2485,15 @@ var audit11 = {
2504
2485
  const nearestContainerAncestor = [...parents].reverse().find((p) => p.elType === "container");
2505
2486
  if (nearestContainerAncestor && isBoxed(nearestContainerAncestor.settings)) {
2506
2487
  violations.push({
2507
- auditId: audit11.id,
2488
+ auditId: audit14.id,
2508
2489
  elementId: node.id,
2509
2490
  targetHint: "element-settings",
2510
- label: (0, import_i18n35.__)("Nested boxed container.", "elementor")
2491
+ label: (0, import_i18n32.__)("Nested boxed container.", "elementor"),
2492
+ angieFix: true,
2493
+ angiePrompt: (0, import_i18n32.__)(
2494
+ "Find containers that have nested containers. Containers can be deep-nested. Update the nested container styling so that when a parent container already uses a boxed setting, any inner container inside it is set to Full Width. Keep the first parent container boxed, remove all boxed styling from the nested inner containers, and preserve the inner container\u2019s content, spacing, alignment, and hierarchy.",
2495
+ "elementor"
2496
+ )
2511
2497
  });
2512
2498
  }
2513
2499
  });
@@ -2516,12 +2502,12 @@ var audit11 = {
2516
2502
  };
2517
2503
 
2518
2504
  // src/audits/page-excerpt.ts
2519
- var import_i18n36 = require("@wordpress/i18n");
2520
- var audit12 = {
2505
+ var import_i18n33 = require("@wordpress/i18n");
2506
+ var audit15 = {
2521
2507
  id: "audits/page-excerpt",
2522
- title: (0, import_i18n36.__)("Page excerpt", "elementor"),
2523
- description: (0, import_i18n36.__)("A descriptive excerpt helps search engines and previews summarize the page.", "elementor"),
2524
- fixHint: (0, import_i18n36.__)("Open Page Settings and write a short excerpt.", "elementor"),
2508
+ title: (0, import_i18n33.__)("Page excerpt", "elementor"),
2509
+ description: (0, import_i18n33.__)("A descriptive excerpt helps search engines and previews summarize the page.", "elementor"),
2510
+ fixHint: (0, import_i18n33.__)("Open Page Settings and write a short excerpt.", "elementor"),
2525
2511
  categories: ["seo"],
2526
2512
  severity: "warning",
2527
2513
  weight: 5,
@@ -2533,8 +2519,8 @@ var audit12 = {
2533
2519
  status: "fail",
2534
2520
  violations: [
2535
2521
  {
2536
- auditId: audit12.id,
2537
- label: (0, import_i18n36.__)("Page has no excerpt.", "elementor"),
2522
+ auditId: audit15.id,
2523
+ label: (0, import_i18n33.__)("Page has no excerpt.", "elementor"),
2538
2524
  targetHint: "page-settings",
2539
2525
  angieFix: true
2540
2526
  }
@@ -2544,12 +2530,12 @@ var audit12 = {
2544
2530
  };
2545
2531
 
2546
2532
  // src/audits/page-featured-image.ts
2547
- var import_i18n37 = require("@wordpress/i18n");
2548
- var audit13 = {
2533
+ var import_i18n34 = require("@wordpress/i18n");
2534
+ var audit16 = {
2549
2535
  id: "audits/page-featured-image",
2550
- title: (0, import_i18n37.__)("Page featured image", "elementor"),
2551
- description: (0, import_i18n37.__)("Featured images are used by social shares and many themes for hero visuals.", "elementor"),
2552
- fixHint: (0, import_i18n37.__)("Open Page Settings and set a featured image.", "elementor"),
2536
+ title: (0, import_i18n34.__)("Page featured image", "elementor"),
2537
+ description: (0, import_i18n34.__)("Featured images are used by social shares and many themes for hero visuals.", "elementor"),
2538
+ fixHint: (0, import_i18n34.__)("Open Page Settings and set a featured image.", "elementor"),
2553
2539
  categories: ["seo"],
2554
2540
  severity: "info",
2555
2541
  weight: 5,
@@ -2561,8 +2547,8 @@ var audit13 = {
2561
2547
  status: "fail",
2562
2548
  violations: [
2563
2549
  {
2564
- auditId: audit13.id,
2565
- label: (0, import_i18n37.__)("No featured image set.", "elementor"),
2550
+ auditId: audit16.id,
2551
+ label: (0, import_i18n34.__)("No featured image set.", "elementor"),
2566
2552
  targetHint: "page-settings"
2567
2553
  }
2568
2554
  ]
@@ -2571,13 +2557,13 @@ var audit13 = {
2571
2557
  };
2572
2558
 
2573
2559
  // src/audits/page-title.ts
2574
- var import_i18n38 = require("@wordpress/i18n");
2560
+ var import_i18n35 = require("@wordpress/i18n");
2575
2561
  var MAX_TITLE_LENGTH = 60;
2576
- var audit14 = {
2562
+ var audit17 = {
2577
2563
  id: "audits/page-title",
2578
- title: (0, import_i18n38.__)("Page title", "elementor"),
2579
- description: (0, import_i18n38.__)("Pages need a clear title for SEO and screen-reader navigation.", "elementor"),
2580
- fixHint: (0, import_i18n38.__)("Open Page Settings and add a title.", "elementor"),
2564
+ title: (0, import_i18n35.__)("Page title", "elementor"),
2565
+ description: (0, import_i18n35.__)("Pages need a clear title for SEO and screen-reader navigation.", "elementor"),
2566
+ fixHint: (0, import_i18n35.__)("Open Page Settings and add a title.", "elementor"),
2581
2567
  categories: ["seo"],
2582
2568
  severity: "error",
2583
2569
  weight: 10,
@@ -2588,11 +2574,11 @@ var audit14 = {
2588
2574
  status: "fail",
2589
2575
  violations: [
2590
2576
  {
2591
- auditId: audit14.id,
2592
- label: (0, import_i18n38.__)("Page has no title.", "elementor"),
2577
+ auditId: audit17.id,
2578
+ label: (0, import_i18n35.__)("Page has no title.", "elementor"),
2593
2579
  targetHint: "page-settings",
2594
2580
  angieFix: true,
2595
- angiePrompt: (0, import_i18n38.__)(
2581
+ angiePrompt: (0, import_i18n35.__)(
2596
2582
  "Based on this page's contents, generate a title for this page.",
2597
2583
  "elementor"
2598
2584
  )
@@ -2605,8 +2591,8 @@ var audit14 = {
2605
2591
  status: "fail",
2606
2592
  violations: [
2607
2593
  {
2608
- auditId: audit14.id,
2609
- label: (0, import_i18n38.__)("Page title is too long.", "elementor"),
2594
+ auditId: audit17.id,
2595
+ label: (0, import_i18n35.__)("Page title is too long.", "elementor"),
2610
2596
  targetHint: "page-settings",
2611
2597
  angieFix: true
2612
2598
  }
@@ -2618,7 +2604,7 @@ var audit14 = {
2618
2604
  };
2619
2605
 
2620
2606
  // src/audits/prefer-global-colors.ts
2621
- var import_i18n39 = require("@wordpress/i18n");
2607
+ var import_i18n36 = require("@wordpress/i18n");
2622
2608
 
2623
2609
  // src/utils/collect-hardcoded-colors.ts
2624
2610
  var RESERVED_SETTING_KEYS = /* @__PURE__ */ new Set(["__globals__", "__dynamic__"]);
@@ -2684,20 +2670,20 @@ function findMatchingGlobalByValue(value, globals) {
2684
2670
  }
2685
2671
 
2686
2672
  // src/audits/prefer-global-colors.ts
2687
- var audit15 = {
2673
+ var audit18 = {
2688
2674
  id: "audits/prefer-global-colors",
2689
- title: (0, import_i18n39.__)("Prefer global colors", "elementor"),
2690
- description: (0, import_i18n39.__)("Global colors make the design consistent and easy to update site-wide.", "elementor"),
2691
- fixHint: (0, import_i18n39.__)("Replace the hard-coded value with the matching global color.", "elementor"),
2675
+ title: (0, import_i18n36.__)("Prefer global colors", "elementor"),
2676
+ description: (0, import_i18n36.__)("Global colors make the design consistent and easy to update site-wide.", "elementor"),
2677
+ fixHint: (0, import_i18n36.__)("Replace the hard-coded value with the matching global color.", "elementor"),
2692
2678
  categories: ["best-practices"],
2693
2679
  severity: "info",
2694
2680
  weight: 3,
2695
2681
  evaluate: (ctx) => {
2696
2682
  if (ctx.elements.tree.length === 0) {
2697
- return { status: "skipped", reason: (0, import_i18n39.__)("No elements", "elementor") };
2683
+ return { status: "skipped", reason: (0, import_i18n36.__)("No elements", "elementor") };
2698
2684
  }
2699
2685
  if (ctx.kit.globals.colors.length === 0) {
2700
- return { status: "skipped", reason: (0, import_i18n39.__)("No global colors", "elementor") };
2686
+ return { status: "skipped", reason: (0, import_i18n36.__)("No global colors", "elementor") };
2701
2687
  }
2702
2688
  const violations = [];
2703
2689
  walkElements(ctx.elements.tree, (node) => {
@@ -2707,13 +2693,13 @@ var audit15 = {
2707
2693
  continue;
2708
2694
  }
2709
2695
  violations.push({
2710
- auditId: audit15.id,
2696
+ auditId: audit18.id,
2711
2697
  elementId: node.id,
2712
2698
  targetHint: "element-settings",
2713
2699
  angieFix: true,
2714
- label: (0, import_i18n39.sprintf)(
2700
+ label: (0, import_i18n36.sprintf)(
2715
2701
  /* translators: 1: hard-coded color value, 2: global color title. */
2716
- (0, import_i18n39.__)('replace "%1$s" with "%2$s" global color', "elementor"),
2702
+ (0, import_i18n36.__)('replace "%1$s" with "%2$s" global color', "elementor"),
2717
2703
  value,
2718
2704
  global.title
2719
2705
  )
@@ -2725,7 +2711,7 @@ var audit15 = {
2725
2711
  };
2726
2712
 
2727
2713
  // src/audits/prefer-global-fonts.ts
2728
- var import_i18n40 = require("@wordpress/i18n");
2714
+ var import_i18n37 = require("@wordpress/i18n");
2729
2715
 
2730
2716
  // src/utils/collect-hardcoded-fonts.ts
2731
2717
  var RESERVED_SETTING_KEYS2 = /* @__PURE__ */ new Set(["__globals__", "__dynamic__"]);
@@ -2783,20 +2769,20 @@ function collectHardcodedFonts(settings, pathPrefix = "") {
2783
2769
  }
2784
2770
 
2785
2771
  // src/audits/prefer-global-fonts.ts
2786
- var audit16 = {
2772
+ var audit19 = {
2787
2773
  id: "audits/prefer-global-fonts",
2788
- title: (0, import_i18n40.__)("Prefer global fonts", "elementor"),
2789
- description: (0, import_i18n40.__)("Global fonts make the design consistent and easy to update site-wide.", "elementor"),
2790
- fixHint: (0, import_i18n40.__)("Replace the hard-coded value with the matching global font.", "elementor"),
2774
+ title: (0, import_i18n37.__)("Prefer global fonts", "elementor"),
2775
+ description: (0, import_i18n37.__)("Global fonts make the design consistent and easy to update site-wide.", "elementor"),
2776
+ fixHint: (0, import_i18n37.__)("Replace the hard-coded value with the matching global font.", "elementor"),
2791
2777
  categories: ["best-practices", "performance"],
2792
2778
  severity: "info",
2793
2779
  weight: 3,
2794
2780
  evaluate: (ctx) => {
2795
2781
  if (ctx.elements.tree.length === 0) {
2796
- return { status: "skipped", reason: (0, import_i18n40.__)("No elements", "elementor") };
2782
+ return { status: "skipped", reason: (0, import_i18n37.__)("No elements", "elementor") };
2797
2783
  }
2798
2784
  if (ctx.kit.globals.fonts.length === 0) {
2799
- return { status: "skipped", reason: (0, import_i18n40.__)("No global fonts", "elementor") };
2785
+ return { status: "skipped", reason: (0, import_i18n37.__)("No global fonts", "elementor") };
2800
2786
  }
2801
2787
  const violations = [];
2802
2788
  walkElements(ctx.elements.tree, (node) => {
@@ -2806,12 +2792,12 @@ var audit16 = {
2806
2792
  continue;
2807
2793
  }
2808
2794
  violations.push({
2809
- auditId: audit16.id,
2795
+ auditId: audit19.id,
2810
2796
  elementId: node.id,
2811
2797
  targetHint: "element-settings",
2812
- label: (0, import_i18n40.sprintf)(
2798
+ label: (0, import_i18n37.sprintf)(
2813
2799
  /* translators: %s: global font title. */
2814
- (0, import_i18n40.__)('replace hardcoded typography with "%s" global font', "elementor"),
2800
+ (0, import_i18n37.__)('replace hardcoded typography with "%s" global font', "elementor"),
2815
2801
  global.title
2816
2802
  )
2817
2803
  });
@@ -2822,15 +2808,15 @@ var audit16 = {
2822
2808
  };
2823
2809
 
2824
2810
  // src/audits/privacy-policy.ts
2825
- var import_i18n41 = require("@wordpress/i18n");
2826
- var audit17 = {
2811
+ var import_i18n38 = require("@wordpress/i18n");
2812
+ var audit20 = {
2827
2813
  id: "audits/privacy-policy",
2828
- title: (0, import_i18n41.__)("Privacy policy", "elementor"),
2829
- description: (0, import_i18n41.__)(
2814
+ title: (0, import_i18n38.__)("Privacy policy", "elementor"),
2815
+ description: (0, import_i18n38.__)(
2830
2816
  "A privacy policy page is required by privacy regulations such as GDPR (EU) and CCPA (US).",
2831
2817
  "elementor"
2832
2818
  ),
2833
- fixHint: (0, import_i18n41.__)(
2819
+ fixHint: (0, import_i18n38.__)(
2834
2820
  "Go to Settings > Privacy in the WordPress admin and assign a published privacy policy page.",
2835
2821
  "elementor"
2836
2822
  ),
@@ -2845,10 +2831,10 @@ var audit17 = {
2845
2831
  status: "fail",
2846
2832
  violations: [
2847
2833
  {
2848
- auditId: audit17.id,
2849
- label: (0, import_i18n41.__)("No privacy policy page is set.", "elementor"),
2834
+ auditId: audit20.id,
2835
+ label: (0, import_i18n38.__)("No privacy policy page is set.", "elementor"),
2850
2836
  externalUrl: ctx.pageContext.privacy_settings_url,
2851
- ctaLabel: (0, import_i18n41.__)("Create", "elementor")
2837
+ ctaLabel: (0, import_i18n38.__)("Create", "elementor")
2852
2838
  }
2853
2839
  ]
2854
2840
  };
@@ -2856,15 +2842,15 @@ var audit17 = {
2856
2842
  };
2857
2843
 
2858
2844
  // src/audits/robots-noindex.ts
2859
- var import_i18n42 = require("@wordpress/i18n");
2860
- var audit18 = {
2845
+ var import_i18n39 = require("@wordpress/i18n");
2846
+ var audit21 = {
2861
2847
  id: "audits/robots-noindex",
2862
- title: (0, import_i18n42.__)("Search engine visibility", "elementor"),
2863
- description: (0, import_i18n42.__)(
2848
+ title: (0, import_i18n39.__)("Search engine visibility", "elementor"),
2849
+ description: (0, import_i18n39.__)(
2864
2850
  "When search engine visibility is checked in WordPress Settings -> Reading -> Search Engine Visibility, search engines are discouraged from indexing the website.",
2865
2851
  "elementor"
2866
2852
  ),
2867
- fixHint: (0, import_i18n42.__)(
2853
+ fixHint: (0, import_i18n39.__)(
2868
2854
  'Go to Settings \u2192 Reading in the WordPress admin and uncheck "Discourage search engines from indexing this site" when the website should be indexed.',
2869
2855
  "elementor"
2870
2856
  ),
@@ -2879,8 +2865,8 @@ var audit18 = {
2879
2865
  status: "fail",
2880
2866
  violations: [
2881
2867
  {
2882
- auditId: audit18.id,
2883
- label: (0, import_i18n42.__)("Search engines are discouraged from indexing this website.", "elementor"),
2868
+ auditId: audit21.id,
2869
+ label: (0, import_i18n39.__)("Search engines are discouraged from indexing this website.", "elementor"),
2884
2870
  externalUrl: ctx.pageContext.reading_settings_url
2885
2871
  }
2886
2872
  ]
@@ -2889,15 +2875,15 @@ var audit18 = {
2889
2875
  };
2890
2876
 
2891
2877
  // src/audits/scan-for-cookies.ts
2892
- var import_i18n43 = require("@wordpress/i18n");
2893
- var audit19 = {
2878
+ var import_i18n40 = require("@wordpress/i18n");
2879
+ var audit22 = {
2894
2880
  id: "audits/scan-for-cookies",
2895
- title: (0, import_i18n43.__)("Scan for cookies", "elementor"),
2896
- description: (0, import_i18n43.__)(
2881
+ title: (0, import_i18n40.__)("Scan for cookies", "elementor"),
2882
+ description: (0, import_i18n40.__)(
2897
2883
  "Your site may be setting cookies you haven't disclosed. Some countries require a banner even without a full consent policy. Scan to see what needs disclosure.",
2898
2884
  "elementor"
2899
2885
  ),
2900
- fixHint: (0, import_i18n43.__)("Run a Cookiez scan on this page to see which cookies need disclosure.", "elementor"),
2886
+ fixHint: (0, import_i18n40.__)("Run a Cookiez scan on this page to see which cookies need disclosure.", "elementor"),
2901
2887
  categories: ["compliance"],
2902
2888
  severity: "info",
2903
2889
  weight: 0,
@@ -2907,8 +2893,8 @@ var audit19 = {
2907
2893
  status: "fail",
2908
2894
  violations: [
2909
2895
  {
2910
- auditId: audit19.id,
2911
- label: (0, import_i18n43.__)("This page has not been scanned for cookies yet.", "elementor"),
2896
+ auditId: audit22.id,
2897
+ label: (0, import_i18n40.__)("This page has not been scanned for cookies yet.", "elementor"),
2912
2898
  externalUrl: isReady ? ctx.pageContext.cookiez_scan_url : ctx.pageContext.cookiez_plugin_action_url
2913
2899
  }
2914
2900
  ]
@@ -2917,29 +2903,29 @@ var audit19 = {
2917
2903
  };
2918
2904
 
2919
2905
  // src/audits/sections-and-columns.ts
2920
- var import_i18n44 = require("@wordpress/i18n");
2921
- var audit20 = {
2906
+ var import_i18n41 = require("@wordpress/i18n");
2907
+ var audit23 = {
2922
2908
  id: "audits/sections-and-columns",
2923
- title: (0, import_i18n44.__)("Sections and columns", "elementor"),
2924
- description: (0, import_i18n44.__)(
2909
+ title: (0, import_i18n41.__)("Sections and columns", "elementor"),
2910
+ description: (0, import_i18n41.__)(
2925
2911
  "Sections and columns are legacy elements. Containers render fewer DOM nodes and are more flexible.",
2926
2912
  "elementor"
2927
2913
  ),
2928
- fixHint: (0, import_i18n44.__)("Use the Container Converter to replace each section/column with a container.", "elementor"),
2914
+ fixHint: (0, import_i18n41.__)("Use the Container Converter to replace each section/column with a container.", "elementor"),
2929
2915
  categories: ["best-practices", "performance"],
2930
2916
  severity: "warning",
2931
2917
  weight: 7,
2932
2918
  evaluate: (ctx) => {
2933
2919
  if (ctx.elements.tree.length === 0) {
2934
- return { status: "skipped", reason: (0, import_i18n44.__)("No elements", "elementor") };
2920
+ return { status: "skipped", reason: (0, import_i18n41.__)("No elements", "elementor") };
2935
2921
  }
2936
2922
  const violations = [];
2937
2923
  walkElements(ctx.elements.tree, (node) => {
2938
2924
  if (node.elType === "section" || node.elType === "column") {
2939
2925
  violations.push({
2940
- auditId: audit20.id,
2926
+ auditId: audit23.id,
2941
2927
  elementId: node.id,
2942
- label: node.elType === "section" ? (0, import_i18n44.__)("Section element", "elementor") : (0, import_i18n44.__)("Column element", "elementor")
2928
+ label: node.elType === "section" ? (0, import_i18n41.__)("Section element", "elementor") : (0, import_i18n41.__)("Column element", "elementor")
2943
2929
  });
2944
2930
  }
2945
2931
  });
@@ -2948,15 +2934,15 @@ var audit20 = {
2948
2934
  };
2949
2935
 
2950
2936
  // src/audits/site-identity.ts
2951
- var import_i18n45 = require("@wordpress/i18n");
2952
- var audit21 = {
2937
+ var import_i18n42 = require("@wordpress/i18n");
2938
+ var audit24 = {
2953
2939
  id: "audits/site-identity",
2954
- title: (0, import_i18n45.__)("Site identity", "elementor"),
2955
- description: (0, import_i18n45.__)(
2940
+ title: (0, import_i18n42.__)("Site identity", "elementor"),
2941
+ description: (0, import_i18n42.__)(
2956
2942
  "Site name, description, logo, and favicon establish your brand and appear in search results and browser tabs.",
2957
2943
  "elementor"
2958
2944
  ),
2959
- fixHint: (0, import_i18n45.__)("Open Site Settings \u2192 Site Identity and complete all the missing fields.", "elementor"),
2945
+ fixHint: (0, import_i18n42.__)("Open Site Settings \u2192 Site Identity and complete all the missing fields.", "elementor"),
2960
2946
  categories: ["best-practices", "seo"],
2961
2947
  severity: "info",
2962
2948
  weight: 7,
@@ -2965,30 +2951,30 @@ var audit21 = {
2965
2951
  const violations = [];
2966
2952
  if (!identity.site_name_set) {
2967
2953
  violations.push({
2968
- auditId: audit21.id,
2969
- label: (0, import_i18n45.__)("Site name is missing or still uses the default.", "elementor"),
2954
+ auditId: audit24.id,
2955
+ label: (0, import_i18n42.__)("Site name is missing or still uses the default.", "elementor"),
2970
2956
  targetHint: "site-identity-settings"
2971
2957
  });
2972
2958
  }
2973
2959
  if (!identity.site_description_set) {
2974
2960
  violations.push({
2975
- auditId: audit21.id,
2976
- label: (0, import_i18n45.__)("Site description is missing or still uses the default.", "elementor"),
2961
+ auditId: audit24.id,
2962
+ label: (0, import_i18n42.__)("Site description is missing or still uses the default.", "elementor"),
2977
2963
  targetHint: "site-identity-settings",
2978
2964
  angieFix: true
2979
2965
  });
2980
2966
  }
2981
2967
  if (!identity.site_logo_set) {
2982
2968
  violations.push({
2983
- auditId: audit21.id,
2984
- label: (0, import_i18n45.__)("Site logo is not set.", "elementor"),
2969
+ auditId: audit24.id,
2970
+ label: (0, import_i18n42.__)("Site logo is not set.", "elementor"),
2985
2971
  targetHint: "site-identity-settings"
2986
2972
  });
2987
2973
  }
2988
2974
  if (!identity.site_favicon_set) {
2989
2975
  violations.push({
2990
- auditId: audit21.id,
2991
- label: (0, import_i18n45.__)("Site favicon is not set.", "elementor"),
2976
+ auditId: audit24.id,
2977
+ label: (0, import_i18n42.__)("Site favicon is not set.", "elementor"),
2992
2978
  targetHint: "site-identity-settings"
2993
2979
  });
2994
2980
  }
@@ -3002,87 +2988,58 @@ var audit21 = {
3002
2988
  }
3003
2989
  };
3004
2990
 
3005
- // src/audits/too-many-widgets.ts
3006
- var import_editor_elements8 = require("@elementor/editor-elements");
3007
- var import_i18n46 = require("@wordpress/i18n");
3008
- var WIDGET_COUNT_THRESHOLD = 100;
3009
- function isCountableWidget(node, widgetsCache) {
3010
- if (node.elType === "widget") {
3011
- return true;
3012
- }
3013
- return !!widgetsCache?.[node.elType]?.meta?.is_compound;
3014
- }
3015
- var audit22 = {
3016
- id: "audits/too-many-widgets",
3017
- title: (0, import_i18n46.__)("Too many widgets", "elementor"),
3018
- description: (0, import_i18n46.__)("Excessive DOM size caused by too many widgets degrades rendering performance.", "elementor"),
3019
- fixHint: (0, import_i18n46.__)("Reduce the number of widgets on the page by removing or combining elements.", "elementor"),
3020
- categories: ["best-practices", "performance"],
3021
- severity: "warning",
3022
- weight: 5,
3023
- evaluate: (ctx) => {
3024
- if (ctx.elements.tree.length === 0) {
3025
- return { status: "skipped", reason: (0, import_i18n46.__)("No elements", "elementor") };
3026
- }
3027
- const widgetsCache = (0, import_editor_elements8.getWidgetsCache)();
3028
- let widgetCount = 0;
3029
- walkElements(ctx.elements.tree, (node) => {
3030
- if (isCountableWidget(node, widgetsCache)) {
3031
- widgetCount++;
3032
- }
3033
- });
3034
- if (widgetCount <= WIDGET_COUNT_THRESHOLD) {
3035
- return { status: "pass" };
3036
- }
3037
- return {
3038
- status: "fail",
3039
- violations: [
3040
- {
3041
- auditId: audit22.id,
3042
- label: (0, import_i18n46.__)("Page has too many widgets.", "elementor")
3043
- }
3044
- ]
3045
- };
3046
- }
3047
- };
3048
-
3049
2991
  // src/register-audits.ts
3050
2992
  var AUDITS = [
3051
- audit14,
3052
- audit12,
3053
- audit13,
3054
- audit8,
3055
- audit5,
3056
- audit22,
3057
- audit20,
3058
- audit3,
3059
- audit11,
3060
- audit4,
3061
- audit21,
2993
+ audit17,
3062
2994
  audit15,
3063
2995
  audit16,
3064
- audit7,
3065
- audit9,
3066
2996
  audit10,
2997
+ audit7,
2998
+ audit23,
2999
+ audit5,
3000
+ audit14,
3001
+ audit6,
3002
+ audit24,
3067
3003
  audit18,
3068
- audit17,
3004
+ audit19,
3005
+ audit9,
3006
+ audit11,
3007
+ audit13,
3008
+ audit12,
3009
+ audit21,
3010
+ audit20,
3069
3011
  audit,
3070
3012
  audit2,
3071
- audit19,
3072
- audit6
3013
+ audit3,
3014
+ audit4,
3015
+ audit22,
3016
+ audit8
3073
3017
  ];
3074
3018
  function registerAllAudits() {
3075
- for (const audit23 of AUDITS) {
3076
- registerAudit(audit23);
3019
+ for (const audit25 of AUDITS) {
3020
+ registerAudit(audit25);
3077
3021
  }
3078
3022
  }
3079
3023
 
3024
+ // src/sync-report-staleness.ts
3025
+ var import_editor_v1_adapters3 = require("@elementor/editor-v1-adapters");
3026
+ var import_store6 = require("@elementor/store");
3027
+ function syncReportStaleness() {
3028
+ (0, import_editor_v1_adapters3.__privateListenTo)((0, import_editor_v1_adapters3.commandEndEvent)("document/save/set-is-modified"), (e) => {
3029
+ const event = e;
3030
+ if (event.args?.status) {
3031
+ (0, import_store6.__dispatch)(slice.actions.reportStale());
3032
+ }
3033
+ });
3034
+ }
3035
+
3080
3036
  // src/init.ts
3081
3037
  function init() {
3082
- (0, import_store4.__registerSlice)(slice);
3038
+ (0, import_store7.__registerSlice)(slice);
3083
3039
  registerAllAudits();
3084
- (0, import_editor_floating_panels3.registerFloatingPanel)(auditPanel.panel);
3040
+ (0, import_editor_floating_panels6.registerFloatingPanel)(auditPanel.panel);
3085
3041
  registerAppBarAuditsToggle();
3042
+ syncReportStaleness();
3086
3043
  }
3087
3044
  // Annotate the CommonJS export names for ESM import in node:
3088
3045
  0 && (module.exports = {