{"version":3,"file":"BqSelect-CtwDq3AK.cjs","names":[],"sources":["../src/components/select/BqSelect.ts"],"sourcesContent":["/**\r\n * Select / dropdown form field.\r\n * @element bq-select\r\n * @prop {string}  label\r\n * @prop {string}  value\r\n * @prop {string}  placeholder\r\n * @prop {string}  name\r\n * @prop {boolean} disabled\r\n * @prop {boolean} required\r\n * @prop {string}  error\r\n * @prop {string}  hint\r\n * @prop {string}  size    - sm | md | lg\r\n * @slot - <option> elements\r\n * @fires bq-change - { value: string }\r\n */\r\nimport type { ComponentDefinition } from '@bquery/bquery/component';\r\nimport { bool, component, html } from '@bquery/bquery/component';\r\nimport { escapeHtml } from '@bquery/bquery/security';\r\nimport { uniqueId } from '../../utils/dom.js';\r\nimport { createFormProxy, type FormProxy } from '../../utils/form.js';\r\nimport { getBaseStyles } from '../../utils/styles.js';\r\n\r\ntype BqSelectProps = {\r\n  label: string;\r\n  value: string;\r\n  placeholder: string;\r\n  name: string;\r\n  disabled: boolean;\r\n  required: boolean;\r\n  error: string;\r\n  hint: string;\r\n  size: string;\r\n};\r\ntype BqSelectState = { uid: string; optionsMarkup: string };\r\n\r\nconst serializeOption = (\r\n  option: Element,\r\n  selectedValue: string,\r\n  hasExplicitValue: boolean\r\n): string => {\r\n  const optionValue =\r\n    option.getAttribute('value') ?? option.textContent?.trim() ?? '';\r\n  const attrs = [`value=\"${escapeHtml(optionValue)}\"`];\r\n  if (option.hasAttribute('disabled')) attrs.push('disabled');\r\n  if (\r\n    hasExplicitValue\r\n      ? optionValue === selectedValue\r\n      : option.hasAttribute('selected')\r\n  ) {\r\n    attrs.push('selected');\r\n  }\r\n  return `<option ${attrs.join(' ')}>${escapeHtml(option.textContent?.trim() ?? '')}</option>`;\r\n};\r\n\r\nconst serializeOptGroup = (\r\n  group: Element,\r\n  selectedValue: string,\r\n  hasExplicitValue: boolean\r\n): string => {\r\n  const optionsMarkup = Array.from(group.children)\r\n    .filter((child) => child.tagName.toLowerCase() === 'option')\r\n    .map((option) => serializeOption(option, selectedValue, hasExplicitValue))\r\n    .join('');\r\n  const disabled = group.hasAttribute('disabled') ? ' disabled' : '';\r\n  return `<optgroup label=\"${escapeHtml(group.getAttribute('label') ?? '')}\"${disabled}>${optionsMarkup}</optgroup>`;\r\n};\r\n\r\nconst buildOptionsMarkup = (host: HTMLElement, placeholder: string): string => {\r\n  const selectedValue = host.getAttribute('value') ?? '';\r\n  const hasExplicitValue = host.hasAttribute('value');\r\n  const placeholderMarkup = placeholder\r\n    ? `<option value=\"\" ${!selectedValue ? 'selected' : ''} disabled>${escapeHtml(placeholder)}</option>`\r\n    : '';\r\n  const childrenMarkup = Array.from(host.children)\r\n    .map((child) => {\r\n      if (child.tagName.toLowerCase() === 'option') {\r\n        return serializeOption(child, selectedValue, hasExplicitValue);\r\n      }\r\n      if (child.tagName.toLowerCase() === 'optgroup') {\r\n        return serializeOptGroup(child, selectedValue, hasExplicitValue);\r\n      }\r\n      return '';\r\n    })\r\n    .join('');\r\n  return `${placeholderMarkup}${childrenMarkup}`;\r\n};\r\n\r\nconst syncSelectValue = (host: HTMLElement): void => {\r\n  const select = host.shadowRoot?.querySelector(\r\n    'select'\r\n  ) as HTMLSelectElement | null;\r\n  if (!select) return;\r\n\r\n  const attrValue = host.getAttribute('value');\r\n  const options = Array.from(select.options);\r\n  const hasMatchingValue =\r\n    attrValue !== null && options.some((option) => option.value === attrValue);\r\n\r\n  if (hasMatchingValue) {\r\n    select.value = attrValue;\r\n  }\r\n\r\n  const normalizedValue = select.value;\r\n  if ((attrValue ?? '') !== normalizedValue) {\r\n    host.setAttribute('value', normalizedValue);\r\n  }\r\n\r\n  const proxy = (host as unknown as Record<string, unknown>)['_formProxy'] as\r\n    | FormProxy\r\n    | undefined;\r\n  proxy?.setValue(normalizedValue);\r\n};\r\n\r\nconst definition: ComponentDefinition<BqSelectProps, BqSelectState> = {\r\n  props: {\r\n    label: { type: String, default: '' },\r\n    value: { type: String, default: '' },\r\n    placeholder: { type: String, default: '' },\r\n    name: { type: String, default: '' },\r\n    disabled: { type: Boolean, default: false },\r\n    required: { type: Boolean, default: false },\r\n    error: { type: String, default: '' },\r\n    hint: { type: String, default: '' },\r\n    size: { type: String, default: 'md' },\r\n  },\r\n  state: {\r\n    uid: '',\r\n    optionsMarkup: '',\r\n  },\r\n  styles: `\r\n    ${getBaseStyles()}\r\n    *, *::before, *::after { box-sizing: border-box; }\r\n    :host { display: block; }\r\n    .field { display: flex; flex-direction: column; gap: 0.375rem; }\r\n    .label { font-size: var(--bq-font-size-sm,0.875rem); font-weight: var(--bq-font-weight-medium,500); color: var(--bq-text-base,#0f172a); font-family: var(--bq-font-family-sans); }\r\n    .required-mark { color: var(--bq-color-danger-600,#dc2626); }\r\n    .select-wrap { position: relative; }\r\n    select {\r\n      display: block; width: 100%;\r\n      border: 1.5px solid var(--bq-border-emphasis,#cbd5e1);\r\n      border-radius: var(--bq-radius-lg,0.5rem); background: var(--bq-bg-base,#fff);\r\n      color: var(--bq-text-base,#0f172a); font-family: var(--bq-font-family-sans);\r\n      appearance: none; -webkit-appearance: none;\r\n      padding-right: 2rem; outline: none; cursor: pointer;\r\n      transition: border-color var(--bq-duration-fast) var(--bq-easing-standard), box-shadow var(--bq-duration-fast) var(--bq-easing-standard);\r\n    }\r\n    :host([size=\"sm\"]) select { font-size: 0.875rem; padding: 0.375rem 2rem 0.375rem 0.75rem; min-height: 2rem; }\r\n    :host([size=\"md\"]) select, :host(:not([size])) select { font-size: 1rem; padding: 0.5rem 2rem 0.5rem 0.875rem; min-height: 2.5rem; }\r\n    :host([size=\"lg\"]) select { font-size: 1.125rem; padding: 0.625rem 2rem 0.625rem 1rem; min-height: 3rem; }\r\n    select:focus { border-color: var(--bq-border-focus,#2563eb); box-shadow: var(--bq-focus-ring); }\r\n    :host([error]:not([error=\"\"])) select { border-color: var(--bq-color-danger-500,#ef4444); }\r\n    :host([disabled]) select { opacity: 0.5; cursor: not-allowed; background: var(--bq-bg-muted,#f1f5f9); }\r\n    .arrow { position: absolute; right: 0.75rem; top: 50%; transform: translateY(-50%); pointer-events: none; color: var(--bq-text-muted,#475569); font-size: 0.75rem; }\r\n    .hint { font-size: var(--bq-font-size-sm,0.875rem); color: var(--bq-text-muted,#475569); font-family: var(--bq-font-family-sans); }\r\n    .error-msg { font-size: var(--bq-font-size-sm,0.875rem); color: var(--bq-color-danger-600,#dc2626); font-family: var(--bq-font-family-sans); }\r\n    @media (prefers-reduced-motion: reduce) {\r\n      select { transition: none; }\r\n    }\r\n  `,\r\n  connected() {\r\n    type BQEl = HTMLElement & {\r\n      setState(k: 'uid' | 'optionsMarkup', v: string): void;\r\n      getState<T>(k: string): T;\r\n    };\r\n    const self = this as unknown as BQEl;\r\n    if (!self.getState<string>('uid'))\r\n      self.setState('uid', uniqueId('bq-select'));\r\n    const syncOptions = () => {\r\n      const markup = buildOptionsMarkup(\r\n        self,\r\n        self.getAttribute('placeholder') ?? ''\r\n      );\r\n      if (self.getState<string>('optionsMarkup') !== markup) {\r\n        self.setState('optionsMarkup', markup);\r\n      }\r\n      requestAnimationFrame(() => syncSelectValue(self));\r\n    };\r\n\r\n    // Form proxy for native <form> participation\r\n    const name = self.getAttribute('name') ?? '';\r\n    const value = self.getAttribute('value') ?? '';\r\n    const disabled = self.hasAttribute('disabled');\r\n    const proxy = createFormProxy(self, name, value, disabled, {\r\n      getLiveValue: () => {\r\n        const select = self.shadowRoot?.querySelector(\r\n          'select'\r\n        ) as HTMLSelectElement | null;\r\n        return select ? select.value : (self.getAttribute('value') ?? '');\r\n      },\r\n    });\r\n    (self as unknown as Record<string, unknown>)['_formProxy'] = proxy;\r\n\r\n    const handler = (e: Event) => {\r\n      const select = e.target as HTMLSelectElement | null;\r\n      if (select?.tagName === 'SELECT') {\r\n        self.setAttribute('value', select.value);\r\n        proxy.setValue(select.value);\r\n        self.dispatchEvent(\r\n          new CustomEvent('bq-change', {\r\n            detail: { value: select.value },\r\n            bubbles: true,\r\n            composed: true,\r\n          })\r\n        );\r\n      }\r\n    };\r\n    const observer = new MutationObserver(() => {\r\n      syncOptions();\r\n    });\r\n    observer.observe(self, {\r\n      childList: true,\r\n      subtree: true,\r\n      characterData: true,\r\n      attributes: true,\r\n      attributeFilter: ['disabled', 'label', 'selected', 'value'],\r\n    });\r\n\r\n    (self as unknown as Record<string, unknown>)['_handler'] = handler;\r\n    (self as unknown as Record<string, unknown>)['_syncOptions'] = syncOptions;\r\n    (self as unknown as Record<string, unknown>)['_observer'] = observer;\r\n    self.shadowRoot?.addEventListener('change', handler);\r\n    syncOptions();\r\n  },\r\n  disconnected() {\r\n    const s = this as unknown as Record<string, unknown>;\r\n    const h = s['_handler'] as EventListener | undefined;\r\n    if (h) this.shadowRoot?.removeEventListener('change', h);\r\n    const observer = s['_observer'] as MutationObserver | undefined;\r\n    observer?.disconnect();\r\n    (s['_formProxy'] as FormProxy | undefined)?.cleanup();\r\n  },\r\n  updated() {\r\n    const s = this as unknown as Record<string, unknown>;\r\n    const proxy = s['_formProxy'] as FormProxy | undefined;\r\n    if (proxy) {\r\n      proxy.setName(this.getAttribute('name') ?? '');\r\n      proxy.setValue(this.getAttribute('value') ?? '');\r\n      proxy.setDisabled(this.hasAttribute('disabled'));\r\n    }\r\n    (s['_syncOptions'] as (() => void) | undefined)?.();\r\n  },\r\n  render({ props, state }) {\r\n    const hasError = Boolean(props.error);\r\n    const hasHint = Boolean(props.hint) && !hasError;\r\n    const uid = state.uid || 'bq-select';\r\n    const optionsMarkup = state.optionsMarkup || '';\r\n    const describedParts: string[] = [];\r\n    if (hasError) describedParts.push(`${uid}-err`);\r\n    if (hasHint) describedParts.push(`${uid}-hint`);\r\n    const describedBy = describedParts.join(' ');\r\n    return html`\r\n      <div class=\"field\" part=\"field\">\r\n        ${props.label\r\n          ? `<label class=\"label\" for=\"${uid}\" part=\"label\">${escapeHtml(props.label)}${props.required ? '<span class=\"required-mark\" aria-hidden=\"true\"> *</span>' : ''}</label>`\r\n          : ''}\r\n        <div class=\"select-wrap\">\r\n          <select\r\n            part=\"select\"\r\n            id=\"${uid}\"\r\n            name=\"${escapeHtml(props.name)}\"\r\n            ${bool('disabled', props.disabled)}\r\n            ${bool('required', props.required)}\r\n            aria-invalid=\"${hasError ? 'true' : 'false'}\"\r\n            ${props.required ? 'aria-required=\"true\"' : ''}\r\n            ${describedBy ? `aria-describedby=\"${describedBy}\"` : ''}\r\n            ${hasError ? `aria-errormessage=\"${uid}-err\"` : ''}\r\n          >\r\n            ${optionsMarkup}\r\n          </select>\r\n          <span class=\"arrow\" aria-hidden=\"true\">&#9660;</span>\r\n        </div>\r\n        ${hasHint\r\n          ? `<span class=\"hint\" id=\"${uid}-hint\" part=\"hint\">${escapeHtml(props.hint)}</span>`\r\n          : ''}\r\n        ${hasError\r\n          ? `<span class=\"error-msg\" id=\"${uid}-err\" role=\"alert\" part=\"error\">${escapeHtml(props.error)}</span>`\r\n          : ''}\r\n      </div>\r\n    `;\r\n  },\r\n};\r\n\r\ncomponent<BqSelectProps, BqSelectState>('bq-select', definition);\r\n"],"mappings":"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"}