@edynamix/mf-isolation 0.0.33 → 0.0.34

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -50,7 +50,8 @@ At runtime, `provideMfOverlayRoot()` and `provideMfOverlayRootFromToken()` start
50
50
  A host calls `adaptHostedModuleStyles(shadowRoot)` from `@edynamix/mf-isolation` on each module shadow root it creates, and the function it returns on unmount. It adapts what a module brings into that root at runtime, with no module rebuild:
51
51
 
52
52
  - rem follows the module's own root font size, as `followModuleRootFontSize()` does above;
53
- - rules a library injects at runtime for the document root never match inside a shadow root (Angular Material's `html { --mat-focus-indicator-display: block; }` left keyboard focus rings off), so they move onto the shadow host, as the PostCSS plugins do at build time: `html`, `body`, `:root` and `html body` become `:host`, and `html .x`, `body > .x` or `:root .x` become `:host .x` and `:host > .x`. A root with conditions of its own (`html.dark`, `body#app`, `:root[dir=rtl]`) is left alone, as is any rule that already names `:host` or `data-edyn-mf`. `rescopeDocumentRootSelectors(selectorText)` is the pure selector step and also reports how many qualified roots it left.
53
+ - rules a library injects at runtime for the document root never match inside a shadow root (Angular Material's `html { --mat-focus-indicator-display: block; }` left keyboard focus rings off), so they move onto the shadow host, as the PostCSS plugins do at build time: `html`, `body`, `:root` and `html body` become `:host`, and `html .x`, `body > .x` or `:root .x` become `:host .x` and `:host > .x`. A root with conditions of its own (`html.dark`, `body#app`, `:root[dir=rtl]`) is left alone, as is any rule that already names `:host` or `data-edyn-mf`. `rescopeDocumentRootSelectors(selectorText)` is the pure selector step and also reports how many qualified roots it left;
54
+ - sticky headers written for the legacy navigation's top bar stick to the top of the host's content area: old pages scrolled under that 49px bar, so a sticky `top` of 49px or more (`top: 49px`, `top: 50px`) moves up by 49px, in stylesheet rules and in inline styles a module sets from code (the position-sticky directives' `position: sticky; top: 50px`), with its priority kept and once per value the module writes (0.0.34 or later). Smaller tops (`0`, `1px`, for pages and panels that already scrolled below the bar), `var()`, `%` and `calc()` values, and non-sticky rules are left alone.
54
55
 
55
56
  It is safe beside a module's own `followModuleRootFontSize()` on the same root (0.0.31 or later): stylesheet changes go through the CSSOM, which no mutation observer sees, every step is idempotent, and an inline style already followed never looks like work again, so neither side acts on the other's writes.
56
57
 
@@ -10,8 +10,10 @@ export declare const followModuleRootFontSize: (root: ShadowRoot) => (() => void
10
10
  * Adapts the CSS a module brings into its shadow root at runtime to the hosted page, for a host to call on each module
11
11
  * shadow root it creates and stop on unmount: rem follows the module's own root font size (as followModuleRootFontSize
12
12
  * does), and rules a library injects for the document root (`html { --mat-focus-indicator-display: block; }`), which
13
- * never match inside a shadow root, move onto `:host` (see rescopeDocumentRootSelectors). Safe beside a module's own
14
- * followModuleRootFontSize on the same root: every step is idempotent, and neither writes anything the other acts on.
13
+ * never match inside a shadow root, move onto `:host` (see rescopeDocumentRootSelectors), and sticky tops written for
14
+ * the legacy navigation's top bar (`top: 49px`), in rules and inline styles, move up by that bar, which a hosted page
15
+ * does not have. Safe beside a module's own followModuleRootFontSize on the same root: every step is idempotent, and
16
+ * neither writes anything the other acts on.
15
17
  */
16
18
  export declare const adaptHostedModuleStyles: (root: ShadowRoot) => (() => void);
17
19
  //# sourceMappingURL=hosted-module-styles.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"hosted-module-styles.d.ts","sourceRoot":"","sources":["../src/hosted-module-styles.ts"],"names":[],"mappings":"AAiPA;;;;;;GAMG;AACH,eAAO,MAAM,wBAAwB,GAAI,MAAM,UAAU,KAAG,CAAC,MAAM,IAAI,CAEtE,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,uBAAuB,GAAI,MAAM,UAAU,KAAG,CAAC,MAAM,IAAI,CAErE,CAAC","sourcesContent":["import { rescopeDocumentRootSelectors } from './document-root-selectors.js';\nimport {\n MF_ROOT_FONT_SIZE_PROPERTY,\n REM_LENGTH_HINT_PATTERN,\n ROOT_FONT_SIZE_DECLARATION_PATTERN,\n rewriteRemLengths,\n UNFOLLOWED_REM_PATTERN,\n} from './root-font-size.js';\n\nconst INLINE_REM_SELECTOR = '[style*=\"rem\" i]';\n\n// By class name, not instanceof: a root in another window (a frame, or a test's own window) has its own CSSFontFaceRule.\nconst isFontFaceRule = (rule: CSSRule): boolean => {\n return rule.constructor?.name === 'CSSFontFaceRule';\n};\n\n// Rule kinds are told apart by what they carry, not by class: @import has a sheet, style and keyframe rules have\n// declarations, and grouping rules (@media, @supports, @container, @layer, @keyframes, nesting) have child rules.\nconst importedSheetOf = (rule: CSSRule): CSSStyleSheet | undefined => {\n return 'styleSheet' in rule && rule.styleSheet ? (rule.styleSheet as CSSStyleSheet) : undefined;\n};\n\nconst declarationsOf = (rule: CSSRule): CSSStyleDeclaration | undefined => {\n return 'style' in rule && rule.style && !isFontFaceRule(rule) ? (rule.style as CSSStyleDeclaration) : undefined;\n};\n\nconst childRulesOf = (rule: CSSRule): CSSRuleList | undefined => {\n return 'cssRules' in rule && rule.cssRules ? (rule.cssRules as CSSRuleList) : undefined;\n};\n\nconst readRules = (sheet: CSSStyleSheet): CSSRuleList | undefined => {\n try {\n return sheet.cssRules;\n } catch {\n // A cross-origin sheet (a font service's @font-face list) cannot be read, and none of its rules are module rules.\n return undefined;\n }\n};\n\nconst walkRules = (sheet: CSSStyleSheet, visit: (rule: CSSRule) => void): void => {\n const walk = (rules: CSSRuleList): void => {\n for (const rule of Array.from(rules)) {\n const importedSheet = importedSheetOf(rule);\n const importedRules = importedSheet ? readRules(importedSheet) : undefined;\n if (importedRules) {\n walk(importedRules);\n }\n visit(rule);\n const childRules = childRulesOf(rule);\n if (childRules) {\n walk(childRules);\n }\n }\n };\n const rules = readRules(sheet);\n if (rules) {\n walk(rules);\n }\n};\n\n// Only declaration blocks change, through the CSSOM: @media, @container and @supports conditions keep their rem (var()\n// is invalid there, and a media query rem is the browser's initial size anyway), and !important stays with its value.\n// CSSOM changes are not DOM mutations, so no observer, this one or another on the same root, sees them.\nconst followRemInSheet = (sheet: CSSStyleSheet): void => {\n walkRules(sheet, (rule) => {\n const declarations = declarationsOf(rule);\n if (declarations && REM_LENGTH_HINT_PATTERN.test(declarations.cssText)) {\n const rewritten = rewriteRemLengths(declarations.cssText);\n if (rewritten !== declarations.cssText) {\n declarations.cssText = rewritten;\n }\n }\n });\n};\n\nconst rescopeDocumentRootRules = (sheet: CSSStyleSheet): void => {\n walkRules(sheet, (rule) => {\n if (!('selectorText' in rule) || typeof rule.selectorText !== 'string') {\n return;\n }\n const { selectorText } = rescopeDocumentRootSelectors(rule.selectorText);\n if (selectorText !== rule.selectorText) {\n (rule as CSSStyleRule).selectorText = selectorText;\n }\n });\n};\n\nconst declaresRootFontSize = (sheet: CSSStyleSheet): boolean => {\n let declares = false;\n walkRules(sheet, (rule) => {\n declares ||= !!declarationsOf(rule)?.getPropertyValue(MF_ROOT_FONT_SIZE_PROPERTY);\n });\n return declares;\n};\n\n// Inline styles carry rem too, from templates (`style=\"padding-bottom: 0.25rem !important\"`) and from style bindings\n// and code. The whole declaration block is rewritten through element.style, as for sheet rules: !important stays with\n// its value, and a shorthand that also holds var() (whose longhands read empty) is still seen. Material sets inline\n// styles constantly (ripples, overlay positions), so anything without an unfollowed rem length returns at once, and a\n// followed value never looks like work again, which is also why a second observer on the same root writes nothing.\nconst followInlineStyle = (element: Element): void => {\n const attribute = element.getAttribute('style');\n if (!attribute || !UNFOLLOWED_REM_PATTERN.test(attribute)) {\n return;\n }\n const style = (element as HTMLElement).style;\n const declarations = style?.cssText;\n if (!declarations) {\n return;\n }\n const rewritten = rewriteRemLengths(declarations);\n if (rewritten !== declarations) {\n style.cssText = rewritten;\n }\n};\n\nconst followInlineStylesWithin = (node: Node): void => {\n if (node.nodeType !== 1) {\n return;\n }\n const element = node as Element;\n followInlineStyle(element);\n if (element.firstElementChild) {\n for (const descendant of Array.from(element.querySelectorAll(INLINE_REM_SELECTOR))) {\n followInlineStyle(descendant);\n }\n }\n};\n\ntype TStyleOwner = HTMLLinkElement | HTMLStyleElement;\n\nconst isStyleOwner = (node: Node): node is TStyleOwner => {\n return node.nodeName === 'STYLE' || (node.nodeName === 'LINK' && (node as HTMLLinkElement).rel.split(/\\s+/).includes('stylesheet'));\n};\n\ninterface IAdaptStylesOptions {\n documentRootRules: boolean;\n}\n\nconst adaptShadowRootStyles = (root: ShadowRoot, { documentRootRules }: IAdaptStylesOptions): (() => void) => {\n // The root's own window, which need not be the global one (a frame, or a test's own window).\n const RootMutationObserver = root.ownerDocument.defaultView?.MutationObserver ?? MutationObserver;\n let followingRem = false;\n const remSheets = new WeakSet<CSSStyleSheet>();\n const rescopedSheets = new WeakSet<CSSStyleSheet>();\n const watchedStyles = new WeakSet<HTMLStyleElement>();\n\n const owners = (): TStyleOwner[] => Array.from(root.childNodes).filter(isStyleOwner);\n\n const sheetsOf = (styleOwners: TStyleOwner[]): CSSStyleSheet[] => {\n return [...(root.adoptedStyleSheets ?? []), ...styleOwners.map((owner) => owner.sheet).filter((sheet): sheet is CSSStyleSheet => !!sheet)];\n };\n\n // A <style>'s own text is the cheap check; linked and adopted sheets have only their rules.\n const findsRootFontSize = (styleOwners: TStyleOwner[]): boolean => {\n const styles = styleOwners.filter((owner): owner is HTMLStyleElement => owner.nodeName === 'STYLE');\n if (styles.some((style) => ROOT_FONT_SIZE_DECLARATION_PATTERN.test(style.textContent ?? ''))) {\n return true;\n }\n const linkedSheets = styleOwners.filter((owner) => owner.nodeName === 'LINK').map((owner) => owner.sheet);\n return [...(root.adoptedStyleSheets ?? []), ...linkedSheets].some((sheet) => !!sheet && declaresRootFontSize(sheet));\n };\n\n // Started only once the module's CSS declares a root size, so a module without one pays nothing. An element gets its\n // template styles before it is inserted, so insertions are scanned as well as later style attribute changes.\n let inlineObserver: MutationObserver | undefined;\n const followInlineStyles = (): void => {\n for (const element of Array.from(root.querySelectorAll(INLINE_REM_SELECTOR))) {\n followInlineStyle(element);\n }\n inlineObserver = new RootMutationObserver((records) => {\n for (const record of records) {\n if (record.type === 'attributes') {\n followInlineStyle(record.target as Element);\n } else {\n for (const node of Array.from(record.addedNodes)) {\n followInlineStylesWithin(node);\n }\n }\n }\n // Only this callback ran since the records were delivered, so what is queued now are its own rewrites.\n inlineObserver?.takeRecords();\n });\n inlineObserver.observe(root, { attributeFilter: ['style'], attributes: true, childList: true, subtree: true });\n };\n\n const observer = new RootMutationObserver(() => update());\n\n const update = (): void => {\n const styleOwners = owners();\n for (const owner of styleOwners) {\n if (owner.nodeName === 'STYLE' && !watchedStyles.has(owner as HTMLStyleElement)) {\n // Replacing a <style>'s text gives it a new sheet, which must be adapted again.\n observer.observe(owner, { characterData: true, childList: true, subtree: true });\n watchedStyles.add(owner as HTMLStyleElement);\n }\n }\n\n const sheets = sheetsOf(styleOwners);\n if (documentRootRules) {\n for (const sheet of sheets) {\n if (!rescopedSheets.has(sheet)) {\n rescopeDocumentRootRules(sheet);\n rescopedSheets.add(sheet);\n }\n }\n }\n\n followingRem ||= findsRootFontSize(styleOwners);\n if (!followingRem) {\n return;\n }\n if (!inlineObserver) {\n followInlineStyles();\n }\n for (const sheet of sheets) {\n if (!remSheets.has(sheet)) {\n followRemInSheet(sheet);\n remSheets.add(sheet);\n }\n }\n };\n\n // A <link> sheet has no rules until it loads; load does not bubble, so it is caught on the way down.\n const onLoad = (event: Event): void => {\n if (event.target instanceof Node && isStyleOwner(event.target)) {\n update();\n }\n };\n\n observer.observe(root, { childList: true });\n root.addEventListener('load', onLoad, true);\n update();\n\n return () => {\n observer.disconnect();\n inlineObserver?.disconnect();\n root.removeEventListener('load', onLoad, true);\n };\n};\n\n/**\n * Makes rem lengths in a hosted module's shadow root follow the root font size its own page had, once the module's\n * CSS declares one (see MF_ROOT_FONT_SIZE_PROPERTY): in stylesheets already in the root and every one added or changed\n * later, such as the component styles Angular adds as components first render, and in inline style attributes,\n * including ones inserted or changed later. A module whose CSS never sizes its root is left untouched. Returns a\n * function that stops following; rewritten rules and styles stay rewritten.\n */\nexport const followModuleRootFontSize = (root: ShadowRoot): (() => void) => {\n return adaptShadowRootStyles(root, { documentRootRules: false });\n};\n\n/**\n * Adapts the CSS a module brings into its shadow root at runtime to the hosted page, for a host to call on each module\n * shadow root it creates and stop on unmount: rem follows the module's own root font size (as followModuleRootFontSize\n * does), and rules a library injects for the document root (`html { --mat-focus-indicator-display: block; }`), which\n * never match inside a shadow root, move onto `:host` (see rescopeDocumentRootSelectors). Safe beside a module's own\n * followModuleRootFontSize on the same root: every step is idempotent, and neither writes anything the other acts on.\n */\nexport const adaptHostedModuleStyles = (root: ShadowRoot): (() => void) => {\n return adaptShadowRootStyles(root, { documentRootRules: true });\n};\n"]}
1
+ {"version":3,"file":"hosted-module-styles.d.ts","sourceRoot":"","sources":["../src/hosted-module-styles.ts"],"names":[],"mappings":"AAyUA;;;;;;GAMG;AACH,eAAO,MAAM,wBAAwB,GAAI,MAAM,UAAU,KAAG,CAAC,MAAM,IAAI,CAEtE,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,uBAAuB,GAAI,MAAM,UAAU,KAAG,CAAC,MAAM,IAAI,CAErE,CAAC","sourcesContent":["import { rescopeDocumentRootSelectors } from './document-root-selectors.js';\nimport {\n MF_ROOT_FONT_SIZE_PROPERTY,\n REM_LENGTH_HINT_PATTERN,\n ROOT_FONT_SIZE_DECLARATION_PATTERN,\n rewriteRemLengths,\n UNFOLLOWED_REM_PATTERN,\n} from './root-font-size.js';\n\nconst INLINE_REM_SELECTOR = '[style*=\"rem\" i]';\nconst INLINE_STICKY_SELECTOR = '[style*=\"sticky\" i]';\n\n// The legacy navigation's top bar (@edyn/navigation: `.mat-sidenav-container { padding-top: 49px }`). Old pages scrolled\n// under it, so their sticky headers stuck below it with `top: 49px` (or 50px, one pixel lower). Hosted, a module scrolls\n// in the host's content area, which has no bar inside it, so those headers would stick 49px too low.\nconst LEGACY_NAVIGATION_BAR_HEIGHT = 49;\nconst PIXEL_LENGTH_PATTERN = /^(\\d+(?:\\.\\d+)?)px$/i;\n\n// A sticky top of at least the bar's height moves up by the bar; smaller ones (0, 1px) belong to pages or panels that\n// scrolled below the bar already, and anything else (var(), %, calc()) is a module's own choice. Returns whether the\n// declarations changed.\nconst liftStickyTop = (declarations: CSSStyleDeclaration): boolean => {\n if (!/sticky/i.test(declarations.getPropertyValue('position'))) {\n return false;\n }\n const top = PIXEL_LENGTH_PATTERN.exec(declarations.getPropertyValue('top').trim());\n const pixels = top ? Number(top[1]) : Number.NaN;\n if (!(pixels >= LEGACY_NAVIGATION_BAR_HEIGHT)) {\n return false;\n }\n declarations.setProperty('top', `${pixels - LEGACY_NAVIGATION_BAR_HEIGHT}px`, declarations.getPropertyPriority('top'));\n return true;\n};\n\n// By class name, not instanceof: a root in another window (a frame, or a test's own window) has its own CSSFontFaceRule.\nconst isFontFaceRule = (rule: CSSRule): boolean => {\n return rule.constructor?.name === 'CSSFontFaceRule';\n};\n\n// Rule kinds are told apart by what they carry, not by class: @import has a sheet, style and keyframe rules have\n// declarations, and grouping rules (@media, @supports, @container, @layer, @keyframes, nesting) have child rules.\nconst importedSheetOf = (rule: CSSRule): CSSStyleSheet | undefined => {\n return 'styleSheet' in rule && rule.styleSheet ? (rule.styleSheet as CSSStyleSheet) : undefined;\n};\n\nconst declarationsOf = (rule: CSSRule): CSSStyleDeclaration | undefined => {\n return 'style' in rule && rule.style && !isFontFaceRule(rule) ? (rule.style as CSSStyleDeclaration) : undefined;\n};\n\nconst childRulesOf = (rule: CSSRule): CSSRuleList | undefined => {\n return 'cssRules' in rule && rule.cssRules ? (rule.cssRules as CSSRuleList) : undefined;\n};\n\nconst readRules = (sheet: CSSStyleSheet): CSSRuleList | undefined => {\n try {\n return sheet.cssRules;\n } catch {\n // A cross-origin sheet (a font service's @font-face list) cannot be read, and none of its rules are module rules.\n return undefined;\n }\n};\n\nconst walkRules = (sheet: CSSStyleSheet, visit: (rule: CSSRule) => void): void => {\n const walk = (rules: CSSRuleList): void => {\n for (const rule of Array.from(rules)) {\n const importedSheet = importedSheetOf(rule);\n const importedRules = importedSheet ? readRules(importedSheet) : undefined;\n if (importedRules) {\n walk(importedRules);\n }\n visit(rule);\n const childRules = childRulesOf(rule);\n if (childRules) {\n walk(childRules);\n }\n }\n };\n const rules = readRules(sheet);\n if (rules) {\n walk(rules);\n }\n};\n\n// Only declaration blocks change, through the CSSOM: @media, @container and @supports conditions keep their rem (var()\n// is invalid there, and a media query rem is the browser's initial size anyway), and !important stays with its value.\n// CSSOM changes are not DOM mutations, so no observer, this one or another on the same root, sees them.\nconst followRemInSheet = (sheet: CSSStyleSheet): void => {\n walkRules(sheet, (rule) => {\n const declarations = declarationsOf(rule);\n if (declarations && REM_LENGTH_HINT_PATTERN.test(declarations.cssText)) {\n const rewritten = rewriteRemLengths(declarations.cssText);\n if (rewritten !== declarations.cssText) {\n declarations.cssText = rewritten;\n }\n }\n });\n};\n\nconst rescopeDocumentRootRules = (sheet: CSSStyleSheet): void => {\n walkRules(sheet, (rule) => {\n if (!('selectorText' in rule) || typeof rule.selectorText !== 'string') {\n return;\n }\n const { selectorText } = rescopeDocumentRootSelectors(rule.selectorText);\n if (selectorText !== rule.selectorText) {\n (rule as CSSStyleRule).selectorText = selectorText;\n }\n });\n};\n\nconst liftStickyTopsInSheet = (sheet: CSSStyleSheet): void => {\n walkRules(sheet, (rule) => {\n const declarations = declarationsOf(rule);\n if (declarations && /sticky/i.test(declarations.cssText)) {\n liftStickyTop(declarations);\n }\n });\n};\n\nconst declaresRootFontSize = (sheet: CSSStyleSheet): boolean => {\n let declares = false;\n walkRules(sheet, (rule) => {\n declares ||= !!declarationsOf(rule)?.getPropertyValue(MF_ROOT_FONT_SIZE_PROPERTY);\n });\n return declares;\n};\n\n// Inline styles carry rem too, from templates (`style=\"padding-bottom: 0.25rem !important\"`) and from style bindings\n// and code. The whole declaration block is rewritten through element.style, as for sheet rules: !important stays with\n// its value, and a shorthand that also holds var() (whose longhands read empty) is still seen. Material sets inline\n// styles constantly (ripples, overlay positions), so anything without an unfollowed rem length returns at once, and a\n// followed value never looks like work again, which is also why a second observer on the same root writes nothing.\nconst followInlineStyle = (element: Element): void => {\n const attribute = element.getAttribute('style');\n if (!attribute || !UNFOLLOWED_REM_PATTERN.test(attribute)) {\n return;\n }\n const style = (element as HTMLElement).style;\n const declarations = style?.cssText;\n if (!declarations) {\n return;\n }\n const rewritten = rewriteRemLengths(declarations);\n if (rewritten !== declarations) {\n style.cssText = rewritten;\n }\n};\n\nconst followInlineStylesWithin = (node: Node): void => {\n if (node.nodeType !== 1) {\n return;\n }\n const element = node as Element;\n followInlineStyle(element);\n if (element.firstElementChild) {\n for (const descendant of Array.from(element.querySelectorAll(INLINE_REM_SELECTOR))) {\n followInlineStyle(descendant);\n }\n }\n};\n\ntype TStyleOwner = HTMLLinkElement | HTMLStyleElement;\n\nconst isStyleOwner = (node: Node): node is TStyleOwner => {\n return node.nodeName === 'STYLE' || (node.nodeName === 'LINK' && (node as HTMLLinkElement).rel.split(/\\s+/).includes('stylesheet'));\n};\n\ninterface IAdaptStylesOptions {\n documentRootRules: boolean;\n stickyTops: boolean;\n}\n\n// Sticky headers set from code carry their top inline (`style.cssText = 'position: sticky; top: 50px'` in the modules'\n// position-sticky directives). Each value a module writes moves up once: the attribute this wrote is remembered, so its\n// own change, which the observer sees too, is not lifted again, while a value the module writes later is.\nconst liftInlineStickyTops = (root: ShadowRoot, RootMutationObserver: typeof MutationObserver): MutationObserver => {\n const lifted = new WeakMap<Element, string>();\n const lift = (element: Element): void => {\n const attribute = element.getAttribute('style');\n if (!attribute || !/sticky/i.test(attribute) || lifted.get(element) === attribute) {\n return;\n }\n const style = (element as HTMLElement).style;\n if (style && liftStickyTop(style)) {\n lifted.set(element, element.getAttribute('style') ?? '');\n }\n };\n const liftWithin = (node: Node): void => {\n if (node.nodeType !== 1) {\n return;\n }\n lift(node as Element);\n for (const descendant of Array.from((node as Element).querySelectorAll(INLINE_STICKY_SELECTOR))) {\n lift(descendant);\n }\n };\n\n for (const element of Array.from(root.querySelectorAll(INLINE_STICKY_SELECTOR))) {\n lift(element);\n }\n const observer = new RootMutationObserver((records) => {\n for (const record of records) {\n if (record.type === 'attributes') {\n lift(record.target as Element);\n } else {\n for (const node of Array.from(record.addedNodes)) {\n liftWithin(node);\n }\n }\n }\n });\n observer.observe(root, { attributeFilter: ['style'], attributes: true, childList: true, subtree: true });\n return observer;\n};\n\nconst adaptShadowRootStyles = (root: ShadowRoot, { documentRootRules, stickyTops }: IAdaptStylesOptions): (() => void) => {\n // The root's own window, which need not be the global one (a frame, or a test's own window).\n const RootMutationObserver = root.ownerDocument.defaultView?.MutationObserver ?? MutationObserver;\n let followingRem = false;\n const remSheets = new WeakSet<CSSStyleSheet>();\n const rescopedSheets = new WeakSet<CSSStyleSheet>();\n const stickySheets = new WeakSet<CSSStyleSheet>();\n const watchedStyles = new WeakSet<HTMLStyleElement>();\n\n const owners = (): TStyleOwner[] => Array.from(root.childNodes).filter(isStyleOwner);\n\n const sheetsOf = (styleOwners: TStyleOwner[]): CSSStyleSheet[] => {\n return [...(root.adoptedStyleSheets ?? []), ...styleOwners.map((owner) => owner.sheet).filter((sheet): sheet is CSSStyleSheet => !!sheet)];\n };\n\n // A <style>'s own text is the cheap check; linked and adopted sheets have only their rules.\n const findsRootFontSize = (styleOwners: TStyleOwner[]): boolean => {\n const styles = styleOwners.filter((owner): owner is HTMLStyleElement => owner.nodeName === 'STYLE');\n if (styles.some((style) => ROOT_FONT_SIZE_DECLARATION_PATTERN.test(style.textContent ?? ''))) {\n return true;\n }\n const linkedSheets = styleOwners.filter((owner) => owner.nodeName === 'LINK').map((owner) => owner.sheet);\n return [...(root.adoptedStyleSheets ?? []), ...linkedSheets].some((sheet) => !!sheet && declaresRootFontSize(sheet));\n };\n\n // Started only once the module's CSS declares a root size, so a module without one pays nothing. An element gets its\n // template styles before it is inserted, so insertions are scanned as well as later style attribute changes.\n let inlineObserver: MutationObserver | undefined;\n const followInlineStyles = (): void => {\n for (const element of Array.from(root.querySelectorAll(INLINE_REM_SELECTOR))) {\n followInlineStyle(element);\n }\n inlineObserver = new RootMutationObserver((records) => {\n for (const record of records) {\n if (record.type === 'attributes') {\n followInlineStyle(record.target as Element);\n } else {\n for (const node of Array.from(record.addedNodes)) {\n followInlineStylesWithin(node);\n }\n }\n }\n // Only this callback ran since the records were delivered, so what is queued now are its own rewrites.\n inlineObserver?.takeRecords();\n });\n inlineObserver.observe(root, { attributeFilter: ['style'], attributes: true, childList: true, subtree: true });\n };\n\n const observer = new RootMutationObserver(() => update());\n\n const update = (): void => {\n const styleOwners = owners();\n for (const owner of styleOwners) {\n if (owner.nodeName === 'STYLE' && !watchedStyles.has(owner as HTMLStyleElement)) {\n // Replacing a <style>'s text gives it a new sheet, which must be adapted again.\n observer.observe(owner, { characterData: true, childList: true, subtree: true });\n watchedStyles.add(owner as HTMLStyleElement);\n }\n }\n\n const sheets = sheetsOf(styleOwners);\n if (documentRootRules) {\n for (const sheet of sheets) {\n if (!rescopedSheets.has(sheet)) {\n rescopeDocumentRootRules(sheet);\n rescopedSheets.add(sheet);\n }\n }\n }\n\n if (stickyTops) {\n for (const sheet of sheets) {\n if (!stickySheets.has(sheet)) {\n liftStickyTopsInSheet(sheet);\n stickySheets.add(sheet);\n }\n }\n }\n\n followingRem ||= findsRootFontSize(styleOwners);\n if (!followingRem) {\n return;\n }\n if (!inlineObserver) {\n followInlineStyles();\n }\n for (const sheet of sheets) {\n if (!remSheets.has(sheet)) {\n followRemInSheet(sheet);\n remSheets.add(sheet);\n }\n }\n };\n\n // A <link> sheet has no rules until it loads; load does not bubble, so it is caught on the way down.\n const onLoad = (event: Event): void => {\n if (event.target instanceof Node && isStyleOwner(event.target)) {\n update();\n }\n };\n\n observer.observe(root, { childList: true });\n root.addEventListener('load', onLoad, true);\n update();\n const stickyObserver = stickyTops ? liftInlineStickyTops(root, RootMutationObserver) : undefined;\n\n return () => {\n observer.disconnect();\n inlineObserver?.disconnect();\n stickyObserver?.disconnect();\n root.removeEventListener('load', onLoad, true);\n };\n};\n\n/**\n * Makes rem lengths in a hosted module's shadow root follow the root font size its own page had, once the module's\n * CSS declares one (see MF_ROOT_FONT_SIZE_PROPERTY): in stylesheets already in the root and every one added or changed\n * later, such as the component styles Angular adds as components first render, and in inline style attributes,\n * including ones inserted or changed later. A module whose CSS never sizes its root is left untouched. Returns a\n * function that stops following; rewritten rules and styles stay rewritten.\n */\nexport const followModuleRootFontSize = (root: ShadowRoot): (() => void) => {\n return adaptShadowRootStyles(root, { documentRootRules: false, stickyTops: false });\n};\n\n/**\n * Adapts the CSS a module brings into its shadow root at runtime to the hosted page, for a host to call on each module\n * shadow root it creates and stop on unmount: rem follows the module's own root font size (as followModuleRootFontSize\n * does), and rules a library injects for the document root (`html { --mat-focus-indicator-display: block; }`), which\n * never match inside a shadow root, move onto `:host` (see rescopeDocumentRootSelectors), and sticky tops written for\n * the legacy navigation's top bar (`top: 49px`), in rules and inline styles, move up by that bar, which a hosted page\n * does not have. Safe beside a module's own followModuleRootFontSize on the same root: every step is idempotent, and\n * neither writes anything the other acts on.\n */\nexport const adaptHostedModuleStyles = (root: ShadowRoot): (() => void) => {\n return adaptShadowRootStyles(root, { documentRootRules: true, stickyTops: true });\n};\n"]}
@@ -1,6 +1,27 @@
1
1
  import { rescopeDocumentRootSelectors } from './document-root-selectors.js';
2
2
  import { MF_ROOT_FONT_SIZE_PROPERTY, REM_LENGTH_HINT_PATTERN, ROOT_FONT_SIZE_DECLARATION_PATTERN, rewriteRemLengths, UNFOLLOWED_REM_PATTERN, } from './root-font-size.js';
3
3
  const INLINE_REM_SELECTOR = '[style*="rem" i]';
4
+ const INLINE_STICKY_SELECTOR = '[style*="sticky" i]';
5
+ // The legacy navigation's top bar (@edyn/navigation: `.mat-sidenav-container { padding-top: 49px }`). Old pages scrolled
6
+ // under it, so their sticky headers stuck below it with `top: 49px` (or 50px, one pixel lower). Hosted, a module scrolls
7
+ // in the host's content area, which has no bar inside it, so those headers would stick 49px too low.
8
+ const LEGACY_NAVIGATION_BAR_HEIGHT = 49;
9
+ const PIXEL_LENGTH_PATTERN = /^(\d+(?:\.\d+)?)px$/i;
10
+ // A sticky top of at least the bar's height moves up by the bar; smaller ones (0, 1px) belong to pages or panels that
11
+ // scrolled below the bar already, and anything else (var(), %, calc()) is a module's own choice. Returns whether the
12
+ // declarations changed.
13
+ const liftStickyTop = (declarations) => {
14
+ if (!/sticky/i.test(declarations.getPropertyValue('position'))) {
15
+ return false;
16
+ }
17
+ const top = PIXEL_LENGTH_PATTERN.exec(declarations.getPropertyValue('top').trim());
18
+ const pixels = top ? Number(top[1]) : Number.NaN;
19
+ if (!(pixels >= LEGACY_NAVIGATION_BAR_HEIGHT)) {
20
+ return false;
21
+ }
22
+ declarations.setProperty('top', `${pixels - LEGACY_NAVIGATION_BAR_HEIGHT}px`, declarations.getPropertyPriority('top'));
23
+ return true;
24
+ };
4
25
  // By class name, not instanceof: a root in another window (a frame, or a test's own window) has its own CSSFontFaceRule.
5
26
  const isFontFaceRule = (rule) => {
6
27
  return rule.constructor?.name === 'CSSFontFaceRule';
@@ -70,6 +91,14 @@ const rescopeDocumentRootRules = (sheet) => {
70
91
  }
71
92
  });
72
93
  };
94
+ const liftStickyTopsInSheet = (sheet) => {
95
+ walkRules(sheet, (rule) => {
96
+ const declarations = declarationsOf(rule);
97
+ if (declarations && /sticky/i.test(declarations.cssText)) {
98
+ liftStickyTop(declarations);
99
+ }
100
+ });
101
+ };
73
102
  const declaresRootFontSize = (sheet) => {
74
103
  let declares = false;
75
104
  walkRules(sheet, (rule) => {
@@ -112,12 +141,55 @@ const followInlineStylesWithin = (node) => {
112
141
  const isStyleOwner = (node) => {
113
142
  return node.nodeName === 'STYLE' || (node.nodeName === 'LINK' && node.rel.split(/\s+/).includes('stylesheet'));
114
143
  };
115
- const adaptShadowRootStyles = (root, { documentRootRules }) => {
144
+ // Sticky headers set from code carry their top inline (`style.cssText = 'position: sticky; top: 50px'` in the modules'
145
+ // position-sticky directives). Each value a module writes moves up once: the attribute this wrote is remembered, so its
146
+ // own change, which the observer sees too, is not lifted again, while a value the module writes later is.
147
+ const liftInlineStickyTops = (root, RootMutationObserver) => {
148
+ const lifted = new WeakMap();
149
+ const lift = (element) => {
150
+ const attribute = element.getAttribute('style');
151
+ if (!attribute || !/sticky/i.test(attribute) || lifted.get(element) === attribute) {
152
+ return;
153
+ }
154
+ const style = element.style;
155
+ if (style && liftStickyTop(style)) {
156
+ lifted.set(element, element.getAttribute('style') ?? '');
157
+ }
158
+ };
159
+ const liftWithin = (node) => {
160
+ if (node.nodeType !== 1) {
161
+ return;
162
+ }
163
+ lift(node);
164
+ for (const descendant of Array.from(node.querySelectorAll(INLINE_STICKY_SELECTOR))) {
165
+ lift(descendant);
166
+ }
167
+ };
168
+ for (const element of Array.from(root.querySelectorAll(INLINE_STICKY_SELECTOR))) {
169
+ lift(element);
170
+ }
171
+ const observer = new RootMutationObserver((records) => {
172
+ for (const record of records) {
173
+ if (record.type === 'attributes') {
174
+ lift(record.target);
175
+ }
176
+ else {
177
+ for (const node of Array.from(record.addedNodes)) {
178
+ liftWithin(node);
179
+ }
180
+ }
181
+ }
182
+ });
183
+ observer.observe(root, { attributeFilter: ['style'], attributes: true, childList: true, subtree: true });
184
+ return observer;
185
+ };
186
+ const adaptShadowRootStyles = (root, { documentRootRules, stickyTops }) => {
116
187
  // The root's own window, which need not be the global one (a frame, or a test's own window).
117
188
  const RootMutationObserver = root.ownerDocument.defaultView?.MutationObserver ?? MutationObserver;
118
189
  let followingRem = false;
119
190
  const remSheets = new WeakSet();
120
191
  const rescopedSheets = new WeakSet();
192
+ const stickySheets = new WeakSet();
121
193
  const watchedStyles = new WeakSet();
122
194
  const owners = () => Array.from(root.childNodes).filter(isStyleOwner);
123
195
  const sheetsOf = (styleOwners) => {
@@ -174,6 +246,14 @@ const adaptShadowRootStyles = (root, { documentRootRules }) => {
174
246
  }
175
247
  }
176
248
  }
249
+ if (stickyTops) {
250
+ for (const sheet of sheets) {
251
+ if (!stickySheets.has(sheet)) {
252
+ liftStickyTopsInSheet(sheet);
253
+ stickySheets.add(sheet);
254
+ }
255
+ }
256
+ }
177
257
  followingRem ||= findsRootFontSize(styleOwners);
178
258
  if (!followingRem) {
179
259
  return;
@@ -197,9 +277,11 @@ const adaptShadowRootStyles = (root, { documentRootRules }) => {
197
277
  observer.observe(root, { childList: true });
198
278
  root.addEventListener('load', onLoad, true);
199
279
  update();
280
+ const stickyObserver = stickyTops ? liftInlineStickyTops(root, RootMutationObserver) : undefined;
200
281
  return () => {
201
282
  observer.disconnect();
202
283
  inlineObserver?.disconnect();
284
+ stickyObserver?.disconnect();
203
285
  root.removeEventListener('load', onLoad, true);
204
286
  };
205
287
  };
@@ -211,16 +293,18 @@ const adaptShadowRootStyles = (root, { documentRootRules }) => {
211
293
  * function that stops following; rewritten rules and styles stay rewritten.
212
294
  */
213
295
  export const followModuleRootFontSize = (root) => {
214
- return adaptShadowRootStyles(root, { documentRootRules: false });
296
+ return adaptShadowRootStyles(root, { documentRootRules: false, stickyTops: false });
215
297
  };
216
298
  /**
217
299
  * Adapts the CSS a module brings into its shadow root at runtime to the hosted page, for a host to call on each module
218
300
  * shadow root it creates and stop on unmount: rem follows the module's own root font size (as followModuleRootFontSize
219
301
  * does), and rules a library injects for the document root (`html { --mat-focus-indicator-display: block; }`), which
220
- * never match inside a shadow root, move onto `:host` (see rescopeDocumentRootSelectors). Safe beside a module's own
221
- * followModuleRootFontSize on the same root: every step is idempotent, and neither writes anything the other acts on.
302
+ * never match inside a shadow root, move onto `:host` (see rescopeDocumentRootSelectors), and sticky tops written for
303
+ * the legacy navigation's top bar (`top: 49px`), in rules and inline styles, move up by that bar, which a hosted page
304
+ * does not have. Safe beside a module's own followModuleRootFontSize on the same root: every step is idempotent, and
305
+ * neither writes anything the other acts on.
222
306
  */
223
307
  export const adaptHostedModuleStyles = (root) => {
224
- return adaptShadowRootStyles(root, { documentRootRules: true });
308
+ return adaptShadowRootStyles(root, { documentRootRules: true, stickyTops: true });
225
309
  };
226
310
  //# sourceMappingURL=hosted-module-styles.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"hosted-module-styles.js","sourceRoot":"","sources":["../src/hosted-module-styles.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,4BAA4B,EAAE,MAAM,8BAA8B,CAAC;AAC5E,OAAO,EACL,0BAA0B,EAC1B,uBAAuB,EACvB,kCAAkC,EAClC,iBAAiB,EACjB,sBAAsB,GACvB,MAAM,qBAAqB,CAAC;AAE7B,MAAM,mBAAmB,GAAG,kBAAkB,CAAC;AAE/C,yHAAyH;AACzH,MAAM,cAAc,GAAG,CAAC,IAAa,EAAW,EAAE;IAChD,OAAO,IAAI,CAAC,WAAW,EAAE,IAAI,KAAK,iBAAiB,CAAC;AACtD,CAAC,CAAC;AAEF,iHAAiH;AACjH,kHAAkH;AAClH,MAAM,eAAe,GAAG,CAAC,IAAa,EAA6B,EAAE;IACnE,OAAO,YAAY,IAAI,IAAI,IAAI,IAAI,CAAC,UAAU,CAAC,CAAC,CAAE,IAAI,CAAC,UAA4B,CAAC,CAAC,CAAC,SAAS,CAAC;AAClG,CAAC,CAAC;AAEF,MAAM,cAAc,GAAG,CAAC,IAAa,EAAmC,EAAE;IACxE,OAAO,OAAO,IAAI,IAAI,IAAI,IAAI,CAAC,KAAK,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC,CAAE,IAAI,CAAC,KAA6B,CAAC,CAAC,CAAC,SAAS,CAAC;AAClH,CAAC,CAAC;AAEF,MAAM,YAAY,GAAG,CAAC,IAAa,EAA2B,EAAE;IAC9D,OAAO,UAAU,IAAI,IAAI,IAAI,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAE,IAAI,CAAC,QAAwB,CAAC,CAAC,CAAC,SAAS,CAAC;AAC1F,CAAC,CAAC;AAEF,MAAM,SAAS,GAAG,CAAC,KAAoB,EAA2B,EAAE;IAClE,IAAI,CAAC;QACH,OAAO,KAAK,CAAC,QAAQ,CAAC;IACxB,CAAC;IAAC,MAAM,CAAC;QACP,kHAAkH;QAClH,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC,CAAC;AAEF,MAAM,SAAS,GAAG,CAAC,KAAoB,EAAE,KAA8B,EAAQ,EAAE;IAC/E,MAAM,IAAI,GAAG,CAAC,KAAkB,EAAQ,EAAE;QACxC,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;YACrC,MAAM,aAAa,GAAG,eAAe,CAAC,IAAI,CAAC,CAAC;YAC5C,MAAM,aAAa,GAAG,aAAa,CAAC,CAAC,CAAC,SAAS,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;YAC3E,IAAI,aAAa,EAAE,CAAC;gBAClB,IAAI,CAAC,aAAa,CAAC,CAAC;YACtB,CAAC;YACD,KAAK,CAAC,IAAI,CAAC,CAAC;YACZ,MAAM,UAAU,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;YACtC,IAAI,UAAU,EAAE,CAAC;gBACf,IAAI,CAAC,UAAU,CAAC,CAAC;YACnB,CAAC;QACH,CAAC;IACH,CAAC,CAAC;IACF,MAAM,KAAK,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC;IAC/B,IAAI,KAAK,EAAE,CAAC;QACV,IAAI,CAAC,KAAK,CAAC,CAAC;IACd,CAAC;AACH,CAAC,CAAC;AAEF,uHAAuH;AACvH,sHAAsH;AACtH,wGAAwG;AACxG,MAAM,gBAAgB,GAAG,CAAC,KAAoB,EAAQ,EAAE;IACtD,SAAS,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,EAAE;QACxB,MAAM,YAAY,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;QAC1C,IAAI,YAAY,IAAI,uBAAuB,CAAC,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,EAAE,CAAC;YACvE,MAAM,SAAS,GAAG,iBAAiB,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC;YAC1D,IAAI,SAAS,KAAK,YAAY,CAAC,OAAO,EAAE,CAAC;gBACvC,YAAY,CAAC,OAAO,GAAG,SAAS,CAAC;YACnC,CAAC;QACH,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC,CAAC;AAEF,MAAM,wBAAwB,GAAG,CAAC,KAAoB,EAAQ,EAAE;IAC9D,SAAS,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,EAAE;QACxB,IAAI,CAAC,CAAC,cAAc,IAAI,IAAI,CAAC,IAAI,OAAO,IAAI,CAAC,YAAY,KAAK,QAAQ,EAAE,CAAC;YACvE,OAAO;QACT,CAAC;QACD,MAAM,EAAE,YAAY,EAAE,GAAG,4BAA4B,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QACzE,IAAI,YAAY,KAAK,IAAI,CAAC,YAAY,EAAE,CAAC;YACtC,IAAqB,CAAC,YAAY,GAAG,YAAY,CAAC;QACrD,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC,CAAC;AAEF,MAAM,oBAAoB,GAAG,CAAC,KAAoB,EAAW,EAAE;IAC7D,IAAI,QAAQ,GAAG,KAAK,CAAC;IACrB,SAAS,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,EAAE;QACxB,QAAQ,KAAK,CAAC,CAAC,cAAc,CAAC,IAAI,CAAC,EAAE,gBAAgB,CAAC,0BAA0B,CAAC,CAAC;IACpF,CAAC,CAAC,CAAC;IACH,OAAO,QAAQ,CAAC;AAClB,CAAC,CAAC;AAEF,qHAAqH;AACrH,sHAAsH;AACtH,oHAAoH;AACpH,sHAAsH;AACtH,mHAAmH;AACnH,MAAM,iBAAiB,GAAG,CAAC,OAAgB,EAAQ,EAAE;IACnD,MAAM,SAAS,GAAG,OAAO,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC;IAChD,IAAI,CAAC,SAAS,IAAI,CAAC,sBAAsB,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC;QAC1D,OAAO;IACT,CAAC;IACD,MAAM,KAAK,GAAI,OAAuB,CAAC,KAAK,CAAC;IAC7C,MAAM,YAAY,GAAG,KAAK,EAAE,OAAO,CAAC;IACpC,IAAI,CAAC,YAAY,EAAE,CAAC;QAClB,OAAO;IACT,CAAC;IACD,MAAM,SAAS,GAAG,iBAAiB,CAAC,YAAY,CAAC,CAAC;IAClD,IAAI,SAAS,KAAK,YAAY,EAAE,CAAC;QAC/B,KAAK,CAAC,OAAO,GAAG,SAAS,CAAC;IAC5B,CAAC;AACH,CAAC,CAAC;AAEF,MAAM,wBAAwB,GAAG,CAAC,IAAU,EAAQ,EAAE;IACpD,IAAI,IAAI,CAAC,QAAQ,KAAK,CAAC,EAAE,CAAC;QACxB,OAAO;IACT,CAAC;IACD,MAAM,OAAO,GAAG,IAAe,CAAC;IAChC,iBAAiB,CAAC,OAAO,CAAC,CAAC;IAC3B,IAAI,OAAO,CAAC,iBAAiB,EAAE,CAAC;QAC9B,KAAK,MAAM,UAAU,IAAI,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,gBAAgB,CAAC,mBAAmB,CAAC,CAAC,EAAE,CAAC;YACnF,iBAAiB,CAAC,UAAU,CAAC,CAAC;QAChC,CAAC;IACH,CAAC;AACH,CAAC,CAAC;AAIF,MAAM,YAAY,GAAG,CAAC,IAAU,EAAuB,EAAE;IACvD,OAAO,IAAI,CAAC,QAAQ,KAAK,OAAO,IAAI,CAAC,IAAI,CAAC,QAAQ,KAAK,MAAM,IAAK,IAAwB,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC,CAAC;AACtI,CAAC,CAAC;AAMF,MAAM,qBAAqB,GAAG,CAAC,IAAgB,EAAE,EAAE,iBAAiB,EAAuB,EAAgB,EAAE;IAC3G,6FAA6F;IAC7F,MAAM,oBAAoB,GAAG,IAAI,CAAC,aAAa,CAAC,WAAW,EAAE,gBAAgB,IAAI,gBAAgB,CAAC;IAClG,IAAI,YAAY,GAAG,KAAK,CAAC;IACzB,MAAM,SAAS,GAAG,IAAI,OAAO,EAAiB,CAAC;IAC/C,MAAM,cAAc,GAAG,IAAI,OAAO,EAAiB,CAAC;IACpD,MAAM,aAAa,GAAG,IAAI,OAAO,EAAoB,CAAC;IAEtD,MAAM,MAAM,GAAG,GAAkB,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;IAErF,MAAM,QAAQ,GAAG,CAAC,WAA0B,EAAmB,EAAE;QAC/D,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,kBAAkB,IAAI,EAAE,CAAC,EAAE,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,KAAK,EAA0B,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;IAC7I,CAAC,CAAC;IAEF,4FAA4F;IAC5F,MAAM,iBAAiB,GAAG,CAAC,WAA0B,EAAW,EAAE;QAChE,MAAM,MAAM,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC,KAAK,EAA6B,EAAE,CAAC,KAAK,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC;QACpG,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,kCAAkC,CAAC,IAAI,CAAC,KAAK,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;YAC7F,OAAO,IAAI,CAAC;QACd,CAAC;QACD,MAAM,YAAY,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,KAAK,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAC1G,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,kBAAkB,IAAI,EAAE,CAAC,EAAE,GAAG,YAAY,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,IAAI,oBAAoB,CAAC,KAAK,CAAC,CAAC,CAAC;IACvH,CAAC,CAAC;IAEF,qHAAqH;IACrH,6GAA6G;IAC7G,IAAI,cAA4C,CAAC;IACjD,MAAM,kBAAkB,GAAG,GAAS,EAAE;QACpC,KAAK,MAAM,OAAO,IAAI,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,gBAAgB,CAAC,mBAAmB,CAAC,CAAC,EAAE,CAAC;YAC7E,iBAAiB,CAAC,OAAO,CAAC,CAAC;QAC7B,CAAC;QACD,cAAc,GAAG,IAAI,oBAAoB,CAAC,CAAC,OAAO,EAAE,EAAE;YACpD,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;gBAC7B,IAAI,MAAM,CAAC,IAAI,KAAK,YAAY,EAAE,CAAC;oBACjC,iBAAiB,CAAC,MAAM,CAAC,MAAiB,CAAC,CAAC;gBAC9C,CAAC;qBAAM,CAAC;oBACN,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,CAAC;wBACjD,wBAAwB,CAAC,IAAI,CAAC,CAAC;oBACjC,CAAC;gBACH,CAAC;YACH,CAAC;YACD,uGAAuG;YACvG,cAAc,EAAE,WAAW,EAAE,CAAC;QAChC,CAAC,CAAC,CAAC;QACH,cAAc,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,eAAe,EAAE,CAAC,OAAO,CAAC,EAAE,UAAU,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;IACjH,CAAC,CAAC;IAEF,MAAM,QAAQ,GAAG,IAAI,oBAAoB,CAAC,GAAG,EAAE,CAAC,MAAM,EAAE,CAAC,CAAC;IAE1D,MAAM,MAAM,GAAG,GAAS,EAAE;QACxB,MAAM,WAAW,GAAG,MAAM,EAAE,CAAC;QAC7B,KAAK,MAAM,KAAK,IAAI,WAAW,EAAE,CAAC;YAChC,IAAI,KAAK,CAAC,QAAQ,KAAK,OAAO,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,KAAyB,CAAC,EAAE,CAAC;gBAChF,gFAAgF;gBAChF,QAAQ,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;gBACjF,aAAa,CAAC,GAAG,CAAC,KAAyB,CAAC,CAAC;YAC/C,CAAC;QACH,CAAC;QAED,MAAM,MAAM,GAAG,QAAQ,CAAC,WAAW,CAAC,CAAC;QACrC,IAAI,iBAAiB,EAAE,CAAC;YACtB,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;gBAC3B,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;oBAC/B,wBAAwB,CAAC,KAAK,CAAC,CAAC;oBAChC,cAAc,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;gBAC5B,CAAC;YACH,CAAC;QACH,CAAC;QAED,YAAY,KAAK,iBAAiB,CAAC,WAAW,CAAC,CAAC;QAChD,IAAI,CAAC,YAAY,EAAE,CAAC;YAClB,OAAO;QACT,CAAC;QACD,IAAI,CAAC,cAAc,EAAE,CAAC;YACpB,kBAAkB,EAAE,CAAC;QACvB,CAAC;QACD,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;YAC3B,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;gBAC1B,gBAAgB,CAAC,KAAK,CAAC,CAAC;gBACxB,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;YACvB,CAAC;QACH,CAAC;IACH,CAAC,CAAC;IAEF,qGAAqG;IACrG,MAAM,MAAM,GAAG,CAAC,KAAY,EAAQ,EAAE;QACpC,IAAI,KAAK,CAAC,MAAM,YAAY,IAAI,IAAI,YAAY,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC;YAC/D,MAAM,EAAE,CAAC;QACX,CAAC;IACH,CAAC,CAAC;IAEF,QAAQ,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC5C,IAAI,CAAC,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC;IAC5C,MAAM,EAAE,CAAC;IAET,OAAO,GAAG,EAAE;QACV,QAAQ,CAAC,UAAU,EAAE,CAAC;QACtB,cAAc,EAAE,UAAU,EAAE,CAAC;QAC7B,IAAI,CAAC,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC;IACjD,CAAC,CAAC;AACJ,CAAC,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,CAAC,IAAgB,EAAgB,EAAE;IACzE,OAAO,qBAAqB,CAAC,IAAI,EAAE,EAAE,iBAAiB,EAAE,KAAK,EAAE,CAAC,CAAC;AACnE,CAAC,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC,IAAgB,EAAgB,EAAE;IACxE,OAAO,qBAAqB,CAAC,IAAI,EAAE,EAAE,iBAAiB,EAAE,IAAI,EAAE,CAAC,CAAC;AAClE,CAAC,CAAC","sourcesContent":["import { rescopeDocumentRootSelectors } from './document-root-selectors.js';\nimport {\n MF_ROOT_FONT_SIZE_PROPERTY,\n REM_LENGTH_HINT_PATTERN,\n ROOT_FONT_SIZE_DECLARATION_PATTERN,\n rewriteRemLengths,\n UNFOLLOWED_REM_PATTERN,\n} from './root-font-size.js';\n\nconst INLINE_REM_SELECTOR = '[style*=\"rem\" i]';\n\n// By class name, not instanceof: a root in another window (a frame, or a test's own window) has its own CSSFontFaceRule.\nconst isFontFaceRule = (rule: CSSRule): boolean => {\n return rule.constructor?.name === 'CSSFontFaceRule';\n};\n\n// Rule kinds are told apart by what they carry, not by class: @import has a sheet, style and keyframe rules have\n// declarations, and grouping rules (@media, @supports, @container, @layer, @keyframes, nesting) have child rules.\nconst importedSheetOf = (rule: CSSRule): CSSStyleSheet | undefined => {\n return 'styleSheet' in rule && rule.styleSheet ? (rule.styleSheet as CSSStyleSheet) : undefined;\n};\n\nconst declarationsOf = (rule: CSSRule): CSSStyleDeclaration | undefined => {\n return 'style' in rule && rule.style && !isFontFaceRule(rule) ? (rule.style as CSSStyleDeclaration) : undefined;\n};\n\nconst childRulesOf = (rule: CSSRule): CSSRuleList | undefined => {\n return 'cssRules' in rule && rule.cssRules ? (rule.cssRules as CSSRuleList) : undefined;\n};\n\nconst readRules = (sheet: CSSStyleSheet): CSSRuleList | undefined => {\n try {\n return sheet.cssRules;\n } catch {\n // A cross-origin sheet (a font service's @font-face list) cannot be read, and none of its rules are module rules.\n return undefined;\n }\n};\n\nconst walkRules = (sheet: CSSStyleSheet, visit: (rule: CSSRule) => void): void => {\n const walk = (rules: CSSRuleList): void => {\n for (const rule of Array.from(rules)) {\n const importedSheet = importedSheetOf(rule);\n const importedRules = importedSheet ? readRules(importedSheet) : undefined;\n if (importedRules) {\n walk(importedRules);\n }\n visit(rule);\n const childRules = childRulesOf(rule);\n if (childRules) {\n walk(childRules);\n }\n }\n };\n const rules = readRules(sheet);\n if (rules) {\n walk(rules);\n }\n};\n\n// Only declaration blocks change, through the CSSOM: @media, @container and @supports conditions keep their rem (var()\n// is invalid there, and a media query rem is the browser's initial size anyway), and !important stays with its value.\n// CSSOM changes are not DOM mutations, so no observer, this one or another on the same root, sees them.\nconst followRemInSheet = (sheet: CSSStyleSheet): void => {\n walkRules(sheet, (rule) => {\n const declarations = declarationsOf(rule);\n if (declarations && REM_LENGTH_HINT_PATTERN.test(declarations.cssText)) {\n const rewritten = rewriteRemLengths(declarations.cssText);\n if (rewritten !== declarations.cssText) {\n declarations.cssText = rewritten;\n }\n }\n });\n};\n\nconst rescopeDocumentRootRules = (sheet: CSSStyleSheet): void => {\n walkRules(sheet, (rule) => {\n if (!('selectorText' in rule) || typeof rule.selectorText !== 'string') {\n return;\n }\n const { selectorText } = rescopeDocumentRootSelectors(rule.selectorText);\n if (selectorText !== rule.selectorText) {\n (rule as CSSStyleRule).selectorText = selectorText;\n }\n });\n};\n\nconst declaresRootFontSize = (sheet: CSSStyleSheet): boolean => {\n let declares = false;\n walkRules(sheet, (rule) => {\n declares ||= !!declarationsOf(rule)?.getPropertyValue(MF_ROOT_FONT_SIZE_PROPERTY);\n });\n return declares;\n};\n\n// Inline styles carry rem too, from templates (`style=\"padding-bottom: 0.25rem !important\"`) and from style bindings\n// and code. The whole declaration block is rewritten through element.style, as for sheet rules: !important stays with\n// its value, and a shorthand that also holds var() (whose longhands read empty) is still seen. Material sets inline\n// styles constantly (ripples, overlay positions), so anything without an unfollowed rem length returns at once, and a\n// followed value never looks like work again, which is also why a second observer on the same root writes nothing.\nconst followInlineStyle = (element: Element): void => {\n const attribute = element.getAttribute('style');\n if (!attribute || !UNFOLLOWED_REM_PATTERN.test(attribute)) {\n return;\n }\n const style = (element as HTMLElement).style;\n const declarations = style?.cssText;\n if (!declarations) {\n return;\n }\n const rewritten = rewriteRemLengths(declarations);\n if (rewritten !== declarations) {\n style.cssText = rewritten;\n }\n};\n\nconst followInlineStylesWithin = (node: Node): void => {\n if (node.nodeType !== 1) {\n return;\n }\n const element = node as Element;\n followInlineStyle(element);\n if (element.firstElementChild) {\n for (const descendant of Array.from(element.querySelectorAll(INLINE_REM_SELECTOR))) {\n followInlineStyle(descendant);\n }\n }\n};\n\ntype TStyleOwner = HTMLLinkElement | HTMLStyleElement;\n\nconst isStyleOwner = (node: Node): node is TStyleOwner => {\n return node.nodeName === 'STYLE' || (node.nodeName === 'LINK' && (node as HTMLLinkElement).rel.split(/\\s+/).includes('stylesheet'));\n};\n\ninterface IAdaptStylesOptions {\n documentRootRules: boolean;\n}\n\nconst adaptShadowRootStyles = (root: ShadowRoot, { documentRootRules }: IAdaptStylesOptions): (() => void) => {\n // The root's own window, which need not be the global one (a frame, or a test's own window).\n const RootMutationObserver = root.ownerDocument.defaultView?.MutationObserver ?? MutationObserver;\n let followingRem = false;\n const remSheets = new WeakSet<CSSStyleSheet>();\n const rescopedSheets = new WeakSet<CSSStyleSheet>();\n const watchedStyles = new WeakSet<HTMLStyleElement>();\n\n const owners = (): TStyleOwner[] => Array.from(root.childNodes).filter(isStyleOwner);\n\n const sheetsOf = (styleOwners: TStyleOwner[]): CSSStyleSheet[] => {\n return [...(root.adoptedStyleSheets ?? []), ...styleOwners.map((owner) => owner.sheet).filter((sheet): sheet is CSSStyleSheet => !!sheet)];\n };\n\n // A <style>'s own text is the cheap check; linked and adopted sheets have only their rules.\n const findsRootFontSize = (styleOwners: TStyleOwner[]): boolean => {\n const styles = styleOwners.filter((owner): owner is HTMLStyleElement => owner.nodeName === 'STYLE');\n if (styles.some((style) => ROOT_FONT_SIZE_DECLARATION_PATTERN.test(style.textContent ?? ''))) {\n return true;\n }\n const linkedSheets = styleOwners.filter((owner) => owner.nodeName === 'LINK').map((owner) => owner.sheet);\n return [...(root.adoptedStyleSheets ?? []), ...linkedSheets].some((sheet) => !!sheet && declaresRootFontSize(sheet));\n };\n\n // Started only once the module's CSS declares a root size, so a module without one pays nothing. An element gets its\n // template styles before it is inserted, so insertions are scanned as well as later style attribute changes.\n let inlineObserver: MutationObserver | undefined;\n const followInlineStyles = (): void => {\n for (const element of Array.from(root.querySelectorAll(INLINE_REM_SELECTOR))) {\n followInlineStyle(element);\n }\n inlineObserver = new RootMutationObserver((records) => {\n for (const record of records) {\n if (record.type === 'attributes') {\n followInlineStyle(record.target as Element);\n } else {\n for (const node of Array.from(record.addedNodes)) {\n followInlineStylesWithin(node);\n }\n }\n }\n // Only this callback ran since the records were delivered, so what is queued now are its own rewrites.\n inlineObserver?.takeRecords();\n });\n inlineObserver.observe(root, { attributeFilter: ['style'], attributes: true, childList: true, subtree: true });\n };\n\n const observer = new RootMutationObserver(() => update());\n\n const update = (): void => {\n const styleOwners = owners();\n for (const owner of styleOwners) {\n if (owner.nodeName === 'STYLE' && !watchedStyles.has(owner as HTMLStyleElement)) {\n // Replacing a <style>'s text gives it a new sheet, which must be adapted again.\n observer.observe(owner, { characterData: true, childList: true, subtree: true });\n watchedStyles.add(owner as HTMLStyleElement);\n }\n }\n\n const sheets = sheetsOf(styleOwners);\n if (documentRootRules) {\n for (const sheet of sheets) {\n if (!rescopedSheets.has(sheet)) {\n rescopeDocumentRootRules(sheet);\n rescopedSheets.add(sheet);\n }\n }\n }\n\n followingRem ||= findsRootFontSize(styleOwners);\n if (!followingRem) {\n return;\n }\n if (!inlineObserver) {\n followInlineStyles();\n }\n for (const sheet of sheets) {\n if (!remSheets.has(sheet)) {\n followRemInSheet(sheet);\n remSheets.add(sheet);\n }\n }\n };\n\n // A <link> sheet has no rules until it loads; load does not bubble, so it is caught on the way down.\n const onLoad = (event: Event): void => {\n if (event.target instanceof Node && isStyleOwner(event.target)) {\n update();\n }\n };\n\n observer.observe(root, { childList: true });\n root.addEventListener('load', onLoad, true);\n update();\n\n return () => {\n observer.disconnect();\n inlineObserver?.disconnect();\n root.removeEventListener('load', onLoad, true);\n };\n};\n\n/**\n * Makes rem lengths in a hosted module's shadow root follow the root font size its own page had, once the module's\n * CSS declares one (see MF_ROOT_FONT_SIZE_PROPERTY): in stylesheets already in the root and every one added or changed\n * later, such as the component styles Angular adds as components first render, and in inline style attributes,\n * including ones inserted or changed later. A module whose CSS never sizes its root is left untouched. Returns a\n * function that stops following; rewritten rules and styles stay rewritten.\n */\nexport const followModuleRootFontSize = (root: ShadowRoot): (() => void) => {\n return adaptShadowRootStyles(root, { documentRootRules: false });\n};\n\n/**\n * Adapts the CSS a module brings into its shadow root at runtime to the hosted page, for a host to call on each module\n * shadow root it creates and stop on unmount: rem follows the module's own root font size (as followModuleRootFontSize\n * does), and rules a library injects for the document root (`html { --mat-focus-indicator-display: block; }`), which\n * never match inside a shadow root, move onto `:host` (see rescopeDocumentRootSelectors). Safe beside a module's own\n * followModuleRootFontSize on the same root: every step is idempotent, and neither writes anything the other acts on.\n */\nexport const adaptHostedModuleStyles = (root: ShadowRoot): (() => void) => {\n return adaptShadowRootStyles(root, { documentRootRules: true });\n};\n"]}
1
+ {"version":3,"file":"hosted-module-styles.js","sourceRoot":"","sources":["../src/hosted-module-styles.ts"],"names":[],"mappings":"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","sourcesContent":["import { rescopeDocumentRootSelectors } from './document-root-selectors.js';\nimport {\n MF_ROOT_FONT_SIZE_PROPERTY,\n REM_LENGTH_HINT_PATTERN,\n ROOT_FONT_SIZE_DECLARATION_PATTERN,\n rewriteRemLengths,\n UNFOLLOWED_REM_PATTERN,\n} from './root-font-size.js';\n\nconst INLINE_REM_SELECTOR = '[style*=\"rem\" i]';\nconst INLINE_STICKY_SELECTOR = '[style*=\"sticky\" i]';\n\n// The legacy navigation's top bar (@edyn/navigation: `.mat-sidenav-container { padding-top: 49px }`). Old pages scrolled\n// under it, so their sticky headers stuck below it with `top: 49px` (or 50px, one pixel lower). Hosted, a module scrolls\n// in the host's content area, which has no bar inside it, so those headers would stick 49px too low.\nconst LEGACY_NAVIGATION_BAR_HEIGHT = 49;\nconst PIXEL_LENGTH_PATTERN = /^(\\d+(?:\\.\\d+)?)px$/i;\n\n// A sticky top of at least the bar's height moves up by the bar; smaller ones (0, 1px) belong to pages or panels that\n// scrolled below the bar already, and anything else (var(), %, calc()) is a module's own choice. Returns whether the\n// declarations changed.\nconst liftStickyTop = (declarations: CSSStyleDeclaration): boolean => {\n if (!/sticky/i.test(declarations.getPropertyValue('position'))) {\n return false;\n }\n const top = PIXEL_LENGTH_PATTERN.exec(declarations.getPropertyValue('top').trim());\n const pixels = top ? Number(top[1]) : Number.NaN;\n if (!(pixels >= LEGACY_NAVIGATION_BAR_HEIGHT)) {\n return false;\n }\n declarations.setProperty('top', `${pixels - LEGACY_NAVIGATION_BAR_HEIGHT}px`, declarations.getPropertyPriority('top'));\n return true;\n};\n\n// By class name, not instanceof: a root in another window (a frame, or a test's own window) has its own CSSFontFaceRule.\nconst isFontFaceRule = (rule: CSSRule): boolean => {\n return rule.constructor?.name === 'CSSFontFaceRule';\n};\n\n// Rule kinds are told apart by what they carry, not by class: @import has a sheet, style and keyframe rules have\n// declarations, and grouping rules (@media, @supports, @container, @layer, @keyframes, nesting) have child rules.\nconst importedSheetOf = (rule: CSSRule): CSSStyleSheet | undefined => {\n return 'styleSheet' in rule && rule.styleSheet ? (rule.styleSheet as CSSStyleSheet) : undefined;\n};\n\nconst declarationsOf = (rule: CSSRule): CSSStyleDeclaration | undefined => {\n return 'style' in rule && rule.style && !isFontFaceRule(rule) ? (rule.style as CSSStyleDeclaration) : undefined;\n};\n\nconst childRulesOf = (rule: CSSRule): CSSRuleList | undefined => {\n return 'cssRules' in rule && rule.cssRules ? (rule.cssRules as CSSRuleList) : undefined;\n};\n\nconst readRules = (sheet: CSSStyleSheet): CSSRuleList | undefined => {\n try {\n return sheet.cssRules;\n } catch {\n // A cross-origin sheet (a font service's @font-face list) cannot be read, and none of its rules are module rules.\n return undefined;\n }\n};\n\nconst walkRules = (sheet: CSSStyleSheet, visit: (rule: CSSRule) => void): void => {\n const walk = (rules: CSSRuleList): void => {\n for (const rule of Array.from(rules)) {\n const importedSheet = importedSheetOf(rule);\n const importedRules = importedSheet ? readRules(importedSheet) : undefined;\n if (importedRules) {\n walk(importedRules);\n }\n visit(rule);\n const childRules = childRulesOf(rule);\n if (childRules) {\n walk(childRules);\n }\n }\n };\n const rules = readRules(sheet);\n if (rules) {\n walk(rules);\n }\n};\n\n// Only declaration blocks change, through the CSSOM: @media, @container and @supports conditions keep their rem (var()\n// is invalid there, and a media query rem is the browser's initial size anyway), and !important stays with its value.\n// CSSOM changes are not DOM mutations, so no observer, this one or another on the same root, sees them.\nconst followRemInSheet = (sheet: CSSStyleSheet): void => {\n walkRules(sheet, (rule) => {\n const declarations = declarationsOf(rule);\n if (declarations && REM_LENGTH_HINT_PATTERN.test(declarations.cssText)) {\n const rewritten = rewriteRemLengths(declarations.cssText);\n if (rewritten !== declarations.cssText) {\n declarations.cssText = rewritten;\n }\n }\n });\n};\n\nconst rescopeDocumentRootRules = (sheet: CSSStyleSheet): void => {\n walkRules(sheet, (rule) => {\n if (!('selectorText' in rule) || typeof rule.selectorText !== 'string') {\n return;\n }\n const { selectorText } = rescopeDocumentRootSelectors(rule.selectorText);\n if (selectorText !== rule.selectorText) {\n (rule as CSSStyleRule).selectorText = selectorText;\n }\n });\n};\n\nconst liftStickyTopsInSheet = (sheet: CSSStyleSheet): void => {\n walkRules(sheet, (rule) => {\n const declarations = declarationsOf(rule);\n if (declarations && /sticky/i.test(declarations.cssText)) {\n liftStickyTop(declarations);\n }\n });\n};\n\nconst declaresRootFontSize = (sheet: CSSStyleSheet): boolean => {\n let declares = false;\n walkRules(sheet, (rule) => {\n declares ||= !!declarationsOf(rule)?.getPropertyValue(MF_ROOT_FONT_SIZE_PROPERTY);\n });\n return declares;\n};\n\n// Inline styles carry rem too, from templates (`style=\"padding-bottom: 0.25rem !important\"`) and from style bindings\n// and code. The whole declaration block is rewritten through element.style, as for sheet rules: !important stays with\n// its value, and a shorthand that also holds var() (whose longhands read empty) is still seen. Material sets inline\n// styles constantly (ripples, overlay positions), so anything without an unfollowed rem length returns at once, and a\n// followed value never looks like work again, which is also why a second observer on the same root writes nothing.\nconst followInlineStyle = (element: Element): void => {\n const attribute = element.getAttribute('style');\n if (!attribute || !UNFOLLOWED_REM_PATTERN.test(attribute)) {\n return;\n }\n const style = (element as HTMLElement).style;\n const declarations = style?.cssText;\n if (!declarations) {\n return;\n }\n const rewritten = rewriteRemLengths(declarations);\n if (rewritten !== declarations) {\n style.cssText = rewritten;\n }\n};\n\nconst followInlineStylesWithin = (node: Node): void => {\n if (node.nodeType !== 1) {\n return;\n }\n const element = node as Element;\n followInlineStyle(element);\n if (element.firstElementChild) {\n for (const descendant of Array.from(element.querySelectorAll(INLINE_REM_SELECTOR))) {\n followInlineStyle(descendant);\n }\n }\n};\n\ntype TStyleOwner = HTMLLinkElement | HTMLStyleElement;\n\nconst isStyleOwner = (node: Node): node is TStyleOwner => {\n return node.nodeName === 'STYLE' || (node.nodeName === 'LINK' && (node as HTMLLinkElement).rel.split(/\\s+/).includes('stylesheet'));\n};\n\ninterface IAdaptStylesOptions {\n documentRootRules: boolean;\n stickyTops: boolean;\n}\n\n// Sticky headers set from code carry their top inline (`style.cssText = 'position: sticky; top: 50px'` in the modules'\n// position-sticky directives). Each value a module writes moves up once: the attribute this wrote is remembered, so its\n// own change, which the observer sees too, is not lifted again, while a value the module writes later is.\nconst liftInlineStickyTops = (root: ShadowRoot, RootMutationObserver: typeof MutationObserver): MutationObserver => {\n const lifted = new WeakMap<Element, string>();\n const lift = (element: Element): void => {\n const attribute = element.getAttribute('style');\n if (!attribute || !/sticky/i.test(attribute) || lifted.get(element) === attribute) {\n return;\n }\n const style = (element as HTMLElement).style;\n if (style && liftStickyTop(style)) {\n lifted.set(element, element.getAttribute('style') ?? '');\n }\n };\n const liftWithin = (node: Node): void => {\n if (node.nodeType !== 1) {\n return;\n }\n lift(node as Element);\n for (const descendant of Array.from((node as Element).querySelectorAll(INLINE_STICKY_SELECTOR))) {\n lift(descendant);\n }\n };\n\n for (const element of Array.from(root.querySelectorAll(INLINE_STICKY_SELECTOR))) {\n lift(element);\n }\n const observer = new RootMutationObserver((records) => {\n for (const record of records) {\n if (record.type === 'attributes') {\n lift(record.target as Element);\n } else {\n for (const node of Array.from(record.addedNodes)) {\n liftWithin(node);\n }\n }\n }\n });\n observer.observe(root, { attributeFilter: ['style'], attributes: true, childList: true, subtree: true });\n return observer;\n};\n\nconst adaptShadowRootStyles = (root: ShadowRoot, { documentRootRules, stickyTops }: IAdaptStylesOptions): (() => void) => {\n // The root's own window, which need not be the global one (a frame, or a test's own window).\n const RootMutationObserver = root.ownerDocument.defaultView?.MutationObserver ?? MutationObserver;\n let followingRem = false;\n const remSheets = new WeakSet<CSSStyleSheet>();\n const rescopedSheets = new WeakSet<CSSStyleSheet>();\n const stickySheets = new WeakSet<CSSStyleSheet>();\n const watchedStyles = new WeakSet<HTMLStyleElement>();\n\n const owners = (): TStyleOwner[] => Array.from(root.childNodes).filter(isStyleOwner);\n\n const sheetsOf = (styleOwners: TStyleOwner[]): CSSStyleSheet[] => {\n return [...(root.adoptedStyleSheets ?? []), ...styleOwners.map((owner) => owner.sheet).filter((sheet): sheet is CSSStyleSheet => !!sheet)];\n };\n\n // A <style>'s own text is the cheap check; linked and adopted sheets have only their rules.\n const findsRootFontSize = (styleOwners: TStyleOwner[]): boolean => {\n const styles = styleOwners.filter((owner): owner is HTMLStyleElement => owner.nodeName === 'STYLE');\n if (styles.some((style) => ROOT_FONT_SIZE_DECLARATION_PATTERN.test(style.textContent ?? ''))) {\n return true;\n }\n const linkedSheets = styleOwners.filter((owner) => owner.nodeName === 'LINK').map((owner) => owner.sheet);\n return [...(root.adoptedStyleSheets ?? []), ...linkedSheets].some((sheet) => !!sheet && declaresRootFontSize(sheet));\n };\n\n // Started only once the module's CSS declares a root size, so a module without one pays nothing. An element gets its\n // template styles before it is inserted, so insertions are scanned as well as later style attribute changes.\n let inlineObserver: MutationObserver | undefined;\n const followInlineStyles = (): void => {\n for (const element of Array.from(root.querySelectorAll(INLINE_REM_SELECTOR))) {\n followInlineStyle(element);\n }\n inlineObserver = new RootMutationObserver((records) => {\n for (const record of records) {\n if (record.type === 'attributes') {\n followInlineStyle(record.target as Element);\n } else {\n for (const node of Array.from(record.addedNodes)) {\n followInlineStylesWithin(node);\n }\n }\n }\n // Only this callback ran since the records were delivered, so what is queued now are its own rewrites.\n inlineObserver?.takeRecords();\n });\n inlineObserver.observe(root, { attributeFilter: ['style'], attributes: true, childList: true, subtree: true });\n };\n\n const observer = new RootMutationObserver(() => update());\n\n const update = (): void => {\n const styleOwners = owners();\n for (const owner of styleOwners) {\n if (owner.nodeName === 'STYLE' && !watchedStyles.has(owner as HTMLStyleElement)) {\n // Replacing a <style>'s text gives it a new sheet, which must be adapted again.\n observer.observe(owner, { characterData: true, childList: true, subtree: true });\n watchedStyles.add(owner as HTMLStyleElement);\n }\n }\n\n const sheets = sheetsOf(styleOwners);\n if (documentRootRules) {\n for (const sheet of sheets) {\n if (!rescopedSheets.has(sheet)) {\n rescopeDocumentRootRules(sheet);\n rescopedSheets.add(sheet);\n }\n }\n }\n\n if (stickyTops) {\n for (const sheet of sheets) {\n if (!stickySheets.has(sheet)) {\n liftStickyTopsInSheet(sheet);\n stickySheets.add(sheet);\n }\n }\n }\n\n followingRem ||= findsRootFontSize(styleOwners);\n if (!followingRem) {\n return;\n }\n if (!inlineObserver) {\n followInlineStyles();\n }\n for (const sheet of sheets) {\n if (!remSheets.has(sheet)) {\n followRemInSheet(sheet);\n remSheets.add(sheet);\n }\n }\n };\n\n // A <link> sheet has no rules until it loads; load does not bubble, so it is caught on the way down.\n const onLoad = (event: Event): void => {\n if (event.target instanceof Node && isStyleOwner(event.target)) {\n update();\n }\n };\n\n observer.observe(root, { childList: true });\n root.addEventListener('load', onLoad, true);\n update();\n const stickyObserver = stickyTops ? liftInlineStickyTops(root, RootMutationObserver) : undefined;\n\n return () => {\n observer.disconnect();\n inlineObserver?.disconnect();\n stickyObserver?.disconnect();\n root.removeEventListener('load', onLoad, true);\n };\n};\n\n/**\n * Makes rem lengths in a hosted module's shadow root follow the root font size its own page had, once the module's\n * CSS declares one (see MF_ROOT_FONT_SIZE_PROPERTY): in stylesheets already in the root and every one added or changed\n * later, such as the component styles Angular adds as components first render, and in inline style attributes,\n * including ones inserted or changed later. A module whose CSS never sizes its root is left untouched. Returns a\n * function that stops following; rewritten rules and styles stay rewritten.\n */\nexport const followModuleRootFontSize = (root: ShadowRoot): (() => void) => {\n return adaptShadowRootStyles(root, { documentRootRules: false, stickyTops: false });\n};\n\n/**\n * Adapts the CSS a module brings into its shadow root at runtime to the hosted page, for a host to call on each module\n * shadow root it creates and stop on unmount: rem follows the module's own root font size (as followModuleRootFontSize\n * does), and rules a library injects for the document root (`html { --mat-focus-indicator-display: block; }`), which\n * never match inside a shadow root, move onto `:host` (see rescopeDocumentRootSelectors), and sticky tops written for\n * the legacy navigation's top bar (`top: 49px`), in rules and inline styles, move up by that bar, which a hosted page\n * does not have. Safe beside a module's own followModuleRootFontSize on the same root: every step is idempotent, and\n * neither writes anything the other acts on.\n */\nexport const adaptHostedModuleStyles = (root: ShadowRoot): (() => void) => {\n return adaptShadowRootStyles(root, { documentRootRules: true, stickyTops: true });\n};\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@edynamix/mf-isolation",
3
- "version": "0.0.33",
3
+ "version": "0.0.34",
4
4
  "description": "CSS and overlay isolation helpers for eDynamix microfrontends.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",