@archie/devtools 0.0.46 → 0.0.48

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.
@@ -9617,6 +9617,7 @@ var combinedCollisionDetection = (args) => {
9617
9617
  // src/client/dnd/DndProvider.tsx
9618
9618
  var import_jsx_runtime5 = require("react/jsx-runtime");
9619
9619
  var externalDragMouse = { x: 0, y: 0, commitRequested: false };
9620
+ var EMPTY_SENSORS = [];
9620
9621
  function DndProvider({ children }) {
9621
9622
  const setActive = useDndStore((s) => s.setActive);
9622
9623
  const dragEndHandlers = useDndStore((s) => s.dragEndHandlers);
@@ -9758,30 +9759,17 @@ function DndProvider({ children }) {
9758
9759
  designModeState.resetArrangeSession();
9759
9760
  designModeState.setEnabled(false);
9760
9761
  }, []);
9761
- if (designModeEnabled) {
9762
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
9763
- children,
9764
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
9765
- DesignModeErrorBoundary,
9766
- {
9767
- onError: handleDesignModeCrash,
9768
- resetKey: designModeEnabled,
9769
- children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(DesignModeOverlay, {})
9770
- }
9771
- )
9772
- ] });
9773
- }
9774
9762
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
9775
9763
  DndContext,
9776
9764
  {
9777
- sensors,
9765
+ sensors: designModeEnabled ? EMPTY_SENSORS : sensors,
9778
9766
  collisionDetection: combinedCollisionDetection,
9779
9767
  onDragStart: handleDragStart,
9780
9768
  onDragEnd: handleDragEnd,
9781
9769
  onDragCancel: () => setActive(null),
9782
9770
  children: [
9783
9771
  children,
9784
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(DragOverlay2, {}),
9772
+ !designModeEnabled && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(DragOverlay2, {}),
9785
9773
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
9786
9774
  DesignModeErrorBoundary,
9787
9775
  {
@@ -9580,8 +9580,9 @@ var combinedCollisionDetection = (args) => {
9580
9580
  };
9581
9581
 
9582
9582
  // src/client/dnd/DndProvider.tsx
9583
- import { Fragment, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
9583
+ import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
9584
9584
  var externalDragMouse = { x: 0, y: 0, commitRequested: false };
9585
+ var EMPTY_SENSORS = [];
9585
9586
  function DndProvider({ children }) {
9586
9587
  const setActive = useDndStore((s) => s.setActive);
9587
9588
  const dragEndHandlers = useDndStore((s) => s.dragEndHandlers);
@@ -9723,30 +9724,17 @@ function DndProvider({ children }) {
9723
9724
  designModeState.resetArrangeSession();
9724
9725
  designModeState.setEnabled(false);
9725
9726
  }, []);
9726
- if (designModeEnabled) {
9727
- return /* @__PURE__ */ jsxs4(Fragment, { children: [
9728
- children,
9729
- /* @__PURE__ */ jsx5(
9730
- DesignModeErrorBoundary,
9731
- {
9732
- onError: handleDesignModeCrash,
9733
- resetKey: designModeEnabled,
9734
- children: /* @__PURE__ */ jsx5(DesignModeOverlay, {})
9735
- }
9736
- )
9737
- ] });
9738
- }
9739
9727
  return /* @__PURE__ */ jsxs4(
9740
9728
  DndContext,
9741
9729
  {
9742
- sensors,
9730
+ sensors: designModeEnabled ? EMPTY_SENSORS : sensors,
9743
9731
  collisionDetection: combinedCollisionDetection,
9744
9732
  onDragStart: handleDragStart,
9745
9733
  onDragEnd: handleDragEnd,
9746
9734
  onDragCancel: () => setActive(null),
9747
9735
  children: [
9748
9736
  children,
9749
- /* @__PURE__ */ jsx5(DragOverlay2, {}),
9737
+ !designModeEnabled && /* @__PURE__ */ jsx5(DragOverlay2, {}),
9750
9738
  /* @__PURE__ */ jsx5(
9751
9739
  DesignModeErrorBoundary,
9752
9740
  {
@@ -3069,25 +3069,254 @@
3069
3069
  return result
3070
3070
  }
3071
3071
 
3072
- /** Same-origin CSS text, concatenated. Cross-origin sheets throw on
3073
- * `cssRules` and are skipped — their styling is simply absent from the
3074
- * capture, which is why the host treats it as best-effort. */
3075
- function collectSameOriginCss() {
3076
- var chunks = []
3077
- for (var i = 0; i < document.styleSheets.length; i++) {
3078
- var sheet = document.styleSheets[i]
3079
- var rules = null
3080
- try {
3081
- rules = sheet.cssRules
3082
- } catch (error) {
3083
- continue
3084
- }
3085
- if (!rules) continue
3086
- for (var j = 0; j < rules.length; j++) {
3087
- chunks.push(rules[j].cssText)
3088
- }
3072
+ var CSS_INLINE_TIMEOUT_MS = 2000
3073
+ var CSS_URL_RE = /url\(\s*(['"]?)([^'")]+)\1\s*\)/g
3074
+
3075
+ /** Resolves once `promise` settles, or once `ms` elapses — whichever
3076
+ * first — falling back to `fallbackValue` instead of rejecting. Used
3077
+ * to bound best-effort work (font/url inlining) that must never block
3078
+ * the capture past a short budget. */
3079
+ function withTimeout(promise, ms, fallbackValue) {
3080
+ return new Promise(function (resolve) {
3081
+ var settled = false
3082
+ var timer = setTimeout(function () {
3083
+ if (settled) return
3084
+ settled = true
3085
+ resolve(fallbackValue)
3086
+ }, ms)
3087
+ promise.then(
3088
+ function (value) {
3089
+ if (settled) return
3090
+ settled = true
3091
+ clearTimeout(timer)
3092
+ resolve(value)
3093
+ },
3094
+ function () {
3095
+ if (settled) return
3096
+ settled = true
3097
+ clearTimeout(timer)
3098
+ resolve(fallbackValue)
3099
+ }
3100
+ )
3101
+ })
3102
+ }
3103
+
3104
+ // Duck-typed against CSSOM's numeric `type` (MEDIA_RULE=4, IMPORT_RULE=3,
3105
+ // FONT_FACE_RULE=5) because CSSMediaRule/CSSImportRule/CSSFontFaceRule
3106
+ // are not guaranteed to exist in every test DOM (e.g. jsdom).
3107
+ function isMediaRule(rule) {
3108
+ if (typeof CSSMediaRule !== "undefined" && rule instanceof CSSMediaRule) {
3109
+ return true
3110
+ }
3111
+ return !!(rule && rule.media && rule.cssRules && rule.type === 4)
3112
+ }
3113
+
3114
+ function isImportRule(rule) {
3115
+ if (typeof CSSImportRule !== "undefined" && rule instanceof CSSImportRule) {
3116
+ return true
3117
+ }
3118
+ return !!(rule && rule.styleSheet && rule.type === 3)
3119
+ }
3120
+
3121
+ function isFontFaceRule(rule) {
3122
+ if (typeof CSSFontFaceRule !== "undefined" && rule instanceof CSSFontFaceRule) {
3123
+ return true
3124
+ }
3125
+ return !!(rule && rule.type === 5)
3126
+ }
3127
+
3128
+ /** The at-rule keyword of `rule.cssText` (e.g. "layer", "supports"),
3129
+ * lowercased. Used where a rule's numeric `.type` is unreliable — the
3130
+ * CSSOM spec never assigned legacy type constants to rules added
3131
+ * after `@supports` (`@layer`, `@container`, `@starting-style`, ...),
3132
+ * so they all report `type === 0`. */
3133
+ function ruleAtKeyword(rule) {
3134
+ var text = rule && rule.cssText ? rule.cssText : ""
3135
+ var match = /^\s*@(?:-webkit-|-moz-|-ms-|-o-)?([a-zA-Z-]+)/.exec(text)
3136
+ return match ? match[1].toLowerCase() : ""
3137
+ }
3138
+
3139
+ /** A rule that carries its own declarations/content and must always be
3140
+ * pushed verbatim, never treated as a group to recurse into — even
3141
+ * though some of these also expose `.cssRules`:
3142
+ * - `CSSStyleRule` (type 1) exposes `.cssRules` in real browsers for
3143
+ * CSS nesting (an empty list on an un-nested rule); treating that
3144
+ * as a grouping rule renders `.foo {}` with the declarations
3145
+ * dropped. Nested `@media` inside a nested style rule is a rare
3146
+ * case and is left as-is inside `cssText` rather than recursed.
3147
+ * - `@keyframes` / `@page` also expose `.cssRules`, but their nested
3148
+ * rules are keyframe selectors / margin boxes, not media-sensitive
3149
+ * content.
3150
+ * `.style` existing is a reliable signal across all of these (and
3151
+ * `CSSKeyframeRule`, type 8) even where `.type` duck-typing can't be
3152
+ * trusted. */
3153
+ function isStyleLikeLeaf(rule) {
3154
+ if (typeof CSSStyleRule !== "undefined" && rule instanceof CSSStyleRule) {
3155
+ return true
3156
+ }
3157
+ if (
3158
+ rule &&
3159
+ (rule.type === 1 || rule.type === 6 || rule.type === 7 || rule.type === 8)
3160
+ ) {
3161
+ return true
3162
+ }
3163
+ return !!(rule && rule.style)
3164
+ }
3165
+
3166
+ var GROUPING_AT_KEYWORDS = {
3167
+ layer: true,
3168
+ supports: true,
3169
+ container: true,
3170
+ scope: true,
3171
+ "starting-style": true,
3172
+ document: true,
3173
+ }
3174
+
3175
+ /** Positive test for "safe to recurse into and rebuild": only at-rules
3176
+ * whose sole job is grouping other rules under a condition/namespace
3177
+ * (`@layer`, `@supports`, `@container`, `@scope`, `@starting-style`,
3178
+ * `@document`), and only when they actually carry nested rules — a
3179
+ * `@layer a, b;` statement form has no `cssRules` and falls through
3180
+ * to the verbatim leaf branch instead. Everything else defaults to
3181
+ * verbatim, rather than recursing by default, precisely because
3182
+ * `CSSStyleRule` also exposes `cssRules` (see `isStyleLikeLeaf`). */
3183
+ function isGroupingRule(rule) {
3184
+ if (!rule || !rule.cssRules) return false
3185
+ if (typeof CSSStyleRule !== "undefined" && rule instanceof CSSStyleRule) {
3186
+ return false
3187
+ }
3188
+ return !!GROUPING_AT_KEYWORDS[ruleAtKeyword(rule)]
3189
+ }
3190
+
3191
+ /** The rule's header text up to (not including) its opening brace,
3192
+ * e.g. `@layer utilities ` or `@supports (display: grid) `. Used to
3193
+ * rebuild a grouping at-rule around its flattened children. */
3194
+ function headerTextOf(rule) {
3195
+ var text = rule && rule.cssText ? rule.cssText : ""
3196
+ var braceIndex = text.indexOf("{")
3197
+ return braceIndex === -1 ? text : text.slice(0, braceIndex)
3198
+ }
3199
+
3200
+ /** Renders a node produced by `flattenStyleRules` back into CSS text.
3201
+ * A "slot" defers to `pieces[index]`, which a font-face/cross-origin
3202
+ * inlining job may still be mutating when this runs — see
3203
+ * `collectCaptureCss`'s `CSS_INLINE_TIMEOUT_MS` race. */
3204
+ function renderNode(node, pieces) {
3205
+ if (typeof node === "string") return node
3206
+ if (node.kind === "slot") return pieces[node.index]
3207
+ if (node.kind === "group") {
3208
+ return node.header + "{" + renderNodes(node.children, pieces) + "}"
3209
+ }
3210
+ return ""
3211
+ }
3212
+
3213
+ function renderNodes(nodes, pieces) {
3214
+ var texts = []
3215
+ for (var i = 0; i < nodes.length; i++) {
3216
+ texts.push(renderNode(nodes[i], pieces))
3089
3217
  }
3090
- return chunks.join("\n")
3218
+ return texts.join("\n")
3219
+ }
3220
+
3221
+ /**
3222
+ * Flattens `rules` into a tree of plain-string leaves, font-face "slot"
3223
+ * placeholders (resolved later by URL-inlining — see `pieces` in
3224
+ * `collectCaptureCss`), and group nodes that preserve a non-media
3225
+ * at-rule's own semantics while still flattening any `@media` nested
3226
+ * inside it. This last case matters in practice: Tailwind v4 emits
3227
+ * responsive utilities as `@layer utilities { @media (min-width: ...)
3228
+ * { ... } }`, so a `@layer`/`@supports`/`@container` wrapper has to be
3229
+ * rebuilt around its recursed children rather than pushed as opaque
3230
+ * `cssText` — otherwise the nested `@media` inside it would still be
3231
+ * evaluated against the tiny rasterized SVG instead of the live
3232
+ * viewport.
3233
+ *
3234
+ * In a rasterized SVG, `@media` is evaluated against the SVG image's
3235
+ * own (tiny) intrinsic size, not the live viewport — so a non-matching
3236
+ * query is dropped up front and a matching one is inlined (unwrapped),
3237
+ * using the live `window.matchMedia` as the source of truth. `@import`
3238
+ * is followed into its target sheet the same way; a cross-origin
3239
+ * import throws on `.styleSheet.cssRules` and is dropped, same as any
3240
+ * other unreachable cross-origin CSS. A rule with no `cssRules` at all
3241
+ * (e.g. a plain style rule, or `@layer a, b;`'s statement form) is
3242
+ * pushed as a plain string leaf.
3243
+ */
3244
+ function flattenStyleRules(rules, sheetHref, fetchCache, enhancementJobs, pieces) {
3245
+ var nodes = []
3246
+ if (!rules) return nodes
3247
+ for (var i = 0; i < rules.length; i++) {
3248
+ ;(function (rule) {
3249
+ if (isMediaRule(rule)) {
3250
+ var conditionText =
3251
+ rule.conditionText || (rule.media && rule.media.mediaText) || ""
3252
+ var matches = true
3253
+ try {
3254
+ matches = window.matchMedia(conditionText).matches
3255
+ } catch (error) {
3256
+ matches = true
3257
+ }
3258
+ if (matches) {
3259
+ var mediaChildren = flattenStyleRules(
3260
+ rule.cssRules,
3261
+ sheetHref,
3262
+ fetchCache,
3263
+ enhancementJobs,
3264
+ pieces
3265
+ )
3266
+ for (var m = 0; m < mediaChildren.length; m++) {
3267
+ nodes.push(mediaChildren[m])
3268
+ }
3269
+ }
3270
+ } else if (isImportRule(rule)) {
3271
+ try {
3272
+ var importedChildren = flattenStyleRules(
3273
+ rule.styleSheet.cssRules,
3274
+ rule.href || sheetHref,
3275
+ fetchCache,
3276
+ enhancementJobs,
3277
+ pieces
3278
+ )
3279
+ for (var k = 0; k < importedChildren.length; k++) {
3280
+ nodes.push(importedChildren[k])
3281
+ }
3282
+ } catch (error) {
3283
+ // cross-origin @import — best-effort, same as other
3284
+ // unreachable cross-origin CSS.
3285
+ }
3286
+ } else if (isFontFaceRule(rule)) {
3287
+ var index = pieces.length
3288
+ pieces.push(rule.cssText)
3289
+ enhancementJobs.push(
3290
+ inlineCssUrls(
3291
+ rule.cssText,
3292
+ sheetHref || document.baseURI,
3293
+ fetchCache
3294
+ ).then(function (text) {
3295
+ pieces[index] = text
3296
+ })
3297
+ )
3298
+ nodes.push({ kind: "slot", index: index })
3299
+ } else if (isStyleLikeLeaf(rule)) {
3300
+ nodes.push(rule.cssText)
3301
+ } else if (isGroupingRule(rule)) {
3302
+ var groupChildren = flattenStyleRules(
3303
+ rule.cssRules,
3304
+ sheetHref,
3305
+ fetchCache,
3306
+ enhancementJobs,
3307
+ pieces
3308
+ )
3309
+ nodes.push({
3310
+ kind: "group",
3311
+ header: headerTextOf(rule),
3312
+ children: groupChildren,
3313
+ })
3314
+ } else {
3315
+ nodes.push(rule.cssText)
3316
+ }
3317
+ })(rules[i])
3318
+ }
3319
+ return nodes
3091
3320
  }
3092
3321
 
3093
3322
  function fetchAsDataUrl(url) {
@@ -3108,6 +3337,168 @@
3108
3337
  })
3109
3338
  }
3110
3339
 
3340
+ function fetchAsDataUrlCors(url) {
3341
+ return fetch(url, { mode: "cors" })
3342
+ .then(function (response) {
3343
+ if (!response.ok) throw new Error("bad status")
3344
+ return response.blob()
3345
+ })
3346
+ .then(function (blob) {
3347
+ return new Promise(function (resolve, reject) {
3348
+ var reader = new FileReader()
3349
+ reader.onload = function () {
3350
+ resolve(String(reader.result))
3351
+ }
3352
+ reader.onerror = reject
3353
+ reader.readAsDataURL(blob)
3354
+ })
3355
+ })
3356
+ .catch(function () {
3357
+ return null
3358
+ })
3359
+ }
3360
+
3361
+ function collectCssUrls(cssText) {
3362
+ var urls = []
3363
+ var seen = {}
3364
+ var match
3365
+ CSS_URL_RE.lastIndex = 0
3366
+ while ((match = CSS_URL_RE.exec(cssText))) {
3367
+ var raw = match[2]
3368
+ if (!raw || raw.indexOf("data:") === 0 || seen[raw]) continue
3369
+ seen[raw] = true
3370
+ urls.push(raw)
3371
+ }
3372
+ return urls
3373
+ }
3374
+
3375
+ /** Replaces every `url(...)` in `cssText` with a `data:` URL, fetched
3376
+ * cross-origin and deduped via `fetchCache` (shared across calls so
3377
+ * the same font file is only ever fetched once). A URL that fails to
3378
+ * resolve or fetch is left as-is rather than aborting the rest. */
3379
+ function inlineCssUrls(cssText, baseHref, fetchCache) {
3380
+ var urls = collectCssUrls(cssText)
3381
+ if (!urls.length) return Promise.resolve(cssText)
3382
+
3383
+ var resolved = {}
3384
+ var jobs = []
3385
+ for (var i = 0; i < urls.length; i++) {
3386
+ ;(function (raw) {
3387
+ var absolute
3388
+ try {
3389
+ absolute = new URL(raw, baseHref).href
3390
+ } catch (error) {
3391
+ return
3392
+ }
3393
+ if (!fetchCache[absolute]) {
3394
+ fetchCache[absolute] = fetchAsDataUrlCors(absolute)
3395
+ }
3396
+ jobs.push(
3397
+ fetchCache[absolute].then(function (dataUrl) {
3398
+ resolved[absolute] = dataUrl
3399
+ })
3400
+ )
3401
+ })(urls[i])
3402
+ }
3403
+
3404
+ return Promise.all(jobs).then(function () {
3405
+ return cssText.replace(CSS_URL_RE, function (full, quote, raw) {
3406
+ if (!raw || raw.indexOf("data:") === 0) return full
3407
+ var absolute
3408
+ try {
3409
+ absolute = new URL(raw, baseHref).href
3410
+ } catch (error) {
3411
+ return full
3412
+ }
3413
+ var dataUrl = resolved[absolute]
3414
+ return dataUrl ? "url(" + dataUrl + ")" : full
3415
+ })
3416
+ })
3417
+ }
3418
+
3419
+ /**
3420
+ * CSS text for the capture clone: same-origin sheets have their
3421
+ * `@media` flattened against the LIVE viewport (see `flattenStyleRules`)
3422
+ * and `:root` rewritten to `html`, because inside the rasterized SVG
3423
+ * the root element is the `<svg>`, not `<html>`. Cross-origin sheets
3424
+ * throw on `cssRules`, so their raw text is fetched instead and every
3425
+ * `url(...)` inlined as a `data:` URL (same-origin `@font-face` rules
3426
+ * get the same treatment); this fetching work is best-effort and
3427
+ * bounded by `CSS_INLINE_TIMEOUT_MS` so a slow or unreachable font
3428
+ * never delays the capture — rules still pending when the budget runs
3429
+ * out simply keep their original (uninlined) `url(...)`.
3430
+ */
3431
+ function collectCaptureCss() {
3432
+ var fetchCache = {}
3433
+ var pieces = []
3434
+ var enhancementJobs = []
3435
+ var topNodes = []
3436
+
3437
+ for (var i = 0; i < document.styleSheets.length; i++) {
3438
+ ;(function (sheet) {
3439
+ var rules = null
3440
+ try {
3441
+ rules = sheet.cssRules
3442
+ } catch (error) {
3443
+ rules = null
3444
+ }
3445
+
3446
+ if (rules) {
3447
+ var nodes = flattenStyleRules(
3448
+ rules,
3449
+ sheet.href,
3450
+ fetchCache,
3451
+ enhancementJobs,
3452
+ pieces
3453
+ )
3454
+ for (var n = 0; n < nodes.length; n++) topNodes.push(nodes[n])
3455
+ return
3456
+ }
3457
+
3458
+ if (!sheet.href) return
3459
+
3460
+ var index = pieces.length
3461
+ pieces.push("")
3462
+ topNodes.push({ kind: "slot", index: index })
3463
+ enhancementJobs.push(
3464
+ fetch(sheet.href, { mode: "cors" })
3465
+ .then(function (response) {
3466
+ if (!response.ok) throw new Error("bad status")
3467
+ return response.text()
3468
+ })
3469
+ .then(function (text) {
3470
+ return inlineCssUrls(text, sheet.href, fetchCache)
3471
+ })
3472
+ .then(function (text) {
3473
+ // Best-effort only: this raw fetched text is not run back
3474
+ // through flattenStyleRules, so any @media/@layer nesting
3475
+ // inside a blocked cross-origin sheet is left unflattened.
3476
+ pieces[index] = text
3477
+ })
3478
+ .catch(function () {
3479
+ // cross-origin sheet unreachable — left blank, same as
3480
+ // the previous best-effort behavior.
3481
+ })
3482
+ )
3483
+ })(document.styleSheets[i])
3484
+ }
3485
+
3486
+ return withTimeout(
3487
+ Promise.all(enhancementJobs),
3488
+ CSS_INLINE_TIMEOUT_MS,
3489
+ null
3490
+ ).then(function () {
3491
+ return renderNodes(topNodes, pieces).replace(/:root\b/g, "html")
3492
+ })
3493
+ }
3494
+
3495
+ // Test hook only — lets inspector.annotation.test.ts assert on the
3496
+ // collected CSS text directly, since the full capture pipeline needs a
3497
+ // browser Image decoder no test DOM provides.
3498
+ if (window.__ARCHIE_INSPECTOR__) {
3499
+ window.__ARCHIE_INSPECTOR__.collectCaptureCss = collectCaptureCss
3500
+ }
3501
+
3111
3502
  /** Rasterizing an SVG cannot fetch subresources, so same-origin <img>
3112
3503
  * sources are inlined first. Failures leave the image blank rather
3113
3504
  * than aborting the whole capture. */
@@ -3141,15 +3532,22 @@
3141
3532
 
3142
3533
  /**
3143
3534
  * Rasterizes the CURRENT viewport into a PNG data URL via an
3144
- * `<svg><foreignObject>` round-trip. Best-effort by construction: web
3145
- * fonts, cross-origin images and <canvas> pixels do not survive it. The
3146
- * host composes the result with the user's drawing, and falls back to
3147
- * the drawing alone when this rejects.
3535
+ * `<svg><foreignObject>` round-trip. Best-effort by construction:
3536
+ * cross-origin images and <canvas> pixels do not survive it, and web
3537
+ * fonts survive only within `CSS_INLINE_TIMEOUT_MS`. The host composes
3538
+ * the result with the user's drawing, and falls back to the drawing
3539
+ * alone when this rejects.
3148
3540
  */
3149
3541
  function captureViewportDataUrl(pixelRatio) {
3150
3542
  var width = window.innerWidth
3151
3543
  var height = window.innerHeight
3152
3544
  var ratio = Math.min(Math.max(Number(pixelRatio) || 1, 1), 2)
3545
+ // The rasterized SVG's root element is the `<svg>` itself, not
3546
+ // `<html>`, so `rem` would otherwise resolve against the UA default
3547
+ // instead of the live page's root font-size.
3548
+ var rootFontSize = window.getComputedStyle(
3549
+ document.documentElement
3550
+ ).fontSize
3153
3551
 
3154
3552
  var clone = document.documentElement.cloneNode(true)
3155
3553
  var strip = clone.querySelectorAll(
@@ -3159,75 +3557,79 @@
3159
3557
  if (strip[i].parentNode) strip[i].parentNode.removeChild(strip[i])
3160
3558
  }
3161
3559
 
3162
- var styleEl = document.createElement("style")
3163
- styleEl.textContent = collectSameOriginCss()
3164
- var head = clone.querySelector("head")
3165
- if (head) head.appendChild(styleEl)
3166
- else clone.insertBefore(styleEl, clone.firstChild)
3167
-
3168
- return inlineImages(clone).then(function () {
3169
- var markup = new XMLSerializer().serializeToString(clone)
3170
- var svg =
3171
- '<svg xmlns="http://www.w3.org/2000/svg" width="' +
3172
- width +
3173
- '" height="' +
3174
- height +
3175
- '">' +
3176
- '<foreignObject x="0" y="0" width="' +
3177
- width +
3178
- '" height="' +
3179
- height +
3180
- '">' +
3181
- '<div xmlns="http://www.w3.org/1999/xhtml" style="width:' +
3182
- width +
3183
- "px;transform:translate(" +
3184
- -window.scrollX +
3185
- "px," +
3186
- -window.scrollY +
3187
- 'px)">' +
3188
- markup +
3189
- "</div></foreignObject></svg>"
3190
-
3191
- return new Promise(function (resolve, reject) {
3192
- var image = new Image()
3193
- // An SVG the browser refuses to decode can fire NEITHER onload nor
3194
- // onerror (malformed serialization, blocked subresource). Without
3195
- // this guard the host would sit on the request until its own
3196
- // timeout with no diagnostic; the deadline turns that into an
3197
- // explicit VIEWPORT_CAPTURE_FAILED.
3198
- var settled = false
3199
- var deadline = setTimeout(function () {
3200
- if (settled) return
3201
- settled = true
3202
- reject(new Error("svg rasterization timed out"))
3203
- }, ANNOTATION_CAPTURE_TIMEOUT_MS)
3204
- var finish = function (fn, value) {
3205
- if (settled) return
3206
- settled = true
3207
- clearTimeout(deadline)
3208
- fn(value)
3209
- }
3210
- image.onload = function () {
3211
- try {
3212
- var canvas = document.createElement("canvas")
3213
- canvas.width = Math.round(width * ratio)
3214
- canvas.height = Math.round(height * ratio)
3215
- var context = canvas.getContext("2d")
3216
- context.fillStyle = "#ffffff"
3217
- context.fillRect(0, 0, canvas.width, canvas.height)
3218
- context.drawImage(image, 0, 0, canvas.width, canvas.height)
3219
- finish(resolve, canvas.toDataURL("image/png"))
3220
- } catch (error) {
3221
- finish(reject, error)
3560
+ return Promise.all([collectCaptureCss(), inlineImages(clone)]).then(
3561
+ function (results) {
3562
+ var styleEl = document.createElement("style")
3563
+ styleEl.textContent = results[0]
3564
+ var head = clone.querySelector("head")
3565
+ if (head) head.appendChild(styleEl)
3566
+ else clone.insertBefore(styleEl, clone.firstChild)
3567
+
3568
+ var markup = new XMLSerializer().serializeToString(clone)
3569
+ var svg =
3570
+ '<svg xmlns="http://www.w3.org/2000/svg" width="' +
3571
+ width +
3572
+ '" height="' +
3573
+ height +
3574
+ '" style="font-size:' +
3575
+ rootFontSize +
3576
+ '">' +
3577
+ '<foreignObject x="0" y="0" width="' +
3578
+ width +
3579
+ '" height="' +
3580
+ height +
3581
+ '">' +
3582
+ '<div xmlns="http://www.w3.org/1999/xhtml" style="width:' +
3583
+ width +
3584
+ "px;transform:translate(" +
3585
+ -window.scrollX +
3586
+ "px," +
3587
+ -window.scrollY +
3588
+ 'px)">' +
3589
+ markup +
3590
+ "</div></foreignObject></svg>"
3591
+
3592
+ return new Promise(function (resolve, reject) {
3593
+ var image = new Image()
3594
+ // An SVG the browser refuses to decode can fire NEITHER onload nor
3595
+ // onerror (malformed serialization, blocked subresource). Without
3596
+ // this guard the host would sit on the request until its own
3597
+ // timeout with no diagnostic; the deadline turns that into an
3598
+ // explicit VIEWPORT_CAPTURE_FAILED.
3599
+ var settled = false
3600
+ var deadline = setTimeout(function () {
3601
+ if (settled) return
3602
+ settled = true
3603
+ reject(new Error("svg rasterization timed out"))
3604
+ }, ANNOTATION_CAPTURE_TIMEOUT_MS)
3605
+ var finish = function (fn, value) {
3606
+ if (settled) return
3607
+ settled = true
3608
+ clearTimeout(deadline)
3609
+ fn(value)
3222
3610
  }
3223
- }
3224
- image.onerror = function () {
3225
- finish(reject, new Error("svg rasterization failed"))
3226
- }
3227
- image.src =
3228
- "data:image/svg+xml;charset=utf-8," + encodeURIComponent(svg)
3229
- })
3230
- })
3611
+ image.onload = function () {
3612
+ try {
3613
+ var canvas = document.createElement("canvas")
3614
+ canvas.width = Math.round(width * ratio)
3615
+ canvas.height = Math.round(height * ratio)
3616
+ var context = canvas.getContext("2d")
3617
+ context.fillStyle = "#ffffff"
3618
+ context.fillRect(0, 0, canvas.width, canvas.height)
3619
+ context.drawImage(image, 0, 0, canvas.width, canvas.height)
3620
+ finish(resolve, canvas.toDataURL("image/png"))
3621
+ } catch (error) {
3622
+ finish(reject, error)
3623
+ }
3624
+ }
3625
+ image.onerror = function () {
3626
+ finish(reject, new Error("svg rasterization failed"))
3627
+ }
3628
+ image.src =
3629
+ "data:image/svg+xml;charset=utf-8," + encodeURIComponent(svg)
3630
+ })
3631
+ }
3632
+ )
3231
3633
  }
3232
3634
 
3233
3635
  function extractMetadata(element) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@archie/devtools",
3
- "version": "0.0.46",
3
+ "version": "0.0.48",
4
4
  "description": "DevTools for Archie generated applications - Route synchronization and editor communication",
5
5
  "repository": {
6
6
  "type": "git",