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