@helixui/library 2.1.2-next.48 → 2.1.2-next.50
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/custom-elements.json +5 -5
- package/dist/components/hx-breadcrumb/hx-breadcrumb.d.ts.map +1 -1
- package/dist/components/hx-breadcrumb/index.js +1 -1
- package/dist/components/hx-checkbox/index.js +1 -1
- package/dist/components/hx-clinical-status/hx-clinical-status.d.ts +7 -0
- package/dist/components/hx-clinical-status/hx-clinical-status.d.ts.map +1 -1
- package/dist/components/hx-clinical-status/index.js +1 -1
- package/dist/components/hx-code-snippet/hx-code-snippet.d.ts +3 -2
- package/dist/components/hx-code-snippet/hx-code-snippet.d.ts.map +1 -1
- package/dist/components/hx-code-snippet/index.js +1 -1
- package/dist/components/hx-color-picker/hx-color-picker.d.ts +6 -10
- package/dist/components/hx-color-picker/hx-color-picker.d.ts.map +1 -1
- package/dist/components/hx-color-picker/hx-color-picker.styles.d.ts.map +1 -1
- package/dist/components/hx-color-picker/index.js +1 -1
- package/dist/components/hx-container/hx-container.styles.d.ts.map +1 -1
- package/dist/components/hx-container/index.js +1 -1
- package/dist/components/hx-link/hx-link.d.ts +2 -0
- package/dist/components/hx-link/hx-link.d.ts.map +1 -1
- package/dist/components/hx-link/index.js +1 -1
- package/dist/components/hx-patient-banner/hx-patient-banner.d.ts.map +1 -1
- package/dist/components/hx-progress-ring/index.js +1 -1
- package/dist/components/hx-select/hx-select.styles.d.ts.map +1 -1
- package/dist/components/hx-select/index.js +1 -1
- package/dist/components/hx-skeleton/hx-skeleton.d.ts +1 -1
- package/dist/components/hx-skeleton/index.js +1 -1
- package/dist/components/hx-spinner/index.js +1 -1
- package/dist/components/hx-style-scope/hx-style-scope.d.ts +2 -2
- package/dist/components/hx-style-scope/hx-style-scope.d.ts.map +1 -1
- package/dist/components/hx-text-input/index.js +1 -1
- package/dist/css/helix-all.css +9 -17
- package/dist/css/helix-core.css +5 -5
- package/dist/css/helix-feedback.css +3 -3
- package/dist/css/helix-forms.css +1 -3
- package/dist/css/helix-layout.css +0 -6
- package/dist/css/hx-color-picker.css +1 -1
- package/dist/css/hx-container.css +0 -6
- package/dist/css/hx-link.css +1 -1
- package/dist/css/hx-progress-ring.css +3 -3
- package/dist/css/hx-select.css +0 -2
- package/dist/css/hx-skeleton.css +1 -1
- package/dist/css/hx-spinner.css +3 -3
- package/dist/css/index.css +1 -1
- package/dist/css/manifest.json +2 -2
- package/dist/index.js +16 -15
- package/dist/index.js.map +1 -1
- package/dist/shared/{id-counter-JhvVCnjh.js → helix-element-CZvaIEQP.js} +19 -31
- package/dist/shared/helix-element-CZvaIEQP.js.map +1 -0
- package/dist/shared/{hx-breadcrumb-item-DzLyeL5Z.js → hx-breadcrumb-item-jLAKK038.js} +2 -2
- package/dist/shared/hx-breadcrumb-item-jLAKK038.js.map +1 -0
- package/dist/shared/{hx-checkbox-CTEZ9IFq.js → hx-checkbox-C82GjRXe.js} +6 -5
- package/dist/shared/{hx-checkbox-CTEZ9IFq.js.map → hx-checkbox-C82GjRXe.js.map} +1 -1
- package/dist/shared/{hx-clinical-status-m4soOOwg.js → hx-clinical-status-BjtT5c0M.js} +24 -23
- package/dist/shared/hx-clinical-status-BjtT5c0M.js.map +1 -0
- package/dist/shared/{hx-code-snippet-CoLYvX1Z.js → hx-code-snippet-DcVENSuC.js} +6 -5
- package/dist/shared/hx-code-snippet-DcVENSuC.js.map +1 -0
- package/dist/shared/{hx-color-picker-DhOaNe6-.js → hx-color-picker-C6EIuS9t.js} +47 -46
- package/dist/shared/hx-color-picker-C6EIuS9t.js.map +1 -0
- package/dist/shared/{hx-container-31QT9KV_.js → hx-container-BwWbMPTH.js} +5 -11
- package/dist/shared/hx-container-BwWbMPTH.js.map +1 -0
- package/dist/shared/{hx-link-B8IwUMSc.js → hx-link-CN7AvGOW.js} +29 -24
- package/dist/shared/hx-link-CN7AvGOW.js.map +1 -0
- package/dist/shared/hx-patient-banner-BKiN7nIE.js.map +1 -1
- package/dist/shared/{hx-progress-ring-BJeiDr3q.js → hx-progress-ring-Cs0WgWDJ.js} +4 -4
- package/dist/shared/hx-progress-ring-Cs0WgWDJ.js.map +1 -0
- package/dist/shared/{hx-select-B5wq9Swh.js → hx-select-CgcgsHU5.js} +3 -4
- package/dist/shared/hx-select-CgcgsHU5.js.map +1 -0
- package/dist/shared/{hx-skeleton-e5K9Qaxq.js → hx-skeleton-tiYvKO-t.js} +11 -11
- package/dist/shared/hx-skeleton-tiYvKO-t.js.map +1 -0
- package/dist/shared/{hx-spinner-Dyese1Tb.js → hx-spinner-D6nzuGmj.js} +8 -8
- package/dist/shared/hx-spinner-D6nzuGmj.js.map +1 -0
- package/dist/shared/hx-style-scope-CsQ2Phf_.js.map +1 -1
- package/dist/shared/{hx-text-input-Scyeefec.js → hx-text-input-Zuodg9s_.js} +3 -2
- package/dist/shared/{hx-text-input-Scyeefec.js.map → hx-text-input-Zuodg9s_.js.map} +1 -1
- package/dist/shared/id-counter-PTgF-zcG.js +15 -0
- package/dist/shared/id-counter-PTgF-zcG.js.map +1 -0
- package/package.json +2 -2
- package/dist/shared/hx-breadcrumb-item-DzLyeL5Z.js.map +0 -1
- package/dist/shared/hx-clinical-status-m4soOOwg.js.map +0 -1
- package/dist/shared/hx-code-snippet-CoLYvX1Z.js.map +0 -1
- package/dist/shared/hx-color-picker-DhOaNe6-.js.map +0 -1
- package/dist/shared/hx-container-31QT9KV_.js.map +0 -1
- package/dist/shared/hx-link-B8IwUMSc.js.map +0 -1
- package/dist/shared/hx-progress-ring-BJeiDr3q.js.map +0 -1
- package/dist/shared/hx-select-B5wq9Swh.js.map +0 -1
- package/dist/shared/hx-skeleton-e5K9Qaxq.js.map +0 -1
- package/dist/shared/hx-spinner-Dyese1Tb.js.map +0 -1
- package/dist/shared/id-counter-JhvVCnjh.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hx-style-scope-CsQ2Phf_.js","sources":["../../src/components/hx-style-scope/hx-style-scope.styles.ts","../../src/utilities/lightStyleRegistry.ts","../../src/utilities/generateScopedSelectors.ts","../../src/utilities/injectLightStyles.ts","../../src/components/hx-style-scope/hx-style-scope.ts"],"sourcesContent":["import { css } from 'lit';\n\n/**\n * Styles for hx-style-scope.\n *\n * The element acts as a transparent wrapper — `display: contents` removes it\n * from the layout box model so slotted children lay out as if the wrapper\n * were absent. This prevents hx-style-scope from introducing unexpected\n * layout shifts or block-formatting-context changes.\n */\nexport const hxStyleScopeStyles = css`\n :host {\n display: contents;\n }\n`;\n","/**\n * @module lightStyleRegistry\n *\n * A Map-based registry that tracks which component stylesheets have been\n * injected into the document's light DOM. Used by injectLightStyles to\n * deduplicate style injection — each component type is injected at most once\n * per page load.\n *\n * @example\n * ```ts\n * import { isStyleRegistered, registerStyle } from './lightStyleRegistry.js';\n *\n * if (!isStyleRegistered('hx-card')) {\n * const el = document.createElement('style');\n * el.textContent = css;\n * document.head.appendChild(el);\n * registerStyle('hx-card', el);\n * }\n * ```\n */\n\n/**\n * Primary registry mapping component names to their injected HTMLStyleElement.\n * Keyed by component tag name (e.g. `'hx-card'`).\n */\nexport const lightStyleRegistry: Map<string, HTMLStyleElement> = new Map();\n\n/**\n * Returns true if a stylesheet for the given component has already been\n * injected into the document.\n *\n * @param componentName - The component tag name (e.g. `'hx-card'`).\n */\nexport function isStyleRegistered(componentName: string): boolean {\n return lightStyleRegistry.has(componentName);\n}\n\n/**\n * Records a stylesheet element in the registry for the given component.\n * Called after the element has been appended to the document.\n *\n * @param componentName - The component tag name (e.g. `'hx-card'`).\n * @param el - The HTMLStyleElement that was injected.\n */\nexport function registerStyle(componentName: string, el: HTMLStyleElement): void {\n lightStyleRegistry.set(componentName, el);\n}\n","/**\n * @module generateScopedSelectors\n *\n * Generates CSS with all selectors scoped under a `[data-hx-styled=\"componentName\"]`\n * attribute selector. This ensures injected light DOM styles only apply to content\n * wrapped in `<hx-style-scope component=\"...\">` and do not leak to the rest of the page.\n *\n * @example\n * ```ts\n * import { generateScopedSelectors } from './generateScopedSelectors.js';\n *\n * const scoped = generateScopedSelectors('hx-card', 'p { color: red; }');\n * // '[data-hx-styled=\"hx-card\"] p { color: red; }'\n * ```\n */\n\n/**\n * Wraps all CSS selectors to be scoped under `[data-hx-styled=\"componentName\"]`.\n *\n * Handles:\n * - Standard selectors: `p { ... }` → `[data-hx-styled=\"hx-card\"] p { ... }`\n * - `::slotted(*)` patterns: `::slotted(p) { ... }` → `[data-hx-styled=\"hx-card\"] p { ... }`\n * - At-rules (`@media`, `@supports`, etc.) are preserved with their inner rules scoped\n * - `:host` selectors are replaced with the scope attribute selector\n *\n * @param componentName - The component tag name used as the scope identifier.\n * @param css - The CSS string to transform.\n * @returns The transformed CSS with all selectors scoped.\n */\nexport function generateScopedSelectors(componentName: string, css: string): string {\n const scopeAttr = `[data-hx-styled=\"${componentName}\"]`;\n return transformCss(css, scopeAttr);\n}\n\n/**\n * Returns the character at position `i` in `str`, or an empty string if out of bounds.\n * Avoids the `string | undefined` issue from `noUncheckedIndexedAccess`.\n */\nfunction charAt(str: string, i: number): string {\n return str[i] ?? '';\n}\n\n/**\n * Recursively transforms CSS text, scoping all selectors under the given\n * attribute selector prefix.\n *\n * @param css - The CSS text to transform.\n * @param scopeAttr - The attribute selector to prepend (e.g. `[data-hx-styled=\"hx-card\"]`).\n * @returns The scoped CSS string.\n */\nfunction transformCss(css: string, scopeAttr: string): string {\n const result: string[] = [];\n let i = 0;\n const len = css.length;\n\n while (i < len) {\n // Skip whitespace\n while (i < len && /\\s/.test(charAt(css, i))) {\n result.push(charAt(css, i));\n i++;\n }\n\n if (i >= len) break;\n\n // Check for at-rule (e.g. @media, @supports, @keyframes)\n if (charAt(css, i) === '@') {\n const atBlock = extractAtRule(css, i);\n result.push(transformAtRule(atBlock.text, scopeAttr));\n i = atBlock.end;\n continue;\n }\n\n // Extract a selector block up to the opening brace\n const selectorEnd = css.indexOf('{', i);\n if (selectorEnd === -1) break;\n\n const selectorRaw = css.slice(i, selectorEnd);\n const bodyStart = selectorEnd + 1;\n const bodyEnd = findMatchingBrace(css, selectorEnd);\n\n if (bodyEnd === -1) break;\n\n const body = css.slice(bodyStart, bodyEnd);\n const scopedSelector = scopeSelector(selectorRaw, scopeAttr);\n result.push(`${scopedSelector}{${body}}`);\n i = bodyEnd + 1;\n }\n\n return result.join('');\n}\n\n/**\n * Scopes a raw selector string under the given attribute selector.\n * Handles comma-separated selectors, `::slotted()`, and `:host`.\n *\n * @param selectorRaw - The raw selector string (may be comma-separated).\n * @param scopeAttr - The attribute selector prefix.\n * @returns The scoped selector string.\n */\nfunction scopeSelector(selectorRaw: string, scopeAttr: string): string {\n return selectorRaw\n .split(',')\n .map((part) => {\n const trimmed = part.trim();\n\n // Replace ::slotted(*) with a direct descendant selector\n if (trimmed.startsWith('::slotted(') && trimmed.endsWith(')')) {\n const inner = trimmed.slice('::slotted('.length, -1).trim();\n return `${scopeAttr} ${inner}`;\n }\n\n // Replace :host with the scope attribute selector\n if (trimmed === ':host' || trimmed.startsWith(':host(') || trimmed.startsWith(':host ')) {\n return trimmed.replace(/^:host(\\([^)]*\\))?/, scopeAttr);\n }\n\n // Standard selector — prepend scope\n return `${scopeAttr} ${trimmed}`;\n })\n .join(', ');\n}\n\n/**\n * Handles transformation of an at-rule block. For container at-rules\n * (`@media`, `@supports`, `@layer`, `@container`) the inner rules are scoped.\n * Non-container at-rules (e.g. `@keyframes`, `@charset`) are passed through unchanged.\n *\n * @param atRuleText - The full at-rule text including its block.\n * @param scopeAttr - The attribute selector prefix.\n * @returns The transformed at-rule text.\n */\nfunction transformAtRule(atRuleText: string, scopeAttr: string): string {\n const containerAtRules = /^@(media|supports|layer|container|document)\\b/;\n const braceIndex = atRuleText.indexOf('{');\n\n if (braceIndex === -1 || !containerAtRules.test(atRuleText.trim())) {\n return atRuleText;\n }\n\n const head = atRuleText.slice(0, braceIndex + 1);\n const innerEnd = atRuleText.lastIndexOf('}');\n const inner = atRuleText.slice(braceIndex + 1, innerEnd);\n const tail = atRuleText.slice(innerEnd);\n\n return `${head}${transformCss(inner, scopeAttr)}${tail}`;\n}\n\n/**\n * Extracts a complete at-rule (potentially with a block) starting at index `start`.\n * Returns the text and the end index.\n */\nfunction extractAtRule(css: string, start: number): { text: string; end: number } {\n // Find either ';' (simple at-rule) or the matching '{...}' (block at-rule)\n let i = start;\n while (i < css.length && charAt(css, i) !== '{' && charAt(css, i) !== ';') {\n i++;\n }\n\n if (i >= css.length) {\n return { text: css.slice(start), end: css.length };\n }\n\n if (charAt(css, i) === ';') {\n return { text: css.slice(start, i + 1), end: i + 1 };\n }\n\n // Block at-rule — find matching closing brace\n const end = findMatchingBrace(css, i);\n if (end === -1) {\n return { text: css.slice(start), end: css.length };\n }\n\n return { text: css.slice(start, end + 1), end: end + 1 };\n}\n\n/**\n * Finds the index of the closing `}` that matches the `{` at `openIndex`.\n * Returns -1 if no matching brace is found.\n */\nfunction findMatchingBrace(css: string, openIndex: number): number {\n let depth = 0;\n for (let i = openIndex; i < css.length; i++) {\n const ch = charAt(css, i);\n if (ch === '{') depth++;\n else if (ch === '}') {\n depth--;\n if (depth === 0) return i;\n }\n }\n return -1;\n}\n","/**\n * @module injectLightStyles\n *\n * Core utility for injecting scoped `<style>` elements into `document.head`\n * to style light DOM slotted content in HELiX components. Uses the\n * `lightStyleRegistry` to deduplicate — each component type is injected at\n * most once per page lifetime.\n *\n * This is the primary mechanism for ensuring Drupal-slotted content inherits\n * proper typography and spacing when rendered into HELiX components.\n *\n * @example\n * ```ts\n * import { injectLightStyles } from '../../utilities/injectLightStyles.js';\n *\n * // In connectedCallback:\n * injectLightStyles('hx-card', 'p { font-size: var(--hx-font-size-md); }');\n * ```\n */\n\nimport { isStyleRegistered, registerStyle } from './lightStyleRegistry.js';\nimport { generateScopedSelectors } from './generateScopedSelectors.js';\n\n/**\n * Injects a scoped `<style>` element into `document.head` for light DOM content\n * belonging to `componentName`. The CSS is automatically scoped via\n * `generateScopedSelectors` so styles only apply inside\n * `[data-hx-styled=\"componentName\"]` wrappers.\n *\n * Safe to call multiple times — subsequent calls for the same `componentName`\n * are no-ops (deduplication via `lightStyleRegistry`).\n *\n * No-op in SSR environments where `document` is not defined.\n *\n * @param componentName - The component tag name used as the scope identifier\n * (e.g. `'hx-card'`). Must be unique per component type.\n * @param css - The raw CSS to scope and inject. Selectors will be prefixed with\n * `[data-hx-styled=\"componentName\"]`.\n */\nexport function injectLightStyles(componentName: string, css: string): void {\n if (typeof document === 'undefined') return;\n\n if (isStyleRegistered(componentName)) return;\n\n const scopedCss = generateScopedSelectors(componentName, css);\n\n const styleEl = document.createElement('style');\n styleEl.setAttribute('data-hx-light-styles', componentName);\n styleEl.textContent = scopedCss;\n\n document.head.appendChild(styleEl);\n registerStyle(componentName, styleEl);\n}\n","import { LitElement, html } from 'lit';\nimport '../../utilities/document-token-adoption.js';\nimport { customElement, property } from 'lit/decorators.js';\nimport { hxStyleScopeStyles } from './hx-style-scope.styles.js';\nimport { injectLightStyles } from '../../utilities/injectLightStyles.js';\n\n/**\n * A transparent wrapper component that injects scoped light DOM styles for its\n * slotted children. Designed for use in Drupal Twig templates where slotted\n * content lives in the light DOM and cannot inherit Shadow DOM styles.\n *\n * On connection, the component:\n * 1. Sets `data-hx-styled` on itself so scoped CSS selectors resolve correctly.\n * 2. Calls `injectLightStyles` to inject a `<style>` into `document.head` — once\n * per component type, deduplicated via `lightStyleRegistry`.\n *\n * The element renders as `display: contents` so it does not affect layout.\n *\n * @summary Scoped light DOM style injection wrapper for Drupal-slotted content.\n *\n * @tag hx-style-scope\n *\n * @slot - Default slot for any light DOM content that should receive scoped styles.\n *\n * @example\n * ```html\n * <hx-style-scope component=\"hx-card\">\n * <p>This paragraph will receive hx-card light DOM styles.</p>\n * </hx-style-scope>\n * ```\n *\n * @example Drupal Twig usage via macro\n * ```twig\n * {% import '@helix/hx-style-scope.macro.twig' as hx %}\n * {{ hx.scope('hx-card', content) }}\n * ```\n */\n@customElement('hx-style-scope')\nexport class HxStyleScope extends LitElement {\n static override styles = [hxStyleScopeStyles];\n\n /**\n * The component tag name whose light DOM styles should be injected.\n * Must match a registered component name (e.g. `'hx-card'`).\n *\n * When set, the element also applies `data-hx-styled` to itself so that\n * scoped CSS selectors generated by `generateScopedSelectors` resolve\n * correctly against slotted child elements.\n *\n * @attr component\n */\n @property({ type: String, reflect: true })\n component: string = '';\n\n /**\n * The CSS to inject as scoped light DOM styles. When provided alongside\n * `component`, this CSS will be scoped under `[data-hx-styled=\"component\"]`\n * and injected into `document.head` (once per component type).\n *\n * If omitted, `injectLightStyles` is still called but with an empty string,\n * which is a no-op for actual style injection.\n *\n * @attr light-css\n */\n @property({ type: String, attribute: 'light-css' })\n lightCss: string = '';\n\n override connectedCallback(): void {\n super.connectedCallback();\n this._applyScope();\n }\n\n override updated(changed: Map<string, unknown>): void {\n if (changed.has('component') || changed.has('lightCss')) {\n this._applyScope();\n }\n }\n\n /**\n * Applies the `data-hx-styled` attribute and triggers style injection.\n * Called on connect and whenever `component` or `lightCss` changes.\n */\n private _applyScope(): void {\n if (!this.component) return;\n\n // Set the scoping attribute so CSS selectors from generateScopedSelectors work.\n this.setAttribute('data-hx-styled', this.component);\n\n if (this.lightCss) {\n injectLightStyles(this.component, this.lightCss);\n }\n }\n\n override render() {\n return html`<slot></slot>`;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'hx-style-scope': HxStyleScope;\n }\n}\n"],"names":["hxStyleScopeStyles","css","lightStyleRegistry","isStyleRegistered","componentName","registerStyle","el","generateScopedSelectors","scopeAttr","transformCss","charAt","str","i","result","len","atBlock","extractAtRule","transformAtRule","selectorEnd","selectorRaw","bodyStart","bodyEnd","findMatchingBrace","body","scopedSelector","scopeSelector","part","trimmed","inner","atRuleText","containerAtRules","braceIndex","head","innerEnd","tail","start","end","openIndex","depth","ch","injectLightStyles","scopedCss","styleEl","HxStyleScope","LitElement","changed","html","__decorateClass","property","customElement"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"hx-style-scope-CsQ2Phf_.js","sources":["../../src/components/hx-style-scope/hx-style-scope.styles.ts","../../src/utilities/lightStyleRegistry.ts","../../src/utilities/generateScopedSelectors.ts","../../src/utilities/injectLightStyles.ts","../../src/components/hx-style-scope/hx-style-scope.ts"],"sourcesContent":["import { css } from 'lit';\n\n/**\n * Styles for hx-style-scope.\n *\n * The element acts as a transparent wrapper — `display: contents` removes it\n * from the layout box model so slotted children lay out as if the wrapper\n * were absent. This prevents hx-style-scope from introducing unexpected\n * layout shifts or block-formatting-context changes.\n */\nexport const hxStyleScopeStyles = css`\n :host {\n display: contents;\n }\n`;\n","/**\n * @module lightStyleRegistry\n *\n * A Map-based registry that tracks which component stylesheets have been\n * injected into the document's light DOM. Used by injectLightStyles to\n * deduplicate style injection — each component type is injected at most once\n * per page load.\n *\n * @example\n * ```ts\n * import { isStyleRegistered, registerStyle } from './lightStyleRegistry.js';\n *\n * if (!isStyleRegistered('hx-card')) {\n * const el = document.createElement('style');\n * el.textContent = css;\n * document.head.appendChild(el);\n * registerStyle('hx-card', el);\n * }\n * ```\n */\n\n/**\n * Primary registry mapping component names to their injected HTMLStyleElement.\n * Keyed by component tag name (e.g. `'hx-card'`).\n */\nexport const lightStyleRegistry: Map<string, HTMLStyleElement> = new Map();\n\n/**\n * Returns true if a stylesheet for the given component has already been\n * injected into the document.\n *\n * @param componentName - The component tag name (e.g. `'hx-card'`).\n */\nexport function isStyleRegistered(componentName: string): boolean {\n return lightStyleRegistry.has(componentName);\n}\n\n/**\n * Records a stylesheet element in the registry for the given component.\n * Called after the element has been appended to the document.\n *\n * @param componentName - The component tag name (e.g. `'hx-card'`).\n * @param el - The HTMLStyleElement that was injected.\n */\nexport function registerStyle(componentName: string, el: HTMLStyleElement): void {\n lightStyleRegistry.set(componentName, el);\n}\n","/**\n * @module generateScopedSelectors\n *\n * Generates CSS with all selectors scoped under a `[data-hx-styled=\"componentName\"]`\n * attribute selector. This ensures injected light DOM styles only apply to content\n * wrapped in `<hx-style-scope component=\"...\">` and do not leak to the rest of the page.\n *\n * @example\n * ```ts\n * import { generateScopedSelectors } from './generateScopedSelectors.js';\n *\n * const scoped = generateScopedSelectors('hx-card', 'p { color: red; }');\n * // '[data-hx-styled=\"hx-card\"] p { color: red; }'\n * ```\n */\n\n/**\n * Wraps all CSS selectors to be scoped under `[data-hx-styled=\"componentName\"]`.\n *\n * Handles:\n * - Standard selectors: `p { ... }` → `[data-hx-styled=\"hx-card\"] p { ... }`\n * - `::slotted(*)` patterns: `::slotted(p) { ... }` → `[data-hx-styled=\"hx-card\"] p { ... }`\n * - At-rules (`@media`, `@supports`, etc.) are preserved with their inner rules scoped\n * - `:host` selectors are replaced with the scope attribute selector\n *\n * @param componentName - The component tag name used as the scope identifier.\n * @param css - The CSS string to transform.\n * @returns The transformed CSS with all selectors scoped.\n */\nexport function generateScopedSelectors(componentName: string, css: string): string {\n const scopeAttr = `[data-hx-styled=\"${componentName}\"]`;\n return transformCss(css, scopeAttr);\n}\n\n/**\n * Returns the character at position `i` in `str`, or an empty string if out of bounds.\n * Avoids the `string | undefined` issue from `noUncheckedIndexedAccess`.\n */\nfunction charAt(str: string, i: number): string {\n return str[i] ?? '';\n}\n\n/**\n * Recursively transforms CSS text, scoping all selectors under the given\n * attribute selector prefix.\n *\n * @param css - The CSS text to transform.\n * @param scopeAttr - The attribute selector to prepend (e.g. `[data-hx-styled=\"hx-card\"]`).\n * @returns The scoped CSS string.\n */\nfunction transformCss(css: string, scopeAttr: string): string {\n const result: string[] = [];\n let i = 0;\n const len = css.length;\n\n while (i < len) {\n // Skip whitespace\n while (i < len && /\\s/.test(charAt(css, i))) {\n result.push(charAt(css, i));\n i++;\n }\n\n if (i >= len) break;\n\n // Check for at-rule (e.g. @media, @supports, @keyframes)\n if (charAt(css, i) === '@') {\n const atBlock = extractAtRule(css, i);\n result.push(transformAtRule(atBlock.text, scopeAttr));\n i = atBlock.end;\n continue;\n }\n\n // Extract a selector block up to the opening brace\n const selectorEnd = css.indexOf('{', i);\n if (selectorEnd === -1) break;\n\n const selectorRaw = css.slice(i, selectorEnd);\n const bodyStart = selectorEnd + 1;\n const bodyEnd = findMatchingBrace(css, selectorEnd);\n\n if (bodyEnd === -1) break;\n\n const body = css.slice(bodyStart, bodyEnd);\n const scopedSelector = scopeSelector(selectorRaw, scopeAttr);\n result.push(`${scopedSelector}{${body}}`);\n i = bodyEnd + 1;\n }\n\n return result.join('');\n}\n\n/**\n * Scopes a raw selector string under the given attribute selector.\n * Handles comma-separated selectors, `::slotted()`, and `:host`.\n *\n * @param selectorRaw - The raw selector string (may be comma-separated).\n * @param scopeAttr - The attribute selector prefix.\n * @returns The scoped selector string.\n */\nfunction scopeSelector(selectorRaw: string, scopeAttr: string): string {\n return selectorRaw\n .split(',')\n .map((part) => {\n const trimmed = part.trim();\n\n // Replace ::slotted(*) with a direct descendant selector\n if (trimmed.startsWith('::slotted(') && trimmed.endsWith(')')) {\n const inner = trimmed.slice('::slotted('.length, -1).trim();\n return `${scopeAttr} ${inner}`;\n }\n\n // Replace :host with the scope attribute selector\n if (trimmed === ':host' || trimmed.startsWith(':host(') || trimmed.startsWith(':host ')) {\n return trimmed.replace(/^:host(\\([^)]*\\))?/, scopeAttr);\n }\n\n // Standard selector — prepend scope\n return `${scopeAttr} ${trimmed}`;\n })\n .join(', ');\n}\n\n/**\n * Handles transformation of an at-rule block. For container at-rules\n * (`@media`, `@supports`, `@layer`, `@container`) the inner rules are scoped.\n * Non-container at-rules (e.g. `@keyframes`, `@charset`) are passed through unchanged.\n *\n * @param atRuleText - The full at-rule text including its block.\n * @param scopeAttr - The attribute selector prefix.\n * @returns The transformed at-rule text.\n */\nfunction transformAtRule(atRuleText: string, scopeAttr: string): string {\n const containerAtRules = /^@(media|supports|layer|container|document)\\b/;\n const braceIndex = atRuleText.indexOf('{');\n\n if (braceIndex === -1 || !containerAtRules.test(atRuleText.trim())) {\n return atRuleText;\n }\n\n const head = atRuleText.slice(0, braceIndex + 1);\n const innerEnd = atRuleText.lastIndexOf('}');\n const inner = atRuleText.slice(braceIndex + 1, innerEnd);\n const tail = atRuleText.slice(innerEnd);\n\n return `${head}${transformCss(inner, scopeAttr)}${tail}`;\n}\n\n/**\n * Extracts a complete at-rule (potentially with a block) starting at index `start`.\n * Returns the text and the end index.\n */\nfunction extractAtRule(css: string, start: number): { text: string; end: number } {\n // Find either ';' (simple at-rule) or the matching '{...}' (block at-rule)\n let i = start;\n while (i < css.length && charAt(css, i) !== '{' && charAt(css, i) !== ';') {\n i++;\n }\n\n if (i >= css.length) {\n return { text: css.slice(start), end: css.length };\n }\n\n if (charAt(css, i) === ';') {\n return { text: css.slice(start, i + 1), end: i + 1 };\n }\n\n // Block at-rule — find matching closing brace\n const end = findMatchingBrace(css, i);\n if (end === -1) {\n return { text: css.slice(start), end: css.length };\n }\n\n return { text: css.slice(start, end + 1), end: end + 1 };\n}\n\n/**\n * Finds the index of the closing `}` that matches the `{` at `openIndex`.\n * Returns -1 if no matching brace is found.\n */\nfunction findMatchingBrace(css: string, openIndex: number): number {\n let depth = 0;\n for (let i = openIndex; i < css.length; i++) {\n const ch = charAt(css, i);\n if (ch === '{') depth++;\n else if (ch === '}') {\n depth--;\n if (depth === 0) return i;\n }\n }\n return -1;\n}\n","/**\n * @module injectLightStyles\n *\n * Core utility for injecting scoped `<style>` elements into `document.head`\n * to style light DOM slotted content in HELiX components. Uses the\n * `lightStyleRegistry` to deduplicate — each component type is injected at\n * most once per page lifetime.\n *\n * This is the primary mechanism for ensuring Drupal-slotted content inherits\n * proper typography and spacing when rendered into HELiX components.\n *\n * @example\n * ```ts\n * import { injectLightStyles } from '../../utilities/injectLightStyles.js';\n *\n * // In connectedCallback:\n * injectLightStyles('hx-card', 'p { font-size: var(--hx-font-size-md); }');\n * ```\n */\n\nimport { isStyleRegistered, registerStyle } from './lightStyleRegistry.js';\nimport { generateScopedSelectors } from './generateScopedSelectors.js';\n\n/**\n * Injects a scoped `<style>` element into `document.head` for light DOM content\n * belonging to `componentName`. The CSS is automatically scoped via\n * `generateScopedSelectors` so styles only apply inside\n * `[data-hx-styled=\"componentName\"]` wrappers.\n *\n * Safe to call multiple times — subsequent calls for the same `componentName`\n * are no-ops (deduplication via `lightStyleRegistry`).\n *\n * No-op in SSR environments where `document` is not defined.\n *\n * @param componentName - The component tag name used as the scope identifier\n * (e.g. `'hx-card'`). Must be unique per component type.\n * @param css - The raw CSS to scope and inject. Selectors will be prefixed with\n * `[data-hx-styled=\"componentName\"]`.\n */\nexport function injectLightStyles(componentName: string, css: string): void {\n if (typeof document === 'undefined') return;\n\n if (isStyleRegistered(componentName)) return;\n\n const scopedCss = generateScopedSelectors(componentName, css);\n\n const styleEl = document.createElement('style');\n styleEl.setAttribute('data-hx-light-styles', componentName);\n styleEl.textContent = scopedCss;\n\n document.head.appendChild(styleEl);\n registerStyle(componentName, styleEl);\n}\n","import { LitElement, html, type PropertyValues } from 'lit';\nimport '../../utilities/document-token-adoption.js';\nimport { customElement, property } from 'lit/decorators.js';\nimport { hxStyleScopeStyles } from './hx-style-scope.styles.js';\nimport { injectLightStyles } from '../../utilities/injectLightStyles.js';\n\n/**\n * A transparent wrapper component that injects scoped light DOM styles for its\n * slotted children. Designed for use in Drupal Twig templates where slotted\n * content lives in the light DOM and cannot inherit Shadow DOM styles.\n *\n * On connection, the component:\n * 1. Sets `data-hx-styled` on itself so scoped CSS selectors resolve correctly.\n * 2. Calls `injectLightStyles` to inject a `<style>` into `document.head` — once\n * per component type, deduplicated via `lightStyleRegistry`.\n *\n * The element renders as `display: contents` so it does not affect layout.\n *\n * @summary Scoped light DOM style injection wrapper for Drupal-slotted content.\n *\n * @tag hx-style-scope\n *\n * @slot - Default slot for any light DOM content that should receive scoped styles.\n *\n * @example\n * ```html\n * <hx-style-scope component=\"hx-card\">\n * <p>This paragraph will receive hx-card light DOM styles.</p>\n * </hx-style-scope>\n * ```\n *\n * @example Drupal Twig usage via macro\n * ```twig\n * {% import '@helix/hx-style-scope.macro.twig' as hx %}\n * {{ hx.scope('hx-card', content) }}\n * ```\n */\n@customElement('hx-style-scope')\nexport class HxStyleScope extends LitElement {\n static override styles = [hxStyleScopeStyles];\n\n /**\n * The component tag name whose light DOM styles should be injected.\n * Must match a registered component name (e.g. `'hx-card'`).\n *\n * When set, the element also applies `data-hx-styled` to itself so that\n * scoped CSS selectors generated by `generateScopedSelectors` resolve\n * correctly against slotted child elements.\n *\n * @attr component\n */\n @property({ type: String, reflect: true })\n component: string = '';\n\n /**\n * The CSS to inject as scoped light DOM styles. When provided alongside\n * `component`, this CSS will be scoped under `[data-hx-styled=\"component\"]`\n * and injected into `document.head` (once per component type).\n *\n * If omitted, `injectLightStyles` is still called but with an empty string,\n * which is a no-op for actual style injection.\n *\n * @attr light-css\n */\n @property({ type: String, attribute: 'light-css' })\n lightCss: string = '';\n\n override connectedCallback(): void {\n super.connectedCallback();\n this._applyScope();\n }\n\n protected override updated(changed: PropertyValues<this>): void {\n if (changed.has('component') || changed.has('lightCss')) {\n this._applyScope();\n }\n }\n\n /**\n * Applies the `data-hx-styled` attribute and triggers style injection.\n * Called on connect and whenever `component` or `lightCss` changes.\n */\n private _applyScope(): void {\n if (!this.component) return;\n\n // Set the scoping attribute so CSS selectors from generateScopedSelectors work.\n this.setAttribute('data-hx-styled', this.component);\n\n if (this.lightCss) {\n injectLightStyles(this.component, this.lightCss);\n }\n }\n\n override render() {\n return html`<slot></slot>`;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'hx-style-scope': HxStyleScope;\n }\n}\n"],"names":["hxStyleScopeStyles","css","lightStyleRegistry","isStyleRegistered","componentName","registerStyle","el","generateScopedSelectors","scopeAttr","transformCss","charAt","str","i","result","len","atBlock","extractAtRule","transformAtRule","selectorEnd","selectorRaw","bodyStart","bodyEnd","findMatchingBrace","body","scopedSelector","scopeSelector","part","trimmed","inner","atRuleText","containerAtRules","braceIndex","head","innerEnd","tail","start","end","openIndex","depth","ch","injectLightStyles","scopedCss","styleEl","HxStyleScope","LitElement","changed","html","__decorateClass","property","customElement"],"mappings":"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"}
|
|
@@ -6,7 +6,8 @@ import { ifDefined as n } from "lit/directives/if-defined.js";
|
|
|
6
6
|
import { live as g } from "lit/directives/live.js";
|
|
7
7
|
import { F as b } from "./FormMixin-Bjvw20G5.js";
|
|
8
8
|
import { d as y } from "./dev-warn-YlwPHjtX.js";
|
|
9
|
-
import { c as S
|
|
9
|
+
import { c as S } from "./id-counter-PTgF-zcG.js";
|
|
10
|
+
import { H as w } from "./helix-element-CZvaIEQP.js";
|
|
10
11
|
var $ = Object.defineProperty, x = (e, t, a, l) => {
|
|
11
12
|
for (var o = void 0, d = e.length - 1, p; d >= 0; d--)
|
|
12
13
|
(p = e[d]) && (o = p(t, a, o) || o);
|
|
@@ -573,4 +574,4 @@ export {
|
|
|
573
574
|
I as F,
|
|
574
575
|
r as H
|
|
575
576
|
};
|
|
576
|
-
//# sourceMappingURL=hx-text-input-
|
|
577
|
+
//# sourceMappingURL=hx-text-input-Zuodg9s_.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hx-text-input-Scyeefec.js","sources":["../../src/mixins/FocusMixin.ts","../../src/components/hx-text-input/hx-text-input.styles.ts","../../src/components/hx-text-input/hx-text-input.ts"],"sourcesContent":["import { LitElement, type PropertyValues } from 'lit';\nimport { property } from 'lit/decorators.js';\n\n/**\n * Mixin constructor type — must use `any[]` args per TypeScript mixin requirements (TS2545).\n * @internal\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype Constructor<T = object> = new (...args: any[]) => T;\n\n/**\n * The public interface added by FocusMixin.\n * Consumers can use this type for typed references.\n *\n * @public\n */\nexport interface FocusMixinInterface {\n /** True when the component host has focus (including descendant focus). */\n readonly focused: boolean;\n /** True when focus arrived via keyboard (not pointer). */\n readonly focusedVisible: boolean;\n}\n\n/**\n * FocusMixin — standardized focus delegation for HELiX web components.\n *\n * Modeled after Lion's `FocusMixin` and Material Web's `mixinDelegatesAria`.\n * Provides:\n * - `_focusableNode` getter for subclasses to declare the inner focusable element\n * - `focused` reflected attribute as a styling hook for `:host([focused])`\n * - `focusedVisible` reflected attribute for keyboard-only focus rings\n * - Delegated `focus()` / `blur()` routing to the inner element\n * - Autofocus support after first render\n *\n * ### Usage\n *\n * ```ts\n * class MyInput extends FocusMixin(HelixElement) {\n * @query('input') private _input: HTMLInputElement | undefined;\n *\n * protected override get _focusableNode(): HTMLElement | null {\n * return this._input ?? null;\n * }\n * }\n * ```\n *\n * @param superClass - A Lit element constructor to mix into\n * @returns A new class with focus management capabilities\n *\n * @public\n */\nexport const FocusMixin = <T extends Constructor<LitElement>>(superClass: T) => {\n class FocusMixinClass extends superClass implements FocusMixinInterface {\n /**\n * True when the component or any of its descendants has focus.\n * Reflects to attribute `focused` for CSS selector use.\n *\n * @attr focused\n */\n @property({ type: Boolean, reflect: true })\n focused = false;\n\n /**\n * True when focus was reached via keyboard navigation (Tab / Shift+Tab)\n * rather than pointer interaction. Reflects to attribute `focused-visible`\n * for rendering keyboard-only focus rings.\n *\n * @attr focused-visible\n */\n @property({ type: Boolean, reflect: true, attribute: 'focused-visible' })\n focusedVisible = false;\n\n /** @internal — whether a `focus()` call was queued before first render */\n private _focusPending = false;\n\n /**\n * @internal — tracks whether the most recent interaction toward this\n * element was via pointer (true) or keyboard (false).\n *\n * Set to false on `pointerdown` so that the subsequent `focusin` can\n * determine it is not keyboard-initiated.\n */\n private _lastInteractionWasPointer = false;\n\n /**\n * Returns the inner focusable element that `focus()` and `blur()` will\n * delegate to. Subclasses MUST override this getter.\n *\n * @returns The inner focusable element, or `null` if not yet rendered.\n * @internal\n */\n protected get _focusableNode(): HTMLElement | null {\n return null;\n }\n\n /**\n * Delegates focus to the inner focusable element.\n * If the element has not yet rendered, queues the focus call for\n * `firstUpdated`.\n */\n override focus(options?: FocusOptions): void {\n const node = this._focusableNode;\n if (node !== null) {\n node.focus(options);\n } else {\n this._focusPending = true;\n }\n }\n\n /** Delegates blur to the inner focusable element. */\n override blur(): void {\n this._focusableNode?.blur();\n }\n\n override connectedCallback(): void {\n super.connectedCallback();\n this.addEventListener('focusin', this._handleFocusIn);\n this.addEventListener('focusout', this._handleFocusOut);\n this.addEventListener('pointerdown', this._handlePointerDown);\n this.addEventListener('keydown', this._handleKeyDown);\n }\n\n override disconnectedCallback(): void {\n super.disconnectedCallback();\n this.removeEventListener('focusin', this._handleFocusIn);\n this.removeEventListener('focusout', this._handleFocusOut);\n this.removeEventListener('pointerdown', this._handlePointerDown);\n this.removeEventListener('keydown', this._handleKeyDown);\n }\n\n override firstUpdated(changedProperties: PropertyValues): void {\n super.firstUpdated(changedProperties);\n\n // Honour the native `autofocus` attribute after the first render cycle\n if (this.hasAttribute('autofocus')) {\n this.focus();\n }\n\n // Flush any focus() call that arrived before the shadow DOM was stamped\n if (this._focusPending) {\n this._focusPending = false;\n this.focus();\n }\n }\n\n // ─── Private Event Handlers ────────────────────────────────────────────────\n\n /** @internal */\n private _handleFocusIn = (): void => {\n this.focused = true;\n // focusedVisible is true only when focus arrived via keyboard\n this.focusedVisible = !this._lastInteractionWasPointer;\n };\n\n /** @internal */\n private _handleFocusOut = (): void => {\n this.focused = false;\n this.focusedVisible = false;\n // Reset for the next interaction cycle\n this._lastInteractionWasPointer = false;\n };\n\n /**\n * Marks the next focusin as pointer-initiated.\n * @internal\n */\n private _handlePointerDown = (): void => {\n this._lastInteractionWasPointer = true;\n };\n\n /**\n * Ensures that keyboard navigation (Tab / Shift+Tab arriving from outside)\n * is correctly flagged as keyboard-initiated even when no prior `pointerdown`\n * fired on this element.\n *\n * This fires BEFORE `focusin` so the flag is in the correct state when\n * `_handleFocusIn` runs.\n * @internal\n */\n private _handleKeyDown = (): void => {\n this._lastInteractionWasPointer = false;\n };\n }\n\n return FocusMixinClass as unknown as T & Constructor<FocusMixinInterface>;\n};\n","import { css } from 'lit';\n\nexport const helixTextInputStyles = css`\n :host {\n display: block;\n }\n\n :host([disabled]) {\n opacity: var(--hx-opacity-disabled, 0.5);\n pointer-events: none;\n }\n\n /*\n * Attribute-based focus hooks set by FocusMixin.\n * :host([focused]) — fires whenever the component or any descendant has focus.\n * :host([focused-visible]) — fires only for keyboard-initiated focus.\n * These complement the :focus-within rules on .field__input-wrapper and are\n * exposed as theming hooks for consumers who target the host element.\n */\n :host([focused]) .field__input-wrapper {\n border-color: var(\n --hx-input-focus-ring-color,\n var(--hx-focus-ring-color, var(--hx-color-primary-400, #60a5fa))\n );\n box-shadow: 0 0 0 var(--hx-focus-ring-width, 2px)\n color-mix(\n in srgb,\n var(\n --hx-input-focus-ring-color,\n var(--hx-focus-ring-color, var(--hx-color-primary-400, #60a5fa))\n )\n calc(var(--hx-focus-ring-opacity, 0.25) * 100%),\n transparent\n );\n }\n\n :host([focused-visible]) .field__input-wrapper {\n border-color: var(\n --hx-input-focus-ring-color,\n var(--hx-focus-ring-color, var(--hx-color-primary-400, #60a5fa))\n );\n box-shadow: 0 0 0 var(--hx-focus-ring-width, 2px)\n color-mix(\n in srgb,\n var(\n --hx-input-focus-ring-color,\n var(--hx-focus-ring-color, var(--hx-color-primary-400, #60a5fa))\n )\n calc(var(--hx-focus-ring-opacity, 0.25) * 100%),\n transparent\n );\n }\n\n * {\n box-sizing: border-box;\n }\n\n .field {\n display: flex;\n flex-direction: column;\n gap: var(--hx-space-1, 0.25rem);\n font-family: var(--hx-input-font-family, var(--hx-font-family-sans, sans-serif));\n }\n\n /* ─── Label ─── */\n\n .field__label-wrapper {\n display: contents;\n }\n\n .field__label {\n display: flex;\n align-items: baseline;\n gap: var(--hx-space-1, 0.25rem);\n font-size: var(--hx-font-size-sm, 0.875rem);\n font-weight: var(--hx-font-weight-medium, 500);\n color: var(--hx-input-label-color, var(--hx-color-neutral-700, #343a40));\n line-height: var(--hx-line-height-normal, 1.5);\n }\n\n .field__required-marker {\n color: var(--hx-input-error-color, var(--hx-color-error-text, #b91c1c));\n font-weight: var(--hx-font-weight-bold, 700);\n }\n\n /* ─── Input Wrapper ─── */\n\n .field__input-wrapper {\n display: flex;\n align-items: center;\n border: var(--hx-border-width-thin, 1px) solid\n var(--hx-input-border-color, var(--hx-color-neutral-300, #ced4da));\n border-radius: var(--hx-input-border-radius, var(--hx-border-radius-md, 0.375rem));\n background-color: var(--hx-input-bg, var(--hx-color-neutral-0, #ffffff));\n transition:\n border-color var(--hx-transition-fast, 150ms ease),\n box-shadow var(--hx-transition-fast, 150ms ease);\n overflow: hidden;\n }\n\n .field__input-wrapper:focus-within {\n border-color: var(\n --hx-input-focus-ring-color,\n var(--hx-focus-ring-color, var(--hx-color-primary-400, #60a5fa))\n );\n box-shadow: 0 0 0 var(--hx-focus-ring-width, 2px)\n color-mix(\n in srgb,\n var(\n --hx-input-focus-ring-color,\n var(--hx-focus-ring-color, var(--hx-color-primary-400, #60a5fa))\n )\n calc(var(--hx-focus-ring-opacity, 0.25) * 100%),\n transparent\n );\n }\n\n /* ─── Error State ─── */\n\n .field--error .field__input-wrapper {\n border-color: var(--hx-input-error-color, var(--hx-color-error-500, #dc3545));\n }\n\n .field--error .field__input-wrapper:focus-within {\n border-color: var(--hx-input-error-color, var(--hx-color-error-500, #dc3545));\n box-shadow: 0 0 0 var(--hx-focus-ring-width, 2px)\n color-mix(\n in srgb,\n var(--hx-input-error-color, var(--hx-color-error-500, #dc3545))\n calc(var(--hx-focus-ring-opacity, 0.25) * 100%),\n transparent\n );\n }\n\n /* ─── Slots (Prefix / Suffix) ─── */\n\n .field__prefix,\n .field__suffix {\n display: flex;\n align-items: center;\n color: var(--hx-color-neutral-500, #6c757d);\n flex-shrink: 0;\n }\n\n /* Only add padding when slot has content — avoids phantom space on empty slots */\n .field__prefix--filled {\n padding: 0 var(--hx-space-3, 0.75rem);\n }\n\n .field__suffix--filled {\n padding: 0 var(--hx-space-3, 0.75rem);\n }\n\n /* ─── Native Input ─── */\n\n .field__input {\n flex: 1;\n border: none;\n outline: none;\n background: transparent;\n padding: var(--hx-space-2, 0.5rem) var(--hx-space-3, 0.75rem);\n font-family: inherit;\n font-size: var(--hx-font-size-md, 1rem);\n color: var(--hx-input-color, var(--hx-color-neutral-800, #212529));\n line-height: var(--hx-line-height-normal, 1.5);\n min-height: var(--hx-size-10, 2.5rem);\n width: 100%;\n }\n\n .field__input::placeholder {\n color: var(--hx-color-neutral-400, #adb5bd);\n }\n\n .field__input:disabled {\n cursor: not-allowed;\n }\n\n /* ─── Size Variants ─── */\n\n .field--size-sm .field__input {\n padding: var(--hx-space-1, 0.25rem) var(--hx-space-2, 0.5rem);\n min-height: var(--hx-size-8, 2rem);\n font-size: var(--hx-input-sm-font-size, 0.875rem);\n }\n\n .field--size-md .field__input {\n /* md is the default — no overrides needed */\n }\n\n .field--size-lg .field__input {\n padding: var(--hx-space-3, 0.75rem) var(--hx-space-4, 1rem);\n min-height: var(--hx-size-12, 3rem);\n font-size: var(--hx-input-lg-font-size, 1.125rem);\n }\n\n /* ─── Help Text & Error Messages ─── */\n\n .field__help-text {\n font-size: var(--hx-font-size-xs, 0.75rem);\n color: var(--hx-color-neutral-500, #6c757d);\n line-height: var(--hx-line-height-normal, 1.5);\n }\n\n .field__error {\n font-size: var(--hx-font-size-xs, 0.75rem);\n color: var(--hx-input-error-color, var(--hx-color-error-text, #b91c1c));\n line-height: var(--hx-line-height-normal, 1.5);\n }\n\n /* ─── Motion ─── */\n\n @media (prefers-reduced-motion: reduce) {\n .field__input-wrapper {\n transition: none;\n }\n }\n`;\n","import { html, nothing, type PropertyValues } from 'lit';\nimport '../../utilities/document-token-adoption.js';\nimport { customElement, property, query, state } from 'lit/decorators.js';\nimport { classMap } from 'lit/directives/class-map.js';\nimport { ifDefined } from 'lit/directives/if-defined.js';\nimport { live } from 'lit/directives/live.js';\nimport { HelixElement, createIdCounter } from '../../base/index.js';\nimport { FocusMixin } from '../../mixins/index.js';\nimport { FormMixin } from '../../mixins/FormMixin.js';\nimport { helixTextInputStyles } from './hx-text-input.styles.js';\nimport { devWarn } from '../../utils/dev-warn.js';\n\n// Module-level counter for stable, SSR-compatible IDs (avoids Math.random() hydration mismatch)\nconst _nextTextInputId = createIdCounter('hx-text-input');\n\n/**\n * A text input component with label, validation, and form association.\n * Supports accessible labeling via `label` property, `aria-label` attribute, or the `label` slot.\n * Uses `aria-invalid` and `aria-describedby` on the native input for screen reader support. Native `required` provides implicit aria-required mapping per HTML-AAM.\n * Error messages are announced via `role=\"alert\"`. Keyboard navigation follows native input behavior.\n *\n * @summary Form-associated text input with built-in label, error, and help text.\n *\n * @tag hx-text-input\n *\n * @slot label - Custom label content (overrides the label property). Use for Drupal Form API rendered labels.\n * @slot prefix - Content rendered before the input (e.g., icon).\n * @slot suffix - Content rendered after the input (e.g., icon or button).\n * @slot help-text - Custom help text content (overrides the helpText property).\n * @slot error - Custom error content (overrides the error property). Use for Drupal Form API rendered errors.\n *\n * @fires {CustomEvent<{value: string}>} hx-input - Dispatched on every keystroke as the user types.\n * @fires {CustomEvent<{value: string}>} hx-change - Dispatched when the input loses focus after its value changed.\n *\n * @csspart field - The outer field container.\n * @csspart label - The label element.\n * @csspart input-wrapper - The wrapper around prefix, input, and suffix.\n * @csspart input - The native input element.\n * @csspart help-text - The help text container.\n * @csspart error - The error message container.\n *\n * @cssprop [--hx-input-bg=var(--hx-color-neutral-0)] - Input background color.\n * @cssprop [--hx-input-color=var(--hx-color-neutral-800)] - Input text color.\n * @cssprop [--hx-input-border-color=var(--hx-color-neutral-300)] - Input border color.\n * @cssprop [--hx-input-border-radius=var(--hx-border-radius-md)] - Input border radius.\n * @cssprop [--hx-input-font-family=var(--hx-font-family-sans)] - Input font family.\n * @cssprop [--hx-input-focus-ring-color=var(--hx-focus-ring-color)] - Focus ring color.\n * @cssprop [--hx-input-error-color=var(--hx-color-error-500)] - Error state color.\n * @cssprop [--hx-input-label-color=var(--hx-color-neutral-700)] - Label text color.\n * @cssprop [--hx-input-sm-font-size=0.875rem] - Font size for the sm size variant.\n * @cssprop [--hx-input-lg-font-size=1.125rem] - Font size for the lg size variant.\n */\n@customElement('hx-text-input')\nexport class HelixTextInput extends FocusMixin(FormMixin(HelixElement)) {\n static override styles = [helixTextInputStyles];\n\n // ─── Form Association ───\n\n /** @internal */\n static override formAssociated = true;\n\n // ─── Properties ───\n\n /**\n * The visible label text for the input.\n * @attr label\n */\n @property({ type: String })\n label = '';\n\n /**\n * Placeholder text shown when the input is empty.\n * @attr placeholder\n */\n @property({ type: String })\n placeholder = '';\n\n /**\n * The current value of the input.\n * @attr value\n * @remarks When `null` is passed programmatically, Lit coerces it to an empty string.\n * This is expected behavior — the input treats null as equivalent to an empty string value.\n */\n @property({ type: String })\n value = '';\n\n /**\n * The type of the native input element.\n * @attr type\n */\n @property({ type: String })\n type: 'text' | 'email' | 'password' | 'tel' | 'url' | 'search' | 'number' | 'date' = 'text';\n\n /**\n * Whether the input is required for form submission.\n * @attr required\n */\n @property({ type: Boolean, reflect: true })\n required = false;\n\n /**\n * Whether the input is disabled.\n * @attr disabled\n */\n @property({ type: Boolean, reflect: true })\n disabled = false;\n\n /**\n * Error message to display. When set, the input enters an error state.\n * @attr error\n */\n @property({ type: String })\n error = '';\n\n /**\n * Help text displayed below the input for guidance.\n * @attr help-text\n */\n @property({ type: String, attribute: 'help-text' })\n helpText = '';\n\n /**\n * The name of the input, used for form submission.\n * @attr name\n */\n @property({ type: String })\n name = '';\n\n /**\n * Accessible name for screen readers, if different from the visible label.\n * @attr aria-label\n */\n @property({ type: String, attribute: 'aria-label' })\n override ariaLabel: string | null = null;\n\n /**\n * Whether the input is read-only.\n * @attr readonly\n */\n @property({ type: Boolean, reflect: true })\n readonly = false;\n\n /**\n * Minimum number of characters allowed.\n * @attr minlength\n */\n @property({ type: Number })\n minlength: number | undefined = undefined;\n\n /**\n * Maximum number of characters allowed.\n * @attr maxlength\n */\n @property({ type: Number })\n maxlength: number | undefined = undefined;\n\n /**\n * A regular expression pattern the value must match for form validation.\n * @attr pattern\n */\n @property({ type: String })\n pattern = '';\n\n /**\n * Hint for the browser's autocomplete feature. Accepts standard HTML autocomplete values.\n * @attr autocomplete\n */\n @property({ type: String })\n autocomplete = '';\n\n /**\n * Validation message shown when the field is required but empty.\n * @attr required-message\n */\n @property({ attribute: 'required-message' })\n requiredMessage = 'This field is required.';\n\n /**\n * Visual size of the input field.\n * @attr hx-size\n */\n @property({ type: String, attribute: 'hx-size', reflect: true })\n size: 'sm' | 'md' | 'lg' = 'md';\n\n // ─── Internal References ───\n\n /** @internal */\n @query('.field__input')\n private _input: HTMLInputElement | undefined;\n\n // ─── FocusMixin integration ───\n\n /**\n * Declares the inner focusable element for FocusMixin delegation.\n * @internal\n */\n protected get _focusableNode(): HTMLElement | null {\n return this._input ?? null;\n }\n\n // ─── Slot Tracking ───\n\n /** @internal */\n @state() private _hasLabelSlot = false;\n /** @internal */\n @state() private _hasErrorSlot = false;\n /** @internal */\n @state() private _hasPrefixSlot = false;\n /** @internal */\n @state() private _hasSuffixSlot = false;\n /** @internal */\n @state() private _hasHelpTextSlot = false;\n\n /** @internal */\n private _handleLabelSlotChange(e: Event): void {\n const slot = e.target as HTMLSlotElement;\n this._hasLabelSlot = slot.assignedElements().length > 0;\n if (this._hasLabelSlot) {\n const slottedLabel = slot.assignedElements()[0];\n if (slottedLabel && !slottedLabel.id) {\n slottedLabel.id = `${this._inputId}-slotted-label`;\n }\n }\n }\n\n /** @internal */\n private _handleErrorSlotChange(e: Event): void {\n const slot = e.target as HTMLSlotElement;\n this._hasErrorSlot = slot.assignedElements().length > 0;\n }\n\n /** @internal */\n private _handlePrefixSlotChange(e: Event): void {\n const slot = e.target as HTMLSlotElement;\n this._hasPrefixSlot = slot.assignedElements().length > 0;\n }\n\n /** @internal */\n private _handleSuffixSlotChange(e: Event): void {\n const slot = e.target as HTMLSlotElement;\n this._hasSuffixSlot = slot.assignedElements().length > 0;\n }\n\n /** @internal */\n private _handleHelpTextSlotChange(e: Event): void {\n const slot = e.target as HTMLSlotElement;\n this._hasHelpTextSlot = slot.assignedElements().length > 0;\n }\n\n // ─── Lifecycle ───\n\n override updated(changedProperties: PropertyValues<this>): void {\n super.updated(changedProperties);\n if (changedProperties.has('value')) {\n this._internals.setFormValue(this.value);\n }\n if (\n (changedProperties.has('value') || changedProperties.has('type')) &&\n this.type === 'number' &&\n this.value !== '' &&\n isNaN(Number(this.value))\n ) {\n devWarn(\n 'hx-text-input',\n `type=\"number\" received non-numeric value \"${this.value}\". Browser will display empty input.`,\n );\n }\n }\n\n // ─── Form Integration ───\n\n /** Returns the associated form element, if any. */\n override get form(): HTMLFormElement | null {\n return this._internals.form;\n }\n\n /** Returns the validation message. */\n override get validationMessage(): string {\n return this._internals.validationMessage;\n }\n\n /** Returns the ValidityState object. */\n override get validity(): ValidityState {\n return this._internals.validity;\n }\n\n /** @internal */\n protected _updateValidity(): void {\n if (this.required && !this.value) {\n this._internals.setValidity(\n { valueMissing: true },\n this.error || this.requiredMessage,\n this._input,\n );\n } else if (\n this.minlength !== undefined &&\n this.value.length > 0 &&\n this.value.length < this.minlength\n ) {\n this._internals.setValidity(\n { tooShort: true },\n this.error || `Please lengthen this text to ${this.minlength} characters or more.`,\n this._input,\n );\n } else if (this.maxlength !== undefined && this.value.length > this.maxlength) {\n this._internals.setValidity(\n { tooLong: true },\n this.error || `Please shorten this text to ${this.maxlength} characters or fewer.`,\n this._input,\n );\n } else {\n this._internals.setValidity({});\n }\n }\n\n // ─── Form Lifecycle Hooks ───\n\n protected override _onFormReset(): void {\n this.value = '';\n this._internals.setFormValue('');\n this._resetInteractionState();\n }\n\n protected override _onFormStateRestore(\n state: File | string | FormData | null,\n _mode: 'restore' | 'autocomplete',\n ): void {\n if (typeof state === 'string') {\n this.value = state;\n }\n }\n\n protected override _onFormDisabled(disabled: boolean): void {\n this.disabled = disabled;\n }\n\n // ─── Event Handling ───\n\n /** @internal */\n private _handleInput(e: Event): void {\n const target = e.target as HTMLInputElement;\n this.value = target.value;\n this._internals.setFormValue(this.value);\n this._handleInteractionInput();\n\n /**\n * Dispatched on every keystroke as the user types.\n * @event hx-input\n */\n this.dispatchEvent(\n new CustomEvent<{ value: string }>('hx-input', {\n bubbles: true,\n composed: true,\n detail: { value: this.value },\n }),\n );\n }\n\n /** @internal */\n private _handleChange(e: Event): void {\n const target = e.target as HTMLInputElement;\n this.value = target.value;\n this._internals.setFormValue(this.value);\n this._handleInteractionBlur();\n\n /**\n * Dispatched when the input loses focus after its value changed.\n * @event hx-change\n */\n this.dispatchEvent(\n new CustomEvent<{ value: string }>('hx-change', {\n bubbles: true,\n composed: true,\n detail: { value: this.value },\n }),\n );\n }\n\n // ─── Public Methods ───\n\n /** Selects all text in the input. */\n select(): void {\n this._input?.select();\n }\n\n // ─── Render ───\n\n /** @internal */\n private _inputId = _nextTextInputId();\n /** @internal */\n private _helpTextId = `${this._inputId}-help`;\n /** @internal */\n private _errorId = `${this._inputId}-error`;\n\n override render() {\n const hasError = !!this.error || this._hasErrorSlot;\n\n const fieldClasses = {\n field: true,\n 'field--error': hasError,\n 'field--disabled': this.disabled,\n 'field--required': this.required,\n [`field--size-${this.size}`]: true,\n };\n\n const describedBy =\n [\n hasError ? this._errorId : null,\n this.helpText || this._hasHelpTextSlot ? this._helpTextId : null,\n ]\n .filter(Boolean)\n .join(' ') || undefined;\n\n return html`\n <div part=\"field\" class=${classMap(fieldClasses)}>\n <div class=\"field__label-wrapper\">\n <slot name=\"label\" @slotchange=${this._handleLabelSlotChange}>\n ${this.label\n ? html`\n <label part=\"label\" class=\"field__label\" for=${this._inputId}>\n ${this.label}\n ${this.required\n ? html`<span class=\"field__required-marker\" aria-hidden=\"true\">*</span>`\n : nothing}\n </label>\n `\n : nothing}\n </slot>\n </div>\n\n <div part=\"input-wrapper\" class=\"field__input-wrapper\">\n <span\n class=${classMap({\n field__prefix: true,\n 'field__prefix--filled': this._hasPrefixSlot,\n })}\n >\n <slot name=\"prefix\" @slotchange=${this._handlePrefixSlotChange}></slot>\n </span>\n\n <input\n part=\"input\"\n class=\"field__input\"\n id=${this._inputId}\n type=${this.type}\n .value=${live(this.value)}\n placeholder=${ifDefined(this.placeholder || undefined)}\n ?required=${this.required}\n ?disabled=${this.disabled}\n ?readonly=${this.readonly}\n name=${ifDefined(this.name || undefined)}\n minlength=${ifDefined(this.minlength)}\n maxlength=${ifDefined(this.maxlength)}\n pattern=${ifDefined(this.pattern || undefined)}\n autocomplete=${ifDefined(this.autocomplete || undefined)}\n aria-label=${ifDefined(this.ariaLabel || undefined)}\n aria-labelledby=${ifDefined(\n this._hasLabelSlot ? `${this._inputId}-slotted-label` : undefined,\n )}\n aria-invalid=${hasError ? 'true' : nothing}\n aria-describedby=${ifDefined(describedBy)}\n @input=${this._handleInput}\n @change=${this._handleChange}\n />\n\n <span\n class=${classMap({\n field__suffix: true,\n 'field__suffix--filled': this._hasSuffixSlot,\n })}\n >\n <slot name=\"suffix\" @slotchange=${this._handleSuffixSlotChange}></slot>\n </span>\n </div>\n\n ${hasError\n ? html`<div\n part=\"error\"\n class=\"field__error\"\n id=${this._errorId}\n role=\"alert\"\n aria-atomic=\"true\"\n >\n <slot name=\"error\" @slotchange=${this._handleErrorSlotChange}>${this.error}</slot>\n </div>`\n : html`<slot name=\"error\" @slotchange=${this._handleErrorSlotChange}></slot>`}\n ${this._hasHelpTextSlot\n ? html`\n <div part=\"help-text\" class=\"field__help-text\" id=${this._helpTextId}>\n <slot name=\"help-text\" @slotchange=${this._handleHelpTextSlotChange}>\n ${this.helpText}\n </slot>\n </div>\n `\n : this.helpText && !hasError\n ? html`\n <div part=\"help-text\" class=\"field__help-text\" id=${this._helpTextId}>\n <slot name=\"help-text\" @slotchange=${this._handleHelpTextSlotChange}>\n ${this.helpText}\n </slot>\n </div>\n `\n : html`<slot name=\"help-text\" @slotchange=${this._handleHelpTextSlotChange}></slot>`}\n </div>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'hx-text-input': HelixTextInput;\n }\n}\n\n/** Primary type alias for hx-text-input */\nexport type HxTextInput = HelixTextInput;\n\n/** @deprecated Use HxTextInput instead */\nexport type WcTextInput = HelixTextInput;\n"],"names":["FocusMixin","superClass","FocusMixinClass","options","node","_a","changedProperties","__decorateClass","property","helixTextInputStyles","css","_nextTextInputId","createIdCounter","HelixTextInput","FormMixin","HelixElement","slot","slottedLabel","devWarn","state","_mode","disabled","target","hasError","fieldClasses","describedBy","html","classMap","nothing","live","ifDefined","query","customElement"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"hx-text-input-Zuodg9s_.js","sources":["../../src/mixins/FocusMixin.ts","../../src/components/hx-text-input/hx-text-input.styles.ts","../../src/components/hx-text-input/hx-text-input.ts"],"sourcesContent":["import { LitElement, type PropertyValues } from 'lit';\nimport { property } from 'lit/decorators.js';\n\n/**\n * Mixin constructor type — must use `any[]` args per TypeScript mixin requirements (TS2545).\n * @internal\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype Constructor<T = object> = new (...args: any[]) => T;\n\n/**\n * The public interface added by FocusMixin.\n * Consumers can use this type for typed references.\n *\n * @public\n */\nexport interface FocusMixinInterface {\n /** True when the component host has focus (including descendant focus). */\n readonly focused: boolean;\n /** True when focus arrived via keyboard (not pointer). */\n readonly focusedVisible: boolean;\n}\n\n/**\n * FocusMixin — standardized focus delegation for HELiX web components.\n *\n * Modeled after Lion's `FocusMixin` and Material Web's `mixinDelegatesAria`.\n * Provides:\n * - `_focusableNode` getter for subclasses to declare the inner focusable element\n * - `focused` reflected attribute as a styling hook for `:host([focused])`\n * - `focusedVisible` reflected attribute for keyboard-only focus rings\n * - Delegated `focus()` / `blur()` routing to the inner element\n * - Autofocus support after first render\n *\n * ### Usage\n *\n * ```ts\n * class MyInput extends FocusMixin(HelixElement) {\n * @query('input') private _input: HTMLInputElement | undefined;\n *\n * protected override get _focusableNode(): HTMLElement | null {\n * return this._input ?? null;\n * }\n * }\n * ```\n *\n * @param superClass - A Lit element constructor to mix into\n * @returns A new class with focus management capabilities\n *\n * @public\n */\nexport const FocusMixin = <T extends Constructor<LitElement>>(superClass: T) => {\n class FocusMixinClass extends superClass implements FocusMixinInterface {\n /**\n * True when the component or any of its descendants has focus.\n * Reflects to attribute `focused` for CSS selector use.\n *\n * @attr focused\n */\n @property({ type: Boolean, reflect: true })\n focused = false;\n\n /**\n * True when focus was reached via keyboard navigation (Tab / Shift+Tab)\n * rather than pointer interaction. Reflects to attribute `focused-visible`\n * for rendering keyboard-only focus rings.\n *\n * @attr focused-visible\n */\n @property({ type: Boolean, reflect: true, attribute: 'focused-visible' })\n focusedVisible = false;\n\n /** @internal — whether a `focus()` call was queued before first render */\n private _focusPending = false;\n\n /**\n * @internal — tracks whether the most recent interaction toward this\n * element was via pointer (true) or keyboard (false).\n *\n * Set to false on `pointerdown` so that the subsequent `focusin` can\n * determine it is not keyboard-initiated.\n */\n private _lastInteractionWasPointer = false;\n\n /**\n * Returns the inner focusable element that `focus()` and `blur()` will\n * delegate to. Subclasses MUST override this getter.\n *\n * @returns The inner focusable element, or `null` if not yet rendered.\n * @internal\n */\n protected get _focusableNode(): HTMLElement | null {\n return null;\n }\n\n /**\n * Delegates focus to the inner focusable element.\n * If the element has not yet rendered, queues the focus call for\n * `firstUpdated`.\n */\n override focus(options?: FocusOptions): void {\n const node = this._focusableNode;\n if (node !== null) {\n node.focus(options);\n } else {\n this._focusPending = true;\n }\n }\n\n /** Delegates blur to the inner focusable element. */\n override blur(): void {\n this._focusableNode?.blur();\n }\n\n override connectedCallback(): void {\n super.connectedCallback();\n this.addEventListener('focusin', this._handleFocusIn);\n this.addEventListener('focusout', this._handleFocusOut);\n this.addEventListener('pointerdown', this._handlePointerDown);\n this.addEventListener('keydown', this._handleKeyDown);\n }\n\n override disconnectedCallback(): void {\n super.disconnectedCallback();\n this.removeEventListener('focusin', this._handleFocusIn);\n this.removeEventListener('focusout', this._handleFocusOut);\n this.removeEventListener('pointerdown', this._handlePointerDown);\n this.removeEventListener('keydown', this._handleKeyDown);\n }\n\n override firstUpdated(changedProperties: PropertyValues): void {\n super.firstUpdated(changedProperties);\n\n // Honour the native `autofocus` attribute after the first render cycle\n if (this.hasAttribute('autofocus')) {\n this.focus();\n }\n\n // Flush any focus() call that arrived before the shadow DOM was stamped\n if (this._focusPending) {\n this._focusPending = false;\n this.focus();\n }\n }\n\n // ─── Private Event Handlers ────────────────────────────────────────────────\n\n /** @internal */\n private _handleFocusIn = (): void => {\n this.focused = true;\n // focusedVisible is true only when focus arrived via keyboard\n this.focusedVisible = !this._lastInteractionWasPointer;\n };\n\n /** @internal */\n private _handleFocusOut = (): void => {\n this.focused = false;\n this.focusedVisible = false;\n // Reset for the next interaction cycle\n this._lastInteractionWasPointer = false;\n };\n\n /**\n * Marks the next focusin as pointer-initiated.\n * @internal\n */\n private _handlePointerDown = (): void => {\n this._lastInteractionWasPointer = true;\n };\n\n /**\n * Ensures that keyboard navigation (Tab / Shift+Tab arriving from outside)\n * is correctly flagged as keyboard-initiated even when no prior `pointerdown`\n * fired on this element.\n *\n * This fires BEFORE `focusin` so the flag is in the correct state when\n * `_handleFocusIn` runs.\n * @internal\n */\n private _handleKeyDown = (): void => {\n this._lastInteractionWasPointer = false;\n };\n }\n\n return FocusMixinClass as unknown as T & Constructor<FocusMixinInterface>;\n};\n","import { css } from 'lit';\n\nexport const helixTextInputStyles = css`\n :host {\n display: block;\n }\n\n :host([disabled]) {\n opacity: var(--hx-opacity-disabled, 0.5);\n pointer-events: none;\n }\n\n /*\n * Attribute-based focus hooks set by FocusMixin.\n * :host([focused]) — fires whenever the component or any descendant has focus.\n * :host([focused-visible]) — fires only for keyboard-initiated focus.\n * These complement the :focus-within rules on .field__input-wrapper and are\n * exposed as theming hooks for consumers who target the host element.\n */\n :host([focused]) .field__input-wrapper {\n border-color: var(\n --hx-input-focus-ring-color,\n var(--hx-focus-ring-color, var(--hx-color-primary-400, #60a5fa))\n );\n box-shadow: 0 0 0 var(--hx-focus-ring-width, 2px)\n color-mix(\n in srgb,\n var(\n --hx-input-focus-ring-color,\n var(--hx-focus-ring-color, var(--hx-color-primary-400, #60a5fa))\n )\n calc(var(--hx-focus-ring-opacity, 0.25) * 100%),\n transparent\n );\n }\n\n :host([focused-visible]) .field__input-wrapper {\n border-color: var(\n --hx-input-focus-ring-color,\n var(--hx-focus-ring-color, var(--hx-color-primary-400, #60a5fa))\n );\n box-shadow: 0 0 0 var(--hx-focus-ring-width, 2px)\n color-mix(\n in srgb,\n var(\n --hx-input-focus-ring-color,\n var(--hx-focus-ring-color, var(--hx-color-primary-400, #60a5fa))\n )\n calc(var(--hx-focus-ring-opacity, 0.25) * 100%),\n transparent\n );\n }\n\n * {\n box-sizing: border-box;\n }\n\n .field {\n display: flex;\n flex-direction: column;\n gap: var(--hx-space-1, 0.25rem);\n font-family: var(--hx-input-font-family, var(--hx-font-family-sans, sans-serif));\n }\n\n /* ─── Label ─── */\n\n .field__label-wrapper {\n display: contents;\n }\n\n .field__label {\n display: flex;\n align-items: baseline;\n gap: var(--hx-space-1, 0.25rem);\n font-size: var(--hx-font-size-sm, 0.875rem);\n font-weight: var(--hx-font-weight-medium, 500);\n color: var(--hx-input-label-color, var(--hx-color-neutral-700, #343a40));\n line-height: var(--hx-line-height-normal, 1.5);\n }\n\n .field__required-marker {\n color: var(--hx-input-error-color, var(--hx-color-error-text, #b91c1c));\n font-weight: var(--hx-font-weight-bold, 700);\n }\n\n /* ─── Input Wrapper ─── */\n\n .field__input-wrapper {\n display: flex;\n align-items: center;\n border: var(--hx-border-width-thin, 1px) solid\n var(--hx-input-border-color, var(--hx-color-neutral-300, #ced4da));\n border-radius: var(--hx-input-border-radius, var(--hx-border-radius-md, 0.375rem));\n background-color: var(--hx-input-bg, var(--hx-color-neutral-0, #ffffff));\n transition:\n border-color var(--hx-transition-fast, 150ms ease),\n box-shadow var(--hx-transition-fast, 150ms ease);\n overflow: hidden;\n }\n\n .field__input-wrapper:focus-within {\n border-color: var(\n --hx-input-focus-ring-color,\n var(--hx-focus-ring-color, var(--hx-color-primary-400, #60a5fa))\n );\n box-shadow: 0 0 0 var(--hx-focus-ring-width, 2px)\n color-mix(\n in srgb,\n var(\n --hx-input-focus-ring-color,\n var(--hx-focus-ring-color, var(--hx-color-primary-400, #60a5fa))\n )\n calc(var(--hx-focus-ring-opacity, 0.25) * 100%),\n transparent\n );\n }\n\n /* ─── Error State ─── */\n\n .field--error .field__input-wrapper {\n border-color: var(--hx-input-error-color, var(--hx-color-error-500, #dc3545));\n }\n\n .field--error .field__input-wrapper:focus-within {\n border-color: var(--hx-input-error-color, var(--hx-color-error-500, #dc3545));\n box-shadow: 0 0 0 var(--hx-focus-ring-width, 2px)\n color-mix(\n in srgb,\n var(--hx-input-error-color, var(--hx-color-error-500, #dc3545))\n calc(var(--hx-focus-ring-opacity, 0.25) * 100%),\n transparent\n );\n }\n\n /* ─── Slots (Prefix / Suffix) ─── */\n\n .field__prefix,\n .field__suffix {\n display: flex;\n align-items: center;\n color: var(--hx-color-neutral-500, #6c757d);\n flex-shrink: 0;\n }\n\n /* Only add padding when slot has content — avoids phantom space on empty slots */\n .field__prefix--filled {\n padding: 0 var(--hx-space-3, 0.75rem);\n }\n\n .field__suffix--filled {\n padding: 0 var(--hx-space-3, 0.75rem);\n }\n\n /* ─── Native Input ─── */\n\n .field__input {\n flex: 1;\n border: none;\n outline: none;\n background: transparent;\n padding: var(--hx-space-2, 0.5rem) var(--hx-space-3, 0.75rem);\n font-family: inherit;\n font-size: var(--hx-font-size-md, 1rem);\n color: var(--hx-input-color, var(--hx-color-neutral-800, #212529));\n line-height: var(--hx-line-height-normal, 1.5);\n min-height: var(--hx-size-10, 2.5rem);\n width: 100%;\n }\n\n .field__input::placeholder {\n color: var(--hx-color-neutral-400, #adb5bd);\n }\n\n .field__input:disabled {\n cursor: not-allowed;\n }\n\n /* ─── Size Variants ─── */\n\n .field--size-sm .field__input {\n padding: var(--hx-space-1, 0.25rem) var(--hx-space-2, 0.5rem);\n min-height: var(--hx-size-8, 2rem);\n font-size: var(--hx-input-sm-font-size, 0.875rem);\n }\n\n .field--size-md .field__input {\n /* md is the default — no overrides needed */\n }\n\n .field--size-lg .field__input {\n padding: var(--hx-space-3, 0.75rem) var(--hx-space-4, 1rem);\n min-height: var(--hx-size-12, 3rem);\n font-size: var(--hx-input-lg-font-size, 1.125rem);\n }\n\n /* ─── Help Text & Error Messages ─── */\n\n .field__help-text {\n font-size: var(--hx-font-size-xs, 0.75rem);\n color: var(--hx-color-neutral-500, #6c757d);\n line-height: var(--hx-line-height-normal, 1.5);\n }\n\n .field__error {\n font-size: var(--hx-font-size-xs, 0.75rem);\n color: var(--hx-input-error-color, var(--hx-color-error-text, #b91c1c));\n line-height: var(--hx-line-height-normal, 1.5);\n }\n\n /* ─── Motion ─── */\n\n @media (prefers-reduced-motion: reduce) {\n .field__input-wrapper {\n transition: none;\n }\n }\n`;\n","import { html, nothing, type PropertyValues } from 'lit';\nimport '../../utilities/document-token-adoption.js';\nimport { customElement, property, query, state } from 'lit/decorators.js';\nimport { classMap } from 'lit/directives/class-map.js';\nimport { ifDefined } from 'lit/directives/if-defined.js';\nimport { live } from 'lit/directives/live.js';\nimport { HelixElement, createIdCounter } from '../../base/index.js';\nimport { FocusMixin } from '../../mixins/index.js';\nimport { FormMixin } from '../../mixins/FormMixin.js';\nimport { helixTextInputStyles } from './hx-text-input.styles.js';\nimport { devWarn } from '../../utils/dev-warn.js';\n\n// Module-level counter for stable, SSR-compatible IDs (avoids Math.random() hydration mismatch)\nconst _nextTextInputId = createIdCounter('hx-text-input');\n\n/**\n * A text input component with label, validation, and form association.\n * Supports accessible labeling via `label` property, `aria-label` attribute, or the `label` slot.\n * Uses `aria-invalid` and `aria-describedby` on the native input for screen reader support. Native `required` provides implicit aria-required mapping per HTML-AAM.\n * Error messages are announced via `role=\"alert\"`. Keyboard navigation follows native input behavior.\n *\n * @summary Form-associated text input with built-in label, error, and help text.\n *\n * @tag hx-text-input\n *\n * @slot label - Custom label content (overrides the label property). Use for Drupal Form API rendered labels.\n * @slot prefix - Content rendered before the input (e.g., icon).\n * @slot suffix - Content rendered after the input (e.g., icon or button).\n * @slot help-text - Custom help text content (overrides the helpText property).\n * @slot error - Custom error content (overrides the error property). Use for Drupal Form API rendered errors.\n *\n * @fires {CustomEvent<{value: string}>} hx-input - Dispatched on every keystroke as the user types.\n * @fires {CustomEvent<{value: string}>} hx-change - Dispatched when the input loses focus after its value changed.\n *\n * @csspart field - The outer field container.\n * @csspart label - The label element.\n * @csspart input-wrapper - The wrapper around prefix, input, and suffix.\n * @csspart input - The native input element.\n * @csspart help-text - The help text container.\n * @csspart error - The error message container.\n *\n * @cssprop [--hx-input-bg=var(--hx-color-neutral-0)] - Input background color.\n * @cssprop [--hx-input-color=var(--hx-color-neutral-800)] - Input text color.\n * @cssprop [--hx-input-border-color=var(--hx-color-neutral-300)] - Input border color.\n * @cssprop [--hx-input-border-radius=var(--hx-border-radius-md)] - Input border radius.\n * @cssprop [--hx-input-font-family=var(--hx-font-family-sans)] - Input font family.\n * @cssprop [--hx-input-focus-ring-color=var(--hx-focus-ring-color)] - Focus ring color.\n * @cssprop [--hx-input-error-color=var(--hx-color-error-500)] - Error state color.\n * @cssprop [--hx-input-label-color=var(--hx-color-neutral-700)] - Label text color.\n * @cssprop [--hx-input-sm-font-size=0.875rem] - Font size for the sm size variant.\n * @cssprop [--hx-input-lg-font-size=1.125rem] - Font size for the lg size variant.\n */\n@customElement('hx-text-input')\nexport class HelixTextInput extends FocusMixin(FormMixin(HelixElement)) {\n static override styles = [helixTextInputStyles];\n\n // ─── Form Association ───\n\n /** @internal */\n static override formAssociated = true;\n\n // ─── Properties ───\n\n /**\n * The visible label text for the input.\n * @attr label\n */\n @property({ type: String })\n label = '';\n\n /**\n * Placeholder text shown when the input is empty.\n * @attr placeholder\n */\n @property({ type: String })\n placeholder = '';\n\n /**\n * The current value of the input.\n * @attr value\n * @remarks When `null` is passed programmatically, Lit coerces it to an empty string.\n * This is expected behavior — the input treats null as equivalent to an empty string value.\n */\n @property({ type: String })\n value = '';\n\n /**\n * The type of the native input element.\n * @attr type\n */\n @property({ type: String })\n type: 'text' | 'email' | 'password' | 'tel' | 'url' | 'search' | 'number' | 'date' = 'text';\n\n /**\n * Whether the input is required for form submission.\n * @attr required\n */\n @property({ type: Boolean, reflect: true })\n required = false;\n\n /**\n * Whether the input is disabled.\n * @attr disabled\n */\n @property({ type: Boolean, reflect: true })\n disabled = false;\n\n /**\n * Error message to display. When set, the input enters an error state.\n * @attr error\n */\n @property({ type: String })\n error = '';\n\n /**\n * Help text displayed below the input for guidance.\n * @attr help-text\n */\n @property({ type: String, attribute: 'help-text' })\n helpText = '';\n\n /**\n * The name of the input, used for form submission.\n * @attr name\n */\n @property({ type: String })\n name = '';\n\n /**\n * Accessible name for screen readers, if different from the visible label.\n * @attr aria-label\n */\n @property({ type: String, attribute: 'aria-label' })\n override ariaLabel: string | null = null;\n\n /**\n * Whether the input is read-only.\n * @attr readonly\n */\n @property({ type: Boolean, reflect: true })\n readonly = false;\n\n /**\n * Minimum number of characters allowed.\n * @attr minlength\n */\n @property({ type: Number })\n minlength: number | undefined = undefined;\n\n /**\n * Maximum number of characters allowed.\n * @attr maxlength\n */\n @property({ type: Number })\n maxlength: number | undefined = undefined;\n\n /**\n * A regular expression pattern the value must match for form validation.\n * @attr pattern\n */\n @property({ type: String })\n pattern = '';\n\n /**\n * Hint for the browser's autocomplete feature. Accepts standard HTML autocomplete values.\n * @attr autocomplete\n */\n @property({ type: String })\n autocomplete = '';\n\n /**\n * Validation message shown when the field is required but empty.\n * @attr required-message\n */\n @property({ attribute: 'required-message' })\n requiredMessage = 'This field is required.';\n\n /**\n * Visual size of the input field.\n * @attr hx-size\n */\n @property({ type: String, attribute: 'hx-size', reflect: true })\n size: 'sm' | 'md' | 'lg' = 'md';\n\n // ─── Internal References ───\n\n /** @internal */\n @query('.field__input')\n private _input: HTMLInputElement | undefined;\n\n // ─── FocusMixin integration ───\n\n /**\n * Declares the inner focusable element for FocusMixin delegation.\n * @internal\n */\n protected get _focusableNode(): HTMLElement | null {\n return this._input ?? null;\n }\n\n // ─── Slot Tracking ───\n\n /** @internal */\n @state() private _hasLabelSlot = false;\n /** @internal */\n @state() private _hasErrorSlot = false;\n /** @internal */\n @state() private _hasPrefixSlot = false;\n /** @internal */\n @state() private _hasSuffixSlot = false;\n /** @internal */\n @state() private _hasHelpTextSlot = false;\n\n /** @internal */\n private _handleLabelSlotChange(e: Event): void {\n const slot = e.target as HTMLSlotElement;\n this._hasLabelSlot = slot.assignedElements().length > 0;\n if (this._hasLabelSlot) {\n const slottedLabel = slot.assignedElements()[0];\n if (slottedLabel && !slottedLabel.id) {\n slottedLabel.id = `${this._inputId}-slotted-label`;\n }\n }\n }\n\n /** @internal */\n private _handleErrorSlotChange(e: Event): void {\n const slot = e.target as HTMLSlotElement;\n this._hasErrorSlot = slot.assignedElements().length > 0;\n }\n\n /** @internal */\n private _handlePrefixSlotChange(e: Event): void {\n const slot = e.target as HTMLSlotElement;\n this._hasPrefixSlot = slot.assignedElements().length > 0;\n }\n\n /** @internal */\n private _handleSuffixSlotChange(e: Event): void {\n const slot = e.target as HTMLSlotElement;\n this._hasSuffixSlot = slot.assignedElements().length > 0;\n }\n\n /** @internal */\n private _handleHelpTextSlotChange(e: Event): void {\n const slot = e.target as HTMLSlotElement;\n this._hasHelpTextSlot = slot.assignedElements().length > 0;\n }\n\n // ─── Lifecycle ───\n\n override updated(changedProperties: PropertyValues<this>): void {\n super.updated(changedProperties);\n if (changedProperties.has('value')) {\n this._internals.setFormValue(this.value);\n }\n if (\n (changedProperties.has('value') || changedProperties.has('type')) &&\n this.type === 'number' &&\n this.value !== '' &&\n isNaN(Number(this.value))\n ) {\n devWarn(\n 'hx-text-input',\n `type=\"number\" received non-numeric value \"${this.value}\". Browser will display empty input.`,\n );\n }\n }\n\n // ─── Form Integration ───\n\n /** Returns the associated form element, if any. */\n override get form(): HTMLFormElement | null {\n return this._internals.form;\n }\n\n /** Returns the validation message. */\n override get validationMessage(): string {\n return this._internals.validationMessage;\n }\n\n /** Returns the ValidityState object. */\n override get validity(): ValidityState {\n return this._internals.validity;\n }\n\n /** @internal */\n protected _updateValidity(): void {\n if (this.required && !this.value) {\n this._internals.setValidity(\n { valueMissing: true },\n this.error || this.requiredMessage,\n this._input,\n );\n } else if (\n this.minlength !== undefined &&\n this.value.length > 0 &&\n this.value.length < this.minlength\n ) {\n this._internals.setValidity(\n { tooShort: true },\n this.error || `Please lengthen this text to ${this.minlength} characters or more.`,\n this._input,\n );\n } else if (this.maxlength !== undefined && this.value.length > this.maxlength) {\n this._internals.setValidity(\n { tooLong: true },\n this.error || `Please shorten this text to ${this.maxlength} characters or fewer.`,\n this._input,\n );\n } else {\n this._internals.setValidity({});\n }\n }\n\n // ─── Form Lifecycle Hooks ───\n\n protected override _onFormReset(): void {\n this.value = '';\n this._internals.setFormValue('');\n this._resetInteractionState();\n }\n\n protected override _onFormStateRestore(\n state: File | string | FormData | null,\n _mode: 'restore' | 'autocomplete',\n ): void {\n if (typeof state === 'string') {\n this.value = state;\n }\n }\n\n protected override _onFormDisabled(disabled: boolean): void {\n this.disabled = disabled;\n }\n\n // ─── Event Handling ───\n\n /** @internal */\n private _handleInput(e: Event): void {\n const target = e.target as HTMLInputElement;\n this.value = target.value;\n this._internals.setFormValue(this.value);\n this._handleInteractionInput();\n\n /**\n * Dispatched on every keystroke as the user types.\n * @event hx-input\n */\n this.dispatchEvent(\n new CustomEvent<{ value: string }>('hx-input', {\n bubbles: true,\n composed: true,\n detail: { value: this.value },\n }),\n );\n }\n\n /** @internal */\n private _handleChange(e: Event): void {\n const target = e.target as HTMLInputElement;\n this.value = target.value;\n this._internals.setFormValue(this.value);\n this._handleInteractionBlur();\n\n /**\n * Dispatched when the input loses focus after its value changed.\n * @event hx-change\n */\n this.dispatchEvent(\n new CustomEvent<{ value: string }>('hx-change', {\n bubbles: true,\n composed: true,\n detail: { value: this.value },\n }),\n );\n }\n\n // ─── Public Methods ───\n\n /** Selects all text in the input. */\n select(): void {\n this._input?.select();\n }\n\n // ─── Render ───\n\n /** @internal */\n private _inputId = _nextTextInputId();\n /** @internal */\n private _helpTextId = `${this._inputId}-help`;\n /** @internal */\n private _errorId = `${this._inputId}-error`;\n\n override render() {\n const hasError = !!this.error || this._hasErrorSlot;\n\n const fieldClasses = {\n field: true,\n 'field--error': hasError,\n 'field--disabled': this.disabled,\n 'field--required': this.required,\n [`field--size-${this.size}`]: true,\n };\n\n const describedBy =\n [\n hasError ? this._errorId : null,\n this.helpText || this._hasHelpTextSlot ? this._helpTextId : null,\n ]\n .filter(Boolean)\n .join(' ') || undefined;\n\n return html`\n <div part=\"field\" class=${classMap(fieldClasses)}>\n <div class=\"field__label-wrapper\">\n <slot name=\"label\" @slotchange=${this._handleLabelSlotChange}>\n ${this.label\n ? html`\n <label part=\"label\" class=\"field__label\" for=${this._inputId}>\n ${this.label}\n ${this.required\n ? html`<span class=\"field__required-marker\" aria-hidden=\"true\">*</span>`\n : nothing}\n </label>\n `\n : nothing}\n </slot>\n </div>\n\n <div part=\"input-wrapper\" class=\"field__input-wrapper\">\n <span\n class=${classMap({\n field__prefix: true,\n 'field__prefix--filled': this._hasPrefixSlot,\n })}\n >\n <slot name=\"prefix\" @slotchange=${this._handlePrefixSlotChange}></slot>\n </span>\n\n <input\n part=\"input\"\n class=\"field__input\"\n id=${this._inputId}\n type=${this.type}\n .value=${live(this.value)}\n placeholder=${ifDefined(this.placeholder || undefined)}\n ?required=${this.required}\n ?disabled=${this.disabled}\n ?readonly=${this.readonly}\n name=${ifDefined(this.name || undefined)}\n minlength=${ifDefined(this.minlength)}\n maxlength=${ifDefined(this.maxlength)}\n pattern=${ifDefined(this.pattern || undefined)}\n autocomplete=${ifDefined(this.autocomplete || undefined)}\n aria-label=${ifDefined(this.ariaLabel || undefined)}\n aria-labelledby=${ifDefined(\n this._hasLabelSlot ? `${this._inputId}-slotted-label` : undefined,\n )}\n aria-invalid=${hasError ? 'true' : nothing}\n aria-describedby=${ifDefined(describedBy)}\n @input=${this._handleInput}\n @change=${this._handleChange}\n />\n\n <span\n class=${classMap({\n field__suffix: true,\n 'field__suffix--filled': this._hasSuffixSlot,\n })}\n >\n <slot name=\"suffix\" @slotchange=${this._handleSuffixSlotChange}></slot>\n </span>\n </div>\n\n ${hasError\n ? html`<div\n part=\"error\"\n class=\"field__error\"\n id=${this._errorId}\n role=\"alert\"\n aria-atomic=\"true\"\n >\n <slot name=\"error\" @slotchange=${this._handleErrorSlotChange}>${this.error}</slot>\n </div>`\n : html`<slot name=\"error\" @slotchange=${this._handleErrorSlotChange}></slot>`}\n ${this._hasHelpTextSlot\n ? html`\n <div part=\"help-text\" class=\"field__help-text\" id=${this._helpTextId}>\n <slot name=\"help-text\" @slotchange=${this._handleHelpTextSlotChange}>\n ${this.helpText}\n </slot>\n </div>\n `\n : this.helpText && !hasError\n ? html`\n <div part=\"help-text\" class=\"field__help-text\" id=${this._helpTextId}>\n <slot name=\"help-text\" @slotchange=${this._handleHelpTextSlotChange}>\n ${this.helpText}\n </slot>\n </div>\n `\n : html`<slot name=\"help-text\" @slotchange=${this._handleHelpTextSlotChange}></slot>`}\n </div>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'hx-text-input': HelixTextInput;\n }\n}\n\n/** Primary type alias for hx-text-input */\nexport type HxTextInput = HelixTextInput;\n\n/** @deprecated Use HxTextInput instead */\nexport type WcTextInput = HelixTextInput;\n"],"names":["FocusMixin","superClass","FocusMixinClass","options","node","_a","changedProperties","__decorateClass","property","helixTextInputStyles","css","_nextTextInputId","createIdCounter","HelixTextInput","FormMixin","HelixElement","slot","slottedLabel","devWarn","state","_mode","disabled","target","hasError","fieldClasses","describedBy","html","classMap","nothing","live","ifDefined","query","customElement"],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const e = /* @__PURE__ */ new Map();
|
|
2
|
+
function n(t) {
|
|
3
|
+
return () => {
|
|
4
|
+
const r = e.get(t) ?? 0;
|
|
5
|
+
return e.set(t, r + 1), `${t}-${r}`;
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
function o(t) {
|
|
9
|
+
t !== void 0 ? e.delete(t) : e.clear();
|
|
10
|
+
}
|
|
11
|
+
export {
|
|
12
|
+
n as c,
|
|
13
|
+
o as r
|
|
14
|
+
};
|
|
15
|
+
//# sourceMappingURL=id-counter-PTgF-zcG.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"id-counter-PTgF-zcG.js","sources":["../../src/base/id-counter.ts"],"sourcesContent":["/**\n * Module-level monotonic ID counter factory.\n *\n * Provides collision-free, deterministic, SSR-safe IDs for components that\n * need stable element IDs (for ARIA relationships, label associations, etc.)\n * without relying on Math.random() which causes hydration mismatches.\n *\n * @public\n */\n\nconst counters = new Map<string, number>();\n\n/**\n * Creates a monotonic ID generator for the given namespace.\n *\n * @param namespace - Component namespace (e.g. 'hx-text-input')\n * @returns A function that returns the next unique ID string\n * @public\n *\n * @example\n * ```ts\n * const nextId = createIdCounter('hx-text-input');\n * private _id = nextId(); // 'hx-text-input-0', 'hx-text-input-1', ...\n * ```\n */\nexport function createIdCounter(namespace: string): () => string {\n return () => {\n const current = counters.get(namespace) ?? 0;\n counters.set(namespace, current + 1);\n return `${namespace}-${current}`;\n };\n}\n\n/**\n * Resets the counter for the given namespace (or all namespaces if omitted).\n * Use in test `beforeEach()` for counter isolation between tests.\n *\n * @param namespace - Optional namespace to reset. If omitted, resets all counters.\n * @public\n */\nexport function resetIdCounter(namespace?: string): void {\n if (namespace !== undefined) {\n counters.delete(namespace);\n } else {\n counters.clear();\n }\n}\n"],"names":["counters","createIdCounter","namespace","current","resetIdCounter"],"mappings":"AAUA,MAAMA,wBAAe,IAAA;AAed,SAASC,EAAgBC,GAAiC;AAC/D,SAAO,MAAM;AACX,UAAMC,IAAUH,EAAS,IAAIE,CAAS,KAAK;AAC3C,WAAAF,EAAS,IAAIE,GAAWC,IAAU,CAAC,GAC5B,GAAGD,CAAS,IAAIC,CAAO;AAAA,EAChC;AACF;AASO,SAASC,EAAeF,GAA0B;AACvD,EAAIA,MAAc,SAChBF,EAAS,OAAOE,CAAS,IAEzBF,EAAS,MAAA;AAEb;"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@helixui/library",
|
|
3
|
-
"version": "2.1.2-next.
|
|
3
|
+
"version": "2.1.2-next.50",
|
|
4
4
|
"description": "Enterprise Web Component Library built with Lit 3.x",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": [
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"customElements": "custom-elements.json",
|
|
46
46
|
"dependencies": {
|
|
47
47
|
"lit": "^3.3.2",
|
|
48
|
-
"@helixui/tokens": "2.1.2-next.
|
|
48
|
+
"@helixui/tokens": "2.1.2-next.50"
|
|
49
49
|
},
|
|
50
50
|
"peerDependencies": {
|
|
51
51
|
"@floating-ui/dom": "^1.7.6"
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"hx-breadcrumb-item-DzLyeL5Z.js","sources":["../../src/components/hx-breadcrumb/hx-breadcrumb.styles.ts","../../src/components/hx-breadcrumb/hx-breadcrumb.ts","../../src/components/hx-breadcrumb/hx-breadcrumb-item.styles.ts","../../src/components/hx-breadcrumb/hx-breadcrumb-item.ts"],"sourcesContent":["import { css } from 'lit';\n\nexport const helixBreadcrumbStyles = css`\n :host {\n display: block;\n font-family: var(\n --hx-breadcrumb-font-family,\n var(--hx-font-family-sans, system-ui, sans-serif)\n );\n font-size: var(--hx-breadcrumb-font-size, var(--hx-font-size-sm, 0.875rem));\n }\n\n [part='nav'] {\n /* nav landmark — no additional styling needed */\n }\n\n [part='list'] {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n list-style: none;\n margin: 0;\n padding: 0;\n gap: 0;\n }\n\n /* Hide middle items when collapsed via maxItems */\n ::slotted([data-bc-hidden]) {\n display: none;\n }\n\n /* Visually hide the separator slot — used only to read text content.\n * display:none is intentional: the slot contains no interactive or focusable\n * content. If a future change adds focusable elements to this slot, switch to\n * visibility:hidden + position:absolute to preserve focus reachability. */\n .separator-slot {\n display: none;\n }\n`;\n","import { LitElement, html, type PropertyValues } from 'lit';\nimport '../../utilities/document-token-adoption.js';\nimport { customElement, property } from 'lit/decorators.js';\nimport { helixBreadcrumbStyles } from './hx-breadcrumb.styles.js';\n\n/** Typed schema.org ListItem entry for JSON-LD BreadcrumbList structured data. */\ninterface JsonLdListItem {\n '@type': string;\n position: number;\n name: string;\n item?: string;\n}\n\n/**\n * Hierarchical page path navigation showing current location in site structure.\n *\n * @summary Navigation breadcrumb showing the page hierarchy. Works with Drupal's breadcrumb system.\n *\n * @tag hx-breadcrumb\n *\n * @slot - Default slot for hx-breadcrumb-item children.\n * @slot separator - Optional separator element. Its text content overrides the `separator` property.\n *\n * @csspart nav - The nav landmark element.\n * @csspart list - The ordered list containing items.\n *\n * @cssprop [--hx-breadcrumb-separator-content='/'] - Separator character between items.\n * NOTE: If overriding this custom property directly in CSS (rather than via the `separator`\n * attribute), the value MUST be quoted: `--hx-breadcrumb-separator-content: \">\"`. An unquoted\n * value is invalid for the CSS `content` property and will silently render nothing.\n * @cssprop [--hx-breadcrumb-separator-color=var(--hx-color-neutral-400)] - Separator color.\n * @cssprop [--hx-breadcrumb-separator-gap=var(--hx-space-1)] - Horizontal gap around separators.\n * @cssprop [--hx-breadcrumb-font-size=var(--hx-font-size-sm)] - Font size.\n * @cssprop [--hx-breadcrumb-link-color=var(--hx-color-primary-600)] - Link color.\n * @cssprop [--hx-breadcrumb-link-hover-color=var(--hx-color-primary-700)] - Link hover color.\n * @cssprop [--hx-breadcrumb-text-color=var(--hx-color-neutral-700)] - Current page text color.\n * @cssprop [--hx-breadcrumb-item-max-width] - Max-width for item text truncation (e.g. `12rem`).\n */\n@customElement('hx-breadcrumb')\nexport class HelixBreadcrumb extends LitElement {\n static override styles = [helixBreadcrumbStyles];\n\n /**\n * Per-instance counter used to generate stable, deterministic IDs for the\n * injected JSON-LD script tags. Deterministic IDs (vs Math.random()) allow\n * SSR frameworks to match server-rendered script tags during hydration.\n * @internal\n */\n private static _instanceCounter = 0;\n\n /**\n * The separator character displayed between breadcrumb items.\n * @attr separator\n */\n @property({ type: String })\n separator = '/';\n\n /**\n * The accessible label for the nav landmark.\n * @attr label\n */\n @property({ type: String })\n label = 'Breadcrumb';\n\n /**\n * Maximum number of items to show before collapsing middle items with an ellipsis.\n * Set to 0 (default) to show all items. The ellipsis is a keyboard-accessible\n * button; activating it expands the full breadcrumb by setting maxItems to 0.\n * @attr max-items\n */\n @property({ type: Number, attribute: 'max-items' })\n maxItems = 0;\n\n /**\n * Accessible label for the expand ellipsis button. Override for i18n.\n * @attr label-ellipsis\n */\n @property({ attribute: 'label-ellipsis' }) labelEllipsis = 'Show all breadcrumb items';\n\n /**\n * When true, injects a JSON-LD BreadcrumbList structured data script into the document head.\n *\n * NOTE: Drupal manages `<head>` content via its own render pipeline. Injecting a\n * `<script>` directly via `document.head.appendChild()` in a Drupal context:\n * 1. Bypasses Drupal's deduplication and `hook_html_head_alter()` hook.\n * 2. Is not cacheable by Drupal's page cache.\n * 3. Will be wiped on BigPipe partial page replacements.\n *\n * For Drupal integrations, leave `json-ld` false and use the structured data\n * Twig template instead (see `hx-breadcrumb.twig` in the component directory).\n *\n * @attr json-ld\n */\n @property({ type: Boolean, attribute: 'json-ld' })\n jsonLd = false;\n\n /** @internal */\n private _ellipsisItem: Element | null = null;\n /** @internal */\n private _jsonLdScript: HTMLScriptElement | null = null;\n /** @internal */\n private readonly _boundEllipsisClick = (e: Event) => this._handleEllipsisClick(e);\n /** @internal */\n private readonly _boundEllipsisKeydown = (e: Event) => this._handleEllipsisKeydown(e);\n\n /**\n * Tracks which items had their `current` attribute set by this component\n * (as opposed to set by a consumer/Drupal template). This lets us re-evaluate\n * positional current-page detection on each slotchange without incorrectly\n * treating a previously component-set `current` attribute as a consumer-set\n * explicit override.\n * @internal\n */\n private readonly _managedCurrentItems = new WeakSet<Element>();\n\n /**\n * Stable per-instance ID used to tag the injected script element so that\n * multiple hx-breadcrumb instances on the same page don't produce conflicting\n * or duplicate structured-data blocks. Each instance owns exactly one script\n * tag identified by this ID; any stale tag from a previous render cycle is\n * removed before a new one is inserted.\n *\n * Uses a static counter (not Math.random()) so IDs are deterministic across\n * server and client renders, enabling SSR hydration matching.\n * @internal\n */\n private readonly _jsonLdId = `hx-breadcrumb-ld-${++HelixBreadcrumb._instanceCounter}`;\n\n // ─── Item Helpers ───\n\n /**\n * Returns only real breadcrumb items, excluding the managed ellipsis element.\n * @internal\n */\n private _getBreadcrumbItems(slot: HTMLSlotElement): Element[] {\n return slot\n .assignedElements({ flatten: true })\n .filter(\n (el) =>\n el.tagName.toLowerCase() === 'hx-breadcrumb-item' &&\n !el.classList.contains('hx-bc-ellipsis'),\n );\n }\n\n /**\n * Applies aria/state attributes to the item list.\n *\n * Current-page detection: if any item has an explicit `current` attribute\n * (e.g. set by a Drupal Twig template), that item is treated as the current\n * page. Otherwise the last item is the current page (default behaviour).\n *\n * This separation allows Drupal to control current-page marking without\n * relying on item order.\n * @internal\n */\n private _applyItemAttributes(items: Element[]): void {\n // Detect consumer-set 'current' attributes. An item has an explicit consumer\n // current if it has the 'current' attribute AND the component did not set it\n // (tracked via _managedCurrentItems). This prevents component-managed state\n // from being misread as a consumer override on subsequent slotchange events.\n const hasExplicitCurrent = items.some(\n (el) => el.hasAttribute('current') && !this._managedCurrentItems.has(el),\n );\n\n items.forEach((item, i) => {\n const el = item as HTMLElement;\n const isLast = i === items.length - 1;\n\n // Separator hiding: always positional — last item has no trailing separator.\n if (isLast) {\n el.setAttribute('data-bc-last', '');\n } else {\n el.removeAttribute('data-bc-last');\n }\n\n // Current-page marker: explicit consumer attribute wins over positional last.\n // The item component renders aria-current=\"page\" on its inner element\n // based on this attribute (see hx-breadcrumb-item.ts).\n if (!hasExplicitCurrent) {\n if (isLast) {\n el.setAttribute('current', '');\n this._managedCurrentItems.add(el);\n } else {\n el.removeAttribute('current');\n this._managedCurrentItems.delete(el);\n }\n }\n // When hasExplicitCurrent is true, leave 'current' attributes as-is so\n // consumer or Drupal template markup is not overridden.\n });\n }\n\n // ─── Slot Handling ───\n\n /** @internal */\n private _handleSlotChange(e: Event): void {\n if (!(e.target instanceof HTMLSlotElement)) return;\n const items = this._getBreadcrumbItems(e.target);\n\n // Handle collapse behaviour\n if (this.maxItems > 0 && items.length > this.maxItems) {\n this._applyCollapse(items);\n } else {\n this._removeCollapse(items);\n }\n\n this._applyItemAttributes(items);\n\n if (this.jsonLd) {\n this._updateJsonLd(items);\n }\n }\n\n /** @internal */\n private _handleSeparatorSlotChange(e: Event): void {\n if (!(e.target instanceof HTMLSlotElement)) return;\n const assigned = e.target.assignedElements({ flatten: true });\n if (assigned.length > 0) {\n const text = (assigned[0] as HTMLElement).textContent?.trim() ?? '';\n this.style.setProperty('--hx-breadcrumb-separator-content', JSON.stringify(text));\n }\n }\n\n // ─── Collapse ───\n\n /** @internal */\n private _applyCollapse(items: Element[]): void {\n // Show only first and last; hide all middle items\n items.forEach((item, i) => {\n const el = item as HTMLElement;\n if (i === 0 || i === items.length - 1) {\n el.removeAttribute('data-bc-hidden');\n } else {\n el.setAttribute('data-bc-hidden', '');\n }\n });\n\n // Create the ellipsis element once (guard for SSR — document is unavailable server-side)\n if (!this._ellipsisItem && typeof document !== 'undefined') {\n const ellipsis = document.createElement('hx-breadcrumb-item');\n ellipsis.classList.add('hx-bc-ellipsis');\n\n // Keyboard-accessible expand button. Events handled via host-level delegation\n // in _handleEllipsisClick / _handleEllipsisKeydown.\n const btn = document.createElement('button');\n btn.type = 'button';\n btn.textContent = '…';\n btn.setAttribute('aria-label', this.labelEllipsis);\n ellipsis.appendChild(btn);\n\n this._ellipsisItem = ellipsis;\n }\n\n // Insert ellipsis after first item only if not already correctly placed\n const firstItem = items[0];\n if (!firstItem || !this._ellipsisItem) return;\n if (this._ellipsisItem.previousElementSibling !== firstItem) {\n firstItem.after(this._ellipsisItem);\n }\n }\n\n /** @internal */\n private _removeCollapse(items: Element[]): void {\n items.forEach((item) => {\n (item as HTMLElement).removeAttribute('data-bc-hidden');\n });\n\n if (this._ellipsisItem?.isConnected) {\n this._ellipsisItem.remove();\n }\n }\n\n /** @internal */\n private _handleEllipsisClick(e: Event): void {\n if ((e.target as Element)?.closest?.('.hx-bc-ellipsis')) {\n this._expandBreadcrumb();\n }\n }\n\n /** @internal */\n private _handleEllipsisKeydown(e: Event): void {\n if (!(e instanceof KeyboardEvent)) return;\n if (e.key === 'Enter' || e.key === ' ') {\n if ((e.target as Element)?.closest?.('.hx-bc-ellipsis')) {\n e.preventDefault();\n this._expandBreadcrumb();\n }\n }\n }\n\n /**\n * Expands a collapsed breadcrumb by resetting maxItems to 0.\n * Called by the ellipsis expand button (click or Enter/Space).\n * @internal\n */\n private _expandBreadcrumb(): void {\n this.maxItems = 0;\n // updated() will detect the maxItems change and call _removeCollapse.\n }\n\n // ─── JSON-LD ───\n\n /**\n * JSON-LD ListItem entry with typed fields to avoid Record<string, unknown>.\n * @internal\n */\n private _buildListItem(item: Element, position: number): JsonLdListItem {\n const href = (item as HTMLElement).getAttribute('href');\n const name = (item as HTMLElement).textContent?.trim() ?? '';\n const entry: JsonLdListItem = {\n '@type': 'ListItem',\n position,\n name,\n };\n if (href) entry.item = href;\n return entry;\n }\n\n /** @internal */\n private _updateJsonLd(items: Element[]): void {\n const schema = {\n '@context': 'https://schema.org',\n '@type': 'BreadcrumbList',\n itemListElement: items.map((item, i) => this._buildListItem(item, i + 1)),\n };\n\n // Guard for SSR — document is unavailable server-side\n if (typeof document === 'undefined') return;\n\n if (!this._jsonLdScript) {\n // Dedup guard: remove any stale script with this instance's ID before\n // creating a fresh one. This handles the edge case where the element was\n // reconnected to the DOM after being disconnected without the script\n // reference being re-established.\n document.getElementById(this._jsonLdId)?.remove();\n\n this._jsonLdScript = document.createElement('script');\n this._jsonLdScript.type = 'application/ld+json';\n this._jsonLdScript.id = this._jsonLdId;\n this._jsonLdScript.setAttribute('data-hx-breadcrumb', '');\n document.head.appendChild(this._jsonLdScript);\n }\n\n this._jsonLdScript.textContent = JSON.stringify(schema);\n }\n\n /** @internal */\n private _removeJsonLd(): void {\n this._jsonLdScript?.remove();\n this._jsonLdScript = null;\n }\n\n // ─── Lifecycle ───\n\n override connectedCallback(): void {\n super.connectedCallback();\n this.addEventListener('click', this._boundEllipsisClick);\n this.addEventListener('keydown', this._boundEllipsisKeydown);\n }\n\n override disconnectedCallback(): void {\n super.disconnectedCallback();\n this.removeEventListener('click', this._boundEllipsisClick);\n this.removeEventListener('keydown', this._boundEllipsisKeydown);\n this._removeJsonLd();\n }\n\n override updated(changedProperties: PropertyValues<this>): void {\n super.updated(changedProperties);\n\n if (changedProperties.has('separator')) {\n // JSON.stringify wraps the string in quotes so the value is valid\n // for use in the CSS `content` property (e.g. '/' becomes '\"/\"').\n this.style.setProperty('--hx-breadcrumb-separator-content', JSON.stringify(this.separator));\n }\n\n if (changedProperties.has('maxItems')) {\n // Re-evaluate collapse state when maxItems changes programmatically\n // (e.g. when the expand button resets maxItems to 0).\n const slot = this.shadowRoot?.querySelector<HTMLSlotElement>('slot:not([name])');\n if (slot) {\n const items = this._getBreadcrumbItems(slot);\n if (this.maxItems > 0 && items.length > this.maxItems) {\n this._applyCollapse(items);\n } else {\n this._removeCollapse(items);\n }\n this._applyItemAttributes(items);\n }\n }\n\n if (changedProperties.has('jsonLd')) {\n if (this.jsonLd) {\n // json-ld toggled on after initial render — inject script immediately.\n const slot = this.shadowRoot?.querySelector<HTMLSlotElement>('slot:not([name])');\n if (slot) {\n this._updateJsonLd(this._getBreadcrumbItems(slot));\n }\n } else {\n // json-ld toggled off — remove existing script.\n this._removeJsonLd();\n }\n }\n }\n\n // ─── Render ───\n\n override render() {\n return html`\n <nav part=\"nav\" aria-label=${this.label}>\n <ol part=\"list\">\n <slot @slotchange=${this._handleSlotChange}></slot>\n </ol>\n </nav>\n <slot\n name=\"separator\"\n class=\"separator-slot\"\n @slotchange=${this._handleSeparatorSlotChange}\n ></slot>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'hx-breadcrumb': HelixBreadcrumb;\n }\n}\n","import { css } from 'lit';\n\nexport const helixBreadcrumbItemStyles = css`\n :host {\n display: inline-flex;\n align-items: center;\n }\n\n /*\n * display: contents removes [part='item'] from the box model entirely.\n * This is intentional — the wrapper exists only for slot selection purposes.\n * Consumers using ::part(item) CANNOT apply box-model properties (padding,\n * margin, background, border) to this part. Use ::part(link) or ::part(text)\n * for visual styling of breadcrumb item content.\n */\n [part='item'] {\n display: contents;\n }\n\n [part='link'] {\n color: var(--hx-breadcrumb-link-color, var(--hx-color-primary-600));\n text-decoration: none;\n cursor: pointer;\n font-family: inherit;\n font-size: inherit;\n max-width: var(--hx-breadcrumb-item-max-width);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n [part='link']:hover {\n color: var(--hx-breadcrumb-link-hover-color, var(--hx-color-primary-700));\n text-decoration: underline;\n }\n\n [part='link']:focus-visible {\n outline: var(--hx-focus-ring-width, 2px) solid\n var(\n --hx-breadcrumb-link-focus-ring-color,\n var(--hx-focus-ring-color, var(--hx-color-primary-500))\n );\n outline-offset: var(--hx-focus-ring-offset, 2px);\n border-radius: var(--hx-border-radius-sm, 0.125rem);\n }\n\n [part='text'] {\n color: var(--hx-breadcrumb-text-color, var(--hx-color-neutral-700));\n font-family: inherit;\n font-size: inherit;\n max-width: var(--hx-breadcrumb-item-max-width);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n .separator {\n margin-inline: var(--hx-breadcrumb-separator-gap, var(--hx-space-1, 0.25rem));\n color: var(--hx-breadcrumb-separator-color, var(--hx-color-neutral-400));\n user-select: none;\n }\n\n .separator::before {\n content: var(--hx-breadcrumb-separator-content, '/');\n }\n\n /* Normalize buttons slotted into breadcrumb items (e.g. the expand-ellipsis button). */\n ::slotted(button) {\n background: none;\n border: none;\n cursor: pointer;\n font: inherit;\n color: inherit;\n padding: 0;\n }\n`;\n","import { LitElement, html, nothing } from 'lit';\nimport '../../utilities/document-token-adoption.js';\nimport { customElement, property } from 'lit/decorators.js';\nimport { helixBreadcrumbItemStyles } from './hx-breadcrumb-item.styles.js';\n\n/**\n * A single breadcrumb navigation item.\n *\n * @summary A navigation item within an hx-breadcrumb component. Renders as a link when `href` is\n * provided, or as static text for the current page item. The current page item is determined by\n * the `current` attribute (set explicitly or automatically by the parent `hx-breadcrumb`).\n *\n * @tag hx-breadcrumb-item\n *\n * @slot - The link or page text content. Accepts text, HTML, or icon elements.\n *\n * @csspart item - Wrapper around the link or text content.\n * @csspart link - The anchor element when href is provided (non-current items only).\n * @csspart text - The span element for the current page or items without href.\n * @csspart separator - The separator element rendered after non-last items.\n *\n * @cssprop [--hx-breadcrumb-link-color=var(--hx-color-primary-600)] - Link text color.\n * @cssprop [--hx-breadcrumb-link-hover-color=var(--hx-color-primary-700)] - Link hover text color.\n * @cssprop [--hx-breadcrumb-text-color=var(--hx-color-neutral-700)] - Current page text color.\n * @cssprop [--hx-breadcrumb-separator-content='/'] - Separator character displayed after non-last items.\n * @cssprop [--hx-breadcrumb-separator-color=var(--hx-color-neutral-400)] - Separator color.\n * @cssprop [--hx-breadcrumb-separator-gap=var(--hx-space-1)] - Horizontal margin around separator.\n * @cssprop [--hx-breadcrumb-item-max-width] - Optional max-width for text truncation.\n * @cssprop [--hx-breadcrumb-link-focus-ring-color=var(--hx-focus-ring-color, var(--hx-color-primary-500))] - Focus ring color for breadcrumb links.\n */\n@customElement('hx-breadcrumb-item')\nexport class HelixBreadcrumbItem extends LitElement {\n static override styles = [helixBreadcrumbItemStyles];\n\n override connectedCallback(): void {\n super.connectedCallback();\n // Only apply role=\"listitem\" when this item is a direct child of an\n // hx-breadcrumb element. Setting the role unconditionally when used\n // standalone (outside a list context) creates an invalid ARIA hierarchy\n // because listitem requires a list ancestor.\n //\n // hx-breadcrumb sets role=\"list\" on its host element so that axe-core's\n // flat-tree traversal (used by @axe-core/playwright) sees a valid list\n // ancestor for these listitems. The shadow-DOM <ol> owns the visual\n // structure; the host role satisfies the ARIA required-parent rule in\n // the composed/flat accessibility tree.\n //\n // IMPORTANT: If programmatically creating an ellipsis element, set aria-hidden\n // BEFORE inserting into the DOM. connectedCallback fires on insertion and sets\n // role=\"listitem\"; setting aria-hidden after would momentarily expose an\n // un-hidden listitem to the accessibility tree.\n if (this.closest('hx-breadcrumb') !== null) {\n this.setAttribute('role', 'listitem');\n } else {\n this.removeAttribute('role');\n }\n }\n\n /**\n * The URL for this breadcrumb link. Omit for the current page item.\n * When `current` is true, this attribute is ignored and the item always\n * renders as static text per WAI-ARIA APG breadcrumb guidance.\n * @attr href\n */\n @property({ type: String, reflect: true })\n href: string | undefined = undefined;\n\n /**\n * Whether this is the last item in the breadcrumb trail. Set by the parent\n * hx-breadcrumb component via the `data-bc-last` boolean attribute. When\n * present the trailing separator is hidden.\n *\n * @attr data-bc-last\n * @internal\n */\n @property({ type: Boolean, attribute: 'data-bc-last', reflect: true })\n dataBcLast = false;\n\n /**\n * Marks this item as the current page. When set, the item always renders as\n * static text (never a navigable link) and `aria-current=\"page\"` is placed on\n * the inner text element per WAI-ARIA APG breadcrumb guidance, yielding the\n * canonical AT announcement (\"current page, Patient Records\").\n *\n * Can be set explicitly by consumers (e.g. Drupal Twig templates) to override\n * the default positional last-item detection in `hx-breadcrumb`. When any item\n * in the breadcrumb has an explicit `current` attribute, the parent will not\n * override it.\n *\n * @attr current\n */\n @property({ type: Boolean, reflect: true })\n current = false;\n\n override render() {\n // Per WAI-ARIA APG, the current page item MUST NOT be a navigable link.\n // aria-current=\"page\" is placed on the inner element (not the listitem host)\n // for canonical AT announcement (\"current page, Patient Records\" vs\n // \"current page, list item\").\n return html`\n <span part=\"item\">\n ${this.current\n ? html`<span part=\"text\" aria-current=\"page\"><slot></slot></span>`\n : this.href\n ? html`<a part=\"link\" href=${this.href}><slot></slot></a>`\n : html`<span part=\"text\"><slot></slot></span>`}\n </span>\n ${!this.dataBcLast\n ? html`<span class=\"separator\" part=\"separator\" aria-hidden=\"true\"></span>`\n : nothing}\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'hx-breadcrumb-item': HelixBreadcrumbItem;\n }\n}\n"],"names":["helixBreadcrumbStyles","css","HelixBreadcrumb","LitElement","e","slot","el","items","hasExplicitCurrent","item","isLast","assigned","text","_a","ellipsis","btn","firstItem","_b","position","href","name","entry","schema","i","changedProperties","html","__decorateClass","property","customElement","helixBreadcrumbItemStyles","HelixBreadcrumbItem","nothing"],"mappings":";;;AAEO,MAAMA,IAAwBC;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;;;;;;ACqC9B,IAAMC,IAAN,cAA8BC,EAAW;AAAA,EAAzC,cAAA;AAAA,UAAA,GAAA,SAAA,GAgBL,KAAA,YAAY,KAOZ,KAAA,QAAQ,cASR,KAAA,WAAW,GAMgC,KAAA,gBAAgB,6BAiB3D,KAAA,SAAS,IAGT,KAAQ,gBAAgC,MAExC,KAAQ,gBAA0C,MAElD,KAAiB,sBAAsB,CAACC,MAAa,KAAK,qBAAqBA,CAAC,GAEhF,KAAiB,wBAAwB,CAACA,MAAa,KAAK,uBAAuBA,CAAC,GAUpF,KAAiB,2CAA2B,QAAA,GAa5C,KAAiB,YAAY,oBAAoB,EAAEF,EAAgB,gBAAgB;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQ3E,oBAAoBG,GAAkC;AAC5D,WAAOA,EACJ,iBAAiB,EAAE,SAAS,GAAA,CAAM,EAClC;AAAA,MACC,CAACC,MACCA,EAAG,QAAQ,YAAA,MAAkB,wBAC7B,CAACA,EAAG,UAAU,SAAS,gBAAgB;AAAA,IAAA;AAAA,EAE/C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaQ,qBAAqBC,GAAwB;AAKnD,UAAMC,IAAqBD,EAAM;AAAA,MAC/B,CAACD,MAAOA,EAAG,aAAa,SAAS,KAAK,CAAC,KAAK,qBAAqB,IAAIA,CAAE;AAAA,IAAA;AAGzE,IAAAC,EAAM,QAAQ,CAACE,GAAM,MAAM;AACzB,YAAMH,IAAKG,GACLC,IAAS,MAAMH,EAAM,SAAS;AAGpC,MAAIG,IACFJ,EAAG,aAAa,gBAAgB,EAAE,IAElCA,EAAG,gBAAgB,cAAc,GAM9BE,MACCE,KACFJ,EAAG,aAAa,WAAW,EAAE,GAC7B,KAAK,qBAAqB,IAAIA,CAAE,MAEhCA,EAAG,gBAAgB,SAAS,GAC5B,KAAK,qBAAqB,OAAOA,CAAE;AAAA,IAKzC,CAAC;AAAA,EACH;AAAA;AAAA;AAAA,EAKQ,kBAAkBF,GAAgB;AACxC,QAAI,EAAEA,EAAE,kBAAkB,iBAAkB;AAC5C,UAAMG,IAAQ,KAAK,oBAAoBH,EAAE,MAAM;AAG/C,IAAI,KAAK,WAAW,KAAKG,EAAM,SAAS,KAAK,WAC3C,KAAK,eAAeA,CAAK,IAEzB,KAAK,gBAAgBA,CAAK,GAG5B,KAAK,qBAAqBA,CAAK,GAE3B,KAAK,UACP,KAAK,cAAcA,CAAK;AAAA,EAE5B;AAAA;AAAA,EAGQ,2BAA2BH,GAAgB;;AACjD,QAAI,EAAEA,EAAE,kBAAkB,iBAAkB;AAC5C,UAAMO,IAAWP,EAAE,OAAO,iBAAiB,EAAE,SAAS,IAAM;AAC5D,QAAIO,EAAS,SAAS,GAAG;AACvB,YAAMC,MAAQC,IAAAF,EAAS,CAAC,EAAkB,gBAA5B,gBAAAE,EAAyC,WAAU;AACjE,WAAK,MAAM,YAAY,qCAAqC,KAAK,UAAUD,CAAI,CAAC;AAAA,IAClF;AAAA,EACF;AAAA;AAAA;AAAA,EAKQ,eAAeL,GAAwB;AAY7C,QAVAA,EAAM,QAAQ,CAACE,GAAM,MAAM;AACzB,YAAMH,IAAKG;AACX,MAAI,MAAM,KAAK,MAAMF,EAAM,SAAS,IAClCD,EAAG,gBAAgB,gBAAgB,IAEnCA,EAAG,aAAa,kBAAkB,EAAE;AAAA,IAExC,CAAC,GAGG,CAAC,KAAK,iBAAiB,OAAO,WAAa,KAAa;AAC1D,YAAMQ,IAAW,SAAS,cAAc,oBAAoB;AAC5D,MAAAA,EAAS,UAAU,IAAI,gBAAgB;AAIvC,YAAMC,IAAM,SAAS,cAAc,QAAQ;AAC3C,MAAAA,EAAI,OAAO,UACXA,EAAI,cAAc,KAClBA,EAAI,aAAa,cAAc,KAAK,aAAa,GACjDD,EAAS,YAAYC,CAAG,GAExB,KAAK,gBAAgBD;AAAA,IACvB;AAGA,UAAME,IAAYT,EAAM,CAAC;AACzB,IAAI,CAACS,KAAa,CAAC,KAAK,iBACpB,KAAK,cAAc,2BAA2BA,KAChDA,EAAU,MAAM,KAAK,aAAa;AAAA,EAEtC;AAAA;AAAA,EAGQ,gBAAgBT,GAAwB;;AAC9C,IAAAA,EAAM,QAAQ,CAACE,MAAS;AACrB,MAAAA,EAAqB,gBAAgB,gBAAgB;AAAA,IACxD,CAAC,IAEGI,IAAA,KAAK,kBAAL,QAAAA,EAAoB,eACtB,KAAK,cAAc,OAAA;AAAA,EAEvB;AAAA;AAAA,EAGQ,qBAAqBT,GAAgB;;AAC3C,KAAKa,KAAAJ,IAAAT,EAAE,WAAF,gBAAAS,EAAsB,YAAtB,QAAAI,EAAA,KAAAJ,GAAgC,sBACnC,KAAK,kBAAA;AAAA,EAET;AAAA;AAAA,EAGQ,uBAAuBT,GAAgB;;AAC7C,IAAMA,aAAa,kBACfA,EAAE,QAAQ,WAAWA,EAAE,QAAQ,SAC5Ba,KAAAJ,IAAAT,EAAE,WAAF,gBAAAS,EAAsB,YAAtB,QAAAI,EAAA,KAAAJ,GAAgC,uBACnCT,EAAE,eAAA,GACF,KAAK,kBAAA;AAAA,EAGX;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,oBAA0B;AAChC,SAAK,WAAW;AAAA,EAElB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQQ,eAAeK,GAAeS,GAAkC;;AACtE,UAAMC,IAAQV,EAAqB,aAAa,MAAM,GAChDW,MAAQP,IAAAJ,EAAqB,gBAArB,gBAAAI,EAAkC,WAAU,IACpDQ,IAAwB;AAAA,MAC5B,SAAS;AAAA,MACT,UAAAH;AAAA,MACA,MAAAE;AAAA,IAAA;AAEF,WAAID,QAAY,OAAOA,IAChBE;AAAA,EACT;AAAA;AAAA,EAGQ,cAAcd,GAAwB;;AAC5C,UAAMe,IAAS;AAAA,MACb,YAAY;AAAA,MACZ,SAAS;AAAA,MACT,iBAAiBf,EAAM,IAAI,CAACE,GAAMc,MAAM,KAAK,eAAed,GAAMc,IAAI,CAAC,CAAC;AAAA,IAAA;AAI1E,IAAI,OAAO,WAAa,QAEnB,KAAK,mBAKRV,IAAA,SAAS,eAAe,KAAK,SAAS,MAAtC,QAAAA,EAAyC,UAEzC,KAAK,gBAAgB,SAAS,cAAc,QAAQ,GACpD,KAAK,cAAc,OAAO,uBAC1B,KAAK,cAAc,KAAK,KAAK,WAC7B,KAAK,cAAc,aAAa,sBAAsB,EAAE,GACxD,SAAS,KAAK,YAAY,KAAK,aAAa,IAG9C,KAAK,cAAc,cAAc,KAAK,UAAUS,CAAM;AAAA,EACxD;AAAA;AAAA,EAGQ,gBAAsB;;AAC5B,KAAAT,IAAA,KAAK,kBAAL,QAAAA,EAAoB,UACpB,KAAK,gBAAgB;AAAA,EACvB;AAAA;AAAA,EAIS,oBAA0B;AACjC,UAAM,kBAAA,GACN,KAAK,iBAAiB,SAAS,KAAK,mBAAmB,GACvD,KAAK,iBAAiB,WAAW,KAAK,qBAAqB;AAAA,EAC7D;AAAA,EAES,uBAA6B;AACpC,UAAM,qBAAA,GACN,KAAK,oBAAoB,SAAS,KAAK,mBAAmB,GAC1D,KAAK,oBAAoB,WAAW,KAAK,qBAAqB,GAC9D,KAAK,cAAA;AAAA,EACP;AAAA,EAES,QAAQW,GAA+C;;AAS9D,QARA,MAAM,QAAQA,CAAiB,GAE3BA,EAAkB,IAAI,WAAW,KAGnC,KAAK,MAAM,YAAY,qCAAqC,KAAK,UAAU,KAAK,SAAS,CAAC,GAGxFA,EAAkB,IAAI,UAAU,GAAG;AAGrC,YAAMnB,KAAOQ,IAAA,KAAK,eAAL,gBAAAA,EAAiB,cAA+B;AAC7D,UAAIR,GAAM;AACR,cAAME,IAAQ,KAAK,oBAAoBF,CAAI;AAC3C,QAAI,KAAK,WAAW,KAAKE,EAAM,SAAS,KAAK,WAC3C,KAAK,eAAeA,CAAK,IAEzB,KAAK,gBAAgBA,CAAK,GAE5B,KAAK,qBAAqBA,CAAK;AAAA,MACjC;AAAA,IACF;AAEA,QAAIiB,EAAkB,IAAI,QAAQ;AAChC,UAAI,KAAK,QAAQ;AAEf,cAAMnB,KAAOY,IAAA,KAAK,eAAL,gBAAAA,EAAiB,cAA+B;AAC7D,QAAIZ,KACF,KAAK,cAAc,KAAK,oBAAoBA,CAAI,CAAC;AAAA,MAErD;AAEE,aAAK,cAAA;AAAA,EAGX;AAAA;AAAA,EAIS,SAAS;AAChB,WAAOoB;AAAA,mCACwB,KAAK,KAAK;AAAA;AAAA,8BAEf,KAAK,iBAAiB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,sBAM9B,KAAK,0BAA0B;AAAA;AAAA;AAAA,EAGnD;AACF;AA9XavB,EACK,SAAS,CAACF,CAAqB;AADpCE,EASI,mBAAmB;AAOlCwB,EAAA;AAAA,EADCC,EAAS,EAAE,MAAM,OAAA,CAAQ;AAAA,GAffzB,EAgBX,WAAA,aAAA,CAAA;AAOAwB,EAAA;AAAA,EADCC,EAAS,EAAE,MAAM,OAAA,CAAQ;AAAA,GAtBfzB,EAuBX,WAAA,SAAA,CAAA;AASAwB,EAAA;AAAA,EADCC,EAAS,EAAE,MAAM,QAAQ,WAAW,aAAa;AAAA,GA/BvCzB,EAgCX,WAAA,YAAA,CAAA;AAM2CwB,EAAA;AAAA,EAA1CC,EAAS,EAAE,WAAW,iBAAA,CAAkB;AAAA,GAtC9BzB,EAsCgC,WAAA,iBAAA,CAAA;AAiB3CwB,EAAA;AAAA,EADCC,EAAS,EAAE,MAAM,SAAS,WAAW,WAAW;AAAA,GAtDtCzB,EAuDX,WAAA,UAAA,CAAA;AAvDWA,IAANwB,EAAA;AAAA,EADNE,EAAc,eAAe;AAAA,GACjB1B,CAAA;ACrCN,MAAM2B,IAA4B5B;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;;;;;;AC6BlC,IAAM6B,IAAN,cAAkC3B,EAAW;AAAA,EAA7C,cAAA;AAAA,UAAA,GAAA,SAAA,GAkCL,KAAA,OAA2B,QAW3B,KAAA,aAAa,IAgBb,KAAA,UAAU;AAAA,EAAA;AAAA,EA1DD,oBAA0B;AACjC,UAAM,kBAAA,GAgBF,KAAK,QAAQ,eAAe,MAAM,OACpC,KAAK,aAAa,QAAQ,UAAU,IAEpC,KAAK,gBAAgB,MAAM;AAAA,EAE/B;AAAA,EAsCS,SAAS;AAKhB,WAAOsB;AAAA;AAAA,UAED,KAAK,UACHA,gEACA,KAAK,OACHA,wBAA2B,KAAK,IAAI,uBACpCA,yCAA4C;AAAA;AAAA,QAEjD,KAAK,aAEJM,IADAN,sEACO;AAAA;AAAA,EAEf;AACF;AAjFaK,EACK,SAAS,CAACD,CAAyB;AAiCnDH,EAAA;AAAA,EADCC,EAAS,EAAE,MAAM,QAAQ,SAAS,IAAM;AAAA,GAjC9BG,EAkCX,WAAA,QAAA,CAAA;AAWAJ,EAAA;AAAA,EADCC,EAAS,EAAE,MAAM,SAAS,WAAW,gBAAgB,SAAS,IAAM;AAAA,GA5C1DG,EA6CX,WAAA,cAAA,CAAA;AAgBAJ,EAAA;AAAA,EADCC,EAAS,EAAE,MAAM,SAAS,SAAS,IAAM;AAAA,GA5D/BG,EA6DX,WAAA,WAAA,CAAA;AA7DWA,IAANJ,EAAA;AAAA,EADNE,EAAc,oBAAoB;AAAA,GACtBE,CAAA;"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"hx-clinical-status-m4soOOwg.js","sources":["../../src/components/hx-clinical-status/hx-clinical-status.styles.ts","../../src/components/hx-clinical-status/hx-clinical-status.ts"],"sourcesContent":["import { css } from 'lit';\n\nexport const helixClinicalStatusStyles = css`\n :host {\n display: block;\n }\n\n :host([hidden]) {\n display: none;\n }\n\n * {\n box-sizing: border-box;\n }\n\n /* ─── Screen-reader-only utility ─── */\n /* @design-system-approved: WCAG standard visually-hidden technique for a11y */\n\n .sr-only {\n position: absolute;\n width: 1px; /* @design-system-approved: standard sr-only technique */\n height: 1px; /* @design-system-approved: standard sr-only technique */\n padding: 0;\n margin: -1px; /* @design-system-approved: standard sr-only technique */\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n white-space: nowrap;\n border: 0;\n }\n\n /* ─── Container ─── */\n\n .clinical-status {\n display: flex;\n align-items: flex-start;\n gap: var(--hx-clinical-status-gap, var(--hx-space-3, 0.75rem));\n padding: var(--hx-clinical-status-padding, var(--hx-space-4, 1rem));\n border: var(--hx-clinical-status-border-width, var(--hx-border-width-thin, 1px)) solid\n var(--hx-clinical-status-border-color, var(--hx-color-info-200, #b3d9ef));\n border-inline-start: var(--hx-clinical-status-accent-width, 4px) solid\n var(--hx-clinical-status-accent-color, var(--hx-color-info-500, #3b82f6));\n border-radius: var(--hx-clinical-status-border-radius, var(--hx-border-radius-md, 0.375rem));\n background-color: var(--hx-clinical-status-bg, var(--hx-color-info-50, #e8f4fd));\n color: var(--hx-clinical-status-color, var(--hx-color-info-800, #1a3a4a));\n font-family: var(--hx-clinical-status-font-family, var(--hx-font-family-sans, sans-serif));\n font-size: var(--hx-font-size-sm, 0.875rem);\n line-height: var(--hx-line-height-normal, 1.5);\n }\n\n /* ─── Compact Mode ─── */\n\n .clinical-status--compact {\n padding: var(\n --hx-clinical-status-compact-padding,\n var(--hx-space-2, 0.5rem) var(--hx-space-3, 0.75rem)\n );\n gap: var(--hx-space-2, 0.5rem);\n font-size: var(--hx-font-size-xs, 0.75rem);\n }\n\n /* ─── Severity: info ─── */\n\n :host([severity='info']) .clinical-status,\n :host(:not([severity])) .clinical-status {\n --hx-clinical-status-bg: var(--hx-color-info-50, #e8f4fd);\n --hx-clinical-status-border-color: var(--hx-color-info-200, #b3d9ef);\n --hx-clinical-status-accent-color: var(--hx-color-info-500, #3b82f6);\n --hx-clinical-status-color: var(--hx-color-info-800, #1a3a4a);\n --hx-clinical-status-icon-color: var(--hx-color-info-500, #3b82f6);\n }\n\n /* ─── Severity: warning ─── */\n\n :host([severity='warning']) .clinical-status {\n --hx-clinical-status-bg: var(--hx-color-warning-50, #fffbeb);\n --hx-clinical-status-border-color: var(--hx-color-warning-200, #fde68a);\n --hx-clinical-status-accent-color: var(--hx-color-warning-500, #f59e0b);\n --hx-clinical-status-color: var(--hx-color-warning-800, #92400e);\n --hx-clinical-status-icon-color: var(--hx-color-warning-500, #f59e0b);\n }\n\n /* ─── Severity: critical ─── */\n\n :host([severity='critical']) .clinical-status {\n --hx-clinical-status-bg: var(--hx-color-error-50, #fef2f2);\n --hx-clinical-status-border-color: var(--hx-color-error-200, #fecaca);\n --hx-clinical-status-accent-color: var(--hx-color-error-500, #ef4444);\n --hx-clinical-status-color: var(--hx-color-error-800, #991b1b);\n --hx-clinical-status-icon-color: var(--hx-color-error-500, #ef4444);\n }\n\n /* ─── Severity: emergent ─── */\n\n :host([severity='emergent']) .clinical-status {\n --hx-clinical-status-bg: var(--hx-color-error-50, #fef2f2);\n --hx-clinical-status-border-color: var(--hx-color-error-300, #fca5a5);\n --hx-clinical-status-accent-color: var(--hx-color-error-700, #b91c1c);\n --hx-clinical-status-color: var(--hx-color-error-900, #7f1d1d);\n --hx-clinical-status-icon-color: var(--hx-color-error-700, #b91c1c);\n border-inline-start-width: var(--hx-clinical-status-emergent-accent-width, 6px);\n font-weight: var(--hx-font-weight-semibold, 600);\n }\n\n /* ─── Icon ─── */\n\n .clinical-status__icon {\n display: flex;\n align-items: center;\n flex-shrink: 0;\n color: var(--hx-clinical-status-icon-color, var(--hx-color-info-500, #3b82f6));\n }\n\n .clinical-status__icon svg {\n width: var(--hx-space-5, 1.25rem);\n height: var(--hx-space-5, 1.25rem);\n fill: currentColor;\n }\n\n .clinical-status--compact .clinical-status__icon svg {\n width: var(--hx-space-4, 1rem);\n height: var(--hx-space-4, 1rem);\n }\n\n /* ─── Message ─── */\n\n .clinical-status__message {\n flex: 1;\n min-width: 0;\n }\n\n /* ─── Severity Label (WCAG 1.4.1) ─── */\n /* @design-system-approved: WCAG 1.4.1 non-color severity indicator for screen readers */\n\n .clinical-status__severity-label {\n position: absolute;\n width: 1px; /* @design-system-approved: standard sr-only technique */\n height: 1px; /* @design-system-approved: standard sr-only technique */\n padding: 0;\n margin: -1px; /* @design-system-approved: standard sr-only technique */\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n white-space: nowrap;\n border: 0;\n }\n\n /* ─── Actions ─── */\n\n .clinical-status__actions {\n display: none;\n align-items: center;\n gap: var(--hx-space-2, 0.5rem);\n margin-inline-start: auto;\n flex-shrink: 0;\n }\n\n .clinical-status__actions--visible {\n display: flex;\n }\n\n /* ─── Dismiss Button ─── */\n\n .clinical-status__dismiss-button {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n min-width: var(--hx-touch-target-size, 44px);\n min-height: var(--hx-touch-target-size, 44px);\n padding: 0;\n border: none;\n border-radius: var(--hx-border-radius-sm, 0.25rem);\n background: transparent;\n color: var(--hx-clinical-status-color, var(--hx-color-info-800, #1a3a4a));\n cursor: pointer;\n font-size: var(--hx-font-size-md, 1rem);\n line-height: 1;\n transition:\n background-color var(--hx-transition-fast, 150ms ease),\n opacity var(--hx-transition-fast, 150ms ease);\n opacity: 0.7;\n }\n\n .clinical-status__dismiss-button:hover {\n opacity: 1;\n background-color: color-mix(in srgb, currentColor 10%, transparent);\n }\n\n .clinical-status__dismiss-button:focus-visible {\n outline: var(--hx-focus-ring-width, 2px) solid\n var(--hx-focus-ring-color, var(--hx-color-focus, #2563eb));\n outline-offset: var(--hx-focus-ring-offset, 2px);\n opacity: 1;\n }\n\n .clinical-status__dismiss-button svg {\n width: var(--hx-space-4, 1rem);\n height: var(--hx-space-4, 1rem);\n fill: currentColor;\n }\n\n .clinical-status--compact .clinical-status__dismiss-button {\n min-width: var(--hx-space-8, 2rem);\n min-height: var(--hx-space-8, 2rem);\n }\n\n /* ─── Acknowledge Button ─── */\n\n .clinical-status__acknowledge-button {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n padding: var(--hx-space-1, 0.25rem) var(--hx-space-3, 0.75rem);\n border: var(--hx-border-width-thin, 1px) solid currentColor;\n border-radius: var(--hx-border-radius-sm, 0.25rem);\n background: transparent;\n color: inherit;\n font-family: inherit;\n font-size: var(--hx-font-size-xs, 0.75rem);\n font-weight: var(--hx-font-weight-semibold, 600);\n text-transform: uppercase;\n letter-spacing: var(--hx-letter-spacing-wide, 0.05em);\n cursor: pointer;\n line-height: var(--hx-line-height-normal, 1.5);\n min-height: var(--hx-touch-target-size, 44px);\n transition:\n background-color var(--hx-transition-fast, 150ms ease),\n opacity var(--hx-transition-fast, 150ms ease);\n }\n\n .clinical-status__acknowledge-button:hover {\n background-color: color-mix(in srgb, currentColor 10%, transparent);\n }\n\n .clinical-status__acknowledge-button:focus-visible {\n outline: var(--hx-focus-ring-width, 2px) solid\n var(--hx-focus-ring-color, var(--hx-color-focus, #2563eb));\n outline-offset: var(--hx-focus-ring-offset, 2px);\n }\n\n .clinical-status--compact .clinical-status__acknowledge-button {\n min-height: var(--hx-space-8, 2rem);\n padding: var(--hx-space-1, 0.25rem) var(--hx-space-2, 0.5rem);\n }\n\n @media (prefers-reduced-motion: reduce) {\n .clinical-status__dismiss-button,\n .clinical-status__acknowledge-button {\n transition: none;\n }\n }\n`;\n","import { html, nothing, type PropertyValues } from 'lit';\nimport '../../utilities/document-token-adoption.js';\nimport { customElement, property, state } from 'lit/decorators.js';\nimport { classMap } from 'lit/directives/class-map.js';\nimport { HelixElement } from '../../base/index.js';\nimport { createIdCounter } from '../../base/index.js';\nimport { helixClinicalStatusStyles } from './hx-clinical-status.styles.js';\n\n/** Clinical severity level for alert fatigue prevention. */\nexport type ClinicalSeverity = 'info' | 'warning' | 'critical' | 'emergent';\n\nconst nextId = createIdCounter('hx-clinical-status');\n\n/**\n * A clinical status indicator designed for healthcare alert fatigue prevention.\n * Standardizes clinical alert presentations to reduce cognitive overload from\n * inconsistent color/icon combinations across healthcare interfaces.\n *\n * @summary Clinical status indicator for standardized healthcare alert fatigue prevention.\n *\n * @tag hx-clinical-status\n *\n * @slot - Default slot for additional message content.\n *\n * @fires {CustomEvent<void>} hx-dismiss - Dispatched when the user dismisses the status.\n * @fires {CustomEvent<{ severity: ClinicalSeverity; persistent: boolean }>} hx-acknowledge - Dispatched when the user acknowledges a critical/emergent status.\n *\n * @csspart container - The outer status container.\n * @csspart icon - The icon container.\n * @csspart message - The message content area.\n * @csspart actions - The actions container (dismiss/acknowledge buttons).\n * @csspart dismiss-button - The dismiss button (only rendered when dismissible).\n * @csspart acknowledge-button - The acknowledge button (only rendered when acknowledgeable).\n *\n * @cssprop [--hx-clinical-status-bg=var(--hx-color-info-50)] - Background color.\n * @cssprop [--hx-clinical-status-color=var(--hx-color-info-800)] - Text color.\n * @cssprop [--hx-clinical-status-border-color=var(--hx-color-info-200)] - Border color.\n * @cssprop [--hx-clinical-status-border-radius=var(--hx-border-radius-md)] - Border radius.\n * @cssprop [--hx-clinical-status-border-width=var(--hx-border-width-thin)] - Border width.\n * @cssprop [--hx-clinical-status-accent-color=var(--hx-color-info-500)] - Left accent stripe color.\n * @cssprop [--hx-clinical-status-accent-width=4px] - Left accent stripe width.\n * @cssprop [--hx-clinical-status-padding=var(--hx-space-4)] - Container padding.\n * @cssprop [--hx-clinical-status-gap=var(--hx-space-3)] - Gap between elements.\n * @cssprop [--hx-clinical-status-icon-color=var(--hx-color-info-500)] - Icon color.\n * @cssprop [--hx-clinical-status-font-family=var(--hx-font-family-sans)] - Font family.\n * @cssprop [--hx-clinical-status-compact-padding] - Padding in compact mode.\n * @cssprop [--hx-clinical-status-emergent-accent-width=6px] - Accent width for emergent severity.\n */\n@customElement('hx-clinical-status')\nexport class HelixClinicalStatus extends HelixElement {\n static override styles = [helixClinicalStatusStyles];\n\n // ─── Properties ───\n\n /**\n * Clinical severity level that determines visual styling and ARIA semantics.\n * - `info`: Informational, non-urgent (role=\"status\")\n * - `warning`: Requires attention but not immediate (role=\"status\")\n * - `critical`: Requires prompt clinical attention (role=\"alert\")\n * - `emergent`: Life-threatening, immediate action required (role=\"alert\")\n * @attr severity\n */\n @property({ type: String, reflect: true })\n severity: ClinicalSeverity = 'info';\n\n /**\n * Status message text. Displayed as the primary content of the indicator.\n * @attr message\n */\n @property({ type: String })\n message = '';\n\n /**\n * Whether the status can be dismissed by the user.\n * Critical and emergent statuses should require acknowledgment rather than simple dismissal.\n * @attr dismissible\n */\n @property({ type: Boolean, reflect: true })\n dismissible = false;\n\n /**\n * Whether the status survives page navigation.\n * Defaults to false for info/warning, true for critical/emergent.\n * When set explicitly, overrides the severity-based default.\n * @attr persistent\n */\n @property({ type: Boolean, reflect: true })\n persistent = false;\n\n /**\n * Optional custom icon name. When not set, a default severity-appropriate icon is shown.\n * @attr icon\n */\n @property({ type: String })\n icon = '';\n\n /**\n * Compact mode for dense clinical UIs (e.g. patient dashboards, bedside displays).\n * Reduces padding, font size, and icon size.\n * @attr compact\n */\n @property({ type: Boolean, reflect: true })\n compact = false;\n\n // ─── State ───\n\n /** @internal */\n @state()\n private _hasSlottedContent = false;\n\n /** @internal */\n @state()\n private _acknowledged = false;\n\n /**\n * SSR-safe unique ID for ARIA relationships.\n * @internal\n */\n private _componentId = nextId();\n\n /**\n * Tracks whether `persistent` was explicitly set by the consumer.\n * @internal\n */\n private _persistentExplicitlySet = false;\n\n // ─── Private Helpers ───\n\n /** Returns true when the severity requires assertive announcement. */\n private get _isAssertive(): boolean {\n return this.severity === 'critical' || this.severity === 'emergent';\n }\n\n /** Returns the appropriate ARIA role based on severity. */\n private get _role(): string {\n return this._isAssertive ? 'alert' : 'status';\n }\n\n /** Returns the default severity label for screen readers (WCAG 1.4.1). */\n private _severityLabel(): string {\n const labels: Record<ClinicalSeverity, string> = {\n info: 'Info:',\n warning: 'Warning:',\n critical: 'Critical:',\n emergent: 'Emergent:',\n };\n return labels[this.severity] ?? '';\n }\n\n /** Returns true when the severity requires explicit acknowledgment. */\n private get _requiresAcknowledgment(): boolean {\n return this.severity === 'critical' || this.severity === 'emergent';\n }\n\n /** Effective persistent value, considering severity-based defaults. */\n private get _effectivePersistent(): boolean {\n if (this._persistentExplicitlySet) {\n return this.persistent;\n }\n return this._isAssertive;\n }\n\n // ─── Lifecycle ───\n\n override connectedCallback(): void {\n super.connectedCallback();\n this.setAttribute('role', this._role);\n }\n\n override attributeChangedCallback(name: string, _old: string | null, value: string | null): void {\n super.attributeChangedCallback(name, _old, value);\n if (name === 'persistent') {\n this._persistentExplicitlySet = value !== null;\n }\n }\n\n protected override updated(changedProperties: PropertyValues<this>): void {\n super.updated(changedProperties);\n if (changedProperties.has('severity')) {\n this.setAttribute('role', this._role);\n }\n }\n\n // ─── Default Icons ───\n\n /** @internal */\n private _renderInfoIcon() {\n return html`<svg viewBox=\"0 0 20 20\" aria-hidden=\"true\">\n <path\n d=\"M10 2a8 8 0 100 16 8 8 0 000-16zm.75 4.75a.75.75 0 11-1.5 0 .75.75 0 011.5 0zM9.25 9a.75.75 0 011.5 0v4a.75.75 0 01-1.5 0V9z\"\n />\n </svg>`;\n }\n\n /** @internal */\n private _renderWarningIcon() {\n return html`<svg viewBox=\"0 0 20 20\" aria-hidden=\"true\">\n <path\n d=\"M8.49 2.92a1.75 1.75 0 013.02 0l6.25 10.83A1.75 1.75 0 0116.25 16H3.75a1.75 1.75 0 01-1.51-2.25L8.49 2.92zM10 7a.75.75 0 01.75.75v3a.75.75 0 01-1.5 0v-3A.75.75 0 0110 7zm0 7.5a.75.75 0 100-1.5.75.75 0 000 1.5z\"\n />\n </svg>`;\n }\n\n /** @internal */\n private _renderCriticalIcon() {\n return html`<svg viewBox=\"0 0 20 20\" aria-hidden=\"true\">\n <path\n d=\"M10 2a8 8 0 100 16 8 8 0 000-16zm-1.72 5.22a.75.75 0 011.06 0L10 7.94l.66-.72a.75.75 0 111.06 1.06L11.06 9l.66.72a.75.75 0 11-1.06 1.06L10 10.06l-.66.72a.75.75 0 01-1.06-1.06L8.94 9l-.66-.72a.75.75 0 010-1.06z\"\n />\n </svg>`;\n }\n\n /** @internal */\n private _renderEmergentIcon() {\n return html`<svg viewBox=\"0 0 20 20\" aria-hidden=\"true\">\n <path\n d=\"M10 2a8 8 0 100 16 8 8 0 000-16zm-1 4a1 1 0 112 0v4a1 1 0 11-2 0V6zm1 9a1.25 1.25 0 100-2.5A1.25 1.25 0 0010 15z\"\n />\n </svg>`;\n }\n\n /** @internal */\n private _renderDefaultIcon() {\n switch (this.severity) {\n case 'warning':\n return this._renderWarningIcon();\n case 'critical':\n return this._renderCriticalIcon();\n case 'emergent':\n return this._renderEmergentIcon();\n case 'info':\n default:\n return this._renderInfoIcon();\n }\n }\n\n /** @internal */\n private _renderCloseIcon() {\n return html`<svg viewBox=\"0 0 20 20\" aria-hidden=\"true\">\n <path\n d=\"M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z\"\n />\n </svg>`;\n }\n\n // ─── Slot Change Handling ───\n\n /** @internal */\n private _handleSlotChange(e: Event): void {\n const slot = e.target as HTMLSlotElement;\n this._hasSlottedContent = slot.assignedNodes({ flatten: true }).length > 0;\n }\n\n // ─── Event Handling ───\n\n /** @internal */\n private _handleDismiss(): void {\n this.dispatchEvent(\n new CustomEvent<void>('hx-dismiss', {\n bubbles: true,\n composed: true,\n }),\n );\n }\n\n /** @internal */\n private _handleAcknowledge(): void {\n this._acknowledged = true;\n this.dispatchEvent(\n new CustomEvent<{ severity: ClinicalSeverity; persistent: boolean }>('hx-acknowledge', {\n bubbles: true,\n composed: true,\n detail: {\n severity: this.severity,\n persistent: this._effectivePersistent,\n },\n }),\n );\n }\n\n // ─── Render ───\n\n override render() {\n const classes = {\n 'clinical-status': true,\n [`clinical-status--${this.severity}`]: true,\n 'clinical-status--compact': this.compact,\n };\n\n const severityLabel = this._severityLabel();\n const messageId = `${this._componentId}-message`;\n\n return html`\n <div part=\"container\" class=${classMap(classes)} aria-labelledby=${messageId}>\n <span class=\"clinical-status__severity-label\">${severityLabel}</span>\n\n <div part=\"icon\" class=\"clinical-status__icon\">\n ${this.icon\n ? html`<span class=\"clinical-status__custom-icon\">${this.icon}</span>`\n : this._renderDefaultIcon()}\n </div>\n\n <div id=${messageId} part=\"message\" class=\"clinical-status__message\">\n ${this.message}\n ${this._hasSlottedContent\n ? html`<div class=\"clinical-status__slot-content\">\n <slot @slotchange=${this._handleSlotChange}></slot>\n </div>`\n : html`<slot @slotchange=${this._handleSlotChange}></slot>`}\n </div>\n\n <div\n part=\"actions\"\n class=${classMap({\n 'clinical-status__actions': true,\n 'clinical-status__actions--visible': this.dismissible || this._requiresAcknowledgment,\n })}\n >\n ${this._requiresAcknowledgment && !this._acknowledged\n ? html`\n <button\n part=\"acknowledge-button\"\n class=\"clinical-status__acknowledge-button\"\n @click=${this._handleAcknowledge}\n >\n Acknowledge\n </button>\n `\n : nothing}\n ${this.dismissible\n ? html`\n <button\n part=\"dismiss-button\"\n class=\"clinical-status__dismiss-button\"\n aria-label=\"Dismiss clinical status\"\n @click=${this._handleDismiss}\n >\n ${this._renderCloseIcon()}\n </button>\n `\n : nothing}\n </div>\n </div>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'hx-clinical-status': HelixClinicalStatus;\n }\n}\n\n/** Canonical type alias for the hx-clinical-status component. */\nexport type HxClinicalStatus = HelixClinicalStatus;\n"],"names":["helixClinicalStatusStyles","css","nextId","createIdCounter","HelixClinicalStatus","HelixElement","name","_old","value","changedProperties","html","e","slot","classes","severityLabel","messageId","classMap","nothing","__decorateClass","property","state","customElement"],"mappings":";;;;;AAEO,MAAMA,IAA4BC;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;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;AA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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"hx-code-snippet-CoLYvX1Z.js","sources":["../../src/components/hx-code-snippet/hx-code-snippet.styles.ts","../../src/components/hx-code-snippet/hx-code-snippet.ts"],"sourcesContent":["import { css } from 'lit';\n\nexport const helixCodeSnippetStyles = css`\n :host {\n display: block;\n }\n\n :host([inline]) {\n display: inline;\n }\n\n /* ─── Inline Mode ─── */\n\n .code-snippet--inline {\n display: inline;\n font-family: var(--hx-code-snippet-font-family, var(--hx-font-family-mono, monospace));\n font-size: var(--hx-code-snippet-font-size, var(--hx-font-size-sm, 0.875em));\n background-color: var(--hx-code-snippet-inline-bg, var(--hx-color-neutral-100, #f1f5f9));\n color: var(--hx-code-snippet-inline-color, var(--hx-color-neutral-900, #0f172a));\n padding: var(--hx-code-snippet-inline-padding-y, 0.125em)\n var(--hx-code-snippet-inline-padding-x, 0.375em);\n border-radius: var(--hx-code-snippet-border-radius, var(--hx-border-radius-sm, 0.25rem));\n }\n\n /* ─── Block Mode Container ─── */\n\n .code-snippet {\n position: relative;\n background-color: var(--hx-code-snippet-bg, var(--hx-color-neutral-900, #0f172a));\n border-radius: var(--hx-code-snippet-border-radius, var(--hx-border-radius-md, 0.375rem));\n overflow: hidden;\n }\n\n /* ─── Hidden Slot ─── */\n\n .code-snippet__slot {\n display: none;\n }\n\n /* ─── Header ─── */\n\n .code-snippet__header {\n display: flex;\n align-items: center;\n justify-content: flex-end;\n padding: var(--hx-space-2, 0.5rem) var(--hx-space-2, 0.5rem) 0;\n min-height: var(--hx-space-8, 2rem);\n }\n\n /* ─── Pre / Code ─── */\n\n .code-snippet__pre {\n margin: 0;\n padding: var(--hx-code-snippet-padding, var(--hx-space-4, 1rem));\n overflow-x: auto;\n white-space: pre;\n }\n\n .code-snippet__pre--wrap {\n white-space: pre-wrap;\n word-break: break-word;\n }\n\n .code-snippet__code {\n display: block;\n font-family: var(--hx-code-snippet-font-family, var(--hx-font-family-mono, monospace));\n font-size: var(--hx-code-snippet-font-size, var(--hx-font-size-sm, 0.875rem));\n line-height: var(--hx-line-height-relaxed, 1.625);\n color: var(--hx-code-snippet-color, var(--hx-color-neutral-100, #f1f5f9));\n tab-size: var(--hx-code-snippet-tab-size, 2);\n }\n\n /* ─── Copy Button ─── */\n\n .code-snippet__copy-button {\n display: inline-flex;\n align-items: center;\n gap: var(--hx-space-1, 0.25rem);\n /* WCAG 2.5.5: minimum 44×44px touch target */\n min-width: var(--hx-touch-target-min, 2.75rem);\n min-height: var(--hx-touch-target-min, 2.75rem);\n padding: var(--hx-space-1, 0.25rem) var(--hx-space-2, 0.5rem);\n border: var(--hx-border-width-thin, 1px) solid var(--hx-color-neutral-600, #475569);\n border-radius: var(--hx-border-radius-sm, 0.25rem);\n background-color: var(--hx-color-neutral-800, #1e293b);\n color: var(--hx-color-neutral-200, #e2e8f0);\n font-family: var(--hx-font-family-sans, sans-serif);\n font-size: var(--hx-font-size-xs, 0.75rem);\n font-weight: var(--hx-font-weight-medium, 500);\n line-height: 1; /* intentional literal: icon/action button needs line-height 1; no token maps to exactly 1 */\n cursor: pointer;\n transition:\n background-color var(--hx-transition-fast, 150ms ease),\n color var(--hx-transition-fast, 150ms ease),\n border-color var(--hx-transition-fast, 150ms ease);\n white-space: nowrap;\n z-index: 1; /* @audit-exception: local stacking context within shadow root */\n }\n\n .code-snippet__copy-button:hover {\n background-color: var(--hx-color-neutral-700, #334155);\n border-color: var(--hx-color-neutral-500, #64748b);\n }\n\n .code-snippet__copy-button:focus-visible {\n outline: var(--hx-focus-ring-width, 2px) solid\n var(--hx-focus-ring-color, var(--hx-color-primary-400, #60a5fa));\n outline-offset: var(--hx-focus-ring-offset, 2px);\n }\n\n .code-snippet__copy-button:active {\n /* Non-standard token — fallback 0.8 applies if token is absent */\n filter: brightness(var(--hx-filter-brightness-active, 0.8));\n }\n\n /* ─── Expand Button ─── */\n\n .code-snippet__expand-button {\n display: block;\n width: 100%;\n /* WCAG 2.5.5: minimum 44×44px touch target */\n min-width: var(--hx-touch-target-min, 2.75rem);\n min-height: var(--hx-touch-target-min, 2.75rem);\n padding: var(--hx-space-2, 0.5rem) var(--hx-space-4, 1rem);\n border: none;\n border-top: var(--hx-border-width-thin, 1px) solid var(--hx-color-neutral-700, #334155);\n background-color: var(--hx-color-neutral-800, #1e293b);\n color: var(--hx-color-neutral-300, #cbd5e1);\n font-family: var(--hx-font-family-sans, sans-serif);\n font-size: var(--hx-font-size-sm, 0.875rem);\n font-weight: var(--hx-font-weight-medium, 500);\n text-align: center;\n cursor: pointer;\n transition: background-color var(--hx-transition-fast, 150ms ease);\n }\n\n .code-snippet__expand-button:hover {\n background-color: var(--hx-color-neutral-700, #334155);\n color: var(--hx-color-neutral-100, #f1f5f9);\n }\n\n .code-snippet__expand-button:focus-visible {\n outline: var(--hx-focus-ring-width, 2px) solid\n var(--hx-focus-ring-color, var(--hx-color-primary-400, #60a5fa));\n outline-offset: var(--hx-focus-ring-offset, 2px);\n }\n\n /* ─── Line Numbers ─── */\n\n .code-snippet__line-number {\n display: inline-block;\n min-width: var(--hx-space-8, 2rem);\n padding-inline-end: var(--hx-space-3, 0.75rem);\n color: var(--hx-code-snippet-line-number-color, var(--hx-color-neutral-500, #64748b));\n user-select: none;\n text-align: end;\n }\n\n @media (prefers-reduced-motion: reduce) {\n .code-snippet__copy-button {\n transition: none;\n }\n\n .code-snippet__expand-button {\n transition: none;\n }\n }\n\n /* ─── Screen-reader only ─── */\n\n .sr-only {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n margin: -1px;\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n white-space: nowrap;\n border: 0;\n }\n`;\n","import { LitElement, html, nothing, TemplateResult } from 'lit';\nimport '../../utilities/document-token-adoption.js';\nimport { customElement, property, state } from 'lit/decorators.js';\nimport { classMap } from 'lit/directives/class-map.js';\nimport { helixCodeSnippetStyles } from './hx-code-snippet.styles.js';\n\n/**\n * A styled code block with optional copy button and max-lines truncation.\n * Supports block (`<pre><code>`) and inline (`<code>`) rendering modes.\n * No external syntax highlighting dependency — use the `language` attribute\n * as a hint for consumers integrating their own highlighter via slotted content.\n *\n * @summary Styled code display component with copy-to-clipboard and expand/collapse.\n *\n * @tag hx-code-snippet\n *\n * @slot - Code content as plain text. Note: HTML markup in slot content will be\n * stripped — only text content is extracted. Pre-highlighted HTML is not supported.\n *\n * @fires {CustomEvent<{text: string}>} hx-copy - Dispatched when the copy button is clicked.\n *\n * @csspart base - The outermost container (block: `<div>`, inline: `<code>`).\n * @csspart header - The header bar containing the copy button (block mode only).\n * @csspart code - The `<code>` element containing the content.\n * @csspart copy-button - The copy-to-clipboard button.\n * @csspart expand-button - The \"Show more / Show less\" button.\n *\n * @cssprop [--hx-code-snippet-bg=var(--hx-color-neutral-900,#0f172a)] - Background color.\n * @cssprop [--hx-code-snippet-color=var(--hx-color-neutral-100,#f1f5f9)] - Text color.\n * @cssprop [--hx-code-snippet-font-family=var(--hx-font-family-mono,monospace)] - Font family.\n * @cssprop [--hx-code-snippet-font-size=var(--hx-font-size-sm,0.875rem)] - Font size.\n * @cssprop [--hx-code-snippet-border-radius=var(--hx-border-radius-md,0.375rem)] - Border radius.\n * @cssprop [--hx-code-snippet-padding=var(--hx-space-4,1rem)] - Inner padding (block mode).\n */\n@customElement('hx-code-snippet')\nexport class HelixCodeSnippet extends LitElement {\n static override styles = [helixCodeSnippetStyles];\n\n // ─── Public Properties ───\n\n /**\n * Language hint for consumers to apply syntax highlighting.\n * Does not affect rendering directly; it is applied as a `language-*` class\n * on the `<code>` element so external highlighters can target it.\n * @attr language\n */\n @property({ type: String, reflect: true })\n language: string = '';\n\n /**\n * When true, renders as an inline `<code>` element instead of a `<pre><code>` block.\n * @attr inline\n */\n @property({ type: Boolean, reflect: true })\n inline: boolean = false;\n\n /**\n * When true, enables word-wrap in block mode.\n * @attr wrap\n */\n @property({ type: Boolean, reflect: true })\n wrap: boolean = false;\n\n /**\n * When true, shows a copy-to-clipboard button. Add the `copyable` attribute to enable it.\n * @attr copyable\n */\n @property({ type: Boolean, reflect: true })\n copyable: boolean = false;\n\n /**\n * Maximum number of lines to display before showing a \"Show more\" button.\n * Set to 0 (default) to disable truncation.\n * @attr max-lines\n */\n @property({ type: Number, attribute: 'max-lines', reflect: true })\n maxLines: number = 0;\n\n /**\n * When true, prepends line numbers to each displayed line in block mode.\n * Line numbers are rendered as `aria-hidden` spans so screen readers skip them.\n * @attr line-numbers\n */\n @property({ type: Boolean, attribute: 'line-numbers', reflect: true })\n lineNumbers: boolean = false;\n\n /**\n * Label for the copy button in idle state.\n * @attr label-copy\n */\n @property({ type: String, attribute: 'label-copy' })\n labelCopy = 'Copy code';\n\n /**\n * Label for the copy button after successful copy.\n * @attr label-copied\n */\n @property({ type: String, attribute: 'label-copied' })\n labelCopied = 'Copied!';\n\n /**\n * Label for the expand button when content is collapsed.\n * @attr label-show-more\n */\n @property({ type: String, attribute: 'label-show-more' })\n labelShowMore = 'Show more';\n\n /**\n * Label for the expand button when content is expanded.\n * @attr label-show-less\n */\n @property({ type: String, attribute: 'label-show-less' })\n labelShowLess = 'Show less';\n\n // ─── Internal State ───\n\n /** @internal */\n @state() private _copied: boolean = false;\n /** @internal */\n @state() private _expanded: boolean = false;\n /** @internal */\n @state() private _codeText: string = '';\n\n /** @internal */\n private _copyTimer: ReturnType<typeof setTimeout> | null = null;\n\n // ─── Lifecycle ───\n\n override firstUpdated(): void {\n // Prevent flash of empty <code> before slotchange fires by eagerly reading\n // text content from host children that are already present on first render.\n if (!this._codeText) {\n const text = this.textContent?.trim() ?? '';\n if (text) {\n this._codeText = text;\n }\n }\n }\n\n override disconnectedCallback(): void {\n super.disconnectedCallback();\n if (this._copyTimer !== null) {\n clearTimeout(this._copyTimer);\n this._copyTimer = null;\n }\n }\n\n // ─── Event Handlers ───\n\n /** @internal */\n private _handleSlotChange(e: Event): void {\n const slot = e.target as HTMLSlotElement | null;\n if (!slot) return;\n const nodes = slot.assignedNodes({ flatten: true });\n this._codeText = nodes.map((n) => n.textContent ?? '').join('');\n }\n\n /** @internal */\n private _handleCopy(): void {\n const text = this._codeText;\n\n navigator?.clipboard?.writeText(text).catch(() => {\n // Clipboard API unavailable (non-HTTPS environments such as Drupal staging) — emit event only.\n // Note: navigator.clipboard requires a secure context (HTTPS or localhost).\n // On HTTP, the copy event still fires but the clipboard is not populated.\n });\n\n this.dispatchEvent(\n new CustomEvent<{ text: string }>('hx-copy', {\n bubbles: true,\n composed: true,\n detail: { text },\n }),\n );\n\n this._copied = true;\n if (this._copyTimer !== null) clearTimeout(this._copyTimer);\n this._copyTimer = setTimeout(() => {\n this._copied = false;\n }, 2000);\n }\n\n /** @internal */\n private _handleExpand(): void {\n this._expanded = !this._expanded;\n }\n\n // ─── Helpers ───\n\n /** @internal */\n private _getDisplayLines(): string[] {\n const lines = this._codeText.split('\\n');\n if (!this.maxLines || this.maxLines <= 0 || this._expanded) {\n return lines;\n }\n if (lines.length <= this.maxLines) {\n return lines;\n }\n return lines.slice(0, this.maxLines);\n }\n\n /** @internal */\n private _isTruncated(): boolean {\n if (!this.maxLines || this.maxLines <= 0) return false;\n const lines = this._codeText.split('\\n');\n return lines.length > this.maxLines;\n }\n\n /** @internal */\n private _renderLines(lines: string[]): TemplateResult {\n if (!this.lineNumbers) {\n return html`${lines.join('\\n')}`;\n }\n return html`${lines.map(\n (line, i) =>\n html`<span aria-hidden=\"true\" class=\"code-snippet__line-number\">${i + 1}</span>${line +\n '\\n'}`,\n )}`;\n }\n\n // ─── Render ───\n\n override render(): TemplateResult | typeof nothing {\n if (this.inline) {\n return html`\n <code part=\"base code\" class=\"code-snippet code-snippet--inline\">\n <slot @slotchange=${this._handleSlotChange}></slot>\n </code>\n `;\n }\n\n const displayLines = this._getDisplayLines();\n const truncated = this._isTruncated();\n const preLabel = this.language ? `Code snippet: ${this.language}` : 'Code snippet';\n const codeClasses = classMap({\n 'code-snippet__code': true,\n [`language-${this.language}`]: Boolean(this.language),\n });\n\n return html`\n <div part=\"base\" class=\"code-snippet\">\n <div part=\"header\" class=\"code-snippet__header\">\n ${this.copyable\n ? html`\n <button\n part=\"copy-button\"\n class=\"code-snippet__copy-button\"\n type=\"button\"\n aria-label=${this._copied ? this.labelCopied : this.labelCopy}\n @click=${this._handleCopy}\n >\n ${this._copied ? this.labelCopied : this.labelCopy}\n </button>\n `\n : nothing}\n </div>\n\n <!-- Visually-hidden live region announces copy confirmation to assistive technology -->\n <span\n aria-live=\"polite\"\n style=\"position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0;\"\n >${this._copied ? this.labelCopied : ''}</span\n >\n\n <pre\n role=\"region\"\n aria-label=${preLabel}\n class=${classMap({\n 'code-snippet__pre': true,\n 'code-snippet__pre--wrap': this.wrap,\n })}\n ><code part=\"code\" class=${codeClasses}>${this._renderLines(displayLines)}</code></pre>\n\n <!-- Hidden slot to capture text content for display and copy -->\n <slot class=\"code-snippet__slot\" @slotchange=${this._handleSlotChange}></slot>\n\n ${truncated\n ? html`\n <button\n part=\"expand-button\"\n class=\"code-snippet__expand-button\"\n type=\"button\"\n aria-expanded=${this._expanded ? 'true' : 'false'}\n @click=${this._handleExpand}\n >\n ${this._expanded ? this.labelShowLess : this.labelShowMore}\n </button>\n `\n : nothing}\n </div>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'hx-code-snippet': HelixCodeSnippet;\n }\n}\n"],"names":["helixCodeSnippetStyles","css","HelixCodeSnippet","LitElement","text","_a","slot","nodes","n","lines","html","line","i","displayLines","truncated","preLabel","codeClasses","classMap","nothing","__decorateClass","property","state","customElement"],"mappings":"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"}
|