@sankhyatronics/sankhya-cms 0.1.1 → 0.1.3
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/LICENSE +21 -0
- package/custom-elements.json +1843 -952
- package/dist/carousel/index.d.ts +38 -2
- package/dist/carousel/index.js +1 -1
- package/dist/chunk-47RPYNFG.js +527 -0
- package/dist/chunk-47RPYNFG.js.map +1 -0
- package/dist/chunk-5DNVCMOO.js +509 -0
- package/dist/chunk-5DNVCMOO.js.map +1 -0
- package/dist/{chunk-RMIJVTFS.js → chunk-FWIG6GEZ.js} +8 -4
- package/dist/chunk-FWIG6GEZ.js.map +1 -0
- package/dist/{chunk-U7YKCCJG.js → chunk-K4ZICRZL.js} +134 -14
- package/dist/chunk-K4ZICRZL.js.map +1 -0
- package/dist/chunk-N63TVWK3.js +203 -0
- package/dist/chunk-N63TVWK3.js.map +1 -0
- package/dist/{chunk-23IHTBAB.js → chunk-PKTRYUGP.js} +108 -2
- package/dist/chunk-PKTRYUGP.js.map +1 -0
- package/dist/{chunk-LHU23TF6.js → chunk-TH5NZXA7.js} +5 -4
- package/dist/chunk-TH5NZXA7.js.map +1 -0
- package/dist/dropdown/index.js +1 -1
- package/dist/header/index.d.ts +18 -0
- package/dist/header/index.js +1 -1
- package/dist/hero/index.d.ts +29 -4
- package/dist/hero/index.js +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +23 -19
- package/dist/menu-item/index.js +1 -1
- package/dist/schema/index.d.ts +1 -0
- package/dist/schema/index.js +1 -0
- package/dist/schema/index.js.map +1 -1
- package/dist/section/index.d.ts +33 -0
- package/dist/section/index.js +10 -0
- package/dist/section/index.js.map +1 -0
- package/dist/testimonials/index.d.ts +62 -6
- package/dist/testimonials/index.js +2 -1
- package/package.json +25 -21
- package/dist/chunk-23IHTBAB.js.map +0 -1
- package/dist/chunk-LHU23TF6.js.map +0 -1
- package/dist/chunk-RMIJVTFS.js.map +0 -1
- package/dist/chunk-U7YKCCJG.js.map +0 -1
- package/dist/chunk-WCO7JAIK.js +0 -195
- package/dist/chunk-WCO7JAIK.js.map +0 -1
- package/dist/chunk-XHBC23TV.js +0 -254
- package/dist/chunk-XHBC23TV.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/header/Header.ts"],"sourcesContent":["import { LitElement, css, html, nothing } from 'lit';\r\nimport { customElement, property, state } from 'lit/decorators.js';\r\n\r\nimport { chevronIcon, closeIcon, menuIcon } from '../shared/icons';\r\nimport { BREAKPOINT_TABLET, buttonResetStyles, controlBaseStyles, dropdownPanelStyles, rootFontFamilyStyles } from '../shared/styles';\r\nimport type { LanguageChangedEventDetail } from './HeaderEvents';\r\n\r\n/**\r\n * The app-shell header bar — brand (image + title, or title alone) on the left, a `slot=\"menu\"`\r\n * for primary nav (e.g. `st-dropdown`/`st-menu-grid` mega-menus, or plain links) taking the\r\n * middle, and a `slot=\"utility\"` for account/app-switcher/theme controls on the right. Ported\r\n * from `SankhyaUI`'s React `Header` (a separate, unrelated marketing-site project) to this\r\n * package's Lit conventions and this workspace's actual needs — that source component takes an\r\n * `imageSrc` logo with no text fallback, but none of this suite's apps has a logo image today, so\r\n * `title` is the primary brand prop here and `brand-image-url` is optional, mirroring\r\n * `st-app-header`'s own brand/title split (this component's slotted-menu/utility shape is a\r\n * deliberately different, more generic surface than `st-app-header`'s fixed nav-links/user-menu\r\n * props — pick whichever fits a given app's header).\r\n *\r\n * Layout follows the source's `Header.css`: the menu starts 4rem after the brand, the utility\r\n * controls sit after a vertical divider, and below `BREAKPOINT_TABLET` (900px here; 768px in the\r\n * source) `menu`/`utility` collapse behind a hamburger toggle into a full-screen layer under the\r\n * toggle (closes on Escape or a click inside it), where the menu's `st-dropdown`s are switched to\r\n * `inline` so they open in the flow — the same two slotted elements just get repositioned by CSS\r\n * (`.mobile-panel`'s `display: contents` at desktop width vs. a real fixed-position box below\r\n * `BREAKPOINT_TABLET`), rather than the slots being duplicated in the template, since a shadow\r\n * root can only ever assign a given light-DOM child to the *first* slot of a given name.\r\n *\r\n * Also carries an optional built-in language switcher (`languages`/`currentLanguageCode`, fires\r\n * `language-change`) — ported from `st-app-header`'s own implementation verbatim rather than\r\n * rebuilt, since the source `Header`'s reference story shows an equivalent flag/language `Select`\r\n * as a first-class header utility. Hidden entirely when `languages` has fewer than two entries.\r\n * This component has no translation logic itself; the consuming app owns what a language `code`\r\n * means and which content it re-renders in response.\r\n */\r\n@customElement('st-header')\r\nexport class Header extends LitElement {\r\n static styles = css`\r\n ${controlBaseStyles}\r\n\r\n :host {\r\n display: flex;\r\n align-items: center;\r\n /* Spacing between brand, menu and utility controls is the rows' own margins (SankhyaUI's\r\n layout), not a flex gap. */\r\n gap: 0;\r\n /* !important on the box-model properties only: a shadow host's own :host rule always loses\r\n * to ANY rule the consuming page has for the same property on that element, regardless of\r\n * specificity (see st-footer's own padding/border-top for the same fix). Without\r\n * box-sizing: border-box pinned here, a page that also sets its own padding on this host\r\n * (e.g. a p-2 utility class) adds that padding on TOP of the fixed 64px height instead of\r\n * being absorbed inside it — which is exactly what happens in Template Hub, the one app that\r\n * doesn't load Tailwind Preflight (Preflight's own box-sizing: border-box reset is what\r\n * silently keeps every other app's header at the intended 64px). */\r\n box-sizing: border-box !important;\r\n padding: 0 2rem !important;\r\n height: 64px !important;\r\n background: var(--st-color-surface);\r\n border-bottom: 1px solid var(--st-color-border) !important;\r\n ${rootFontFamilyStyles}\r\n color: var(--st-color-text-strong);\r\n }\r\n\r\n :host([sticky]) {\r\n position: sticky;\r\n top: 0;\r\n z-index: 100;\r\n }\r\n\r\n :host([inverted]) {\r\n background: var(--st-color-text-strong);\r\n color: var(--st-color-on-solid);\r\n border-bottom-color: transparent;\r\n }\r\n\r\n :host([sticky]),\r\n :host([transparent]),\r\n :host([hide-on-scroll]) {\r\n transition:\r\n background-color 0.3s ease,\r\n color 0.3s ease,\r\n box-shadow 0.3s ease,\r\n border-color 0.3s ease,\r\n transform 0.3s ease;\r\n }\r\n\r\n /* Once the page has scrolled, a sticky/fixed header lifts off the content. */\r\n :host([scrolled][sticky]),\r\n :host([scrolled][transparent]),\r\n :host([scrolled][hide-on-scroll]) {\r\n box-shadow: 0 4px 16px var(--st-shadow-color-md);\r\n border-bottom-color: transparent !important;\r\n }\r\n\r\n /* transparent: fixed over the top of the page (e.g. a dark hero), see-through until scrolled. */\r\n :host([transparent]) {\r\n position: fixed;\r\n top: 0;\r\n left: 0;\r\n right: 0;\r\n z-index: 100;\r\n }\r\n\r\n :host([transparent]:not([scrolled]):not([mobile-open])) {\r\n background: transparent;\r\n border-bottom-color: transparent !important;\r\n color: var(--st-header-transparent-color, var(--st-color-on-solid));\r\n /* Read by st-menu-item / st-dropdown triggers in the bar (their panels reset them). */\r\n --st-nav-color: var(--st-header-transparent-color, var(--st-color-on-solid));\r\n --st-nav-hover-bg: color-mix(in oklch, currentColor 14%, transparent);\r\n }\r\n\r\n :host([transparent]:not([scrolled]):not([mobile-open])) .utility-row {\r\n border-left-color: color-mix(in oklch, currentColor 35%, transparent);\r\n }\r\n\r\n :host([transparent]:not([scrolled]):not([mobile-open])) .lang-trigger {\r\n color: inherit;\r\n }\r\n\r\n :host([transparent]:not([scrolled]):not([mobile-open])) .lang-trigger:hover,\r\n :host([transparent]:not([scrolled]):not([mobile-open])) .toggle:hover {\r\n background: color-mix(in oklch, currentColor 14%, transparent);\r\n }\r\n\r\n /* hide-on-scroll: slides away while scrolling down, back as soon as the user scrolls up. */\r\n :host([hide-on-scroll]:not([sticky]):not([transparent])) {\r\n position: sticky;\r\n top: 0;\r\n z-index: 100;\r\n }\r\n\r\n :host([scroll-hidden]:not([mobile-open])) {\r\n transform: translateY(-100%);\r\n box-shadow: none;\r\n }\r\n\r\n .brand {\r\n display: flex;\r\n align-items: center;\r\n gap: var(--st-space-10);\r\n flex: none;\r\n text-decoration: none;\r\n color: inherit;\r\n }\r\n\r\n .brand-mark {\r\n width: var(--st-size-28);\r\n height: var(--st-size-28);\r\n border-radius: var(--st-radius-sm);\r\n flex: none;\r\n object-fit: contain;\r\n }\r\n\r\n .brand-title {\r\n font-size: var(--st-text-xl);\r\n font-weight: var(--st-font-weight-bold);\r\n letter-spacing: var(--st-tracking-tight);\r\n white-space: nowrap;\r\n }\r\n\r\n /* Desktop: a transparent pass-through so .menu-row/.utility-row lay out directly as :host's\r\n own flex children. Mobile (see below) turns this into the actual overlay box. */\r\n .mobile-panel {\r\n display: contents;\r\n }\r\n\r\n /* Desktop layout from SankhyaUI's Header: the menu starts 4rem after the brand and takes the\r\n free width; the utility controls sit after a vertical divider. Between 901px and 1200px the\r\n gaps tighten so the menu still fits down to the 900px hamburger breakpoint. */\r\n .menu-row {\r\n flex: 1;\r\n display: flex;\r\n align-items: center;\r\n gap: 1.5rem;\r\n min-width: 0;\r\n margin-left: 4rem;\r\n }\r\n\r\n .utility-row {\r\n flex: none;\r\n display: flex;\r\n align-items: center;\r\n gap: 1rem;\r\n margin-left: 1.5rem;\r\n padding-left: 1.5rem;\r\n border-left: 1px solid var(--st-color-border);\r\n }\r\n\r\n :host([inverted]) .utility-row {\r\n border-left-color: currentColor;\r\n }\r\n\r\n .utility-row.empty {\r\n display: none;\r\n }\r\n\r\n @media (max-width: 1200px) {\r\n :host {\r\n padding: 0 1rem !important;\r\n }\r\n\r\n .menu-row {\r\n margin-left: 0.5rem;\r\n }\r\n\r\n .utility-row {\r\n margin-left: 0.5rem;\r\n padding-left: 0.5rem;\r\n }\r\n }\r\n\r\n @keyframes st-header-fade-in {\r\n from {\r\n opacity: 0;\r\n }\r\n to {\r\n opacity: 1;\r\n }\r\n }\r\n\r\n .toggle {\r\n display: none;\r\n align-items: center;\r\n justify-content: center;\r\n flex: none;\r\n position: relative;\r\n z-index: 1002;\r\n width: var(--st-size-32);\r\n height: var(--st-size-32);\r\n margin-left: auto;\r\n padding: 0.5rem;\r\n border: 0;\r\n border-radius: var(--st-radius-sm);\r\n background: transparent;\r\n color: inherit;\r\n cursor: pointer;\r\n }\r\n\r\n .toggle:hover {\r\n background: var(--st-color-hover);\r\n }\r\n\r\n .lang-menu {\r\n position: relative;\r\n flex: none;\r\n }\r\n\r\n .lang-trigger {\r\n ${buttonResetStyles}\r\n display: flex;\r\n align-items: center;\r\n gap: var(--st-space-4);\r\n height: var(--st-size-32);\r\n padding: 0 var(--st-space-8);\r\n border-radius: var(--st-radius-sm);\r\n font-size: var(--st-text-base);\r\n font-weight: var(--st-font-weight-semibold);\r\n color: var(--st-color-text-muted);\r\n }\r\n\r\n .lang-trigger:hover {\r\n background: var(--st-color-hover);\r\n color: var(--st-color-text-strong);\r\n }\r\n\r\n .lang-dropdown {\r\n ${dropdownPanelStyles}\r\n min-width: 140px;\r\n }\r\n\r\n .lang-option {\r\n ${buttonResetStyles}\r\n display: block;\r\n width: 100%;\r\n padding: var(--st-space-10) var(--st-space-14);\r\n font-size: var(--st-text-base);\r\n font-weight: var(--st-font-weight-semibold);\r\n text-align: left;\r\n color: inherit;\r\n }\r\n\r\n .lang-option:hover {\r\n background: var(--st-color-hover);\r\n }\r\n\r\n .lang-option.active {\r\n color: var(--st-color-accent-text);\r\n }\r\n\r\n /* Hamburger layout from SankhyaUI's Header: a full-screen layer under the toggle, menu above\r\n the utility controls, the menu's dropdowns opening in the flow (see syncInlineDropdowns). */\r\n @media (max-width: ${BREAKPOINT_TABLET}px) {\r\n :host {\r\n padding: 0 1rem !important;\r\n }\r\n\r\n .mobile-panel {\r\n display: none;\r\n }\r\n\r\n .toggle {\r\n display: inline-flex;\r\n }\r\n\r\n :host([mobile-open]) .mobile-panel {\r\n display: flex;\r\n flex-direction: column;\r\n gap: 2rem;\r\n position: fixed;\r\n inset: 0;\r\n background: var(--st-color-surface);\r\n padding: 5rem 1.5rem 2rem;\r\n overflow-y: auto;\r\n z-index: 1001;\r\n animation: st-header-fade-in 0.3s ease;\r\n }\r\n\r\n :host([mobile-open]) .menu-row,\r\n :host([mobile-open]) .utility-row {\r\n flex-direction: column;\r\n align-items: stretch;\r\n gap: 1.5rem;\r\n margin-left: 0;\r\n }\r\n\r\n :host([mobile-open]) .utility-row {\r\n padding: 1.5rem 0 0;\r\n border-left: 0;\r\n border-top: 1px solid var(--st-color-border);\r\n }\r\n\r\n /* Apps usually slot one flex-row wrapper per slot; stack its items as the source does. */\r\n :host([mobile-open]) ::slotted([slot='menu']),\r\n :host([mobile-open]) ::slotted([slot='utility']) {\r\n flex-direction: column !important;\r\n align-items: stretch !important;\r\n margin: 0 !important;\r\n }\r\n }\r\n\r\n /* The same collapsed layout, switched on by script when the menu no longer fits the bar at\r\n desktop width (see evaluateFit) — without it the menu spills over the utility controls. */\r\n :host([compact]) .mobile-panel {\r\n display: none;\r\n }\r\n\r\n :host([compact]) .toggle {\r\n display: inline-flex;\r\n }\r\n\r\n :host([compact][mobile-open]) .mobile-panel {\r\n display: flex;\r\n flex-direction: column;\r\n gap: 2rem;\r\n position: fixed;\r\n inset: 0;\r\n background: var(--st-color-surface);\r\n padding: 5rem 1.5rem 2rem;\r\n overflow-y: auto;\r\n z-index: 1001;\r\n animation: st-header-fade-in 0.3s ease;\r\n }\r\n\r\n :host([compact][mobile-open]) .menu-row,\r\n :host([compact][mobile-open]) .utility-row {\r\n flex-direction: column;\r\n align-items: stretch;\r\n gap: 1.5rem;\r\n margin-left: 0;\r\n }\r\n\r\n :host([compact][mobile-open]) .utility-row {\r\n padding: 1.5rem 0 0;\r\n border-left: 0;\r\n border-top: 1px solid var(--st-color-border);\r\n }\r\n\r\n :host([compact][mobile-open]) ::slotted([slot='menu']),\r\n :host([compact][mobile-open]) ::slotted([slot='utility']) {\r\n flex-direction: column !important;\r\n align-items: stretch !important;\r\n margin: 0 !important;\r\n }\r\n\r\n @media (prefers-reduced-motion: reduce) {\r\n :host,\r\n .mobile-panel {\r\n animation: none !important;\r\n transition: none !important;\r\n }\r\n }\r\n `;\r\n\r\n @property({ type: String })\r\n title = '';\r\n\r\n /** Optional logo image URL, shown before `title`. Omitted entirely when empty — none of this suite's apps has one today. Matches the source component's `imageSrc` (renamed here to pair with `title`/`logoHref`, since this component's only image use is the brand mark). */\r\n @property({ type: String, attribute: 'brand-image-url' })\r\n brandImageUrl = '';\r\n\r\n /** `alt` text for the brand image — matches the source component's `altText` (default `'Logo'`). */\r\n @property({ type: String, attribute: 'alt-text' })\r\n altText = 'Logo';\r\n\r\n /** Wraps the brand in an `<a>` when set (e.g. `/`); a plain non-link `<div>` otherwise. */\r\n @property({ type: String })\r\n logoHref = '';\r\n\r\n /** `target` for the brand `<a>` — matches the source component's `logoTarget` (default `_self`). No effect when `logoHref` isn't set. */\r\n @property({ type: String, attribute: 'logo-target' })\r\n logoTarget = '_self';\r\n\r\n /** Extra class(es) appended to the brand link/div — matches the source component's `logoClassName`. */\r\n @property({ type: String, attribute: 'logo-class-name' })\r\n logoClassName = '';\r\n\r\n /** Inverts the header's color roles (light-on-dark instead of dark-on-light) — matches the\r\n * source component's `inverted` (from its shared `BaseProps`). */\r\n @property({ type: Boolean, reflect: true })\r\n inverted = false;\r\n\r\n @property({ type: Boolean, reflect: true })\r\n sticky = false;\r\n\r\n /**\r\n * Fixes the header over the top of the page and makes it see-through, with light text\r\n * (`--st-header-transparent-color`), until the page scrolls past `scroll-threshold` — for a\r\n * header laid over a dark hero. It then takes its normal background and a shadow. The page\r\n * content starts under it, so the first section should leave room for the 64px bar.\r\n */\r\n @property({ type: Boolean, reflect: true })\r\n transparent = false;\r\n\r\n /** Slides the header out of view while the page scrolls down and back in when it scrolls up. */\r\n @property({ type: Boolean, reflect: true, attribute: 'hide-on-scroll' })\r\n hideOnScroll = false;\r\n\r\n /** Scroll distance in px after which the header counts as `scrolled`. */\r\n @property({ type: Number, attribute: 'scroll-threshold' })\r\n scrollThreshold = 8;\r\n\r\n /** True once the page has scrolled past `scroll-threshold`; reflected so pages can style `st-header[scrolled]`. Set by the header, never from outside. */\r\n @property({ type: Boolean, reflect: true })\r\n scrolled = false;\r\n\r\n /** True while `hide-on-scroll` has slid the header away. Set by the header, never from outside. */\r\n @property({ type: Boolean, reflect: true, attribute: 'scroll-hidden' })\r\n scrollHidden = false;\r\n\r\n private lastScrollY = 0;\r\n private scrollFrame = 0;\r\n\r\n private onScroll = (): void => {\r\n if (this.scrollFrame) return;\r\n this.scrollFrame = requestAnimationFrame(() => {\r\n this.scrollFrame = 0;\r\n const y = Math.max(0, window.scrollY);\r\n this.scrolled = y > this.scrollThreshold;\r\n if (this.hideOnScroll) {\r\n const delta = y - this.lastScrollY;\r\n // Ignore tiny jitters; never hide while still at the top of the page or with the menu open.\r\n if (Math.abs(delta) > 4) this.scrollHidden = delta > 0 && y > this.offsetHeight && !this.mobileOpen;\r\n } else {\r\n this.scrollHidden = false;\r\n }\r\n this.lastScrollY = y;\r\n });\r\n };\r\n\r\n /**\r\n * Language switcher options, e.g. `[{ code: 'en', label: 'English' }, ...]` — hidden entirely\r\n * when empty or when only one language is offered (same convention as `st-app-header`'s\r\n * `languages`, which this reuses verbatim). This component has no translation logic itself —\r\n * selecting an option fires `language-change` (detail: `{ code }`), and the consuming app owns\r\n * what `code` actually means and which content it re-renders.\r\n */\r\n @property({ attribute: false })\r\n languages: { code: string; label: string }[] = [];\r\n\r\n @property({ type: String, attribute: 'current-language-code' })\r\n currentLanguageCode = '';\r\n\r\n @property({ type: String, attribute: 'language-menu-label' })\r\n languageMenuLabel = 'Language';\r\n\r\n /** Internal open/closed state for the mobile nav overlay, reflected to an attribute purely so\r\n * the `:host([mobile-open])` CSS selector above can react to it — not meant to be set from\r\n * outside. */\r\n @property({ type: Boolean, reflect: true, attribute: 'mobile-open' })\r\n mobileOpen = false;\r\n\r\n /** True while the menu doesn't fit the bar at the current width: the header then shows the same\r\n * hamburger layout it uses on a narrow screen. Set by `evaluateFit`, never from outside. */\r\n @property({ type: Boolean, reflect: true })\r\n compact = false;\r\n\r\n @state() private languageMenuOpen = false;\r\n\r\n /** Whether `slot=\"utility\"` has content — an empty utility row (and its divider) is hidden. */\r\n @state() private hasUtility = false;\r\n\r\n private resizeObserver?: ResizeObserver;\r\n\r\n /** Matches below BREAKPOINT_TABLET, where the hamburger layout is always on. */\r\n private narrowQuery?: MediaQueryList;\r\n\r\n /** The dropdowns this header switched to `inline` — only these are switched back, so a caller's\r\n * own `inline` setting is left alone. */\r\n private inlinedDropdowns = new Set<HTMLElement & { inline: boolean }>();\r\n\r\n /** While the hamburger layout is on, the dropdowns in the header open in the flow of the\r\n * full-screen panel instead of floating over it (SankhyaUI's touch-device layout). */\r\n private syncInlineDropdowns = (): void => {\r\n const collapsed = this.compact || !!this.narrowQuery?.matches;\r\n const dropdowns = Array.from(this.querySelectorAll('st-dropdown')) as (HTMLElement & { inline: boolean })[];\r\n if (collapsed) {\r\n for (const dropdown of dropdowns) {\r\n if (dropdown.inline) continue;\r\n dropdown.inline = true;\r\n this.inlinedDropdowns.add(dropdown);\r\n }\r\n } else {\r\n for (const dropdown of this.inlinedDropdowns) dropdown.inline = false;\r\n this.inlinedDropdowns.clear();\r\n }\r\n };\r\n\r\n private onUtilitySlotChange = (event: Event): void => {\r\n this.hasUtility = (event.target as HTMLSlotElement).assignedElements({ flatten: true }).length > 0;\r\n this.evaluateFit();\r\n };\r\n\r\n private onMenuSlotChange = (): void => {\r\n this.evaluateFit();\r\n this.syncInlineDropdowns();\r\n };\r\n\r\n protected updated(changed: Map<string, unknown>): void {\r\n if (changed.has('compact')) this.syncInlineDropdowns();\r\n }\r\n\r\n /** The bar width at which the menu last overflowed — the bar only expands again once it is\r\n * clearly wider than this, so it can't flip back and forth right at the threshold. */\r\n private collapsedAtWidth = 0;\r\n\r\n /** Collapses to the hamburger layout when the slotted menu overflows its row, and expands again\r\n * once there is room. The collapsed layout hides the menu, so the room it needs can't be\r\n * measured then; the width remembered when it collapsed stands in for it. */\r\n private evaluateFit = (): void => {\r\n const width = this.clientWidth;\r\n if (this.compact) {\r\n if (width > this.collapsedAtWidth + 24) {\r\n this.compact = false;\r\n this.mobileOpen = false;\r\n void this.updateComplete.then(this.evaluateFit);\r\n }\r\n return;\r\n }\r\n if (this.menuOverflows()) {\r\n this.collapsedAtWidth = width;\r\n this.compact = true;\r\n }\r\n };\r\n\r\n /** Whether the visible menu items reach past the right edge of the menu row. Measured from the\r\n * items' own boxes (the slotted elements and their direct children), not the row's scrollWidth:\r\n * a closed dropdown's absolutely positioned panel also counts towards scrollWidth, which would\r\n * report overflow at any width. */\r\n private menuOverflows(): boolean {\r\n const row = this.renderRoot.querySelector<HTMLElement>('.menu-row');\r\n const slot = this.renderRoot.querySelector<HTMLSlotElement>('slot[name=\"menu\"]');\r\n if (!row || !slot) return false;\r\n const limit = row.getBoundingClientRect().right + 1;\r\n let right = 0;\r\n for (const el of slot.assignedElements({ flatten: true })) {\r\n for (const box of [el, ...Array.from(el.children)]) {\r\n const rect = box.getBoundingClientRect();\r\n if (rect.width > 0) right = Math.max(right, rect.right);\r\n }\r\n }\r\n return right > limit;\r\n }\r\n\r\n protected firstUpdated(): void {\r\n this.resizeObserver = new ResizeObserver(this.evaluateFit);\r\n this.resizeObserver.observe(this);\r\n this.evaluateFit();\r\n this.syncInlineDropdowns();\r\n }\r\n\r\n connectedCallback(): void {\r\n super.connectedCallback();\r\n window.addEventListener('keydown', this.closeOnEscape);\r\n window.addEventListener('click', this.closeLanguageMenuOnOutsideClick);\r\n window.addEventListener('scroll', this.onScroll, { passive: true });\r\n this.onScroll();\r\n this.narrowQuery = window.matchMedia(`(max-width: ${BREAKPOINT_TABLET}px)`);\r\n this.narrowQuery.addEventListener('change', this.syncInlineDropdowns);\r\n // Menus often render their dropdowns after the header (e.g. once the user's role loads),\r\n // deeper than a slotchange reports.\r\n this.lightDomObserver = new MutationObserver(this.syncInlineDropdowns);\r\n this.lightDomObserver.observe(this, { childList: true, subtree: true });\r\n }\r\n\r\n private lightDomObserver?: MutationObserver;\r\n\r\n disconnectedCallback(): void {\r\n this.lightDomObserver?.disconnect();\r\n this.narrowQuery?.removeEventListener('change', this.syncInlineDropdowns);\r\n this.resizeObserver?.disconnect();\r\n window.removeEventListener('keydown', this.closeOnEscape);\r\n window.removeEventListener('click', this.closeLanguageMenuOnOutsideClick);\r\n window.removeEventListener('scroll', this.onScroll);\r\n if (this.scrollFrame) cancelAnimationFrame(this.scrollFrame);\r\n this.scrollFrame = 0;\r\n super.disconnectedCallback();\r\n }\r\n\r\n private closeOnEscape = (event: KeyboardEvent): void => {\r\n if (this.mobileOpen && event.key === 'Escape') this.mobileOpen = false;\r\n if (this.languageMenuOpen && event.key === 'Escape') this.languageMenuOpen = false;\r\n };\r\n\r\n private closeLanguageMenuOnOutsideClick = (event: Event): void => {\r\n if (this.languageMenuOpen && !event.composedPath().includes(this)) this.languageMenuOpen = false;\r\n };\r\n\r\n private renderLanguageMenu() {\r\n if (this.languages.length < 2) return nothing;\r\n const current = this.languages.find((lang) => lang.code === this.currentLanguageCode);\r\n return html`\r\n <div class=\"lang-menu\" part=\"language-menu\">\r\n <button\r\n part=\"language-menu-trigger\"\r\n type=\"button\"\r\n class=\"lang-trigger\"\r\n aria-label=${this.languageMenuLabel}\r\n aria-expanded=${this.languageMenuOpen}\r\n @click=${(event: Event) => {\r\n event.stopPropagation();\r\n this.languageMenuOpen = !this.languageMenuOpen;\r\n }}\r\n >\r\n <span part=\"language-menu-current\">${current?.label ?? this.languageMenuLabel}</span>\r\n ${chevronIcon('down', 10)}\r\n </button>\r\n ${this.languageMenuOpen\r\n ? html`\r\n <div part=\"language-dropdown\" class=\"lang-dropdown\">\r\n ${this.languages.map(\r\n (lang) => html`\r\n <button\r\n part=\"language-option\"\r\n type=\"button\"\r\n class=\"lang-option ${lang.code === this.currentLanguageCode ? 'active' : ''}\"\r\n aria-current=${lang.code === this.currentLanguageCode}\r\n @click=${() => this.onLanguageSelect(lang.code)}\r\n >\r\n ${lang.label}\r\n </button>\r\n `\r\n )}\r\n </div>\r\n `\r\n : nothing}\r\n </div>\r\n `;\r\n }\r\n\r\n private onLanguageSelect(code: string): void {\r\n this.languageMenuOpen = false;\r\n if (code === this.currentLanguageCode) return;\r\n this.dispatchEvent(new CustomEvent<LanguageChangedEventDetail>('language-change', { detail: { code }, bubbles: true, composed: true }));\r\n }\r\n\r\n private renderBrand() {\r\n const content = html`\r\n ${this.brandImageUrl ? html`<img part=\"brand-mark\" class=\"brand-mark\" src=${this.brandImageUrl} alt=${this.altText} /> ` : nothing}\r\n <span part=\"brand-title\" class=\"brand-title\">${this.title}</span>\r\n `;\r\n return this.logoHref\r\n ? html`<a part=\"brand\" class=\"brand ${this.logoClassName}\" href=${this.logoHref} target=${this.logoTarget}>${content}</a>`\r\n : html`<div part=\"brand\" class=\"brand ${this.logoClassName}\">${content}</div>`;\r\n }\r\n\r\n render() {\r\n return html`\r\n ${this.renderBrand()}\r\n\r\n <div class=\"mobile-panel\" part=\"mobile-panel\" @click=${() => (this.mobileOpen = false)}>\r\n <div class=\"menu-row\" part=\"menu-row\"><slot name=\"menu\" @slotchange=${this.onMenuSlotChange}></slot></div>\r\n <div class=\"utility-row ${this.hasUtility || this.languages.length >= 2 ? '' : 'empty'}\" part=\"utility-row\">\r\n ${this.renderLanguageMenu()}<slot name=\"utility\" @slotchange=${this.onUtilitySlotChange}></slot>\r\n </div>\r\n </div>\r\n\r\n <button\r\n part=\"toggle\"\r\n type=\"button\"\r\n class=\"toggle\"\r\n aria-label=${this.mobileOpen ? 'Close menu' : 'Open menu'}\r\n aria-expanded=${this.mobileOpen}\r\n @click=${() => (this.mobileOpen = !this.mobileOpen)}\r\n >\r\n ${this.mobileOpen ? closeIcon() : menuIcon()}\r\n </button>\r\n `;\r\n }\r\n}\r\n"],"mappings":";;;;;;;;;;;;;;;;;AAAA,SAAS,YAAY,KAAK,MAAM,eAAe;AAC/C,SAAS,eAAe,UAAU,aAAa;AAmCxC,IAAM,SAAN,cAAqB,WAAW;AAAA,EAAhC;AAAA;AAuWL,iBAAQ;AAIR,yBAAgB;AAIhB,mBAAU;AAIV,oBAAW;AAIX,sBAAa;AAIb,yBAAgB;AAKhB,oBAAW;AAGX,kBAAS;AAST,uBAAc;AAId,wBAAe;AAIf,2BAAkB;AAIlB,oBAAW;AAIX,wBAAe;AAEf,SAAQ,cAAc;AACtB,SAAQ,cAAc;AAEtB,SAAQ,WAAW,MAAY;AAC7B,UAAI,KAAK,YAAa;AACtB,WAAK,cAAc,sBAAsB,MAAM;AAC7C,aAAK,cAAc;AACnB,cAAM,IAAI,KAAK,IAAI,GAAG,OAAO,OAAO;AACpC,aAAK,WAAW,IAAI,KAAK;AACzB,YAAI,KAAK,cAAc;AACrB,gBAAM,QAAQ,IAAI,KAAK;AAEvB,cAAI,KAAK,IAAI,KAAK,IAAI,EAAG,MAAK,eAAe,QAAQ,KAAK,IAAI,KAAK,gBAAgB,CAAC,KAAK;AAAA,QAC3F,OAAO;AACL,eAAK,eAAe;AAAA,QACtB;AACA,aAAK,cAAc;AAAA,MACrB,CAAC;AAAA,IACH;AAUA,qBAA+C,CAAC;AAGhD,+BAAsB;AAGtB,6BAAoB;AAMpB,sBAAa;AAKb,mBAAU;AAED,SAAQ,mBAAmB;AAG3B,SAAQ,aAAa;AAS9B;AAAA;AAAA,SAAQ,mBAAmB,oBAAI,IAAuC;AAItE;AAAA;AAAA,SAAQ,sBAAsB,MAAY;AACxC,YAAM,YAAY,KAAK,WAAW,CAAC,CAAC,KAAK,aAAa;AACtD,YAAM,YAAY,MAAM,KAAK,KAAK,iBAAiB,aAAa,CAAC;AACjE,UAAI,WAAW;AACb,mBAAW,YAAY,WAAW;AAChC,cAAI,SAAS,OAAQ;AACrB,mBAAS,SAAS;AAClB,eAAK,iBAAiB,IAAI,QAAQ;AAAA,QACpC;AAAA,MACF,OAAO;AACL,mBAAW,YAAY,KAAK,iBAAkB,UAAS,SAAS;AAChE,aAAK,iBAAiB,MAAM;AAAA,MAC9B;AAAA,IACF;AAEA,SAAQ,sBAAsB,CAAC,UAAuB;AACpD,WAAK,aAAc,MAAM,OAA2B,iBAAiB,EAAE,SAAS,KAAK,CAAC,EAAE,SAAS;AACjG,WAAK,YAAY;AAAA,IACnB;AAEA,SAAQ,mBAAmB,MAAY;AACrC,WAAK,YAAY;AACjB,WAAK,oBAAoB;AAAA,IAC3B;AAQA;AAAA;AAAA,SAAQ,mBAAmB;AAK3B;AAAA;AAAA;AAAA,SAAQ,cAAc,MAAY;AAChC,YAAM,QAAQ,KAAK;AACnB,UAAI,KAAK,SAAS;AAChB,YAAI,QAAQ,KAAK,mBAAmB,IAAI;AACtC,eAAK,UAAU;AACf,eAAK,aAAa;AAClB,eAAK,KAAK,eAAe,KAAK,KAAK,WAAW;AAAA,QAChD;AACA;AAAA,MACF;AACA,UAAI,KAAK,cAAc,GAAG;AACxB,aAAK,mBAAmB;AACxB,aAAK,UAAU;AAAA,MACjB;AAAA,IACF;AAwDA,SAAQ,gBAAgB,CAAC,UAA+B;AACtD,UAAI,KAAK,cAAc,MAAM,QAAQ,SAAU,MAAK,aAAa;AACjE,UAAI,KAAK,oBAAoB,MAAM,QAAQ,SAAU,MAAK,mBAAmB;AAAA,IAC/E;AAEA,SAAQ,kCAAkC,CAAC,UAAuB;AAChE,UAAI,KAAK,oBAAoB,CAAC,MAAM,aAAa,EAAE,SAAS,IAAI,EAAG,MAAK,mBAAmB;AAAA,IAC7F;AAAA;AAAA,EAxFU,QAAQ,SAAqC;AACrD,QAAI,QAAQ,IAAI,SAAS,EAAG,MAAK,oBAAoB;AAAA,EACvD;AAAA;AAAA;AAAA;AAAA;AAAA,EA6BQ,gBAAyB;AAC/B,UAAM,MAAM,KAAK,WAAW,cAA2B,WAAW;AAClE,UAAM,OAAO,KAAK,WAAW,cAA+B,mBAAmB;AAC/E,QAAI,CAAC,OAAO,CAAC,KAAM,QAAO;AAC1B,UAAM,QAAQ,IAAI,sBAAsB,EAAE,QAAQ;AAClD,QAAI,QAAQ;AACZ,eAAW,MAAM,KAAK,iBAAiB,EAAE,SAAS,KAAK,CAAC,GAAG;AACzD,iBAAW,OAAO,CAAC,IAAI,GAAG,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG;AAClD,cAAM,OAAO,IAAI,sBAAsB;AACvC,YAAI,KAAK,QAAQ,EAAG,SAAQ,KAAK,IAAI,OAAO,KAAK,KAAK;AAAA,MACxD;AAAA,IACF;AACA,WAAO,QAAQ;AAAA,EACjB;AAAA,EAEU,eAAqB;AAC7B,SAAK,iBAAiB,IAAI,eAAe,KAAK,WAAW;AACzD,SAAK,eAAe,QAAQ,IAAI;AAChC,SAAK,YAAY;AACjB,SAAK,oBAAoB;AAAA,EAC3B;AAAA,EAEA,oBAA0B;AACxB,UAAM,kBAAkB;AACxB,WAAO,iBAAiB,WAAW,KAAK,aAAa;AACrD,WAAO,iBAAiB,SAAS,KAAK,+BAA+B;AACrE,WAAO,iBAAiB,UAAU,KAAK,UAAU,EAAE,SAAS,KAAK,CAAC;AAClE,SAAK,SAAS;AACd,SAAK,cAAc,OAAO,WAAW,eAAe,iBAAiB,KAAK;AAC1E,SAAK,YAAY,iBAAiB,UAAU,KAAK,mBAAmB;AAGpE,SAAK,mBAAmB,IAAI,iBAAiB,KAAK,mBAAmB;AACrE,SAAK,iBAAiB,QAAQ,MAAM,EAAE,WAAW,MAAM,SAAS,KAAK,CAAC;AAAA,EACxE;AAAA,EAIA,uBAA6B;AAC3B,SAAK,kBAAkB,WAAW;AAClC,SAAK,aAAa,oBAAoB,UAAU,KAAK,mBAAmB;AACxE,SAAK,gBAAgB,WAAW;AAChC,WAAO,oBAAoB,WAAW,KAAK,aAAa;AACxD,WAAO,oBAAoB,SAAS,KAAK,+BAA+B;AACxE,WAAO,oBAAoB,UAAU,KAAK,QAAQ;AAClD,QAAI,KAAK,YAAa,sBAAqB,KAAK,WAAW;AAC3D,SAAK,cAAc;AACnB,UAAM,qBAAqB;AAAA,EAC7B;AAAA,EAWQ,qBAAqB;AAC3B,QAAI,KAAK,UAAU,SAAS,EAAG,QAAO;AACtC,UAAM,UAAU,KAAK,UAAU,KAAK,CAAC,SAAS,KAAK,SAAS,KAAK,mBAAmB;AACpF,WAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,uBAMY,KAAK,iBAAiB;AAAA,0BACnB,KAAK,gBAAgB;AAAA,mBAC5B,CAAC,UAAiB;AACzB,YAAM,gBAAgB;AACtB,WAAK,mBAAmB,CAAC,KAAK;AAAA,IAChC,CAAC;AAAA;AAAA,+CAEoC,SAAS,SAAS,KAAK,iBAAiB;AAAA,YAC3E,YAAY,QAAQ,EAAE,CAAC;AAAA;AAAA,UAEzB,KAAK,mBACH;AAAA;AAAA,kBAEM,KAAK,UAAU;AAAA,MACf,CAAC,SAAS;AAAA;AAAA;AAAA;AAAA,2CAIe,KAAK,SAAS,KAAK,sBAAsB,WAAW,EAAE;AAAA,qCAC5D,KAAK,SAAS,KAAK,mBAAmB;AAAA,+BAC5C,MAAM,KAAK,iBAAiB,KAAK,IAAI,CAAC;AAAA;AAAA,wBAE7C,KAAK,KAAK;AAAA;AAAA;AAAA,IAGlB,CAAC;AAAA;AAAA,gBAGL,OAAO;AAAA;AAAA;AAAA,EAGjB;AAAA,EAEQ,iBAAiB,MAAoB;AAC3C,SAAK,mBAAmB;AACxB,QAAI,SAAS,KAAK,oBAAqB;AACvC,SAAK,cAAc,IAAI,YAAwC,mBAAmB,EAAE,QAAQ,EAAE,KAAK,GAAG,SAAS,MAAM,UAAU,KAAK,CAAC,CAAC;AAAA,EACxI;AAAA,EAEQ,cAAc;AACpB,UAAM,UAAU;AAAA,QACZ,KAAK,gBAAgB,qDAAqD,KAAK,aAAa,QAAQ,KAAK,OAAO,SAAS,OAAO;AAAA,qDACnF,KAAK,KAAK;AAAA;AAE3D,WAAO,KAAK,WACR,oCAAoC,KAAK,aAAa,UAAU,KAAK,QAAQ,WAAW,KAAK,UAAU,IAAI,OAAO,SAClH,sCAAsC,KAAK,aAAa,KAAK,OAAO;AAAA,EAC1E;AAAA,EAEA,SAAS;AACP,WAAO;AAAA,QACH,KAAK,YAAY,CAAC;AAAA;AAAA,6DAEmC,MAAO,KAAK,aAAa,KAAM;AAAA,8EACd,KAAK,gBAAgB;AAAA,kCACjE,KAAK,cAAc,KAAK,UAAU,UAAU,IAAI,KAAK,OAAO;AAAA,YAClF,KAAK,mBAAmB,CAAC,oCAAoC,KAAK,mBAAmB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,qBAQ5E,KAAK,aAAa,eAAe,WAAW;AAAA,wBACzC,KAAK,UAAU;AAAA,iBACtB,MAAO,KAAK,aAAa,CAAC,KAAK,UAAW;AAAA;AAAA,UAEjD,KAAK,aAAa,UAAU,IAAI,SAAS,CAAC;AAAA;AAAA;AAAA,EAGlD;AACF;AAjqBa,OACJ,SAAS;AAAA,MACZ,iBAAiB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAqBf,oBAAoB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QA8LpB,iBAAiB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAkBjB,mBAAmB;AAAA;AAAA;AAAA;AAAA;AAAA,QAKnB,iBAAiB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,yBAoBA,iBAAiB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAuGxC;AAAA,EADC,SAAS,EAAE,MAAM,OAAO,CAAC;AAAA,GAtWf,OAuWX;AAIA;AAAA,EADC,SAAS,EAAE,MAAM,QAAQ,WAAW,kBAAkB,CAAC;AAAA,GA1W7C,OA2WX;AAIA;AAAA,EADC,SAAS,EAAE,MAAM,QAAQ,WAAW,WAAW,CAAC;AAAA,GA9WtC,OA+WX;AAIA;AAAA,EADC,SAAS,EAAE,MAAM,OAAO,CAAC;AAAA,GAlXf,OAmXX;AAIA;AAAA,EADC,SAAS,EAAE,MAAM,QAAQ,WAAW,cAAc,CAAC;AAAA,GAtXzC,OAuXX;AAIA;AAAA,EADC,SAAS,EAAE,MAAM,QAAQ,WAAW,kBAAkB,CAAC;AAAA,GA1X7C,OA2XX;AAKA;AAAA,EADC,SAAS,EAAE,MAAM,SAAS,SAAS,KAAK,CAAC;AAAA,GA/X/B,OAgYX;AAGA;AAAA,EADC,SAAS,EAAE,MAAM,SAAS,SAAS,KAAK,CAAC;AAAA,GAlY/B,OAmYX;AASA;AAAA,EADC,SAAS,EAAE,MAAM,SAAS,SAAS,KAAK,CAAC;AAAA,GA3Y/B,OA4YX;AAIA;AAAA,EADC,SAAS,EAAE,MAAM,SAAS,SAAS,MAAM,WAAW,iBAAiB,CAAC;AAAA,GA/Y5D,OAgZX;AAIA;AAAA,EADC,SAAS,EAAE,MAAM,QAAQ,WAAW,mBAAmB,CAAC;AAAA,GAnZ9C,OAoZX;AAIA;AAAA,EADC,SAAS,EAAE,MAAM,SAAS,SAAS,KAAK,CAAC;AAAA,GAvZ/B,OAwZX;AAIA;AAAA,EADC,SAAS,EAAE,MAAM,SAAS,SAAS,MAAM,WAAW,gBAAgB,CAAC;AAAA,GA3Z3D,OA4ZX;AA8BA;AAAA,EADC,SAAS,EAAE,WAAW,MAAM,CAAC;AAAA,GAzbnB,OA0bX;AAGA;AAAA,EADC,SAAS,EAAE,MAAM,QAAQ,WAAW,wBAAwB,CAAC;AAAA,GA5bnD,OA6bX;AAGA;AAAA,EADC,SAAS,EAAE,MAAM,QAAQ,WAAW,sBAAsB,CAAC;AAAA,GA/bjD,OAgcX;AAMA;AAAA,EADC,SAAS,EAAE,MAAM,SAAS,SAAS,MAAM,WAAW,cAAc,CAAC;AAAA,GArczD,OAscX;AAKA;AAAA,EADC,SAAS,EAAE,MAAM,SAAS,SAAS,KAAK,CAAC;AAAA,GA1c/B,OA2cX;AAEiB;AAAA,EAAhB,MAAM;AAAA,GA7cI,OA6cM;AAGA;AAAA,EAAhB,MAAM;AAAA,GAhdI,OAgdM;AAhdN,SAAN;AAAA,EADN,cAAc,WAAW;AAAA,GACb;","names":[]}
|
|
@@ -90,7 +90,8 @@ MenuItem.styles = css`
|
|
|
90
90
|
padding: 12px 16px;
|
|
91
91
|
font-size: 0.875rem;
|
|
92
92
|
text-decoration: none;
|
|
93
|
-
|
|
93
|
+
/* --st-nav-*: set by a host bar (st-header's transparent state) to recolour its nav items. */
|
|
94
|
+
color: var(--st-nav-color, var(--st-color-text));
|
|
94
95
|
border-radius: 6px;
|
|
95
96
|
/* The source's 'all 0.15s', minus visibility: animating the visibility this link inherits
|
|
96
97
|
from a dropdown panel kept it unfocusable for a frame after the panel opened. */
|
|
@@ -105,8 +106,8 @@ MenuItem.styles = css`
|
|
|
105
106
|
Keyboard focus gets the same treatment as hover. */
|
|
106
107
|
.menu-item:hover,
|
|
107
108
|
.menu-item:focus-visible {
|
|
108
|
-
background: var(--st-color-hover);
|
|
109
|
-
color: var(--st-color-text-strong);
|
|
109
|
+
background: var(--st-nav-hover-bg, var(--st-color-hover));
|
|
110
|
+
color: var(--st-nav-color, var(--st-color-text-strong));
|
|
110
111
|
padding-left: 20px;
|
|
111
112
|
transition-duration: 0.1s;
|
|
112
113
|
}
|
|
@@ -270,4 +271,4 @@ MenuItem = __decorateClass([
|
|
|
270
271
|
export {
|
|
271
272
|
MenuItem
|
|
272
273
|
};
|
|
273
|
-
//# sourceMappingURL=chunk-
|
|
274
|
+
//# sourceMappingURL=chunk-TH5NZXA7.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/menu-item/MenuItem.ts"],"sourcesContent":["import { LitElement, css, html, nothing } from 'lit';\r\nimport { customElement, property, state } from 'lit/decorators.js';\r\n\r\nimport { controlBaseStyles, fontFamilyStyles } from '../shared/styles';\r\nimport type { MenuItemSelectEventDetail } from './MenuItemEvents';\r\n\r\n/**\r\n * A single flat nav row — icon (`slot=\"icon\"`) + title + badge on one line, an optional\r\n * description below — as opposed to `st-menu-grid-item`'s card for a multi-column grid. A 1:1\r\n * port of `SankhyaUI`'s React `MenuItem` (`SankhyaCMS/SankhyaUI/src/components/Menu`): same\r\n * markup and CSS, with its `motion` effects redone in CSS — nudges right and scales to 1.01 on\r\n * hover / 0.99 while pressed, the icon tilts on hover, the badge pops in, the description fades\r\n * in. Colors come from the suite theme rather than per-item utility classes.\r\n *\r\n * Renders a real `<a href>` but fires a *cancelable* `menu-item-select` event (detail: `{ href }`)\r\n * before navigating — a host doing client-side routing calls `event.preventDefault()` on it (the\r\n * source used react-router's `Link` here; the suite apps have no router).\r\n */\r\n@customElement('st-menu-item')\r\nexport class MenuItem extends LitElement {\r\n /** `focus()` on the element focuses its link, so `st-dropdown` can arrow-key between items. */\r\n static shadowRootOptions = { ...LitElement.shadowRootOptions, delegatesFocus: true };\r\n\r\n static styles = css`\r\n ${controlBaseStyles}\r\n\r\n :host {\r\n display: block;\r\n ${fontFamilyStyles}\r\n }\r\n\r\n @keyframes st-menu-item-badge-pop {\r\n from {\r\n transform: scale(0);\r\n }\r\n 70% {\r\n transform: scale(1.12);\r\n }\r\n to {\r\n transform: scale(1);\r\n }\r\n }\r\n\r\n @keyframes st-menu-item-fade-in {\r\n from {\r\n opacity: 0;\r\n }\r\n to {\r\n opacity: 1;\r\n }\r\n }\r\n\r\n .menu-item {\r\n display: flex;\r\n align-items: flex-start;\r\n gap: 12px;\r\n padding: 12px 16px;\r\n font-size: 0.875rem;\r\n text-decoration: none;\r\n /* --st-nav-*: set by a host bar (st-header's transparent state) to recolour its nav items. */\r\n color: var(--st-nav-color, var(--st-color-text));\r\n border-radius: 6px;\r\n /* The source's 'all 0.15s', minus visibility: animating the visibility this link inherits\r\n from a dropdown panel kept it unfocusable for a frame after the panel opened. */\r\n transition-property: background-color, color, padding, transform, border-color;\r\n transition-duration: 0.15s;\r\n transition-timing-function: ease;\r\n box-sizing: border-box;\r\n }\r\n\r\n /* The source's hover nudge (x: 4px) is done with padding, not a transform: a transform would\r\n spill past a scrolling panel's edge (st-source-bar's menus) and show a horizontal scrollbar.\r\n Keyboard focus gets the same treatment as hover. */\r\n .menu-item:hover,\r\n .menu-item:focus-visible {\r\n background: var(--st-nav-hover-bg, var(--st-color-hover));\r\n color: var(--st-nav-color, var(--st-color-text-strong));\r\n padding-left: 20px;\r\n transition-duration: 0.1s;\r\n }\r\n\r\n .menu-item.compact:hover,\r\n .menu-item.compact:focus-visible {\r\n padding-left: 16px;\r\n }\r\n\r\n .menu-item:active {\r\n transform: scale(0.99);\r\n transition-duration: 0.1s;\r\n }\r\n\r\n .menu-item.active {\r\n background-color: var(--st-color-accent-subtle);\r\n color: var(--st-color-accent-text);\r\n border-left: 4px solid var(--st-color-accent);\r\n }\r\n\r\n .menu-item-icon {\r\n display: flex;\r\n flex-shrink: 0;\r\n margin-top: 2px;\r\n transition: transform 0.2s ease;\r\n }\r\n\r\n .menu-item-icon:hover {\r\n transform: rotate(5deg);\r\n }\r\n\r\n .menu-item-icon[hidden] {\r\n display: none;\r\n }\r\n\r\n .menu-item-icon ::slotted(*) {\r\n width: 16px;\r\n height: 16px;\r\n }\r\n\r\n .menu-item-content {\r\n flex: 1;\r\n min-width: 0;\r\n }\r\n\r\n .menu-item-header {\r\n display: flex;\r\n align-items: center;\r\n justify-content: space-between;\r\n gap: 8px;\r\n }\r\n\r\n .menu-item-title {\r\n font-weight: 500;\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n white-space: nowrap;\r\n }\r\n\r\n .menu-item-badge {\r\n display: inline-flex;\r\n align-items: center;\r\n justify-content: center;\r\n min-width: 20px;\r\n height: 20px;\r\n padding: 0 6px;\r\n font-size: 0.75rem;\r\n font-weight: 500;\r\n border-radius: 9999px;\r\n flex-shrink: 0;\r\n background: var(--st-color-accent-subtle);\r\n color: var(--st-color-accent-text);\r\n animation: st-menu-item-badge-pop 0.35s ease-out;\r\n }\r\n\r\n .menu-item-description {\r\n display: -webkit-box;\r\n margin: 4px 0 0;\r\n font-size: 0.75rem;\r\n color: var(--st-color-text-muted);\r\n -webkit-box-orient: vertical;\r\n overflow: hidden;\r\n line-height: 1.4;\r\n animation: st-menu-item-fade-in 0.2s ease;\r\n }\r\n\r\n .menu-item.compact {\r\n padding: 8px 12px;\r\n gap: 8px;\r\n }\r\n\r\n .menu-item.compact .menu-item-icon ::slotted(*) {\r\n width: 14px;\r\n height: 14px;\r\n }\r\n\r\n .menu-item.compact .menu-item-title {\r\n font-size: 0.75rem;\r\n }\r\n\r\n .menu-item.compact .menu-item-description {\r\n font-size: 0.75rem;\r\n }\r\n\r\n .menu-item.no-description {\r\n align-items: center;\r\n }\r\n\r\n .menu-item.no-description .menu-item-icon {\r\n margin-top: 0;\r\n }\r\n\r\n @media (prefers-reduced-motion: reduce) {\r\n .menu-item,\r\n .menu-item-icon {\r\n transition: none;\r\n }\r\n\r\n .menu-item:active,\r\n .menu-item-icon:hover {\r\n transform: none;\r\n }\r\n\r\n .menu-item-badge,\r\n .menu-item-description {\r\n animation: none;\r\n }\r\n }\r\n `;\r\n\r\n @property({ type: String })\r\n title = '';\r\n\r\n @property({ type: String })\r\n href = '';\r\n\r\n @property({ type: String })\r\n description = '';\r\n\r\n /** Small trailing label, e.g. a count or \"New\"/\"Beta\" — omit for none. */\r\n @property({ type: String })\r\n badge = '';\r\n\r\n /** Highlights this row as the current page (source: `isActive`). */\r\n @property({ type: Boolean, reflect: true })\r\n active = false;\r\n\r\n @property({ type: Boolean, reflect: true })\r\n compact = false;\r\n\r\n /** Suppresses the description even when `description` is set (source: `showDescription`, default `true`). */\r\n @property({ type: Boolean, attribute: 'show-description' })\r\n showDescription = true;\r\n\r\n /** `target` for the `<a>` (source default `_self`). */\r\n @property({ type: String })\r\n target = '_self';\r\n\r\n /** Whether `slot=\"icon\"` has content, so an omitted icon leaves no empty box. */\r\n @state() private hasIcon = false;\r\n\r\n private onIconSlotChange = (event: Event): void => {\r\n this.hasIcon = (event.target as HTMLSlotElement).assignedNodes({ flatten: true }).length > 0;\r\n };\r\n\r\n private onClick = (event: MouseEvent): void => {\r\n const selectEvent = new CustomEvent<MenuItemSelectEventDetail>('menu-item-select', {\r\n detail: { href: this.href },\r\n bubbles: true,\r\n composed: true,\r\n cancelable: true\r\n });\r\n this.dispatchEvent(selectEvent);\r\n if (selectEvent.defaultPrevented) event.preventDefault();\r\n };\r\n\r\n render() {\r\n const showDescription = !!this.description && this.showDescription;\r\n const classes = ['menu-item', this.active ? 'active' : '', this.compact ? 'compact' : '', showDescription ? '' : 'no-description']\r\n .filter(Boolean)\r\n .join(' ');\r\n return html`\r\n <a part=\"item\" class=${classes} href=${this.href} target=${this.target} @click=${this.onClick}>\r\n <span class=\"menu-item-icon\" part=\"icon\" ?hidden=${!this.hasIcon}><slot name=\"icon\" @slotchange=${this.onIconSlotChange}></slot></span>\r\n <span class=\"menu-item-content\" part=\"content\">\r\n <span class=\"menu-item-header\" part=\"header\">\r\n <span part=\"title\" class=\"menu-item-title\">${this.title}</span>\r\n ${this.badge ? html`<span part=\"badge\" class=\"menu-item-badge\">${this.badge}</span>` : nothing}\r\n </span>\r\n ${showDescription ? html`<p part=\"description\" class=\"menu-item-description\">${this.description}</p>` : nothing}\r\n </span>\r\n </a>\r\n `;\r\n }\r\n}\r\n"],"mappings":"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","names":[]}
|
package/dist/dropdown/index.js
CHANGED
package/dist/header/index.d.ts
CHANGED
|
@@ -46,6 +46,24 @@ declare class Header extends LitElement {
|
|
|
46
46
|
* source component's `inverted` (from its shared `BaseProps`). */
|
|
47
47
|
inverted: boolean;
|
|
48
48
|
sticky: boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Fixes the header over the top of the page and makes it see-through, with light text
|
|
51
|
+
* (`--st-header-transparent-color`), until the page scrolls past `scroll-threshold` — for a
|
|
52
|
+
* header laid over a dark hero. It then takes its normal background and a shadow. The page
|
|
53
|
+
* content starts under it, so the first section should leave room for the 64px bar.
|
|
54
|
+
*/
|
|
55
|
+
transparent: boolean;
|
|
56
|
+
/** Slides the header out of view while the page scrolls down and back in when it scrolls up. */
|
|
57
|
+
hideOnScroll: boolean;
|
|
58
|
+
/** Scroll distance in px after which the header counts as `scrolled`. */
|
|
59
|
+
scrollThreshold: number;
|
|
60
|
+
/** True once the page has scrolled past `scroll-threshold`; reflected so pages can style `st-header[scrolled]`. Set by the header, never from outside. */
|
|
61
|
+
scrolled: boolean;
|
|
62
|
+
/** True while `hide-on-scroll` has slid the header away. Set by the header, never from outside. */
|
|
63
|
+
scrollHidden: boolean;
|
|
64
|
+
private lastScrollY;
|
|
65
|
+
private scrollFrame;
|
|
66
|
+
private onScroll;
|
|
49
67
|
/**
|
|
50
68
|
* Language switcher options, e.g. `[{ code: 'en', label: 'English' }, ...]` — hidden entirely
|
|
51
69
|
* when empty or when only one language is offered (same convention as `st-app-header`'s
|
package/dist/header/index.js
CHANGED
package/dist/hero/index.d.ts
CHANGED
|
@@ -2,10 +2,20 @@ import * as lit from 'lit';
|
|
|
2
2
|
import { LitElement } from 'lit';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* Full-width hero banner: background image with a darkening overlay,
|
|
6
|
-
*
|
|
7
|
-
* as CSS keyframes (skipped under `prefers-reduced-motion`).
|
|
8
|
-
*
|
|
5
|
+
* Full-width hero banner: background image with a darkening overlay, optional eyebrow, title,
|
|
6
|
+
* optional subtitle and up to two call-to-action links. Port of `SankhyaUI`'s React `Hero`; the
|
|
7
|
+
* framer-motion entrance is redone as CSS keyframes (skipped under `prefers-reduced-motion`).
|
|
8
|
+
*
|
|
9
|
+
* `size`, `content-position` and `overlay="gradient"` give the large editorial layout (copy on the
|
|
10
|
+
* lower left over a gradient that darkens towards it) used by enterprise marketing heroes; the
|
|
11
|
+
* defaults keep the original centered banner. Put several heroes in an `st-carousel` with
|
|
12
|
+
* `indicator-style="tabs"` for a tabbed hero carousel.
|
|
13
|
+
*
|
|
14
|
+
* @fires action - Cancelable, before navigating. Detail `{ href, kind }`, `kind` being `primary` or `secondary`.
|
|
15
|
+
* @cssprop --st-hero-text-color - Text colour over the image.
|
|
16
|
+
* @cssprop --st-hero-min-height - Minimum height when `size` is `large` (default `640px`).
|
|
17
|
+
* @csspart eyebrow - The small label above the title.
|
|
18
|
+
* @csspart secondary-action - The outlined second button.
|
|
9
19
|
*/
|
|
10
20
|
declare class Hero extends LitElement {
|
|
11
21
|
static styles: lit.CSSResult;
|
|
@@ -18,15 +28,30 @@ declare class Hero extends LitElement {
|
|
|
18
28
|
padding: 'none' | 'small' | 'medium' | 'large';
|
|
19
29
|
inverted: boolean;
|
|
20
30
|
focalPoint: 'top' | 'left' | 'right' | 'bottom' | 'center';
|
|
31
|
+
/** Small uppercase label above the title (e.g. an event or product name). */
|
|
32
|
+
eyebrow: string;
|
|
33
|
+
/** Label of an outlined second button next to the primary one. */
|
|
34
|
+
secondaryActionLabel: string;
|
|
35
|
+
secondaryHref: string;
|
|
36
|
+
/** `auto` sizes to the content; `large` is a tall editorial hero (`--st-hero-min-height`); `full` fills the viewport. */
|
|
37
|
+
size: 'auto' | 'large' | 'full';
|
|
38
|
+
/** `center` keeps the copy in the middle; `bottom` puts it in the lower part of the hero, on the page container. */
|
|
39
|
+
contentPosition: 'center' | 'bottom';
|
|
40
|
+
/** `even` darkens the whole image; `gradient` darkens behind the copy only; `none` shows the image as is. */
|
|
41
|
+
overlay: 'even' | 'gradient' | 'none';
|
|
21
42
|
private static positions;
|
|
22
43
|
protected willUpdate(): void;
|
|
44
|
+
private emitAction;
|
|
23
45
|
private onClick;
|
|
46
|
+
private onSecondaryClick;
|
|
24
47
|
render(): lit.TemplateResult<1>;
|
|
25
48
|
}
|
|
26
49
|
|
|
27
50
|
/** Fired by `st-hero` / `st-promo-banner` when the call-to-action is activated (source: `onAction`). */
|
|
28
51
|
interface HeroActionEventDetail {
|
|
29
52
|
href: string;
|
|
53
|
+
/** Which `st-hero` button was activated; `st-promo-banner` leaves it unset. */
|
|
54
|
+
kind?: 'primary' | 'secondary';
|
|
30
55
|
}
|
|
31
56
|
|
|
32
57
|
export { Hero, type HeroActionEventDetail };
|
package/dist/hero/index.js
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -16,7 +16,8 @@ export { MenuGrid } from './menu-grid/index.js';
|
|
|
16
16
|
export { MenuGridItem, MenuGridItemSelectEventDetail } from './menu-grid-item/index.js';
|
|
17
17
|
export { MenuItem, MenuItemSelectEventDetail } from './menu-item/index.js';
|
|
18
18
|
export { PromoBanner } from './promo-banner/index.js';
|
|
19
|
+
export { Section } from './section/index.js';
|
|
19
20
|
export { Select, SelectChangedEventDetail, SelectOption } from './select/index.js';
|
|
20
21
|
export { StatItem, Stats } from './stats/index.js';
|
|
21
|
-
export { TestimonialItem, Testimonials } from './testimonials/index.js';
|
|
22
|
+
export { TestimonialItem, TestimonialMetric, Testimonials, TestimonialsLinkEventDetail } from './testimonials/index.js';
|
|
22
23
|
import 'lit';
|
package/dist/index.js
CHANGED
|
@@ -1,35 +1,37 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Section
|
|
3
|
+
} from "./chunk-N63TVWK3.js";
|
|
1
4
|
import {
|
|
2
5
|
Testimonials
|
|
3
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-5DNVCMOO.js";
|
|
4
7
|
import {
|
|
5
8
|
MenuGridItem
|
|
6
9
|
} from "./chunk-4HKSA5UT.js";
|
|
7
|
-
import {
|
|
8
|
-
MenuItem
|
|
9
|
-
} from "./chunk-LHU23TF6.js";
|
|
10
10
|
import {
|
|
11
11
|
PromoBanner
|
|
12
12
|
} from "./chunk-3ERKQNZT.js";
|
|
13
|
+
import {
|
|
14
|
+
MenuItem
|
|
15
|
+
} from "./chunk-TH5NZXA7.js";
|
|
13
16
|
import {
|
|
14
17
|
Select
|
|
15
18
|
} from "./chunk-5UFRGZWG.js";
|
|
16
19
|
import {
|
|
17
20
|
Stats
|
|
18
21
|
} from "./chunk-USNFT4DU.js";
|
|
19
|
-
import {
|
|
20
|
-
FeatureItem,
|
|
21
|
-
FeaturesSection
|
|
22
|
-
} from "./chunk-E52WO7R7.js";
|
|
23
22
|
import {
|
|
24
23
|
Footer
|
|
25
24
|
} from "./chunk-CDC4P77V.js";
|
|
26
25
|
import {
|
|
27
|
-
|
|
28
|
-
} from "./chunk-
|
|
29
|
-
import "./chunk-3TCP25JG.js";
|
|
26
|
+
FeatureSplit
|
|
27
|
+
} from "./chunk-5REZSZCK.js";
|
|
30
28
|
import {
|
|
31
29
|
Hero
|
|
32
|
-
} from "./chunk-
|
|
30
|
+
} from "./chunk-K4ZICRZL.js";
|
|
31
|
+
import {
|
|
32
|
+
Header
|
|
33
|
+
} from "./chunk-PKTRYUGP.js";
|
|
34
|
+
import "./chunk-3TCP25JG.js";
|
|
33
35
|
import {
|
|
34
36
|
IconButton
|
|
35
37
|
} from "./chunk-XXPBFHWR.js";
|
|
@@ -42,12 +44,9 @@ import {
|
|
|
42
44
|
import {
|
|
43
45
|
BentoGrid
|
|
44
46
|
} from "./chunk-OEX5FDMR.js";
|
|
45
|
-
import {
|
|
46
|
-
Card
|
|
47
|
-
} from "./chunk-7PSYCTEN.js";
|
|
48
47
|
import {
|
|
49
48
|
Carousel
|
|
50
|
-
} from "./chunk-
|
|
49
|
+
} from "./chunk-47RPYNFG.js";
|
|
51
50
|
import {
|
|
52
51
|
ContentBlock
|
|
53
52
|
} from "./chunk-IMACP7HN.js";
|
|
@@ -56,10 +55,14 @@ import {
|
|
|
56
55
|
} from "./chunk-2VZFHTBC.js";
|
|
57
56
|
import {
|
|
58
57
|
Dropdown
|
|
59
|
-
} from "./chunk-
|
|
58
|
+
} from "./chunk-FWIG6GEZ.js";
|
|
60
59
|
import {
|
|
61
|
-
|
|
62
|
-
|
|
60
|
+
FeatureItem,
|
|
61
|
+
FeaturesSection
|
|
62
|
+
} from "./chunk-E52WO7R7.js";
|
|
63
|
+
import {
|
|
64
|
+
Card
|
|
65
|
+
} from "./chunk-7PSYCTEN.js";
|
|
63
66
|
import {
|
|
64
67
|
Markdown
|
|
65
68
|
} from "./chunk-3JU3R22O.js";
|
|
@@ -86,6 +89,7 @@ export {
|
|
|
86
89
|
MenuGridItem,
|
|
87
90
|
MenuItem,
|
|
88
91
|
PromoBanner,
|
|
92
|
+
Section,
|
|
89
93
|
Select,
|
|
90
94
|
Stats,
|
|
91
95
|
Testimonials
|
package/dist/menu-item/index.js
CHANGED
package/dist/schema/index.d.ts
CHANGED
|
@@ -52,6 +52,7 @@ declare const componentTags: {
|
|
|
52
52
|
readonly MenuGridItem: "st-menu-grid-item";
|
|
53
53
|
readonly MenuItem: "st-menu-item";
|
|
54
54
|
readonly PromoBanner: "st-promo-banner";
|
|
55
|
+
readonly Section: "st-section";
|
|
55
56
|
readonly Select: "st-select";
|
|
56
57
|
readonly Stats: "st-stats";
|
|
57
58
|
readonly Testimonials: "st-testimonials";
|
package/dist/schema/index.js
CHANGED
package/dist/schema/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/schema/node.ts","../../src/schema/nodeUtils.ts","../../src/schema/components.ts","../../src/schema/validate.ts"],"sourcesContent":["/** Dependency-free node types, safe to import from the content API layer. */\r\n\r\n/** Action reference in JSON, resolved against the `handlers` prop of `DynamicRenderer`. */\r\nexport type ActionRef = `@action:${string}`;\r\n\r\n/** Keys the renderer owns; everything else on a node is passed to the component as a prop. */\r\nexport interface NodeEnvelope<TNode> {\r\n id?: string;\r\n /** Named slot in the parent (e.g. Header: \"utility\"). Omit for the default slot. */\r\n slot?: string;\r\n /** Editor-only label, never passed to the component. */\r\n label?: string;\r\n children?: TNode[];\r\n}\r\n\r\n/**\r\n * A node as it appears in JSON: flat, no `data` wrapper.\r\n * `{ \"type\": \"Hero\", \"id\": \"home-hero\", \"title\": \"...\", \"children\": [...] }`\r\n */\r\nexport interface ComponentNode extends NodeEnvelope<ComponentNode> {\r\n type: string;\r\n [prop: string]: unknown;\r\n}\r\n\r\n/** A page is a list of nodes. */\r\nexport type PageContent = ComponentNode[];\r\n\r\nexport function isComponentNode(value: unknown): value is ComponentNode {\r\n return (\r\n typeof value === 'object' &&\r\n value !== null &&\r\n !Array.isArray(value) &&\r\n typeof (value as { type?: unknown }).type === 'string' &&\r\n (value as { type: string }).type.trim().length > 0\r\n );\r\n}\r\n","import type { ComponentNode } from './node';\r\n\r\n/**\r\n * Returns a copy of `nodes` where the node with the given `id` has `props` merged in.\r\n * Use it to inject runtime state (e.g. the selected language) into fetched JSON without mutating it.\r\n */\r\nexport function patchNodeById(nodes: ComponentNode[], id: string, props: Record<string, unknown>): ComponentNode[] {\r\n return nodes.map(node => {\r\n const patched = node.id === id ? { ...node, ...props } : node;\r\n return patched.children\r\n ? { ...patched, children: patchNodeById(patched.children, id, props) }\r\n : patched;\r\n });\r\n}\r\n","/**\r\n * The JSON `type` of every base component, and the Lit element that renders it.\r\n * This is the contract page JSON is written against — renderers (React, or any other binding)\r\n * map each `type` to their own component for that element.\r\n */\r\nexport const componentTags = {\r\n BentoGrid: 'st-bento-grid',\r\n Card: 'st-card',\r\n Carousel: 'st-carousel',\r\n ContentBlock: 'st-content-block',\r\n CookieConsent: 'st-cookie-consent',\r\n Dropdown: 'st-dropdown',\r\n FeatureItem: 'st-feature-item',\r\n FeatureSplit: 'st-feature-split',\r\n FeaturesSection: 'st-features-section',\r\n Footer: 'st-footer',\r\n Header: 'st-header',\r\n Hero: 'st-hero',\r\n IconButton: 'st-icon-button',\r\n ItemsAccordion: 'st-items-accordion',\r\n MenuGrid: 'st-menu-grid',\r\n MenuGridItem: 'st-menu-grid-item',\r\n MenuItem: 'st-menu-item',\r\n PromoBanner: 'st-promo-banner',\r\n Select: 'st-select',\r\n Stats: 'st-stats',\r\n Testimonials: 'st-testimonials'\r\n} as const;\r\n\r\n/** Every `type` the base set knows. */\r\nexport type ComponentType = keyof typeof componentTags;\r\n\r\nexport function isBaseComponentType(type: string): type is ComponentType {\r\n return Object.hasOwn(componentTags, type);\r\n}\r\n","import { isBaseComponentType } from './components';\r\nimport type { ComponentNode } from './node';\r\n\r\n/** Keys from the pre-flat schema; their presence means the JSON still needs `scripts/migrate-json.mjs`. */\r\nconst LEGACY_KEYS = ['data', 'embeddedView', 'data-position', 'image', 'contentUrl'] as const;\r\n\r\n/**\r\n * Returns the schema violations for a node and its descendants (empty when valid).\r\n * `isKnownType` defaults to the base component set; pass a wider predicate when a renderer also\r\n * registers custom components.\r\n */\r\nexport function validateNode(node: unknown, where = 'node', isKnownType: (type: string) => boolean = isBaseComponentType): string[] {\r\n if (typeof node !== 'object' || node === null || Array.isArray(node)) return [`${where}: node must be an object`];\r\n const n = node as ComponentNode;\r\n const found: string[] = [];\r\n if (typeof n.type !== 'string' || !isKnownType(n.type)) found.push(`${where}: unknown type \"${String(n.type)}\"`);\r\n for (const legacy of LEGACY_KEYS) {\r\n if (legacy in n) found.push(`${where}: legacy key \"${legacy}\"`);\r\n }\r\n if (Array.isArray(n.items) && n.items.some(item => typeof item === 'object' && item !== null && 'type' in item)) {\r\n found.push(`${where}: \"items\" must hold plain data; nest components under \"children\"`);\r\n }\r\n if (n.children !== undefined) {\r\n if (!Array.isArray(n.children)) found.push(`${where}: children must be an array`);\r\n else n.children.forEach((child, i) => found.push(...validateNode(child, `${where}.children[${i}]`, isKnownType)));\r\n }\r\n return found;\r\n}\r\n\r\n/** Validates a page: a node or a list of nodes. */\r\nexport function validatePage(page: unknown, isKnownType?: (type: string) => boolean): string[] {\r\n const nodes = Array.isArray(page) ? page : [page];\r\n return nodes.flatMap((node, i) => validateNode(node, `[${i}]`, isKnownType));\r\n}\r\n"],"mappings":";;;AA2BO,SAAS,gBAAgB,OAAwC;AACpE,SACI,OAAO,UAAU,YACjB,UAAU,QACV,CAAC,MAAM,QAAQ,KAAK,KACpB,OAAQ,MAA6B,SAAS,YAC7C,MAA2B,KAAK,KAAK,EAAE,SAAS;AAEzD;;;AC7BO,SAAS,cAAc,OAAwB,IAAY,OAAiD;AAC/G,SAAO,MAAM,IAAI,UAAQ;AACrB,UAAM,UAAU,KAAK,OAAO,KAAK,EAAE,GAAG,MAAM,GAAG,MAAM,IAAI;AACzD,WAAO,QAAQ,WACT,EAAE,GAAG,SAAS,UAAU,cAAc,QAAQ,UAAU,IAAI,KAAK,EAAE,IACnE;AAAA,EACV,CAAC;AACL;;;ACRO,IAAM,gBAAgB;AAAA,EACzB,WAAW;AAAA,EACX,MAAM;AAAA,EACN,UAAU;AAAA,EACV,cAAc;AAAA,EACd,eAAe;AAAA,EACf,UAAU;AAAA,EACV,aAAa;AAAA,EACb,cAAc;AAAA,EACd,iBAAiB;AAAA,EACjB,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,YAAY;AAAA,EACZ,gBAAgB;AAAA,EAChB,UAAU;AAAA,EACV,cAAc;AAAA,EACd,UAAU;AAAA,EACV,aAAa;AAAA,EACb,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,cAAc;AAClB;AAKO,SAAS,oBAAoB,MAAqC;AACrE,SAAO,OAAO,OAAO,eAAe,IAAI;AAC5C;;;
|
|
1
|
+
{"version":3,"sources":["../../src/schema/node.ts","../../src/schema/nodeUtils.ts","../../src/schema/components.ts","../../src/schema/validate.ts"],"sourcesContent":["/** Dependency-free node types, safe to import from the content API layer. */\r\n\r\n/** Action reference in JSON, resolved against the `handlers` prop of `DynamicRenderer`. */\r\nexport type ActionRef = `@action:${string}`;\r\n\r\n/** Keys the renderer owns; everything else on a node is passed to the component as a prop. */\r\nexport interface NodeEnvelope<TNode> {\r\n id?: string;\r\n /** Named slot in the parent (e.g. Header: \"utility\"). Omit for the default slot. */\r\n slot?: string;\r\n /** Editor-only label, never passed to the component. */\r\n label?: string;\r\n children?: TNode[];\r\n}\r\n\r\n/**\r\n * A node as it appears in JSON: flat, no `data` wrapper.\r\n * `{ \"type\": \"Hero\", \"id\": \"home-hero\", \"title\": \"...\", \"children\": [...] }`\r\n */\r\nexport interface ComponentNode extends NodeEnvelope<ComponentNode> {\r\n type: string;\r\n [prop: string]: unknown;\r\n}\r\n\r\n/** A page is a list of nodes. */\r\nexport type PageContent = ComponentNode[];\r\n\r\nexport function isComponentNode(value: unknown): value is ComponentNode {\r\n return (\r\n typeof value === 'object' &&\r\n value !== null &&\r\n !Array.isArray(value) &&\r\n typeof (value as { type?: unknown }).type === 'string' &&\r\n (value as { type: string }).type.trim().length > 0\r\n );\r\n}\r\n","import type { ComponentNode } from './node';\r\n\r\n/**\r\n * Returns a copy of `nodes` where the node with the given `id` has `props` merged in.\r\n * Use it to inject runtime state (e.g. the selected language) into fetched JSON without mutating it.\r\n */\r\nexport function patchNodeById(nodes: ComponentNode[], id: string, props: Record<string, unknown>): ComponentNode[] {\r\n return nodes.map(node => {\r\n const patched = node.id === id ? { ...node, ...props } : node;\r\n return patched.children\r\n ? { ...patched, children: patchNodeById(patched.children, id, props) }\r\n : patched;\r\n });\r\n}\r\n","/**\r\n * The JSON `type` of every base component, and the Lit element that renders it.\r\n * This is the contract page JSON is written against — renderers (React, or any other binding)\r\n * map each `type` to their own component for that element.\r\n */\r\nexport const componentTags = {\r\n BentoGrid: 'st-bento-grid',\r\n Card: 'st-card',\r\n Carousel: 'st-carousel',\r\n ContentBlock: 'st-content-block',\r\n CookieConsent: 'st-cookie-consent',\r\n Dropdown: 'st-dropdown',\r\n FeatureItem: 'st-feature-item',\r\n FeatureSplit: 'st-feature-split',\r\n FeaturesSection: 'st-features-section',\r\n Footer: 'st-footer',\r\n Header: 'st-header',\r\n Hero: 'st-hero',\r\n IconButton: 'st-icon-button',\r\n ItemsAccordion: 'st-items-accordion',\r\n MenuGrid: 'st-menu-grid',\r\n MenuGridItem: 'st-menu-grid-item',\r\n MenuItem: 'st-menu-item',\r\n PromoBanner: 'st-promo-banner',\r\n Section: 'st-section',\r\n Select: 'st-select',\r\n Stats: 'st-stats',\r\n Testimonials: 'st-testimonials'\r\n} as const;\r\n\r\n/** Every `type` the base set knows. */\r\nexport type ComponentType = keyof typeof componentTags;\r\n\r\nexport function isBaseComponentType(type: string): type is ComponentType {\r\n return Object.hasOwn(componentTags, type);\r\n}\r\n","import { isBaseComponentType } from './components';\r\nimport type { ComponentNode } from './node';\r\n\r\n/** Keys from the pre-flat schema; their presence means the JSON still needs `scripts/migrate-json.mjs`. */\r\nconst LEGACY_KEYS = ['data', 'embeddedView', 'data-position', 'image', 'contentUrl'] as const;\r\n\r\n/**\r\n * Returns the schema violations for a node and its descendants (empty when valid).\r\n * `isKnownType` defaults to the base component set; pass a wider predicate when a renderer also\r\n * registers custom components.\r\n */\r\nexport function validateNode(node: unknown, where = 'node', isKnownType: (type: string) => boolean = isBaseComponentType): string[] {\r\n if (typeof node !== 'object' || node === null || Array.isArray(node)) return [`${where}: node must be an object`];\r\n const n = node as ComponentNode;\r\n const found: string[] = [];\r\n if (typeof n.type !== 'string' || !isKnownType(n.type)) found.push(`${where}: unknown type \"${String(n.type)}\"`);\r\n for (const legacy of LEGACY_KEYS) {\r\n if (legacy in n) found.push(`${where}: legacy key \"${legacy}\"`);\r\n }\r\n if (Array.isArray(n.items) && n.items.some(item => typeof item === 'object' && item !== null && 'type' in item)) {\r\n found.push(`${where}: \"items\" must hold plain data; nest components under \"children\"`);\r\n }\r\n if (n.children !== undefined) {\r\n if (!Array.isArray(n.children)) found.push(`${where}: children must be an array`);\r\n else n.children.forEach((child, i) => found.push(...validateNode(child, `${where}.children[${i}]`, isKnownType)));\r\n }\r\n return found;\r\n}\r\n\r\n/** Validates a page: a node or a list of nodes. */\r\nexport function validatePage(page: unknown, isKnownType?: (type: string) => boolean): string[] {\r\n const nodes = Array.isArray(page) ? page : [page];\r\n return nodes.flatMap((node, i) => validateNode(node, `[${i}]`, isKnownType));\r\n}\r\n"],"mappings":";;;AA2BO,SAAS,gBAAgB,OAAwC;AACpE,SACI,OAAO,UAAU,YACjB,UAAU,QACV,CAAC,MAAM,QAAQ,KAAK,KACpB,OAAQ,MAA6B,SAAS,YAC7C,MAA2B,KAAK,KAAK,EAAE,SAAS;AAEzD;;;AC7BO,SAAS,cAAc,OAAwB,IAAY,OAAiD;AAC/G,SAAO,MAAM,IAAI,UAAQ;AACrB,UAAM,UAAU,KAAK,OAAO,KAAK,EAAE,GAAG,MAAM,GAAG,MAAM,IAAI;AACzD,WAAO,QAAQ,WACT,EAAE,GAAG,SAAS,UAAU,cAAc,QAAQ,UAAU,IAAI,KAAK,EAAE,IACnE;AAAA,EACV,CAAC;AACL;;;ACRO,IAAM,gBAAgB;AAAA,EACzB,WAAW;AAAA,EACX,MAAM;AAAA,EACN,UAAU;AAAA,EACV,cAAc;AAAA,EACd,eAAe;AAAA,EACf,UAAU;AAAA,EACV,aAAa;AAAA,EACb,cAAc;AAAA,EACd,iBAAiB;AAAA,EACjB,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,YAAY;AAAA,EACZ,gBAAgB;AAAA,EAChB,UAAU;AAAA,EACV,cAAc;AAAA,EACd,UAAU;AAAA,EACV,aAAa;AAAA,EACb,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,cAAc;AAClB;AAKO,SAAS,oBAAoB,MAAqC;AACrE,SAAO,OAAO,OAAO,eAAe,IAAI;AAC5C;;;AC/BA,IAAM,cAAc,CAAC,QAAQ,gBAAgB,iBAAiB,SAAS,YAAY;AAO5E,SAAS,aAAa,MAAe,QAAQ,QAAQ,cAAyC,qBAA+B;AAChI,MAAI,OAAO,SAAS,YAAY,SAAS,QAAQ,MAAM,QAAQ,IAAI,EAAG,QAAO,CAAC,GAAG,KAAK,0BAA0B;AAChH,QAAM,IAAI;AACV,QAAM,QAAkB,CAAC;AACzB,MAAI,OAAO,EAAE,SAAS,YAAY,CAAC,YAAY,EAAE,IAAI,EAAG,OAAM,KAAK,GAAG,KAAK,mBAAmB,OAAO,EAAE,IAAI,CAAC,GAAG;AAC/G,aAAW,UAAU,aAAa;AAC9B,QAAI,UAAU,EAAG,OAAM,KAAK,GAAG,KAAK,iBAAiB,MAAM,GAAG;AAAA,EAClE;AACA,MAAI,MAAM,QAAQ,EAAE,KAAK,KAAK,EAAE,MAAM,KAAK,UAAQ,OAAO,SAAS,YAAY,SAAS,QAAQ,UAAU,IAAI,GAAG;AAC7G,UAAM,KAAK,GAAG,KAAK,kEAAkE;AAAA,EACzF;AACA,MAAI,EAAE,aAAa,QAAW;AAC1B,QAAI,CAAC,MAAM,QAAQ,EAAE,QAAQ,EAAG,OAAM,KAAK,GAAG,KAAK,6BAA6B;AAAA,QAC3E,GAAE,SAAS,QAAQ,CAAC,OAAO,MAAM,MAAM,KAAK,GAAG,aAAa,OAAO,GAAG,KAAK,aAAa,CAAC,KAAK,WAAW,CAAC,CAAC;AAAA,EACpH;AACA,SAAO;AACX;AAGO,SAAS,aAAa,MAAe,aAAmD;AAC3F,QAAM,QAAQ,MAAM,QAAQ,IAAI,IAAI,OAAO,CAAC,IAAI;AAChD,SAAO,MAAM,QAAQ,CAAC,MAAM,MAAM,aAAa,MAAM,IAAI,CAAC,KAAK,WAAW,CAAC;AAC/E;","names":[]}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import * as lit from 'lit';
|
|
2
|
+
import { LitElement } from 'lit';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A page band that holds other content: sets the background, the spacing above and below, and
|
|
6
|
+
* the content width, with an optional eyebrow/title/subtitle heading over its slotted children.
|
|
7
|
+
* Use it to give a run of components one dark or tinted backdrop and a consistent vertical rhythm,
|
|
8
|
+
* the way enterprise marketing pages stack full-width sections.
|
|
9
|
+
*
|
|
10
|
+
* @slot - The section's content.
|
|
11
|
+
* @cssprop --st-section-bg - Overrides the background colour picked by `background`.
|
|
12
|
+
* @csspart inner - The width-limited content box.
|
|
13
|
+
* @csspart header - The heading block.
|
|
14
|
+
*/
|
|
15
|
+
declare class Section extends LitElement {
|
|
16
|
+
static styles: lit.CSSResult;
|
|
17
|
+
eyebrow: string;
|
|
18
|
+
title: string;
|
|
19
|
+
subtitle: string;
|
|
20
|
+
/** `default` (page surface), `subtle`, `dark`, `accent` or `transparent`. */
|
|
21
|
+
background: 'default' | 'subtle' | 'dark' | 'accent' | 'transparent';
|
|
22
|
+
/** Space above the content: `none`, `s`, `m` (default), `l`, `xl` or `xxl`. */
|
|
23
|
+
spacingAbove: 'none' | 's' | 'm' | 'l' | 'xl' | 'xxl';
|
|
24
|
+
/** Space below the content: `none`, `s`, `m` (default), `l`, `xl` or `xxl`. */
|
|
25
|
+
spacingBelow: 'none' | 's' | 'm' | 'l' | 'xl' | 'xxl';
|
|
26
|
+
/** `contained` (default, the page container width), `narrow` (reading width) or `full` (edge to edge). */
|
|
27
|
+
width: 'contained' | 'narrow' | 'full';
|
|
28
|
+
/** Alignment of the heading. */
|
|
29
|
+
textAlignment: 'left' | 'center';
|
|
30
|
+
render(): lit.TemplateResult<1>;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export { Section };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -1,25 +1,81 @@
|
|
|
1
1
|
import * as lit from 'lit';
|
|
2
2
|
import { LitElement } from 'lit';
|
|
3
3
|
|
|
4
|
+
interface TestimonialMetric {
|
|
5
|
+
/** The figure, e.g. `100M` or `85%`. */
|
|
6
|
+
value: string;
|
|
7
|
+
label: string;
|
|
8
|
+
}
|
|
4
9
|
interface TestimonialItem {
|
|
5
10
|
id?: string;
|
|
6
|
-
name
|
|
7
|
-
role
|
|
11
|
+
name?: string;
|
|
12
|
+
role?: string;
|
|
8
13
|
company?: string;
|
|
14
|
+
/** Author photo. */
|
|
9
15
|
imageSrc?: string;
|
|
10
|
-
quote
|
|
11
|
-
/** Number of stars
|
|
16
|
+
quote?: string;
|
|
17
|
+
/** Number of stars. Default: 5 for a plain quote card; no stars on a story card (one with a `headline` or `logoSrc`). `0` hides them. */
|
|
12
18
|
rating?: number;
|
|
19
|
+
/** Customer logo, shown at the top of the card body. */
|
|
20
|
+
logoSrc?: string;
|
|
21
|
+
/** Wide image across the top of the card. */
|
|
22
|
+
mediaSrc?: string;
|
|
23
|
+
/** Story headline, e.g. "Acme gives 100 million users a seamless sign-in". */
|
|
24
|
+
headline?: string;
|
|
25
|
+
/** Key results, shown as large figures. */
|
|
26
|
+
metrics?: TestimonialMetric[];
|
|
27
|
+
/** Link to the full story. */
|
|
28
|
+
href?: string;
|
|
29
|
+
/** Text of the story link; defaults to the section's `link-label`. */
|
|
30
|
+
linkLabel?: string;
|
|
13
31
|
}
|
|
14
|
-
/**
|
|
32
|
+
/**
|
|
33
|
+
* Section heading plus customer quotes or stories. Port of React `Testimonials`.
|
|
34
|
+
*
|
|
35
|
+
* Each card shows whatever its item has: a cover image, the customer logo, a story headline, a
|
|
36
|
+
* quote with star rating, result metrics, the author and a "See full story" link. `layout="grid"`
|
|
37
|
+
* (default) lays the cards out in a grid; `layout="carousel"` puts them on one horizontally
|
|
38
|
+
* scrolling, snapping row with previous/next buttons next to the heading — the customer-story
|
|
39
|
+
* carousel of enterprise marketing pages. `action-label`/`action-href` add a section-level link
|
|
40
|
+
* (e.g. "Explore customer stories").
|
|
41
|
+
*
|
|
42
|
+
* @fires testimonials-link - Cancelable, before following a story or section link. Detail `{ href }`.
|
|
43
|
+
*/
|
|
15
44
|
declare class Testimonials extends LitElement {
|
|
16
45
|
static styles: lit.CSSResult;
|
|
17
46
|
title: string;
|
|
18
47
|
subtitle: string;
|
|
19
48
|
items: TestimonialItem[];
|
|
20
49
|
inverted: boolean;
|
|
50
|
+
/** `grid` (default) or `carousel`: one scrolling row with previous/next buttons. */
|
|
51
|
+
layout: 'grid' | 'carousel';
|
|
52
|
+
/** Default text of each card's story link. */
|
|
53
|
+
linkLabel: string;
|
|
54
|
+
/** Section-level link text, e.g. "Explore customer stories". */
|
|
55
|
+
actionLabel: string;
|
|
56
|
+
actionHref: string;
|
|
57
|
+
private atStart;
|
|
58
|
+
private atEnd;
|
|
59
|
+
private get track();
|
|
60
|
+
private resizeObserver?;
|
|
61
|
+
protected updated(changed: Map<string, unknown>): void;
|
|
62
|
+
disconnectedCallback(): void;
|
|
63
|
+
private syncArrows;
|
|
64
|
+
private scrollByCard;
|
|
65
|
+
private onLinkClick;
|
|
66
|
+
private renderLink;
|
|
21
67
|
private renderItem;
|
|
22
68
|
render(): lit.TemplateResult<1>;
|
|
23
69
|
}
|
|
24
70
|
|
|
25
|
-
|
|
71
|
+
/** Fired by `st-testimonials` when a card's story link or the section link is activated. */
|
|
72
|
+
interface TestimonialsLinkEventDetail {
|
|
73
|
+
href: string;
|
|
74
|
+
}
|
|
75
|
+
declare global {
|
|
76
|
+
interface HTMLElementEventMap {
|
|
77
|
+
'testimonials-link': CustomEvent<TestimonialsLinkEventDetail>;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export { type TestimonialItem, type TestimonialMetric, Testimonials, type TestimonialsLinkEventDetail };
|