@oicl/openbridge-webcomponents 2.1.0-next.9 → 2.1.0

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.
Files changed (45) hide show
  1. package/bundle/openbridge-webcomponents.bundle.js +23 -32
  2. package/bundle/openbridge-webcomponents.bundle.js.map +1 -1
  3. package/custom-elements.json +42 -42
  4. package/dist/components/alert-menu/alert-menu.d.ts +5 -5
  5. package/dist/components/alert-menu/alert-menu.js.map +1 -1
  6. package/dist/components/app-menu/app-menu.d.ts +5 -5
  7. package/dist/components/app-menu/app-menu.js.map +1 -1
  8. package/dist/components/brilliance-menu/brilliance-menu.d.ts +5 -5
  9. package/dist/components/brilliance-menu/brilliance-menu.js.map +1 -1
  10. package/dist/components/command-menu/command-menu.d.ts +5 -5
  11. package/dist/components/command-menu/command-menu.js.map +1 -1
  12. package/dist/components/context-menu-input/context-menu-input.d.ts +5 -5
  13. package/dist/components/context-menu-input/context-menu-input.js.map +1 -1
  14. package/dist/components/navigation-menu/navigation-menu.d.ts +5 -5
  15. package/dist/components/navigation-menu/navigation-menu.js.map +1 -1
  16. package/dist/components/slide-button/slide-button.d.ts.map +1 -1
  17. package/dist/components/slide-button/slide-button.js +1 -1
  18. package/dist/components/slide-button/slide-button.js.map +1 -1
  19. package/dist/components/start-stop-switch/start-stop-switch.d.ts.map +1 -1
  20. package/dist/components/start-stop-switch/start-stop-switch.js +3 -10
  21. package/dist/components/start-stop-switch/start-stop-switch.js.map +1 -1
  22. package/dist/components/system-menu/system-menu.d.ts +5 -5
  23. package/dist/components/system-menu/system-menu.js.map +1 -1
  24. package/dist/components/user-menu/user-menu.d.ts +5 -5
  25. package/dist/components/user-menu/user-menu.js.map +1 -1
  26. package/dist/internal/popover-controller.d.ts +4 -0
  27. package/dist/internal/popover-controller.d.ts.map +1 -1
  28. package/dist/internal/popover-controller.js +4 -0
  29. package/dist/internal/popover-controller.js.map +1 -1
  30. package/dist/navigation-instruments/readout/readout.css.js +5 -4
  31. package/dist/navigation-instruments/readout/readout.css.js.map +1 -1
  32. package/dist/navigation-instruments/readout/readout.d.ts +3 -4
  33. package/dist/navigation-instruments/readout/readout.d.ts.map +1 -1
  34. package/dist/navigation-instruments/readout/readout.js +3 -4
  35. package/dist/navigation-instruments/readout/readout.js.map +1 -1
  36. package/dist/navigation-instruments/readout-list/readout-list.css.js +3 -5
  37. package/dist/navigation-instruments/readout-list/readout-list.css.js.map +1 -1
  38. package/dist/navigation-instruments/readout-list-item/readout-list-item.css.js +4 -4
  39. package/dist/navigation-instruments/readout-list-item/readout-list-item.css.js.map +1 -1
  40. package/dist/navigation-instruments/readout-list-item/readout-list-item.d.ts +8 -7
  41. package/dist/navigation-instruments/readout-list-item/readout-list-item.d.ts.map +1 -1
  42. package/dist/navigation-instruments/readout-list-item/readout-list-item.js +4 -4
  43. package/dist/navigation-instruments/readout-list-item/readout-list-item.js.map +1 -1
  44. package/dist/openbridge.css +389 -263
  45. package/package.json +1 -1
@@ -131,20 +131,20 @@ export declare enum ObcNavigationMenuFlyoutVariant {
131
131
  * `icon-only-large` is the icon-only menu that does, `compact` is the
132
132
  * space-saving menu, and `tree` expands groups inline and indents rows by
133
133
  * depth.
134
- * @property softDismiss - Let the browser close this menu on its own: on a click outside it, on `Escape`, or when another menu opens. Leave it off to keep showing and hiding the menu yourself.
135
- * @property open - Whether the menu is showing.
134
+ * @property softDismiss - Lets the menu close itself on a click outside it, on `Escape`, or when another menu opens. The page is covered while the menu is open, so the closing click reaches nothing there; only the top bar's strip stays clickable, which is how its buttons switch or close menus in one click. Wire it with three bindings: set `softDismiss`, bind `open` to a flag you keep, and reset that flag on `close`. The button that opens the menu needs no wiring of its own: flip the flag from its click, or from the button event of `obc-top-bar` or `obc-integration-bar`. The user menu's *Soft Dismiss From A Bar Button* story shows that wiring, the brilliance menu's *Soft Dismiss* story the one for a button you hold yourself (`bindPopoverTrigger`), and the vue demo runs its top bar this way. Leave it off to show and hide the menu yourself.
135
+ * @property open - Whether the menu is showing. Bind it to a flag you keep rather than toggling it from the menu's own value: when the menu closes itself, `open` is written back to `false` before the button's click reaches your handler, so `menu.open = !menu.open` gets out of step. Reset the flag on `close`.
136
136
  * @availableWhen open softDismiss==true
137
137
  * @slot main - Slot for primary navigation items and groups.
138
138
  * @slot footer - Slot for secondary navigation items (e.g., settings, help).
139
139
  * @slot logo - Slot for branding/logo area.
140
- * @fires {CustomEvent<void>} close - Fired when the menu closed on its own, from a click outside, `Escape`, or another menu opening. `open` is already `false` by the time it arrives.
140
+ * @fires {CustomEvent<void>} close - Fired when the menu closed on its own, from a click outside, `Escape`, or another menu opening. `open` is already `false` by the time it arrives; reset the flag you bind to `open` here, or the next click on the button finds it still set and asks a closed menu to close.
141
141
  * @stable
142
142
  */
143
143
  export declare class ObcNavigationMenu extends LitElement {
144
- /** Let the browser close this menu on its own: on a click outside it, on `Escape`, or when another menu opens. Leave it off to keep showing and hiding the menu yourself. */
144
+ /** Lets the menu close itself on a click outside it, on `Escape`, or when another menu opens. The page is covered while the menu is open, so the closing click reaches nothing there; only the top bar's strip stays clickable, which is how its buttons switch or close menus in one click. Wire it with three bindings: set `softDismiss`, bind `open` to a flag you keep, and reset that flag on `close`. The button that opens the menu needs no wiring of its own: flip the flag from its click, or from the button event of `obc-top-bar` or `obc-integration-bar`. The user menu's *Soft Dismiss From A Bar Button* story shows that wiring, the brilliance menu's *Soft Dismiss* story the one for a button you hold yourself (`bindPopoverTrigger`), and the vue demo runs its top bar this way. Leave it off to show and hide the menu yourself. */
145
145
  softDismiss: boolean;
146
146
  /**
147
- * Whether the menu is showing.
147
+ * Whether the menu is showing. Bind it to a flag you keep rather than toggling it from the menu's own value: when the menu closes itself, `open` is written back to `false` before the button's click reaches your handler, so `menu.open = !menu.open` gets out of step. Reset the flag on `close`.
148
148
  *
149
149
  * Available when `softDismiss==true`.
150
150
  */
@@ -1 +1 @@
1
- {"version":3,"file":"navigation-menu.js","names":[],"sources":["../../../src/components/navigation-menu/navigation-menu.ts"],"sourcesContent":["import {LitElement, PropertyValues, html, nothing, unsafeCSS} from 'lit';\nimport {property, state} from 'lit/decorators.js';\nimport compentStyle from './navigation-menu.css?inline';\nimport {ObcNavigationItemGroup} from '../navigation-item-group/navigation-item-group.js';\nimport {ObcNavigationItem} from '../navigation-item/navigation-item.js';\nimport {customElement} from '../../decorator.js';\nimport {\n ObcTreeNavigationItem,\n TreeBranchType,\n} from '../tree-navigation-item/tree-navigation-item.js';\nimport {\n TreeRovingNavigator,\n TreeRovingAdapter,\n} from '../../internal/tree-roving-navigator.js';\nimport {PopoverController} from '../../internal/popover-controller.js';\n\n/** A nav-menu tree row is either a navigation item or a navigation item group. */\ntype NavTreeRow = ObcNavigationItem | ObcNavigationItemGroup;\n\nconst NAV_ITEM_TAG = 'obc-navigation-item';\nconst NAV_GROUP_TAG = 'obc-navigation-item-group';\n\nfunction isNavGroup(el: Element): el is ObcNavigationItemGroup {\n return el.tagName.toLowerCase() === NAV_GROUP_TAG;\n}\n\nfunction isNavRow(el: Element): el is NavTreeRow {\n const tag = el.tagName.toLowerCase();\n return tag === NAV_ITEM_TAG || tag === NAV_GROUP_TAG;\n}\n\n/**\n * `ObcNavigationMenuVariant` – Enumerates the available visual and behavioral variants for `<obc-navigation-menu>`.\n *\n * - `Full`: Standard navigation menu with both icons and labels.\n * - `IconOnly`: Compact menu showing only icons (should only be used when no flyouts/submenus are present).\n * - `IconOnlyLarge`: Icon-only menu variant designed for use when flyouts/submenus are present.\n * - `Compact`: Space-saving menu with reduced padding and layout.\n * - `Tree`: Hierarchical tree — groups expand inline and rows are indented by depth.\n *\n * Use these variants to adapt the navigation menu to different layouts or device sizes.\n */\nexport enum ObcNavigationMenuVariant {\n Full = 'full',\n IconOnly = 'icon-only', // Should only be used when no flyouts are present in the navigation menu\n IconOnlyLarge = 'icon-only-large', // Should be used when flyouts are present in the navigation menu\n Compact = 'compact',\n Tree = 'tree',\n}\n\n/**\n * `ObcNavigationMenuFlyoutVariant` – Enumerates the available visual and behavioral variants for the flyout.\n *\n * - `Full`: Standard navigation flyout that takes the full height.\n * - `Compact`: Space-saving menu with reduced padding and layout.\n *\n * Use these variants to adapt the flyout menu to different layouts or device sizes.\n */\nexport enum ObcNavigationMenuFlyoutVariant {\n Full = 'full',\n Compact = 'compact',\n}\n\n/**\n * `<obc-navigation-menu>` – A flexible, slot-based navigation menu component for organizing primary and secondary navigation items.\n *\n * This component provides a vertical navigation structure supporting groups, flyouts, and footer sections. It adapts to various layouts and device sizes via its `variant` and `smallScreen` properties. Items and groups are provided via slots, allowing for icons, labels, and nested navigation hierarchies.\n *\n * Appears as a sidebar or persistent navigation panel, supporting both icon-only and full-label modes. Designed for use as the main navigation in applications, dashboards, or any interface requiring structured navigation.\n *\n * ## Features\n *\n * - **Variants:**\n * - **Full:** Displays both icons and labels for all navigation items (default).\n * - **IconOnly:** Shows only icons for a compact appearance. *Should only be used when no navigation items have sub-items or flyouts.*\n * - **IconOnlyLarge:** Icon-only mode that supports flyouts/submenus. Use when navigation contains groups or nested items.\n * - **Compact:** Reduces padding and overall width for a space-saving layout.\n * - **Tree:** Renders the same `obc-navigation-item` / `obc-navigation-item-group`\n * markup as a hierarchical tree. Groups expand inline (instead of as flyouts),\n * rows are indented by depth, multiple branches can stay open at once, and the\n * tree-row presentation (alert badges, terminal marker) is available via the\n * items' `alerts`/`terminalType` properties.\n * - **Responsive Layout:**\n * - `smallScreen` property adapts the footer and logo layout for smaller viewports.\n * - **Slot-based Content:**\n * - `main` slot for primary navigation items and groups.\n * - `footer` slot for secondary actions or links.\n * - `logo` slot for branding or logo placement (position adapts based on variant and screen size).\n * - **Nested Navigation:**\n * - Supports nested groups and flyouts via `<obc-navigation-item-group>`.\n * - **Automatic Variant Propagation:**\n * - Child items and groups automatically receive the correct variant for consistent appearance.\n * - **Dynamic Content Handling:**\n * - Reacts to dynamic addition/removal of items and groups, updating layout and variants as needed.\n * - **Interaction:**\n * - Clicking a navigation item closes all open groups/flyouts for streamlined navigation.\n *\n * ## Usage Guidelines\n *\n * Use `<obc-navigation-menu>` as the main navigation container in your application layout. Place navigation items and groups in the `main` slot for primary navigation, and use the `footer` slot for secondary actions (such as settings or help). The `logo` slot is intended for branding and is positioned according to the selected variant and screen size.\n *\n * - Use the `Full` variant for standard navigation with both icons and labels.\n * - Use `IconOnly` only when there are no nested groups or flyouts; otherwise, use `IconOnlyLarge` for icon-only navigation with flyout support.\n * - The `Compact` variant is suitable for layouts with limited space or when a minimal navigation appearance is desired.\n * - Use the `Tree` variant for hierarchical navigation (file trees, nested\n * sections). The existing item/group markup is reused unchanged — only the\n * `variant` changes. Footer and logo slots remain flat. Mark a group with\n * `defaultOpen` to have it start expanded.\n * - Set `smallScreen` to `true` to optimize the layout for smaller devices or responsive breakpoints.\n *\n * **TODO(designer):** Provide additional guidance on when to use each variant and recommended slot content for best usability.\n *\n * ## Slots\n *\n * | Slot Name | Renders When... | Purpose |\n * |-----------|----------------|---------|\n * | main | Always | Primary navigation items and groups. |\n * | footer | Always | Secondary navigation items (e.g., settings, help). |\n * | logo | Always | Branding/logo area (position varies by variant and screen size). |\n *\n * Place `<obc-navigation-item>`, `<obc-navigation-item-group>`, or other suitable elements in these slots. For icons, use `<obi-placeholder>`, `<obi-applications>`, or other OpenBridge icon components in the `icon` slot of each navigation item.\n *\n * ## Properties\n *\n * - `variant` (`ObcNavigationMenuVariant`): Controls the visual style and layout of the menu. Default is `Full`.\n * - `smallScreen` (`boolean`): When `true`, adapts the layout for small screens (e.g., moves logo into the footer area).\n *\n * ## Best Practices and Constraints\n *\n * - Only use the `IconOnly` variant when there are no navigation items with sub-items or flyouts. Use `IconOnlyLarge` if your navigation includes groups or nested items.\n * - Place only navigation-related components in the `main` and `footer` slots for clarity and accessibility.\n * - For best accessibility, ensure each navigation item has a clear label and, if using icons, a suitable `aria-label` or accessible name.\n * - Avoid placing interactive elements other than navigation items/groups in the `main` or `footer` slots.\n *\n * ## Example\n *\n * ```html\n * <obc-navigation-menu variant=\"full\">\n * <obc-navigation-item-group slot=\"main\" label=\"Apps\">\n * <obi-applications slot=\"icon\"></obi-applications>\n * <obc-navigation-item label=\"Sub item 1\" hasIcon href=\"#\">\n * <obi-placeholder slot=\"icon\"></obi-placeholder>\n * </obc-navigation-item>\n * </obc-navigation-item-group>\n * <obc-navigation-item slot=\"footer\" label=\"Settings\" hasIcon href=\"#\">\n * <obi-settings-iec slot=\"icon\"></obi-settings-iec>\n * </obc-navigation-item>\n * <obc-vendor-button imageSrc=\"/companylogo-day.png\" alt=\"logo\" slot=\"logo\"></obc-vendor-button>\n * </obc-navigation-menu>\n * ```\n *\n * In this example, the menu displays a group with sub-items in the main navigation, several footer actions, and a logo.\n *\n * @property flyoutVariant - Visual variant of the flyout.\n * One of `Full` (default) or `Compact`.\n * @availableWhen flyoutVariant variant==Full\n * @property smallScreen - When `true`, adapts the layout for small screens (e.g., moves logo into the footer area and adjusts item layout).\n * @property variant - Visual style and layout: `full` (default) shows icons and labels,\n * `icon-only` is the compact icon-only menu and takes no flyouts or groups,\n * `icon-only-large` is the icon-only menu that does, `compact` is the\n * space-saving menu, and `tree` expands groups inline and indents rows by\n * depth.\n * @property softDismiss - Let the browser close this menu on its own: on a click outside it, on `Escape`, or when another menu opens. Leave it off to keep showing and hiding the menu yourself.\n * @property open - Whether the menu is showing.\n * @availableWhen open softDismiss==true\n * @slot main - Slot for primary navigation items and groups.\n * @slot footer - Slot for secondary navigation items (e.g., settings, help).\n * @slot logo - Slot for branding/logo area.\n * @fires {CustomEvent<void>} close - Fired when the menu closed on its own, from a click outside, `Escape`, or another menu opening. `open` is already `false` by the time it arrives.\n * @stable\n */\n@customElement('obc-navigation-menu')\nexport class ObcNavigationMenu extends LitElement {\n @property({type: Boolean}) softDismiss = false;\n\n @property({type: Boolean}) open = false;\n\n protected readonly softDismissController = new PopoverController(this);\n\n @property({type: String}) variant: ObcNavigationMenuVariant =\n ObcNavigationMenuVariant.Full;\n\n @property({type: String}) flyoutVariant: ObcNavigationMenuFlyoutVariant =\n ObcNavigationMenuFlyoutVariant.Full;\n\n @property({type: Boolean}) smallScreen = false;\n\n private slotObservers: MutationObserver[] = [];\n @state() private hasFooter = false;\n\n /**\n * Roving-tabindex + arrow-key navigation for the Tree variant, sharing the\n * navigator that drives `obc-tree-navigation`. Engaged only while\n * `variant === Tree` (see `onTreeKeydown`).\n */\n private readonly treeNavigator = new TreeRovingNavigator<NavTreeRow>({\n getRows: () => this.treeRootRows(),\n childRows: (row) => this.treeChildRows(row),\n isGroup: (row) => isNavGroup(row),\n // Read the group's own synchronous open state, not the shadow header's\n // attribute (which lags a render tick behind a keyboard expand/collapse).\n isExpanded: (row) => isNavGroup(row) && row.expanded,\n setExpanded: (row, expanded) => {\n if (!isNavGroup(row)) return;\n if (expanded) row.open();\n else row.close();\n },\n innerItem: (row) => this.treeInnerItem(row),\n } satisfies TreeRovingAdapter<NavTreeRow>);\n\n /** Top-level tree rows of the main slot (mirrors `assignTreeBranches`'s filter). */\n private treeRootRows(): NavTreeRow[] {\n return Array.from(this.children).filter((child): child is NavTreeRow => {\n if (!isNavRow(child)) return false;\n const slot = child.getAttribute('slot');\n return slot === null || slot === 'main';\n });\n }\n\n /** Direct tree-row children of a row, in document order. */\n private treeChildRows(row: NavTreeRow): NavTreeRow[] {\n return Array.from(row.children).filter(isNavRow);\n }\n\n /** The inline `obc-tree-navigation-item` a tree-mode row renders in its shadow root. */\n private treeInnerItem(row: NavTreeRow): ObcTreeNavigationItem | null {\n return (\n row.shadowRoot?.querySelector<ObcTreeNavigationItem>(\n 'obc-tree-navigation-item'\n ) ?? null\n );\n }\n\n private onTreeKeydown = (event: KeyboardEvent): void => {\n if (this.variant !== ObcNavigationMenuVariant.Tree) return;\n if (this.treeNavigator.handleKeydown(event)) event.preventDefault();\n };\n\n override connectedCallback(): void {\n super.connectedCallback();\n this.addEventListener('keydown', this.onTreeKeydown);\n }\n\n findAllElements<T extends Element>(\n el: Element,\n tag: string,\n {\n slot,\n stopTag,\n }: {\n slot?: 'main' | 'footer' | 'logo';\n stopTag?: string;\n } = {}\n ): T[] {\n const elements: T[] = [];\n for (const child of el.children) {\n if (child.tagName.toLowerCase() === tag) {\n if (slot && child.getAttribute('slot') !== slot) {\n continue;\n }\n elements.push(child as T);\n } else if (stopTag && child.tagName.toLowerCase() === stopTag) {\n continue;\n } else {\n if (slot && child.getAttribute('slot') !== slot) {\n continue;\n }\n elements.push(...this.findAllElements<T>(child, tag, {stopTag}));\n }\n }\n return elements;\n }\n\n findAllGroups(el: Element): ObcNavigationItemGroup[] {\n // Find all groups that are not in a group\n return this.findAllElements<ObcNavigationItemGroup>(\n el,\n 'obc-navigation-item-group'\n );\n }\n\n findRootItems(el: Element): ObcNavigationItem[] {\n // Find all items that are not in a group or in an item\n return this.findAllElements<ObcNavigationItem>(el, 'obc-navigation-item', {\n stopTag: 'obc-navigation-item-group',\n });\n }\n\n findAllItems(\n el: Element,\n slot?: 'main' | 'footer' | 'logo'\n ): ObcNavigationItem[] {\n return this.findAllElements<ObcNavigationItem>(el, 'obc-navigation-item', {\n slot,\n });\n }\n\n closeAllGroups() {\n const groups = this.findAllGroups(this);\n groups.forEach((group) => {\n group.close();\n });\n }\n\n registerGroup(groups: ObcNavigationItemGroup[]) {\n groups.forEach((group) => {\n group.addEventListener('open', () => {\n if (this.variant === ObcNavigationMenuVariant.Tree) return;\n groups.forEach((g) => {\n if (g !== group) {\n g.close();\n }\n });\n });\n const subGroups = this.findAllGroups(group);\n this.registerGroup(subGroups);\n });\n }\n\n private cleanupSlotObservers() {\n this.slotObservers.forEach((observer) => observer.disconnect());\n this.slotObservers = [];\n }\n\n private setupSlotObservers() {\n this.cleanupSlotObservers();\n\n const mainSlot = this.shadowRoot?.querySelector(\n 'slot[name=\"main\"]'\n ) as HTMLSlotElement;\n const footerSlot = this.shadowRoot?.querySelector(\n 'slot[name=\"footer\"]'\n ) as HTMLSlotElement;\n\n this.hasFooter = footerSlot?.assignedElements().length > 0;\n\n [mainSlot, footerSlot].forEach((slot) => {\n if (slot) {\n const slottedElements = slot.assignedElements();\n slottedElements.forEach((element) => {\n const observer = new MutationObserver(() => {\n this.setupItems();\n });\n\n observer.observe(element, {\n childList: true,\n subtree: true,\n });\n\n this.slotObservers.push(observer);\n });\n }\n });\n }\n\n protected override firstUpdated(_changedProperties: PropertyValues): void {\n super.firstUpdated(_changedProperties);\n const groups = this.findAllGroups(this);\n this.registerGroup(groups);\n }\n\n protected override updated(_changedProperties: PropertyValues): void {\n super.updated(_changedProperties);\n if (\n _changedProperties.has('variant') ||\n _changedProperties.has('flyoutVariant')\n ) {\n this.setupItems();\n }\n }\n\n // Recursively set variant for children of groups\n private setVariantToFlyoutItems(el: Element) {\n // Find all descendant items inside this element (not direct children of the nav menu)\n const items = this.findAllElements<ObcNavigationItem>(\n el,\n 'obc-navigation-item'\n );\n items.forEach((item) => {\n item.variant = ObcNavigationMenuVariant.Full;\n });\n\n const groups = this.findAllGroups(el);\n groups.forEach((group) => {\n group.variant = ObcNavigationMenuVariant.Full;\n this.setVariantToFlyoutItems(group);\n });\n }\n\n override disconnectedCallback(): void {\n super.disconnectedCallback();\n this.cleanupSlotObservers();\n this.removeEventListener('keydown', this.onTreeKeydown);\n }\n\n private handleSlotChange() {\n this.setupItems();\n this.setupSlotObservers();\n }\n\n private assignTreeBranches(el: Element, depth: number): void {\n for (const child of el.children) {\n const tag = child.tagName.toLowerCase();\n const isItem = tag === 'obc-navigation-item';\n const isGroup = tag === 'obc-navigation-item-group';\n if (!isItem && !isGroup) continue;\n // Top-level rows are slotted; only treeify the main slot. Nested rows have\n // no slot attribute and always belong to their group's tree.\n if (depth === 0) {\n const slot = child.getAttribute('slot');\n if (slot !== null && slot !== 'main') continue;\n }\n const branches: TreeBranchType[] = Array.from(\n {length: depth},\n () => TreeBranchType.blank\n );\n const row = child as ObcNavigationItem | ObcNavigationItemGroup;\n row.treeMode = true;\n row.treeBranches = branches;\n if (isGroup) {\n this.assignTreeBranches(child, depth + 1);\n }\n }\n }\n\n // Reset tree mode on every descendant item/group so switching away from the\n // Tree variant restores the flat rendering.\n private clearTreeMode(el: Element): void {\n for (const child of el.children) {\n const tag = child.tagName.toLowerCase();\n const isItem = tag === 'obc-navigation-item';\n const isGroup = tag === 'obc-navigation-item-group';\n if (!isItem && !isGroup) continue;\n const row = child as ObcNavigationItem | ObcNavigationItemGroup;\n row.treeMode = false;\n row.treeBranches = [];\n if (isGroup) {\n this.clearTreeMode(child);\n }\n }\n }\n\n private setupItems() {\n if (this.variant === ObcNavigationMenuVariant.Tree) {\n this.assignTreeBranches(this, 0);\n // Footer and logo are not part of the tree — keep them as flat full-variant\n // items (and clear any tree mode left over from the main slot's walk).\n (['footer', 'logo'] as const).forEach((slot) => {\n this.findAllItems(this, slot).forEach((item) => {\n item.treeMode = false;\n item.treeBranches = [];\n item.variant = ObcNavigationMenuVariant.Full;\n });\n });\n // Defer so the rows' inline tree-item headers exist before the roving\n // tabindex is assigned to them.\n queueMicrotask(() => this.treeNavigator.refresh());\n return;\n }\n\n this.clearTreeMode(this);\n\n const hug =\n this.variant !== ObcNavigationMenuVariant.Full ||\n this.flyoutVariant === ObcNavigationMenuFlyoutVariant.Compact;\n this.setHugToGroups(this, hug);\n\n // Set variant to all groups (top-level)\n const groups = this.findAllGroups(this);\n groups.forEach((group) => {\n group.variant = this.variant;\n // But for flyout children, force variant to full\n this.setVariantToFlyoutItems(group);\n });\n\n // Set variant to all root items (not in group)\n this.findRootItems(this).forEach((item) => {\n item.variant = this.variant;\n });\n\n // Footer and logo logic (same as before)\n const footerVariant =\n this.smallScreen && this.variant === ObcNavigationMenuVariant.Full\n ? ObcNavigationMenuVariant.Compact\n : this.variant;\n this.findAllItems(this, 'footer').forEach((item) => {\n item.variant = footerVariant;\n });\n this.findAllItems(this, 'logo').forEach((item) => {\n item.variant = footerVariant;\n });\n\n // Close all groups on item click (unchanged)\n this.findAllItems(this).forEach((item) => {\n item.addEventListener('click', () => {\n this.closeAllGroups();\n });\n });\n }\n\n private setHugToGroups(el: Element, hug: boolean) {\n const groups = this.findAllGroups(el);\n groups.forEach((group) => {\n group.hug = hug;\n this.setHugToGroups(group, hug);\n });\n }\n\n override render() {\n return html`\n <div\n class=\"wrapper ${this.variant} ${\n this.smallScreen ? 'small-screen' : ''\n }\"\n >\n <nav class=\"main\">\n <ol>\n <slot name=\"main\" @slotchange=${this.handleSlotChange}></slot>\n </ol>\n </nav>\n <div class=\"footer ${this.hasFooter ? 'has-footer' : ''}\">\n <nav>\n <ol>\n <slot name=\"footer\" @slotchange=${this.handleSlotChange}></slot>\n ${this.smallScreen ? html` <slot name=\"logo\"></slot> ` : nothing}\n </ol>\n </nav>\n ${\n this.smallScreen\n ? nothing\n : html`\n <div class=\"logo\">\n <slot name=\"logo\"></slot>\n </div>\n `\n }\n </div>\n </div>\n `;\n }\n\n static override styles = unsafeCSS(compentStyle);\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'obc-navigation-menu': ObcNavigationMenu;\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"navigation-menu.js","names":[],"sources":["../../../src/components/navigation-menu/navigation-menu.ts"],"sourcesContent":["import {LitElement, PropertyValues, html, nothing, unsafeCSS} from 'lit';\nimport {property, state} from 'lit/decorators.js';\nimport compentStyle from './navigation-menu.css?inline';\nimport {ObcNavigationItemGroup} from '../navigation-item-group/navigation-item-group.js';\nimport {ObcNavigationItem} from '../navigation-item/navigation-item.js';\nimport {customElement} from '../../decorator.js';\nimport {\n ObcTreeNavigationItem,\n TreeBranchType,\n} from '../tree-navigation-item/tree-navigation-item.js';\nimport {\n TreeRovingNavigator,\n TreeRovingAdapter,\n} from '../../internal/tree-roving-navigator.js';\nimport {PopoverController} from '../../internal/popover-controller.js';\n\n/** A nav-menu tree row is either a navigation item or a navigation item group. */\ntype NavTreeRow = ObcNavigationItem | ObcNavigationItemGroup;\n\nconst NAV_ITEM_TAG = 'obc-navigation-item';\nconst NAV_GROUP_TAG = 'obc-navigation-item-group';\n\nfunction isNavGroup(el: Element): el is ObcNavigationItemGroup {\n return el.tagName.toLowerCase() === NAV_GROUP_TAG;\n}\n\nfunction isNavRow(el: Element): el is NavTreeRow {\n const tag = el.tagName.toLowerCase();\n return tag === NAV_ITEM_TAG || tag === NAV_GROUP_TAG;\n}\n\n/**\n * `ObcNavigationMenuVariant` – Enumerates the available visual and behavioral variants for `<obc-navigation-menu>`.\n *\n * - `Full`: Standard navigation menu with both icons and labels.\n * - `IconOnly`: Compact menu showing only icons (should only be used when no flyouts/submenus are present).\n * - `IconOnlyLarge`: Icon-only menu variant designed for use when flyouts/submenus are present.\n * - `Compact`: Space-saving menu with reduced padding and layout.\n * - `Tree`: Hierarchical tree — groups expand inline and rows are indented by depth.\n *\n * Use these variants to adapt the navigation menu to different layouts or device sizes.\n */\nexport enum ObcNavigationMenuVariant {\n Full = 'full',\n IconOnly = 'icon-only', // Should only be used when no flyouts are present in the navigation menu\n IconOnlyLarge = 'icon-only-large', // Should be used when flyouts are present in the navigation menu\n Compact = 'compact',\n Tree = 'tree',\n}\n\n/**\n * `ObcNavigationMenuFlyoutVariant` – Enumerates the available visual and behavioral variants for the flyout.\n *\n * - `Full`: Standard navigation flyout that takes the full height.\n * - `Compact`: Space-saving menu with reduced padding and layout.\n *\n * Use these variants to adapt the flyout menu to different layouts or device sizes.\n */\nexport enum ObcNavigationMenuFlyoutVariant {\n Full = 'full',\n Compact = 'compact',\n}\n\n/**\n * `<obc-navigation-menu>` – A flexible, slot-based navigation menu component for organizing primary and secondary navigation items.\n *\n * This component provides a vertical navigation structure supporting groups, flyouts, and footer sections. It adapts to various layouts and device sizes via its `variant` and `smallScreen` properties. Items and groups are provided via slots, allowing for icons, labels, and nested navigation hierarchies.\n *\n * Appears as a sidebar or persistent navigation panel, supporting both icon-only and full-label modes. Designed for use as the main navigation in applications, dashboards, or any interface requiring structured navigation.\n *\n * ## Features\n *\n * - **Variants:**\n * - **Full:** Displays both icons and labels for all navigation items (default).\n * - **IconOnly:** Shows only icons for a compact appearance. *Should only be used when no navigation items have sub-items or flyouts.*\n * - **IconOnlyLarge:** Icon-only mode that supports flyouts/submenus. Use when navigation contains groups or nested items.\n * - **Compact:** Reduces padding and overall width for a space-saving layout.\n * - **Tree:** Renders the same `obc-navigation-item` / `obc-navigation-item-group`\n * markup as a hierarchical tree. Groups expand inline (instead of as flyouts),\n * rows are indented by depth, multiple branches can stay open at once, and the\n * tree-row presentation (alert badges, terminal marker) is available via the\n * items' `alerts`/`terminalType` properties.\n * - **Responsive Layout:**\n * - `smallScreen` property adapts the footer and logo layout for smaller viewports.\n * - **Slot-based Content:**\n * - `main` slot for primary navigation items and groups.\n * - `footer` slot for secondary actions or links.\n * - `logo` slot for branding or logo placement (position adapts based on variant and screen size).\n * - **Nested Navigation:**\n * - Supports nested groups and flyouts via `<obc-navigation-item-group>`.\n * - **Automatic Variant Propagation:**\n * - Child items and groups automatically receive the correct variant for consistent appearance.\n * - **Dynamic Content Handling:**\n * - Reacts to dynamic addition/removal of items and groups, updating layout and variants as needed.\n * - **Interaction:**\n * - Clicking a navigation item closes all open groups/flyouts for streamlined navigation.\n *\n * ## Usage Guidelines\n *\n * Use `<obc-navigation-menu>` as the main navigation container in your application layout. Place navigation items and groups in the `main` slot for primary navigation, and use the `footer` slot for secondary actions (such as settings or help). The `logo` slot is intended for branding and is positioned according to the selected variant and screen size.\n *\n * - Use the `Full` variant for standard navigation with both icons and labels.\n * - Use `IconOnly` only when there are no nested groups or flyouts; otherwise, use `IconOnlyLarge` for icon-only navigation with flyout support.\n * - The `Compact` variant is suitable for layouts with limited space or when a minimal navigation appearance is desired.\n * - Use the `Tree` variant for hierarchical navigation (file trees, nested\n * sections). The existing item/group markup is reused unchanged — only the\n * `variant` changes. Footer and logo slots remain flat. Mark a group with\n * `defaultOpen` to have it start expanded.\n * - Set `smallScreen` to `true` to optimize the layout for smaller devices or responsive breakpoints.\n *\n * **TODO(designer):** Provide additional guidance on when to use each variant and recommended slot content for best usability.\n *\n * ## Slots\n *\n * | Slot Name | Renders When... | Purpose |\n * |-----------|----------------|---------|\n * | main | Always | Primary navigation items and groups. |\n * | footer | Always | Secondary navigation items (e.g., settings, help). |\n * | logo | Always | Branding/logo area (position varies by variant and screen size). |\n *\n * Place `<obc-navigation-item>`, `<obc-navigation-item-group>`, or other suitable elements in these slots. For icons, use `<obi-placeholder>`, `<obi-applications>`, or other OpenBridge icon components in the `icon` slot of each navigation item.\n *\n * ## Properties\n *\n * - `variant` (`ObcNavigationMenuVariant`): Controls the visual style and layout of the menu. Default is `Full`.\n * - `smallScreen` (`boolean`): When `true`, adapts the layout for small screens (e.g., moves logo into the footer area).\n *\n * ## Best Practices and Constraints\n *\n * - Only use the `IconOnly` variant when there are no navigation items with sub-items or flyouts. Use `IconOnlyLarge` if your navigation includes groups or nested items.\n * - Place only navigation-related components in the `main` and `footer` slots for clarity and accessibility.\n * - For best accessibility, ensure each navigation item has a clear label and, if using icons, a suitable `aria-label` or accessible name.\n * - Avoid placing interactive elements other than navigation items/groups in the `main` or `footer` slots.\n *\n * ## Example\n *\n * ```html\n * <obc-navigation-menu variant=\"full\">\n * <obc-navigation-item-group slot=\"main\" label=\"Apps\">\n * <obi-applications slot=\"icon\"></obi-applications>\n * <obc-navigation-item label=\"Sub item 1\" hasIcon href=\"#\">\n * <obi-placeholder slot=\"icon\"></obi-placeholder>\n * </obc-navigation-item>\n * </obc-navigation-item-group>\n * <obc-navigation-item slot=\"footer\" label=\"Settings\" hasIcon href=\"#\">\n * <obi-settings-iec slot=\"icon\"></obi-settings-iec>\n * </obc-navigation-item>\n * <obc-vendor-button imageSrc=\"/companylogo-day.png\" alt=\"logo\" slot=\"logo\"></obc-vendor-button>\n * </obc-navigation-menu>\n * ```\n *\n * In this example, the menu displays a group with sub-items in the main navigation, several footer actions, and a logo.\n *\n * @property flyoutVariant - Visual variant of the flyout.\n * One of `Full` (default) or `Compact`.\n * @availableWhen flyoutVariant variant==Full\n * @property smallScreen - When `true`, adapts the layout for small screens (e.g., moves logo into the footer area and adjusts item layout).\n * @property variant - Visual style and layout: `full` (default) shows icons and labels,\n * `icon-only` is the compact icon-only menu and takes no flyouts or groups,\n * `icon-only-large` is the icon-only menu that does, `compact` is the\n * space-saving menu, and `tree` expands groups inline and indents rows by\n * depth.\n * @property softDismiss - Lets the menu close itself on a click outside it, on `Escape`, or when another menu opens. The page is covered while the menu is open, so the closing click reaches nothing there; only the top bar's strip stays clickable, which is how its buttons switch or close menus in one click. Wire it with three bindings: set `softDismiss`, bind `open` to a flag you keep, and reset that flag on `close`. The button that opens the menu needs no wiring of its own: flip the flag from its click, or from the button event of `obc-top-bar` or `obc-integration-bar`. The user menu's *Soft Dismiss From A Bar Button* story shows that wiring, the brilliance menu's *Soft Dismiss* story the one for a button you hold yourself (`bindPopoverTrigger`), and the vue demo runs its top bar this way. Leave it off to show and hide the menu yourself.\n * @property open - Whether the menu is showing. Bind it to a flag you keep rather than toggling it from the menu's own value: when the menu closes itself, `open` is written back to `false` before the button's click reaches your handler, so `menu.open = !menu.open` gets out of step. Reset the flag on `close`.\n * @availableWhen open softDismiss==true\n * @slot main - Slot for primary navigation items and groups.\n * @slot footer - Slot for secondary navigation items (e.g., settings, help).\n * @slot logo - Slot for branding/logo area.\n * @fires {CustomEvent<void>} close - Fired when the menu closed on its own, from a click outside, `Escape`, or another menu opening. `open` is already `false` by the time it arrives; reset the flag you bind to `open` here, or the next click on the button finds it still set and asks a closed menu to close.\n * @stable\n */\n@customElement('obc-navigation-menu')\nexport class ObcNavigationMenu extends LitElement {\n @property({type: Boolean}) softDismiss = false;\n\n @property({type: Boolean}) open = false;\n\n protected readonly softDismissController = new PopoverController(this);\n\n @property({type: String}) variant: ObcNavigationMenuVariant =\n ObcNavigationMenuVariant.Full;\n\n @property({type: String}) flyoutVariant: ObcNavigationMenuFlyoutVariant =\n ObcNavigationMenuFlyoutVariant.Full;\n\n @property({type: Boolean}) smallScreen = false;\n\n private slotObservers: MutationObserver[] = [];\n @state() private hasFooter = false;\n\n /**\n * Roving-tabindex + arrow-key navigation for the Tree variant, sharing the\n * navigator that drives `obc-tree-navigation`. Engaged only while\n * `variant === Tree` (see `onTreeKeydown`).\n */\n private readonly treeNavigator = new TreeRovingNavigator<NavTreeRow>({\n getRows: () => this.treeRootRows(),\n childRows: (row) => this.treeChildRows(row),\n isGroup: (row) => isNavGroup(row),\n // Read the group's own synchronous open state, not the shadow header's\n // attribute (which lags a render tick behind a keyboard expand/collapse).\n isExpanded: (row) => isNavGroup(row) && row.expanded,\n setExpanded: (row, expanded) => {\n if (!isNavGroup(row)) return;\n if (expanded) row.open();\n else row.close();\n },\n innerItem: (row) => this.treeInnerItem(row),\n } satisfies TreeRovingAdapter<NavTreeRow>);\n\n /** Top-level tree rows of the main slot (mirrors `assignTreeBranches`'s filter). */\n private treeRootRows(): NavTreeRow[] {\n return Array.from(this.children).filter((child): child is NavTreeRow => {\n if (!isNavRow(child)) return false;\n const slot = child.getAttribute('slot');\n return slot === null || slot === 'main';\n });\n }\n\n /** Direct tree-row children of a row, in document order. */\n private treeChildRows(row: NavTreeRow): NavTreeRow[] {\n return Array.from(row.children).filter(isNavRow);\n }\n\n /** The inline `obc-tree-navigation-item` a tree-mode row renders in its shadow root. */\n private treeInnerItem(row: NavTreeRow): ObcTreeNavigationItem | null {\n return (\n row.shadowRoot?.querySelector<ObcTreeNavigationItem>(\n 'obc-tree-navigation-item'\n ) ?? null\n );\n }\n\n private onTreeKeydown = (event: KeyboardEvent): void => {\n if (this.variant !== ObcNavigationMenuVariant.Tree) return;\n if (this.treeNavigator.handleKeydown(event)) event.preventDefault();\n };\n\n override connectedCallback(): void {\n super.connectedCallback();\n this.addEventListener('keydown', this.onTreeKeydown);\n }\n\n findAllElements<T extends Element>(\n el: Element,\n tag: string,\n {\n slot,\n stopTag,\n }: {\n slot?: 'main' | 'footer' | 'logo';\n stopTag?: string;\n } = {}\n ): T[] {\n const elements: T[] = [];\n for (const child of el.children) {\n if (child.tagName.toLowerCase() === tag) {\n if (slot && child.getAttribute('slot') !== slot) {\n continue;\n }\n elements.push(child as T);\n } else if (stopTag && child.tagName.toLowerCase() === stopTag) {\n continue;\n } else {\n if (slot && child.getAttribute('slot') !== slot) {\n continue;\n }\n elements.push(...this.findAllElements<T>(child, tag, {stopTag}));\n }\n }\n return elements;\n }\n\n findAllGroups(el: Element): ObcNavigationItemGroup[] {\n // Find all groups that are not in a group\n return this.findAllElements<ObcNavigationItemGroup>(\n el,\n 'obc-navigation-item-group'\n );\n }\n\n findRootItems(el: Element): ObcNavigationItem[] {\n // Find all items that are not in a group or in an item\n return this.findAllElements<ObcNavigationItem>(el, 'obc-navigation-item', {\n stopTag: 'obc-navigation-item-group',\n });\n }\n\n findAllItems(\n el: Element,\n slot?: 'main' | 'footer' | 'logo'\n ): ObcNavigationItem[] {\n return this.findAllElements<ObcNavigationItem>(el, 'obc-navigation-item', {\n slot,\n });\n }\n\n closeAllGroups() {\n const groups = this.findAllGroups(this);\n groups.forEach((group) => {\n group.close();\n });\n }\n\n registerGroup(groups: ObcNavigationItemGroup[]) {\n groups.forEach((group) => {\n group.addEventListener('open', () => {\n if (this.variant === ObcNavigationMenuVariant.Tree) return;\n groups.forEach((g) => {\n if (g !== group) {\n g.close();\n }\n });\n });\n const subGroups = this.findAllGroups(group);\n this.registerGroup(subGroups);\n });\n }\n\n private cleanupSlotObservers() {\n this.slotObservers.forEach((observer) => observer.disconnect());\n this.slotObservers = [];\n }\n\n private setupSlotObservers() {\n this.cleanupSlotObservers();\n\n const mainSlot = this.shadowRoot?.querySelector(\n 'slot[name=\"main\"]'\n ) as HTMLSlotElement;\n const footerSlot = this.shadowRoot?.querySelector(\n 'slot[name=\"footer\"]'\n ) as HTMLSlotElement;\n\n this.hasFooter = footerSlot?.assignedElements().length > 0;\n\n [mainSlot, footerSlot].forEach((slot) => {\n if (slot) {\n const slottedElements = slot.assignedElements();\n slottedElements.forEach((element) => {\n const observer = new MutationObserver(() => {\n this.setupItems();\n });\n\n observer.observe(element, {\n childList: true,\n subtree: true,\n });\n\n this.slotObservers.push(observer);\n });\n }\n });\n }\n\n protected override firstUpdated(_changedProperties: PropertyValues): void {\n super.firstUpdated(_changedProperties);\n const groups = this.findAllGroups(this);\n this.registerGroup(groups);\n }\n\n protected override updated(_changedProperties: PropertyValues): void {\n super.updated(_changedProperties);\n if (\n _changedProperties.has('variant') ||\n _changedProperties.has('flyoutVariant')\n ) {\n this.setupItems();\n }\n }\n\n // Recursively set variant for children of groups\n private setVariantToFlyoutItems(el: Element) {\n // Find all descendant items inside this element (not direct children of the nav menu)\n const items = this.findAllElements<ObcNavigationItem>(\n el,\n 'obc-navigation-item'\n );\n items.forEach((item) => {\n item.variant = ObcNavigationMenuVariant.Full;\n });\n\n const groups = this.findAllGroups(el);\n groups.forEach((group) => {\n group.variant = ObcNavigationMenuVariant.Full;\n this.setVariantToFlyoutItems(group);\n });\n }\n\n override disconnectedCallback(): void {\n super.disconnectedCallback();\n this.cleanupSlotObservers();\n this.removeEventListener('keydown', this.onTreeKeydown);\n }\n\n private handleSlotChange() {\n this.setupItems();\n this.setupSlotObservers();\n }\n\n private assignTreeBranches(el: Element, depth: number): void {\n for (const child of el.children) {\n const tag = child.tagName.toLowerCase();\n const isItem = tag === 'obc-navigation-item';\n const isGroup = tag === 'obc-navigation-item-group';\n if (!isItem && !isGroup) continue;\n // Top-level rows are slotted; only treeify the main slot. Nested rows have\n // no slot attribute and always belong to their group's tree.\n if (depth === 0) {\n const slot = child.getAttribute('slot');\n if (slot !== null && slot !== 'main') continue;\n }\n const branches: TreeBranchType[] = Array.from(\n {length: depth},\n () => TreeBranchType.blank\n );\n const row = child as ObcNavigationItem | ObcNavigationItemGroup;\n row.treeMode = true;\n row.treeBranches = branches;\n if (isGroup) {\n this.assignTreeBranches(child, depth + 1);\n }\n }\n }\n\n // Reset tree mode on every descendant item/group so switching away from the\n // Tree variant restores the flat rendering.\n private clearTreeMode(el: Element): void {\n for (const child of el.children) {\n const tag = child.tagName.toLowerCase();\n const isItem = tag === 'obc-navigation-item';\n const isGroup = tag === 'obc-navigation-item-group';\n if (!isItem && !isGroup) continue;\n const row = child as ObcNavigationItem | ObcNavigationItemGroup;\n row.treeMode = false;\n row.treeBranches = [];\n if (isGroup) {\n this.clearTreeMode(child);\n }\n }\n }\n\n private setupItems() {\n if (this.variant === ObcNavigationMenuVariant.Tree) {\n this.assignTreeBranches(this, 0);\n // Footer and logo are not part of the tree — keep them as flat full-variant\n // items (and clear any tree mode left over from the main slot's walk).\n (['footer', 'logo'] as const).forEach((slot) => {\n this.findAllItems(this, slot).forEach((item) => {\n item.treeMode = false;\n item.treeBranches = [];\n item.variant = ObcNavigationMenuVariant.Full;\n });\n });\n // Defer so the rows' inline tree-item headers exist before the roving\n // tabindex is assigned to them.\n queueMicrotask(() => this.treeNavigator.refresh());\n return;\n }\n\n this.clearTreeMode(this);\n\n const hug =\n this.variant !== ObcNavigationMenuVariant.Full ||\n this.flyoutVariant === ObcNavigationMenuFlyoutVariant.Compact;\n this.setHugToGroups(this, hug);\n\n // Set variant to all groups (top-level)\n const groups = this.findAllGroups(this);\n groups.forEach((group) => {\n group.variant = this.variant;\n // But for flyout children, force variant to full\n this.setVariantToFlyoutItems(group);\n });\n\n // Set variant to all root items (not in group)\n this.findRootItems(this).forEach((item) => {\n item.variant = this.variant;\n });\n\n // Footer and logo logic (same as before)\n const footerVariant =\n this.smallScreen && this.variant === ObcNavigationMenuVariant.Full\n ? ObcNavigationMenuVariant.Compact\n : this.variant;\n this.findAllItems(this, 'footer').forEach((item) => {\n item.variant = footerVariant;\n });\n this.findAllItems(this, 'logo').forEach((item) => {\n item.variant = footerVariant;\n });\n\n // Close all groups on item click (unchanged)\n this.findAllItems(this).forEach((item) => {\n item.addEventListener('click', () => {\n this.closeAllGroups();\n });\n });\n }\n\n private setHugToGroups(el: Element, hug: boolean) {\n const groups = this.findAllGroups(el);\n groups.forEach((group) => {\n group.hug = hug;\n this.setHugToGroups(group, hug);\n });\n }\n\n override render() {\n return html`\n <div\n class=\"wrapper ${this.variant} ${\n this.smallScreen ? 'small-screen' : ''\n }\"\n >\n <nav class=\"main\">\n <ol>\n <slot name=\"main\" @slotchange=${this.handleSlotChange}></slot>\n </ol>\n </nav>\n <div class=\"footer ${this.hasFooter ? 'has-footer' : ''}\">\n <nav>\n <ol>\n <slot name=\"footer\" @slotchange=${this.handleSlotChange}></slot>\n ${this.smallScreen ? html` <slot name=\"logo\"></slot> ` : nothing}\n </ol>\n </nav>\n ${\n this.smallScreen\n ? nothing\n : html`\n <div class=\"logo\">\n <slot name=\"logo\"></slot>\n </div>\n `\n }\n </div>\n </div>\n `;\n }\n\n static override styles = unsafeCSS(compentStyle);\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'obc-navigation-menu': ObcNavigationMenu;\n }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"slide-button.d.ts","sourceRoot":"","sources":["../../../src/components/slide-button/slide-button.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,UAAU,EAA2B,MAAM,KAAK,CAAC;AAKzD,OAAO,iDAAiD,CAAC;AAGzD,MAAM,MAAM,wBAAwB,GAAG,WAAW,CAAC;IAAC,SAAS,EAAE,OAAO,CAAA;CAAC,CAAC,CAAC;AAEzE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2EG;AACH,qBACa,cAAe,SAAQ,UAAU;IACjB,QAAQ,UAAS;IAEjB,cAAc,UAAS;IAEvB,UAAU,UAAS;IAEnB,WAAW,UAAS;IAEtC,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,aAAa,CAAS;IAEvC,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,WAAW,CAAK;IACxB,OAAO,CAAC,cAAc,CAAO;IAG7B,OAAO,CAAC,SAAS,CAAC,CAAc;IAGhC,OAAO,CAAC,kBAAkB,CAAC,CAAc;IAEzC,OAAO,CAAC,WAAW,CAiBjB;IAEF,OAAO,CAAC,UAAU,CAchB;IAEF,OAAO,CAAC,SAAS,CAyBf;IAEF,OAAO,CAAC,kBAAkB;IAO1B,OAAO,CAAC,aAAa;IAgBrB,OAAO,CAAC,aAAa,CAMnB;IAEF,OAAO,CAAC,wBAAwB,CAAuC;IAEvE,OAAO,CAAC,oBAAoB;IAuB5B,OAAO,CAAC,cAAc;IAUb,YAAY;IAUZ,MAAM;IA8Df,OAAgB,MAAM,0BAA6B;CACpD;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,kBAAkB,EAAE,cAAc,CAAC;KACpC;CACF"}
1
+ {"version":3,"file":"slide-button.d.ts","sourceRoot":"","sources":["../../../src/components/slide-button/slide-button.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,UAAU,EAA2B,MAAM,KAAK,CAAC;AAKzD,OAAO,iDAAiD,CAAC;AAGzD,MAAM,MAAM,wBAAwB,GAAG,WAAW,CAAC;IAAC,SAAS,EAAE,OAAO,CAAA;CAAC,CAAC,CAAC;AAEzE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2EG;AACH,qBACa,cAAe,SAAQ,UAAU;IACjB,QAAQ,UAAS;IAEjB,cAAc,UAAS;IAEvB,UAAU,UAAS;IAEnB,WAAW,UAAS;IAEtC,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,aAAa,CAAS;IAEvC,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,WAAW,CAAK;IACxB,OAAO,CAAC,cAAc,CAAO;IAG7B,OAAO,CAAC,SAAS,CAAC,CAAc;IAGhC,OAAO,CAAC,kBAAkB,CAAC,CAAc;IAEzC,OAAO,CAAC,WAAW,CAiBjB;IAEF,OAAO,CAAC,UAAU,CAiBhB;IAEF,OAAO,CAAC,SAAS,CAyBf;IAEF,OAAO,CAAC,kBAAkB;IAO1B,OAAO,CAAC,aAAa;IAgBrB,OAAO,CAAC,aAAa,CAMnB;IAEF,OAAO,CAAC,wBAAwB,CAAuC;IAEvE,OAAO,CAAC,oBAAoB;IAuB5B,OAAO,CAAC,cAAc;IAUb,YAAY;IAUZ,MAAM;IA8Df,OAAgB,MAAM,0BAA6B;CACpD;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,kBAAkB,EAAE,cAAc,CAAC;KACpC;CACF"}
@@ -44,7 +44,7 @@ var ObcSlideButton = class ObcSlideButton extends LitElement {
44
44
  e.preventDefault();
45
45
  }
46
46
  this.dragCurrentX = clientX;
47
- this.dragOffset = this.dragCurrentX - this.dragStartX;
47
+ this.dragOffset = (this.dragCurrentX - this.dragStartX) / (this.currentCSSZoom ?? 1);
48
48
  this.requestUpdate();
49
49
  };
50
50
  this.onDragEnd = () => {
@@ -1 +1 @@
1
- {"version":3,"file":"slide-button.js","names":[],"sources":["../../../src/components/slide-button/slide-button.ts"],"sourcesContent":["import {LitElement, html, nothing, unsafeCSS} from 'lit';\nimport {property, query, state} from 'lit/decorators.js';\nimport componentStyle from './slide-button.css?inline';\nimport {classMap} from 'lit/directives/class-map.js';\nimport {ifDefined} from 'lit/directives/if-defined.js';\nimport '../../icons/icon-chevron-double-right-google.js';\nimport {customElement} from '../../decorator.js';\n\nexport type ObcSlideButtonSlideEvent = CustomEvent<{completed: boolean}>;\n\n/**\n * `<obc-slide-button>` – A slide-to-confirm button that requires users to drag the button to the end of a track to trigger an action.\n *\n * Appears as a horizontal button with a draggable handle. The user must slide the handle to the right to complete the action, helping prevent accidental activations. Commonly used for critical or irreversible actions where explicit user intent is required (e.g., \"Delete\", \"Confirm\", \"Unlock\").\n *\n * ---\n *\n * ### Features\n * - **Slide-to-activate interaction:** Requires a deliberate sliding gesture to trigger the action, reducing accidental clicks.\n * - **Disabled state:** Can be disabled to prevent interaction (`disabled` property).\n * - **Leading icon (optional):** Supports a leading icon via the `leading-icon` slot, controlled by the `hasLeadingIcon` property.\n * - **Custom label:** Label text is provided via the `label` slot.\n * - **Auto-disable option:** When `autoDisable` is true, the button disables itself after a successful slide, preventing repeated actions.\n * - **Hug content:** The `hugContent` property allows the button to shrink to fit its content, rather than stretching to full width.\n * - **Visual feedback:** Provides animated feedback when sliding and snapping back if not completed.\n * - **Trailing icon:** Always displays a trailing double-chevron icon to indicate sliding direction.\n * - **Keyboard accessible:** The handle is focusable; `Enter` or `Space` triggers the action (with the same `slide` event and `autoDisable` behavior as a completed drag).\n *\n * ---\n *\n * ### Usage Guidelines\n * Use `obc-slide-button` for actions that require extra confirmation or where accidental activation could have significant consequences. Typical scenarios include confirming deletions, submitting irreversible forms, or unlocking sensitive features. Avoid using for routine or low-risk actions where a standard button suffices.\n *\n * - The sliding gesture provides a clear affordance for intentional actions.\n * - If you want the button to automatically disable after use (e.g., to prevent double submissions), set `autoDisable` to true.\n * - For best usability, keep the label concise and action-oriented (e.g., \"Slide to confirm\", \"Slide to delete\").\n * - The leading icon is optional and can be omitted for a simpler appearance.\n * - The button will snap back if the slide is not completed past the threshold.\n * - Only one slide button should be used for a given action to avoid confusion.\n * - Keyboard users activate with `Enter`/`Space` (single press — Tab + activation is considered deliberate). Provide `aria-label` on the host if the slotted label is not descriptive on its own (`aria-labelledby` is not supported — ID references cannot cross the shadow boundary).\n *\n * **TODO(designer):** Confirm if there are recommended minimum/maximum label lengths or if there are accessibility guidelines for color contrast and drag affordance.\n *\n * ---\n *\n * ### Slots\n *\n * | Slot Name | Renders When... | Purpose |\n * | -------------- | --------------------- | --------------------------------------------- |\n * | leading-icon | `hasLeadingIcon` true | Icon at the start of the button (optional) |\n * | label | Always | Main label text for the button |\n *\n * ---\n *\n * ### Properties and Attributes\n * - `disabled` (boolean): Disables the button and prevents interaction. When true, the button is visually muted and cannot be slid.\n * - `hasLeadingIcon` (boolean): Controls whether the leading icon slot is shown. Set to false to hide the icon area.\n * - `hugContent` (boolean): If true, the button shrinks to fit its content instead of stretching to full width.\n * - `autoDisable` (boolean): When true, the button automatically disables itself after a successful slide action.\n *\n * ---\n *\n * ### Events\n * - `slide` – Fired when the slide action is completed (i.e., the handle is dragged past the threshold). The event detail contains `{completed: true}`.\n *\n * ---\n *\n * ### Example\n *\n * ```html\n * <obc-slide-button autoDisable>\n * <obi-placeholder slot=\"leading-icon\"></obi-placeholder>\n * <span slot=\"label\">Slide to confirm</span>\n * </obc-slide-button>\n * ```\n * In this example, the button displays a leading icon and a label. After sliding to the end, it disables itself automatically.\n *\n * @property disabled - Whether the slide button is disabled. When true, the button cannot be interacted with and appears visually muted.\n * @property hasLeadingIcon - Whether to show the leading icon slot. Set to true to show the icon area at the start of the button.\n * @property hugContent - Whether the button should hug its content. When true, the button width shrinks to fit its content instead of stretching to fill the container.\n * @property autoDisable - Whether to automatically disable the button after a successful slide. When true, the button disables itself after the slide action completes.\n * @slot leading-icon - The icon to display at the start of the button content (shown when `hasLeadingIcon` is true).\n * @slot label - The label text to display in the button.\n * @fires {ObcSlideButtonSlideEvent} slide - Emitted when the slide action is completed.\n * @experimental\n */\n@customElement('obc-slide-button')\nexport class ObcSlideButton extends LitElement {\n @property({type: Boolean}) disabled = false;\n\n @property({type: Boolean}) hasLeadingIcon = false;\n\n @property({type: Boolean}) hugContent = false;\n\n @property({type: Boolean}) autoDisable = false;\n\n @state() private dragging = false;\n @state() private animatingBack = false;\n\n private dragStartX = 0;\n private dragCurrentX = 0;\n private dragOffset = 0;\n private trackWidth = 0;\n private buttonWidth = 0;\n private slideThreshold = 0.8;\n\n @query('.slide-button')\n private buttonRef?: HTMLElement;\n\n @query('.visual-container')\n private visualContainerRef?: HTMLElement;\n\n private onDragStart = (e: MouseEvent | TouchEvent) => {\n if (this.disabled) return;\n\n this.dragging = true;\n if (e instanceof MouseEvent) {\n this.dragStartX = e.clientX;\n } else {\n this.dragStartX = e.touches[0].clientX;\n }\n this.dragCurrentX = this.dragStartX;\n this.trackWidth = this.visualContainerRef?.offsetWidth || 0;\n this.buttonWidth = this.buttonRef?.offsetWidth || 0;\n\n window.addEventListener('mousemove', this.onDragMove);\n window.addEventListener('touchmove', this.onDragMove, {passive: false});\n window.addEventListener('mouseup', this.onDragEnd);\n window.addEventListener('touchend', this.onDragEnd);\n };\n\n private onDragMove = (e: MouseEvent | TouchEvent) => {\n if (!this.dragging) return;\n\n let clientX;\n if (e instanceof MouseEvent) {\n clientX = e.clientX;\n } else {\n clientX = e.touches[0].clientX;\n e.preventDefault();\n }\n\n this.dragCurrentX = clientX;\n this.dragOffset = this.dragCurrentX - this.dragStartX;\n this.requestUpdate();\n };\n\n private onDragEnd = () => {\n if (!this.dragging) return;\n\n this.dragging = false;\n this.animatingBack = true;\n\n const maxOffset = this.trackWidth - this.buttonWidth;\n const dragProgress = Math.max(0, this.dragOffset) / maxOffset;\n\n if (dragProgress >= this.slideThreshold) {\n this.completeSlide();\n }\n\n this.dragOffset = 0;\n this.requestUpdate();\n\n setTimeout(() => {\n this.animatingBack = false;\n this.requestUpdate();\n }, this.getResetDurationMs());\n\n window.removeEventListener('mousemove', this.onDragMove);\n window.removeEventListener('touchmove', this.onDragMove);\n window.removeEventListener('mouseup', this.onDragEnd);\n window.removeEventListener('touchend', this.onDragEnd);\n };\n\n private getResetDurationMs(): number {\n const resetDuration = getComputedStyle(this).getPropertyValue(\n '--slide-button-reset-duration'\n );\n return parseFloat(resetDuration) * 1000;\n }\n\n private completeSlide() {\n /**\n * Emitted when the slide action is completed (handle dragged past the\n * threshold or activated via keyboard).\n * @fires slide\n */\n this.dispatchEvent(new CustomEvent('slide', {detail: {completed: true}}));\n\n if (this.autoDisable) {\n setTimeout(() => {\n this.disabled = true;\n this.requestUpdate();\n }, this.getResetDurationMs());\n }\n }\n\n private handleKeydown = (e: KeyboardEvent) => {\n if (e.key !== 'Enter' && e.key !== ' ') return;\n e.preventDefault();\n if (this.disabled) return;\n this.completeSlide();\n this.playKeyboardFeedback();\n };\n\n private keyboardFeedbackTimeouts: ReturnType<typeof setTimeout>[] = [];\n\n private playKeyboardFeedback() {\n this.keyboardFeedbackTimeouts.forEach(clearTimeout);\n this.keyboardFeedbackTimeouts = [];\n this.trackWidth = this.visualContainerRef?.offsetWidth || 0;\n this.buttonWidth = this.buttonRef?.offsetWidth || 0;\n this.dragOffset = this.trackWidth - this.buttonWidth;\n this.animatingBack = true;\n this.requestUpdate();\n const durationMs = this.getResetDurationMs();\n this.keyboardFeedbackTimeouts.push(\n setTimeout(() => {\n this.dragOffset = 0;\n this.requestUpdate();\n this.keyboardFeedbackTimeouts.push(\n setTimeout(() => {\n this.animatingBack = false;\n this.requestUpdate();\n }, durationMs)\n );\n }, durationMs)\n );\n }\n\n private getButtonStyle() {\n if (!this.dragging && !this.animatingBack) return '';\n\n const maxOffset = this.trackWidth - this.buttonWidth + 1;\n let left = Math.max(-1, this.dragOffset - 1);\n left = Math.min(left, maxOffset - 1);\n\n return `left: ${left}px;`;\n }\n\n override firstUpdated() {\n const resizeObserver = new ResizeObserver(() => {\n this.trackWidth = this.visualContainerRef?.offsetWidth || 0;\n this.buttonWidth = this.buttonRef?.offsetWidth || 0;\n });\n if (this.visualContainerRef)\n resizeObserver.observe(this.visualContainerRef);\n if (this.buttonRef) resizeObserver.observe(this.buttonRef);\n }\n\n override render() {\n const containerClasses = {\n 'slide-button-container': true,\n dragging: this.dragging,\n 'animating-back': this.animatingBack,\n disabled: this.disabled,\n 'hug-content': this.hugContent,\n };\n\n const visualClasses = {\n 'visual-container': true,\n };\n\n const buttonClasses = {\n 'slide-button': true,\n 'has-leading-icon': this.hasLeadingIcon,\n };\n\n const forwardedAriaLabel = this.getAttribute('aria-label') ?? undefined;\n\n return html`\n <div class=${classMap(containerClasses)}>\n <div class=${classMap(visualClasses)}>\n <div\n class=\"button-touch-target\"\n style=${this.getButtonStyle()}\n role=\"button\"\n tabindex=${this.disabled ? '-1' : '0'}\n aria-disabled=${this.disabled ? 'true' : 'false'}\n aria-label=${ifDefined(forwardedAriaLabel)}\n @mousedown=${this.onDragStart}\n @touchstart=${this.onDragStart}\n @keydown=${this.handleKeydown}\n >\n <div class=${classMap(buttonClasses)}>\n <div class=\"button-content\">\n ${\n this.hasLeadingIcon\n ? html`\n <div class=\"leading-icon\">\n <slot name=\"leading-icon\"></slot>\n </div>\n `\n : nothing\n }\n <div class=\"button-label\">\n <slot name=\"label\"></slot>\n </div>\n </div>\n </div>\n </div>\n <div class=\"trailing-icon-area\">\n <obi-chevron-double-right-google\n class=\"trailing-icon\"\n aria-hidden=\"true\"\n ></obi-chevron-double-right-google>\n </div>\n </div>\n </div>\n `;\n }\n\n static override styles = unsafeCSS(componentStyle);\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'obc-slide-button': ObcSlideButton;\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"slide-button.js","names":[],"sources":["../../../src/components/slide-button/slide-button.ts"],"sourcesContent":["import {LitElement, html, nothing, unsafeCSS} from 'lit';\nimport {property, query, state} from 'lit/decorators.js';\nimport componentStyle from './slide-button.css?inline';\nimport {classMap} from 'lit/directives/class-map.js';\nimport {ifDefined} from 'lit/directives/if-defined.js';\nimport '../../icons/icon-chevron-double-right-google.js';\nimport {customElement} from '../../decorator.js';\n\nexport type ObcSlideButtonSlideEvent = CustomEvent<{completed: boolean}>;\n\n/**\n * `<obc-slide-button>` – A slide-to-confirm button that requires users to drag the button to the end of a track to trigger an action.\n *\n * Appears as a horizontal button with a draggable handle. The user must slide the handle to the right to complete the action, helping prevent accidental activations. Commonly used for critical or irreversible actions where explicit user intent is required (e.g., \"Delete\", \"Confirm\", \"Unlock\").\n *\n * ---\n *\n * ### Features\n * - **Slide-to-activate interaction:** Requires a deliberate sliding gesture to trigger the action, reducing accidental clicks.\n * - **Disabled state:** Can be disabled to prevent interaction (`disabled` property).\n * - **Leading icon (optional):** Supports a leading icon via the `leading-icon` slot, controlled by the `hasLeadingIcon` property.\n * - **Custom label:** Label text is provided via the `label` slot.\n * - **Auto-disable option:** When `autoDisable` is true, the button disables itself after a successful slide, preventing repeated actions.\n * - **Hug content:** The `hugContent` property allows the button to shrink to fit its content, rather than stretching to full width.\n * - **Visual feedback:** Provides animated feedback when sliding and snapping back if not completed.\n * - **Trailing icon:** Always displays a trailing double-chevron icon to indicate sliding direction.\n * - **Keyboard accessible:** The handle is focusable; `Enter` or `Space` triggers the action (with the same `slide` event and `autoDisable` behavior as a completed drag).\n *\n * ---\n *\n * ### Usage Guidelines\n * Use `obc-slide-button` for actions that require extra confirmation or where accidental activation could have significant consequences. Typical scenarios include confirming deletions, submitting irreversible forms, or unlocking sensitive features. Avoid using for routine or low-risk actions where a standard button suffices.\n *\n * - The sliding gesture provides a clear affordance for intentional actions.\n * - If you want the button to automatically disable after use (e.g., to prevent double submissions), set `autoDisable` to true.\n * - For best usability, keep the label concise and action-oriented (e.g., \"Slide to confirm\", \"Slide to delete\").\n * - The leading icon is optional and can be omitted for a simpler appearance.\n * - The button will snap back if the slide is not completed past the threshold.\n * - Only one slide button should be used for a given action to avoid confusion.\n * - Keyboard users activate with `Enter`/`Space` (single press — Tab + activation is considered deliberate). Provide `aria-label` on the host if the slotted label is not descriptive on its own (`aria-labelledby` is not supported — ID references cannot cross the shadow boundary).\n *\n * **TODO(designer):** Confirm if there are recommended minimum/maximum label lengths or if there are accessibility guidelines for color contrast and drag affordance.\n *\n * ---\n *\n * ### Slots\n *\n * | Slot Name | Renders When... | Purpose |\n * | -------------- | --------------------- | --------------------------------------------- |\n * | leading-icon | `hasLeadingIcon` true | Icon at the start of the button (optional) |\n * | label | Always | Main label text for the button |\n *\n * ---\n *\n * ### Properties and Attributes\n * - `disabled` (boolean): Disables the button and prevents interaction. When true, the button is visually muted and cannot be slid.\n * - `hasLeadingIcon` (boolean): Controls whether the leading icon slot is shown. Set to false to hide the icon area.\n * - `hugContent` (boolean): If true, the button shrinks to fit its content instead of stretching to full width.\n * - `autoDisable` (boolean): When true, the button automatically disables itself after a successful slide action.\n *\n * ---\n *\n * ### Events\n * - `slide` – Fired when the slide action is completed (i.e., the handle is dragged past the threshold). The event detail contains `{completed: true}`.\n *\n * ---\n *\n * ### Example\n *\n * ```html\n * <obc-slide-button autoDisable>\n * <obi-placeholder slot=\"leading-icon\"></obi-placeholder>\n * <span slot=\"label\">Slide to confirm</span>\n * </obc-slide-button>\n * ```\n * In this example, the button displays a leading icon and a label. After sliding to the end, it disables itself automatically.\n *\n * @property disabled - Whether the slide button is disabled. When true, the button cannot be interacted with and appears visually muted.\n * @property hasLeadingIcon - Whether to show the leading icon slot. Set to true to show the icon area at the start of the button.\n * @property hugContent - Whether the button should hug its content. When true, the button width shrinks to fit its content instead of stretching to fill the container.\n * @property autoDisable - Whether to automatically disable the button after a successful slide. When true, the button disables itself after the slide action completes.\n * @slot leading-icon - The icon to display at the start of the button content (shown when `hasLeadingIcon` is true).\n * @slot label - The label text to display in the button.\n * @fires {ObcSlideButtonSlideEvent} slide - Emitted when the slide action is completed.\n * @experimental\n */\n@customElement('obc-slide-button')\nexport class ObcSlideButton extends LitElement {\n @property({type: Boolean}) disabled = false;\n\n @property({type: Boolean}) hasLeadingIcon = false;\n\n @property({type: Boolean}) hugContent = false;\n\n @property({type: Boolean}) autoDisable = false;\n\n @state() private dragging = false;\n @state() private animatingBack = false;\n\n private dragStartX = 0;\n private dragCurrentX = 0;\n private dragOffset = 0;\n private trackWidth = 0;\n private buttonWidth = 0;\n private slideThreshold = 0.8;\n\n @query('.slide-button')\n private buttonRef?: HTMLElement;\n\n @query('.visual-container')\n private visualContainerRef?: HTMLElement;\n\n private onDragStart = (e: MouseEvent | TouchEvent) => {\n if (this.disabled) return;\n\n this.dragging = true;\n if (e instanceof MouseEvent) {\n this.dragStartX = e.clientX;\n } else {\n this.dragStartX = e.touches[0].clientX;\n }\n this.dragCurrentX = this.dragStartX;\n this.trackWidth = this.visualContainerRef?.offsetWidth || 0;\n this.buttonWidth = this.buttonRef?.offsetWidth || 0;\n\n window.addEventListener('mousemove', this.onDragMove);\n window.addEventListener('touchmove', this.onDragMove, {passive: false});\n window.addEventListener('mouseup', this.onDragEnd);\n window.addEventListener('touchend', this.onDragEnd);\n };\n\n private onDragMove = (e: MouseEvent | TouchEvent) => {\n if (!this.dragging) return;\n\n let clientX;\n if (e instanceof MouseEvent) {\n clientX = e.clientX;\n } else {\n clientX = e.touches[0].clientX;\n e.preventDefault();\n }\n\n this.dragCurrentX = clientX;\n // clientX is viewport px; the thumb's `left` is CSS px, which an ancestor\n // `zoom` scales again, so the delta is divided by that zoom.\n this.dragOffset =\n (this.dragCurrentX - this.dragStartX) / (this.currentCSSZoom ?? 1);\n this.requestUpdate();\n };\n\n private onDragEnd = () => {\n if (!this.dragging) return;\n\n this.dragging = false;\n this.animatingBack = true;\n\n const maxOffset = this.trackWidth - this.buttonWidth;\n const dragProgress = Math.max(0, this.dragOffset) / maxOffset;\n\n if (dragProgress >= this.slideThreshold) {\n this.completeSlide();\n }\n\n this.dragOffset = 0;\n this.requestUpdate();\n\n setTimeout(() => {\n this.animatingBack = false;\n this.requestUpdate();\n }, this.getResetDurationMs());\n\n window.removeEventListener('mousemove', this.onDragMove);\n window.removeEventListener('touchmove', this.onDragMove);\n window.removeEventListener('mouseup', this.onDragEnd);\n window.removeEventListener('touchend', this.onDragEnd);\n };\n\n private getResetDurationMs(): number {\n const resetDuration = getComputedStyle(this).getPropertyValue(\n '--slide-button-reset-duration'\n );\n return parseFloat(resetDuration) * 1000;\n }\n\n private completeSlide() {\n /**\n * Emitted when the slide action is completed (handle dragged past the\n * threshold or activated via keyboard).\n * @fires slide\n */\n this.dispatchEvent(new CustomEvent('slide', {detail: {completed: true}}));\n\n if (this.autoDisable) {\n setTimeout(() => {\n this.disabled = true;\n this.requestUpdate();\n }, this.getResetDurationMs());\n }\n }\n\n private handleKeydown = (e: KeyboardEvent) => {\n if (e.key !== 'Enter' && e.key !== ' ') return;\n e.preventDefault();\n if (this.disabled) return;\n this.completeSlide();\n this.playKeyboardFeedback();\n };\n\n private keyboardFeedbackTimeouts: ReturnType<typeof setTimeout>[] = [];\n\n private playKeyboardFeedback() {\n this.keyboardFeedbackTimeouts.forEach(clearTimeout);\n this.keyboardFeedbackTimeouts = [];\n this.trackWidth = this.visualContainerRef?.offsetWidth || 0;\n this.buttonWidth = this.buttonRef?.offsetWidth || 0;\n this.dragOffset = this.trackWidth - this.buttonWidth;\n this.animatingBack = true;\n this.requestUpdate();\n const durationMs = this.getResetDurationMs();\n this.keyboardFeedbackTimeouts.push(\n setTimeout(() => {\n this.dragOffset = 0;\n this.requestUpdate();\n this.keyboardFeedbackTimeouts.push(\n setTimeout(() => {\n this.animatingBack = false;\n this.requestUpdate();\n }, durationMs)\n );\n }, durationMs)\n );\n }\n\n private getButtonStyle() {\n if (!this.dragging && !this.animatingBack) return '';\n\n const maxOffset = this.trackWidth - this.buttonWidth + 1;\n let left = Math.max(-1, this.dragOffset - 1);\n left = Math.min(left, maxOffset - 1);\n\n return `left: ${left}px;`;\n }\n\n override firstUpdated() {\n const resizeObserver = new ResizeObserver(() => {\n this.trackWidth = this.visualContainerRef?.offsetWidth || 0;\n this.buttonWidth = this.buttonRef?.offsetWidth || 0;\n });\n if (this.visualContainerRef)\n resizeObserver.observe(this.visualContainerRef);\n if (this.buttonRef) resizeObserver.observe(this.buttonRef);\n }\n\n override render() {\n const containerClasses = {\n 'slide-button-container': true,\n dragging: this.dragging,\n 'animating-back': this.animatingBack,\n disabled: this.disabled,\n 'hug-content': this.hugContent,\n };\n\n const visualClasses = {\n 'visual-container': true,\n };\n\n const buttonClasses = {\n 'slide-button': true,\n 'has-leading-icon': this.hasLeadingIcon,\n };\n\n const forwardedAriaLabel = this.getAttribute('aria-label') ?? undefined;\n\n return html`\n <div class=${classMap(containerClasses)}>\n <div class=${classMap(visualClasses)}>\n <div\n class=\"button-touch-target\"\n style=${this.getButtonStyle()}\n role=\"button\"\n tabindex=${this.disabled ? '-1' : '0'}\n aria-disabled=${this.disabled ? 'true' : 'false'}\n aria-label=${ifDefined(forwardedAriaLabel)}\n @mousedown=${this.onDragStart}\n @touchstart=${this.onDragStart}\n @keydown=${this.handleKeydown}\n >\n <div class=${classMap(buttonClasses)}>\n <div class=\"button-content\">\n ${\n this.hasLeadingIcon\n ? html`\n <div class=\"leading-icon\">\n <slot name=\"leading-icon\"></slot>\n </div>\n `\n : nothing\n }\n <div class=\"button-label\">\n <slot name=\"label\"></slot>\n </div>\n </div>\n </div>\n </div>\n <div class=\"trailing-icon-area\">\n <obi-chevron-double-right-google\n class=\"trailing-icon\"\n aria-hidden=\"true\"\n ></obi-chevron-double-right-google>\n </div>\n </div>\n </div>\n `;\n }\n\n static override styles = unsafeCSS(componentStyle);\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'obc-slide-button': ObcSlideButton;\n }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"start-stop-switch.d.ts","sourceRoot":"","sources":["../../../src/components/start-stop-switch/start-stop-switch.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,UAAU,EAAkB,MAAM,KAAK,CAAC;AAKhD,OAAO,wCAAwC,CAAC;AAIhD;;;;;GAKG;AACH,oBAAY,sBAAsB;IAChC,MAAM,WAAW;IACjB,OAAO,YAAY;IACnB,OAAO,YAAY;CACpB;AAED;;;;GAIG;AACH,oBAAY,mBAAmB;IAC7B,OAAO,YAAY;IACnB,KAAK,UAAU;CAChB;AAED,MAAM,MAAM,6BAA6B,GAAG,WAAW,CAAC;IACtD,OAAO,EAAE,OAAO,CAAC;CAClB,CAAC,CAAC;AAQH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoGG;AACH,qBACa,kBAAmB,SAAQ,UAAU;IACN,OAAO,UAAS;IAEjB,OAAO,EAAE,sBAAsB,CACxC;IAES,IAAI,EAAE,mBAAmB,CACpC;IAEY,QAAQ,UAAS;IAEhC,QAAQ,UAAS;IAEjB,cAAc,UAAS;IAExB,WAAW,SAAwB;IAEpD,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,UAAU,CAAS;IAEpC,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,WAAW,CAAK;IACxB,OAAO,CAAC,cAAc,CAAC,CAAiB;IAGxC,OAAO,CAAC,SAAS,CAAC,CAAc;IAGhC,OAAO,CAAC,QAAQ,CAAC,CAAc;IAE/B,OAAO,CAAC,WAAW,CAwBjB;IAEF,OAAO,CAAC,UAAU,CAgBhB;IAEF,OAAO,CAAC,SAAS,CAwBf;IAEF,OAAO,CAAC,aAAa,CAOnB;IAEF,OAAO,CAAC,mBAAmB;IAU3B,OAAO,CAAC,cAAc;IAiBb,YAAY;IASZ,OAAO,CAAC,iBAAiB,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC;IAiC/C,oBAAoB;IASpB,MAAM;IA+Ff,OAAgB,MAAM,0BAA6B;CACpD;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,uBAAuB,EAAE,kBAAkB,CAAC;KAC7C;CACF"}
1
+ {"version":3,"file":"start-stop-switch.d.ts","sourceRoot":"","sources":["../../../src/components/start-stop-switch/start-stop-switch.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,UAAU,EAAkB,MAAM,KAAK,CAAC;AAKhD,OAAO,wCAAwC,CAAC;AAIhD;;;;;GAKG;AACH,oBAAY,sBAAsB;IAChC,MAAM,WAAW;IACjB,OAAO,YAAY;IACnB,OAAO,YAAY;CACpB;AAED;;;;GAIG;AACH,oBAAY,mBAAmB;IAC7B,OAAO,YAAY;IACnB,KAAK,UAAU;CAChB;AAED,MAAM,MAAM,6BAA6B,GAAG,WAAW,CAAC;IACtD,OAAO,EAAE,OAAO,CAAC;CAClB,CAAC,CAAC;AAQH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoGG;AACH,qBACa,kBAAmB,SAAQ,UAAU;IACN,OAAO,UAAS;IAEjB,OAAO,EAAE,sBAAsB,CACxC;IAES,IAAI,EAAE,mBAAmB,CACpC;IAEY,QAAQ,UAAS;IAEhC,QAAQ,UAAS;IAEjB,cAAc,UAAS;IAExB,WAAW,SAAwB;IAEpD,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,UAAU,CAAS;IAEpC,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,WAAW,CAAK;IACxB,OAAO,CAAC,cAAc,CAAC,CAAiB;IAGxC,OAAO,CAAC,SAAS,CAAC,CAAc;IAGhC,OAAO,CAAC,QAAQ,CAAC,CAAc;IAE/B,OAAO,CAAC,WAAW,CAejB;IAEF,OAAO,CAAC,UAAU,CAmBhB;IAEF,OAAO,CAAC,SAAS,CAwBf;IAEF,OAAO,CAAC,aAAa,CAOnB;IAEF,OAAO,CAAC,mBAAmB;IAU3B,OAAO,CAAC,cAAc;IAiBb,YAAY;IASZ,OAAO,CAAC,iBAAiB,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC;IAiC/C,oBAAoB;IASpB,MAAM;IA+Ff,OAAgB,MAAM,0BAA6B;CACpD;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,uBAAuB,EAAE,kBAAkB,CAAC;KAC7C;CACF"}
@@ -57,15 +57,8 @@ var ObcStartStopSwitch = class ObcStartStopSwitch extends LitElement {
57
57
  if (e instanceof MouseEvent) this.dragStartX = e.clientX;
58
58
  else this.dragStartX = e.touches[0].clientX;
59
59
  this.dragCurrentX = this.dragStartX;
60
- const trackRect = this.trackRef?.getBoundingClientRect();
61
- const buttonRect = this.buttonRef?.getBoundingClientRect();
62
- if (trackRect && buttonRect) {
63
- this.trackWidth = trackRect.width;
64
- this.buttonWidth = buttonRect.width;
65
- } else {
66
- this.trackWidth = this.trackRef?.offsetWidth || 0;
67
- this.buttonWidth = this.buttonRef?.offsetWidth || 0;
68
- }
60
+ this.trackWidth = this.trackRef?.offsetWidth || 0;
61
+ this.buttonWidth = this.buttonRef?.offsetWidth || 0;
69
62
  window.addEventListener("mousemove", this.onDragMove);
70
63
  window.addEventListener("touchmove", this.onDragMove, { passive: false });
71
64
  window.addEventListener("mouseup", this.onDragEnd);
@@ -80,7 +73,7 @@ var ObcStartStopSwitch = class ObcStartStopSwitch extends LitElement {
80
73
  e.preventDefault();
81
74
  }
82
75
  this.dragCurrentX = clientX;
83
- this.dragOffset = this.dragCurrentX - this.dragStartX;
76
+ this.dragOffset = (this.dragCurrentX - this.dragStartX) / (this.currentCSSZoom ?? 1);
84
77
  const percent = Math.abs(this.dragOffset / (this.trackWidth - this.buttonWidth));
85
78
  this.tmpChecked = this.checked ? percent < .5 : percent > .5;
86
79
  this.requestUpdate();
@@ -1 +1 @@
1
- {"version":3,"file":"start-stop-switch.js","names":[],"sources":["../../../src/components/start-stop-switch/start-stop-switch.ts"],"sourcesContent":["import {LitElement, html, unsafeCSS} from 'lit';\nimport {property, query, state} from 'lit/decorators.js';\nimport componentStyle from './start-stop-switch.css?inline';\nimport {classMap} from 'lit/directives/class-map.js';\nimport {ifDefined} from 'lit/directives/if-defined.js';\nimport '../../icons/icon-arrow-right-google.js';\nimport {customElement} from '../../decorator.js';\nimport {clamp} from '../../svghelpers/math.js';\n\n/**\n * The visual variant for the start-stop switch when checked.\n * - `normal`: Standard blue appearance (default)\n * - `running`: Green appearance indicating motor/process is running\n * - `loading`: Light blue appearance indicating a pending/loading state\n */\nexport enum StartStopSwitchVariant {\n normal = 'normal',\n running = 'running',\n loading = 'loading',\n}\n\n/**\n * The possible sizes for the start-stop switch.\n * - `regular`: Standard size with 32px track height\n * - `large`: Larger size with 48px track height\n */\nexport enum StartStopSwitchSize {\n regular = 'regular',\n large = 'large',\n}\n\nexport type ObcStartStopSwitchChangeEvent = CustomEvent<{\n checked: boolean;\n}>;\n\n/**\n * Threshold for completing a drag gesture (90% of track width).\n * User must drag past this threshold to toggle the switch state.\n */\nconst DRAG_COMPLETE_THRESHOLD = 0.9;\n\n/**\n * `<obc-start-stop-switch>` – A two-state draggable toggle switch for significant binary actions like start/stop or enable/disable.\n *\n * This switch provides a visually distinct, draggable toggle for binary actions where both states need to be explicitly labeled and reinforced. Unlike a simple on/off toggle, it displays both the current state label and an action label on the draggable thumb, making it ideal for consequential actions where user awareness is critical.\n *\n * ### Features\n * - **Drag-to-toggle interaction:** Users must drag the thumb across the track to change state (90% threshold required). This deliberate interaction prevents accidental toggles.\n * - **Dual state labels:** Displays both a state label (e.g., \"Running\", \"Stopped\") and an action label on the thumb (e.g., \"Start\", \"Stop\").\n * - **Visual variants:**\n * - `normal`: Standard blue appearance when checked (default)\n * - `running`: Green appearance indicating an active process (e.g., motor running)\n * - `loading`: Light blue appearance indicating a pending/transitional state\n * - **Size options:** `regular` (32px track height) or `large` (48px track height, matching touch target).\n * - **Alert frame:** Optional red border to indicate alarm or critical state.\n * - **Description text:** Optional text below the switch for additional context.\n * - **Animated transitions:** Smooth CSS transitions for thumb movement and state changes.\n * - **Keyboard accessible:** The switch is focusable and follows the ARIA switch pattern — `Enter` or `Space` toggles the state and fires the same `change` event as a completed drag.\n *\n * ### Usage Guidelines\n * Use `<obc-start-stop-switch>` when you need a prominent control for toggling between two mutually exclusive states where:\n * - Both states should be explicitly labeled (not just \"on/off\")\n * - The action has significant consequences (starting a motor, enabling a system)\n * - Accidental activation should be prevented through deliberate drag interaction\n *\n * Avoid using this component for simple preferences or settings where a standard `<obc-switch>` or checkbox would suffice. This component is best suited for operational controls where the user must be aware of both the current state and the action they're about to take.\n *\n * **Related components:** For simple binary toggles without labels, use `<obc-switch>`. For grouped selections, use `<obc-radio>` or `<obc-checkbox>`.\n *\n * ### Slots\n * | Slot Name | Renders When... | Purpose |\n * |---------------------------|----------------------|------------------------------------------------------------|\n * | checked-state-icon | Always (if provided) | Icon displayed in the checked state area. |\n * | unchecked-state-icon | Always (if provided) | Icon displayed in the unchecked state area. |\n * | checked-state-label | Always | Label shown when switch is in checked position. |\n * | unchecked-state-label | Always | Label shown when switch is in unchecked position. |\n * | to-checked-action-label | When unchecked | Action label on thumb prompting user to activate. |\n * | to-unchecked-action-label | When checked | Action label on thumb prompting user to deactivate. |\n *\n * ### Events\n * - `change` – Fired when the switch state changes after a successful drag. The event detail contains `{checked: boolean}` indicating the new state.\n *\n * ### Best Practices\n * - Keep labels concise (1-2 words) to fit within the track width.\n * - Use meaningful action verbs: \"Start/Stop\", \"Enable/Disable\", \"Request/Release\".\n * - Use the `running` variant to indicate an actively running process.\n * - Use the `loading` variant during async operations before transitioning to `running` or `normal`.\n * - Consider using `hasAlert` when the switch controls a critical system in alarm state.\n * - The accessible name is computed from the slotted labels; provide `aria-label` on the host when those labels alone are not descriptive (`aria-labelledby` is not supported — ID references cannot cross the shadow boundary).\n *\n * ### Example\n * ```html\n * <obc-start-stop-switch checked variant=\"running\">\n * <div slot=\"checked-state-icon\"><obi-placeholder></obi-placeholder></div>\n * <div slot=\"checked-state-label\">Running</div>\n * <div slot=\"unchecked-state-label\">Stopped</div>\n * <div slot=\"to-checked-action-label\">Start</div>\n * <div slot=\"to-unchecked-action-label\">Stop</div>\n * </obc-start-stop-switch>\n * ```\n *\n * ### Keyboard\n * [APG Switch](https://www.w3.org/WAI/ARIA/apg/patterns/switch/): `Space` and\n * `Enter` toggle it. The knob inside is a drag handle for pointers and stays\n * out of the tab sequence.\n *\n * Left out: nothing.\n *\n * @property checked - Whether the switch is in the checked (active) state.\n * When checked, the thumb is on the right side.\n * When unchecked, the thumb is on the left side.\n * Defaults to `false`.\n * @property variant - Visual style of the checked side: `normal` (default) is\n * blue, `running` green for the running state and `loading` light blue for a\n * pending one. It does not move the thumb.\n * @property disabled - Whether the switch is disabled.\n * When disabled, the switch cannot be interacted with and displays a muted appearance.\n * In the disabled state, the thumb is hidden and only the state label is shown.\n * Defaults to `false`.\n * @property hasAlert - Whether to show an alert frame around the switch.\n * When true, displays a red alert border around the switch to indicate\n * an alarm or critical state.\n * Defaults to `false`.\n * @property hasDescription - Whether to show the description below the switch.\n * When true, displays the description text below the switch track.\n * The component's minimum height is maintained at the touch target size.\n * Defaults to `false`.\n * @property description - Description text displayed below the switch when `hasDescription` is true.\n * Use this to provide additional context about the switch's purpose or current state.\n * Defaults to `'Action description'`.\n * @availableWhen description hasDescription==true\n * @property size - Switch size: `regular` (default) has a 32px track height, `large` a 48px\n * one.\n * @slot checked-state-icon - Icon displayed in the checked (active) state area.\n * @slot unchecked-state-icon - Icon displayed in the unchecked (inactive) state area.\n * @slot checked-state-label - Label for the checked state (e.g., \"Running\", \"Enabled\").\n * @slot unchecked-state-label - Label for the unchecked state (e.g., \"Stopped\", \"Disabled\").\n * @slot to-checked-action-label - Action label on thumb when unchecked (e.g., \"Start\", \"Enable\").\n * @slot to-unchecked-action-label - Action label on thumb when checked (e.g., \"Stop\", \"Disable\").\n * @fires {ObcStartStopSwitchChangeEvent} change - Emitted when the switch is toggled via drag interaction.\n * @stable\n */\n@customElement('obc-start-stop-switch')\nexport class ObcStartStopSwitch extends LitElement {\n @property({type: Boolean, reflect: true}) checked = false;\n\n @property({type: String, reflect: true}) variant: StartStopSwitchVariant =\n StartStopSwitchVariant.normal;\n\n @property({type: String, reflect: true}) size: StartStopSwitchSize =\n StartStopSwitchSize.regular;\n\n @property({type: Boolean, reflect: true}) disabled = false;\n\n @property({type: Boolean}) hasAlert = false;\n\n @property({type: Boolean}) hasDescription = false;\n\n @property({type: String}) description = 'Action description';\n\n @state() private dragging = false;\n @state() private tmpChecked = false;\n\n private dragStartX = 0;\n private dragCurrentX = 0;\n private dragOffset = 0;\n private trackWidth = 0;\n private buttonWidth = 0;\n private resizeObserver?: ResizeObserver;\n\n @query('.button')\n private buttonRef?: HTMLElement;\n\n @query('.button-track')\n private trackRef?: HTMLElement;\n\n private onDragStart = (e: MouseEvent | TouchEvent) => {\n this.dragging = true;\n if (e instanceof MouseEvent) {\n this.dragStartX = e.clientX;\n } else {\n this.dragStartX = e.touches[0].clientX;\n }\n this.dragCurrentX = this.dragStartX;\n\n const trackRect = this.trackRef?.getBoundingClientRect();\n const buttonRect = this.buttonRef?.getBoundingClientRect();\n\n if (trackRect && buttonRect) {\n this.trackWidth = trackRect.width;\n this.buttonWidth = buttonRect.width;\n } else {\n this.trackWidth = this.trackRef?.offsetWidth || 0;\n this.buttonWidth = this.buttonRef?.offsetWidth || 0;\n }\n\n window.addEventListener('mousemove', this.onDragMove);\n window.addEventListener('touchmove', this.onDragMove, {passive: false});\n window.addEventListener('mouseup', this.onDragEnd);\n window.addEventListener('touchend', this.onDragEnd);\n };\n\n private onDragMove = (e: MouseEvent | TouchEvent) => {\n if (!this.dragging) return;\n let clientX;\n if (e instanceof MouseEvent) {\n clientX = e.clientX;\n } else {\n clientX = e.touches[0].clientX;\n e.preventDefault();\n }\n this.dragCurrentX = clientX;\n this.dragOffset = this.dragCurrentX - this.dragStartX;\n const percent = Math.abs(\n this.dragOffset / (this.trackWidth - this.buttonWidth)\n );\n this.tmpChecked = this.checked ? percent < 0.5 : percent > 0.5;\n this.requestUpdate();\n };\n\n private onDragEnd = () => {\n if (!this.dragging) return;\n this.dragging = false;\n\n const maxOffset = this.trackWidth - this.buttonWidth;\n const startPosition = this.checked ? maxOffset : 0;\n const newPosition = startPosition + this.dragOffset;\n const percent = newPosition / maxOffset;\n\n if (!this.checked && percent > DRAG_COMPLETE_THRESHOLD) {\n this.checked = true;\n this.dispatchChangeEvent();\n } else if (this.checked && percent < 1 - DRAG_COMPLETE_THRESHOLD) {\n this.checked = false;\n this.dispatchChangeEvent();\n }\n\n this.tmpChecked = this.checked;\n this.dragOffset = 0;\n window.removeEventListener('mousemove', this.onDragMove);\n window.removeEventListener('touchmove', this.onDragMove);\n window.removeEventListener('mouseup', this.onDragEnd);\n window.removeEventListener('touchend', this.onDragEnd);\n this.requestUpdate();\n };\n\n private handleKeydown = (e: KeyboardEvent) => {\n if (e.key !== 'Enter' && e.key !== ' ') return;\n e.preventDefault();\n if (this.disabled) return;\n this.checked = !this.checked;\n this.tmpChecked = this.checked;\n this.dispatchChangeEvent();\n };\n\n private dispatchChangeEvent() {\n this.dispatchEvent(\n new CustomEvent('change', {\n detail: {checked: this.checked},\n bubbles: true,\n composed: true,\n })\n );\n }\n\n private getButtonStyle() {\n if (!this.dragging) return '';\n\n // Keep same positioning mode as CSS to avoid subpixel issues when switching modes\n if (this.checked) {\n let right = -1 - this.dragOffset;\n const maxRight = this.trackWidth - this.buttonWidth + 1;\n right = clamp(right, -1, Math.max(-1, maxRight));\n return `right: ${right}px; left: auto; transition: none;`;\n } else {\n let left = -1 + this.dragOffset;\n const maxLeft = this.trackWidth - this.buttonWidth + 1;\n left = clamp(left, -1, Math.max(-1, maxLeft));\n return `left: ${left}px; right: auto; transition: none;`;\n }\n }\n\n override firstUpdated() {\n this.resizeObserver = new ResizeObserver(() => {\n this.trackWidth = this.trackRef?.offsetWidth || 0;\n this.buttonWidth = this.buttonRef?.offsetWidth || 0;\n });\n if (this.trackRef) this.resizeObserver.observe(this.trackRef);\n if (this.buttonRef) this.resizeObserver.observe(this.buttonRef);\n }\n\n override updated(changedProperties: Map<string, unknown>) {\n super.updated(changedProperties);\n // CSS Anchor Positioning (`right: anchor(center)`) on .button-track-checked\n // may not repaint when the thumb moves via a CSS transition (checked property change).\n // Force the browser to recalculate anchor positions after the transition completes.\n if (changedProperties.has('checked') && !this.dragging) {\n const trackChecked = this.renderRoot.querySelector(\n '.button-track-checked'\n ) as HTMLElement | null;\n const button = this.buttonRef;\n if (trackChecked && button) {\n const nudgeRepaint = () => {\n trackChecked.style.display = 'none';\n // Force a synchronous layout recalculation\n void trackChecked.offsetHeight;\n trackChecked.style.display = '';\n };\n // Wait one frame for the browser to start any CSS transitions,\n // then wait for all animations on the thumb to finish before nudging.\n requestAnimationFrame(() => {\n const animations = button.getAnimations();\n if (animations.length > 0) {\n Promise.allSettled(animations.map((a) => a.finished)).then(\n nudgeRepaint\n );\n } else {\n nudgeRepaint();\n }\n });\n }\n }\n }\n\n override disconnectedCallback() {\n super.disconnectedCallback();\n this.resizeObserver?.disconnect();\n window.removeEventListener('mousemove', this.onDragMove);\n window.removeEventListener('touchmove', this.onDragMove);\n window.removeEventListener('mouseup', this.onDragEnd);\n window.removeEventListener('touchend', this.onDragEnd);\n }\n\n override render() {\n const isChecked = this.checked;\n const forwardedAriaLabel = this.getAttribute('aria-label') ?? undefined;\n\n return html`\n <div class=\"outer-container\">\n <div\n class=${classMap({\n wrapper: true,\n checked: isChecked,\n unchecked: !isChecked,\n dragging: this.dragging,\n 'tmp-checked': this.tmpChecked && this.dragging,\n 'tmp-unchecked': !this.tmpChecked && this.dragging,\n disabled: this.disabled,\n 'has-alert': this.hasAlert,\n 'size-regular': this.size === 'regular',\n 'size-large': this.size === 'large',\n 'variant-normal': this.variant === 'normal',\n 'variant-running': this.variant === 'running',\n 'variant-loading': this.variant === 'loading',\n })}\n tabindex=${this.disabled ? -1 : 0}\n role=\"switch\"\n aria-checked=${isChecked ? 'true' : 'false'}\n aria-disabled=${this.disabled ? 'true' : 'false'}\n aria-label=${ifDefined(forwardedAriaLabel)}\n @keydown=${this.handleKeydown}\n >\n <div class=\"button-track\">\n <button\n class=\"button\"\n style=${this.getButtonStyle()}\n @mousedown=${this.disabled ? undefined : this.onDragStart}\n @touchstart=${\n this.disabled ? undefined : this.onDragStart\n }\n ?disabled=${this.disabled}\n tabindex=\"-1\"\n >\n ${\n !this.disabled\n ? html`\n <div class=\"button-visible\">\n <obi-arrow-right-google\n class=\"button-icon\"\n aria-hidden=\"true\"\n ></obi-arrow-right-google>\n <div class=\"button-label\">\n <slot\n name=${\n isChecked\n ? 'to-unchecked-action-label'\n : 'to-checked-action-label'\n }\n ></slot>\n </div>\n </div>\n `\n : ''\n }\n </button>\n ${\n !this.disabled\n ? html`<div class=\"button-track-checked\"></div>`\n : ''\n }\n <div class=\"checked state\">\n <slot name=\"checked-state-icon\"></slot>\n <div class=\"state-label\">\n <slot name=\"checked-state-label\"></slot>\n </div>\n </div>\n <div class=\"unchecked state\">\n <slot name=\"unchecked-state-icon\"></slot>\n <div class=\"state-label\">\n <slot name=\"unchecked-state-label\"></slot>\n </div>\n </div>\n </div>\n ${this.hasAlert ? html`<div class=\"alert-frame\"></div>` : ''}\n </div>\n ${\n this.hasDescription\n ? html`<div class=\"description\">${this.description}</div>`\n : ''\n }\n </div>\n </div>\n </div>\n </div>\n </div>\n `;\n }\n\n static override styles = unsafeCSS(componentStyle);\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'obc-start-stop-switch': ObcStartStopSwitch;\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"start-stop-switch.js","names":[],"sources":["../../../src/components/start-stop-switch/start-stop-switch.ts"],"sourcesContent":["import {LitElement, html, unsafeCSS} from 'lit';\nimport {property, query, state} from 'lit/decorators.js';\nimport componentStyle from './start-stop-switch.css?inline';\nimport {classMap} from 'lit/directives/class-map.js';\nimport {ifDefined} from 'lit/directives/if-defined.js';\nimport '../../icons/icon-arrow-right-google.js';\nimport {customElement} from '../../decorator.js';\nimport {clamp} from '../../svghelpers/math.js';\n\n/**\n * The visual variant for the start-stop switch when checked.\n * - `normal`: Standard blue appearance (default)\n * - `running`: Green appearance indicating motor/process is running\n * - `loading`: Light blue appearance indicating a pending/loading state\n */\nexport enum StartStopSwitchVariant {\n normal = 'normal',\n running = 'running',\n loading = 'loading',\n}\n\n/**\n * The possible sizes for the start-stop switch.\n * - `regular`: Standard size with 32px track height\n * - `large`: Larger size with 48px track height\n */\nexport enum StartStopSwitchSize {\n regular = 'regular',\n large = 'large',\n}\n\nexport type ObcStartStopSwitchChangeEvent = CustomEvent<{\n checked: boolean;\n}>;\n\n/**\n * Threshold for completing a drag gesture (90% of track width).\n * User must drag past this threshold to toggle the switch state.\n */\nconst DRAG_COMPLETE_THRESHOLD = 0.9;\n\n/**\n * `<obc-start-stop-switch>` – A two-state draggable toggle switch for significant binary actions like start/stop or enable/disable.\n *\n * This switch provides a visually distinct, draggable toggle for binary actions where both states need to be explicitly labeled and reinforced. Unlike a simple on/off toggle, it displays both the current state label and an action label on the draggable thumb, making it ideal for consequential actions where user awareness is critical.\n *\n * ### Features\n * - **Drag-to-toggle interaction:** Users must drag the thumb across the track to change state (90% threshold required). This deliberate interaction prevents accidental toggles.\n * - **Dual state labels:** Displays both a state label (e.g., \"Running\", \"Stopped\") and an action label on the thumb (e.g., \"Start\", \"Stop\").\n * - **Visual variants:**\n * - `normal`: Standard blue appearance when checked (default)\n * - `running`: Green appearance indicating an active process (e.g., motor running)\n * - `loading`: Light blue appearance indicating a pending/transitional state\n * - **Size options:** `regular` (32px track height) or `large` (48px track height, matching touch target).\n * - **Alert frame:** Optional red border to indicate alarm or critical state.\n * - **Description text:** Optional text below the switch for additional context.\n * - **Animated transitions:** Smooth CSS transitions for thumb movement and state changes.\n * - **Keyboard accessible:** The switch is focusable and follows the ARIA switch pattern — `Enter` or `Space` toggles the state and fires the same `change` event as a completed drag.\n *\n * ### Usage Guidelines\n * Use `<obc-start-stop-switch>` when you need a prominent control for toggling between two mutually exclusive states where:\n * - Both states should be explicitly labeled (not just \"on/off\")\n * - The action has significant consequences (starting a motor, enabling a system)\n * - Accidental activation should be prevented through deliberate drag interaction\n *\n * Avoid using this component for simple preferences or settings where a standard `<obc-switch>` or checkbox would suffice. This component is best suited for operational controls where the user must be aware of both the current state and the action they're about to take.\n *\n * **Related components:** For simple binary toggles without labels, use `<obc-switch>`. For grouped selections, use `<obc-radio>` or `<obc-checkbox>`.\n *\n * ### Slots\n * | Slot Name | Renders When... | Purpose |\n * |---------------------------|----------------------|------------------------------------------------------------|\n * | checked-state-icon | Always (if provided) | Icon displayed in the checked state area. |\n * | unchecked-state-icon | Always (if provided) | Icon displayed in the unchecked state area. |\n * | checked-state-label | Always | Label shown when switch is in checked position. |\n * | unchecked-state-label | Always | Label shown when switch is in unchecked position. |\n * | to-checked-action-label | When unchecked | Action label on thumb prompting user to activate. |\n * | to-unchecked-action-label | When checked | Action label on thumb prompting user to deactivate. |\n *\n * ### Events\n * - `change` – Fired when the switch state changes after a successful drag. The event detail contains `{checked: boolean}` indicating the new state.\n *\n * ### Best Practices\n * - Keep labels concise (1-2 words) to fit within the track width.\n * - Use meaningful action verbs: \"Start/Stop\", \"Enable/Disable\", \"Request/Release\".\n * - Use the `running` variant to indicate an actively running process.\n * - Use the `loading` variant during async operations before transitioning to `running` or `normal`.\n * - Consider using `hasAlert` when the switch controls a critical system in alarm state.\n * - The accessible name is computed from the slotted labels; provide `aria-label` on the host when those labels alone are not descriptive (`aria-labelledby` is not supported — ID references cannot cross the shadow boundary).\n *\n * ### Example\n * ```html\n * <obc-start-stop-switch checked variant=\"running\">\n * <div slot=\"checked-state-icon\"><obi-placeholder></obi-placeholder></div>\n * <div slot=\"checked-state-label\">Running</div>\n * <div slot=\"unchecked-state-label\">Stopped</div>\n * <div slot=\"to-checked-action-label\">Start</div>\n * <div slot=\"to-unchecked-action-label\">Stop</div>\n * </obc-start-stop-switch>\n * ```\n *\n * ### Keyboard\n * [APG Switch](https://www.w3.org/WAI/ARIA/apg/patterns/switch/): `Space` and\n * `Enter` toggle it. The knob inside is a drag handle for pointers and stays\n * out of the tab sequence.\n *\n * Left out: nothing.\n *\n * @property checked - Whether the switch is in the checked (active) state.\n * When checked, the thumb is on the right side.\n * When unchecked, the thumb is on the left side.\n * Defaults to `false`.\n * @property variant - Visual style of the checked side: `normal` (default) is\n * blue, `running` green for the running state and `loading` light blue for a\n * pending one. It does not move the thumb.\n * @property disabled - Whether the switch is disabled.\n * When disabled, the switch cannot be interacted with and displays a muted appearance.\n * In the disabled state, the thumb is hidden and only the state label is shown.\n * Defaults to `false`.\n * @property hasAlert - Whether to show an alert frame around the switch.\n * When true, displays a red alert border around the switch to indicate\n * an alarm or critical state.\n * Defaults to `false`.\n * @property hasDescription - Whether to show the description below the switch.\n * When true, displays the description text below the switch track.\n * The component's minimum height is maintained at the touch target size.\n * Defaults to `false`.\n * @property description - Description text displayed below the switch when `hasDescription` is true.\n * Use this to provide additional context about the switch's purpose or current state.\n * Defaults to `'Action description'`.\n * @availableWhen description hasDescription==true\n * @property size - Switch size: `regular` (default) has a 32px track height, `large` a 48px\n * one.\n * @slot checked-state-icon - Icon displayed in the checked (active) state area.\n * @slot unchecked-state-icon - Icon displayed in the unchecked (inactive) state area.\n * @slot checked-state-label - Label for the checked state (e.g., \"Running\", \"Enabled\").\n * @slot unchecked-state-label - Label for the unchecked state (e.g., \"Stopped\", \"Disabled\").\n * @slot to-checked-action-label - Action label on thumb when unchecked (e.g., \"Start\", \"Enable\").\n * @slot to-unchecked-action-label - Action label on thumb when checked (e.g., \"Stop\", \"Disable\").\n * @fires {ObcStartStopSwitchChangeEvent} change - Emitted when the switch is toggled via drag interaction.\n * @stable\n */\n@customElement('obc-start-stop-switch')\nexport class ObcStartStopSwitch extends LitElement {\n @property({type: Boolean, reflect: true}) checked = false;\n\n @property({type: String, reflect: true}) variant: StartStopSwitchVariant =\n StartStopSwitchVariant.normal;\n\n @property({type: String, reflect: true}) size: StartStopSwitchSize =\n StartStopSwitchSize.regular;\n\n @property({type: Boolean, reflect: true}) disabled = false;\n\n @property({type: Boolean}) hasAlert = false;\n\n @property({type: Boolean}) hasDescription = false;\n\n @property({type: String}) description = 'Action description';\n\n @state() private dragging = false;\n @state() private tmpChecked = false;\n\n private dragStartX = 0;\n private dragCurrentX = 0;\n private dragOffset = 0;\n private trackWidth = 0;\n private buttonWidth = 0;\n private resizeObserver?: ResizeObserver;\n\n @query('.button')\n private buttonRef?: HTMLElement;\n\n @query('.button-track')\n private trackRef?: HTMLElement;\n\n private onDragStart = (e: MouseEvent | TouchEvent) => {\n this.dragging = true;\n if (e instanceof MouseEvent) {\n this.dragStartX = e.clientX;\n } else {\n this.dragStartX = e.touches[0].clientX;\n }\n this.dragCurrentX = this.dragStartX;\n this.trackWidth = this.trackRef?.offsetWidth || 0;\n this.buttonWidth = this.buttonRef?.offsetWidth || 0;\n\n window.addEventListener('mousemove', this.onDragMove);\n window.addEventListener('touchmove', this.onDragMove, {passive: false});\n window.addEventListener('mouseup', this.onDragEnd);\n window.addEventListener('touchend', this.onDragEnd);\n };\n\n private onDragMove = (e: MouseEvent | TouchEvent) => {\n if (!this.dragging) return;\n let clientX;\n if (e instanceof MouseEvent) {\n clientX = e.clientX;\n } else {\n clientX = e.touches[0].clientX;\n e.preventDefault();\n }\n this.dragCurrentX = clientX;\n // clientX is viewport px; the thumb's `left`/`right` are CSS px, which an\n // ancestor `zoom` scales again, so the delta is divided by that zoom.\n this.dragOffset =\n (this.dragCurrentX - this.dragStartX) / (this.currentCSSZoom ?? 1);\n const percent = Math.abs(\n this.dragOffset / (this.trackWidth - this.buttonWidth)\n );\n this.tmpChecked = this.checked ? percent < 0.5 : percent > 0.5;\n this.requestUpdate();\n };\n\n private onDragEnd = () => {\n if (!this.dragging) return;\n this.dragging = false;\n\n const maxOffset = this.trackWidth - this.buttonWidth;\n const startPosition = this.checked ? maxOffset : 0;\n const newPosition = startPosition + this.dragOffset;\n const percent = newPosition / maxOffset;\n\n if (!this.checked && percent > DRAG_COMPLETE_THRESHOLD) {\n this.checked = true;\n this.dispatchChangeEvent();\n } else if (this.checked && percent < 1 - DRAG_COMPLETE_THRESHOLD) {\n this.checked = false;\n this.dispatchChangeEvent();\n }\n\n this.tmpChecked = this.checked;\n this.dragOffset = 0;\n window.removeEventListener('mousemove', this.onDragMove);\n window.removeEventListener('touchmove', this.onDragMove);\n window.removeEventListener('mouseup', this.onDragEnd);\n window.removeEventListener('touchend', this.onDragEnd);\n this.requestUpdate();\n };\n\n private handleKeydown = (e: KeyboardEvent) => {\n if (e.key !== 'Enter' && e.key !== ' ') return;\n e.preventDefault();\n if (this.disabled) return;\n this.checked = !this.checked;\n this.tmpChecked = this.checked;\n this.dispatchChangeEvent();\n };\n\n private dispatchChangeEvent() {\n this.dispatchEvent(\n new CustomEvent('change', {\n detail: {checked: this.checked},\n bubbles: true,\n composed: true,\n })\n );\n }\n\n private getButtonStyle() {\n if (!this.dragging) return '';\n\n // Keep same positioning mode as CSS to avoid subpixel issues when switching modes\n if (this.checked) {\n let right = -1 - this.dragOffset;\n const maxRight = this.trackWidth - this.buttonWidth + 1;\n right = clamp(right, -1, Math.max(-1, maxRight));\n return `right: ${right}px; left: auto; transition: none;`;\n } else {\n let left = -1 + this.dragOffset;\n const maxLeft = this.trackWidth - this.buttonWidth + 1;\n left = clamp(left, -1, Math.max(-1, maxLeft));\n return `left: ${left}px; right: auto; transition: none;`;\n }\n }\n\n override firstUpdated() {\n this.resizeObserver = new ResizeObserver(() => {\n this.trackWidth = this.trackRef?.offsetWidth || 0;\n this.buttonWidth = this.buttonRef?.offsetWidth || 0;\n });\n if (this.trackRef) this.resizeObserver.observe(this.trackRef);\n if (this.buttonRef) this.resizeObserver.observe(this.buttonRef);\n }\n\n override updated(changedProperties: Map<string, unknown>) {\n super.updated(changedProperties);\n // CSS Anchor Positioning (`right: anchor(center)`) on .button-track-checked\n // may not repaint when the thumb moves via a CSS transition (checked property change).\n // Force the browser to recalculate anchor positions after the transition completes.\n if (changedProperties.has('checked') && !this.dragging) {\n const trackChecked = this.renderRoot.querySelector(\n '.button-track-checked'\n ) as HTMLElement | null;\n const button = this.buttonRef;\n if (trackChecked && button) {\n const nudgeRepaint = () => {\n trackChecked.style.display = 'none';\n // Force a synchronous layout recalculation\n void trackChecked.offsetHeight;\n trackChecked.style.display = '';\n };\n // Wait one frame for the browser to start any CSS transitions,\n // then wait for all animations on the thumb to finish before nudging.\n requestAnimationFrame(() => {\n const animations = button.getAnimations();\n if (animations.length > 0) {\n Promise.allSettled(animations.map((a) => a.finished)).then(\n nudgeRepaint\n );\n } else {\n nudgeRepaint();\n }\n });\n }\n }\n }\n\n override disconnectedCallback() {\n super.disconnectedCallback();\n this.resizeObserver?.disconnect();\n window.removeEventListener('mousemove', this.onDragMove);\n window.removeEventListener('touchmove', this.onDragMove);\n window.removeEventListener('mouseup', this.onDragEnd);\n window.removeEventListener('touchend', this.onDragEnd);\n }\n\n override render() {\n const isChecked = this.checked;\n const forwardedAriaLabel = this.getAttribute('aria-label') ?? undefined;\n\n return html`\n <div class=\"outer-container\">\n <div\n class=${classMap({\n wrapper: true,\n checked: isChecked,\n unchecked: !isChecked,\n dragging: this.dragging,\n 'tmp-checked': this.tmpChecked && this.dragging,\n 'tmp-unchecked': !this.tmpChecked && this.dragging,\n disabled: this.disabled,\n 'has-alert': this.hasAlert,\n 'size-regular': this.size === 'regular',\n 'size-large': this.size === 'large',\n 'variant-normal': this.variant === 'normal',\n 'variant-running': this.variant === 'running',\n 'variant-loading': this.variant === 'loading',\n })}\n tabindex=${this.disabled ? -1 : 0}\n role=\"switch\"\n aria-checked=${isChecked ? 'true' : 'false'}\n aria-disabled=${this.disabled ? 'true' : 'false'}\n aria-label=${ifDefined(forwardedAriaLabel)}\n @keydown=${this.handleKeydown}\n >\n <div class=\"button-track\">\n <button\n class=\"button\"\n style=${this.getButtonStyle()}\n @mousedown=${this.disabled ? undefined : this.onDragStart}\n @touchstart=${\n this.disabled ? undefined : this.onDragStart\n }\n ?disabled=${this.disabled}\n tabindex=\"-1\"\n >\n ${\n !this.disabled\n ? html`\n <div class=\"button-visible\">\n <obi-arrow-right-google\n class=\"button-icon\"\n aria-hidden=\"true\"\n ></obi-arrow-right-google>\n <div class=\"button-label\">\n <slot\n name=${\n isChecked\n ? 'to-unchecked-action-label'\n : 'to-checked-action-label'\n }\n ></slot>\n </div>\n </div>\n `\n : ''\n }\n </button>\n ${\n !this.disabled\n ? html`<div class=\"button-track-checked\"></div>`\n : ''\n }\n <div class=\"checked state\">\n <slot name=\"checked-state-icon\"></slot>\n <div class=\"state-label\">\n <slot name=\"checked-state-label\"></slot>\n </div>\n </div>\n <div class=\"unchecked state\">\n <slot name=\"unchecked-state-icon\"></slot>\n <div class=\"state-label\">\n <slot name=\"unchecked-state-label\"></slot>\n </div>\n </div>\n </div>\n ${this.hasAlert ? html`<div class=\"alert-frame\"></div>` : ''}\n </div>\n ${\n this.hasDescription\n ? html`<div class=\"description\">${this.description}</div>`\n : ''\n }\n </div>\n </div>\n </div>\n </div>\n </div>\n `;\n }\n\n static override styles = unsafeCSS(componentStyle);\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'obc-start-stop-switch': ObcStartStopSwitch;\n }\n}\n"],"mappings":"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"}
@@ -80,8 +80,8 @@ export declare enum SystemMenuControlMode {
80
80
  }
81
81
  export type VolumeChangeEvent = CustomEvent<number>;
82
82
  /**
83
- * @property softDismiss - Let the browser close this menu on its own: on a click outside it, on `Escape`, or when another menu opens. Leave it off to keep showing and hiding the menu yourself.
84
- * @property open - Whether the menu is showing.
83
+ * @property softDismiss - Lets the menu close itself on a click outside it, on `Escape`, or when another menu opens. The page is covered while the menu is open, so the closing click reaches nothing there; only the top bar's strip stays clickable, which is how its buttons switch or close menus in one click. Wire it with three bindings: set `softDismiss`, bind `open` to a flag you keep, and reset that flag on `close`. The button that opens the menu needs no wiring of its own: flip the flag from its click, or from the button event of `obc-top-bar` or `obc-integration-bar`. The user menu's *Soft Dismiss From A Bar Button* story shows that wiring, the brilliance menu's *Soft Dismiss* story the one for a button you hold yourself (`bindPopoverTrigger`), and the vue demo runs its top bar this way. Leave it off to show and hide the menu yourself.
84
+ * @property open - Whether the menu is showing. Bind it to a flag you keep rather than toggling it from the menu's own value: when the menu closes itself, `open` is written back to `false` before the button's click reaches your handler, so `menu.open = !menu.open` gets out of step. Reset the flag on `close`.
85
85
  * @availableWhen open softDismiss==true
86
86
  * @fires wifi-click - When the Wi-Fi button is clicked
87
87
  * @fires audio-click - When the Audio button is clicked
@@ -99,14 +99,14 @@ export type VolumeChangeEvent = CustomEvent<number>;
99
99
  * @fires wifi-options-click - When the Wi-Fi options are clicked
100
100
  * @fires wifi-disconnect-click - When the Wi-Fi disconnect is clicked
101
101
  * @slot battery-status - Custom battery status text (falls back to the built-in charging/battery label).
102
- * @fires {CustomEvent<void>} close - Fired when the menu closed on its own, from a click outside, `Escape`, or another menu opening. `open` is already `false` by the time it arrives.
102
+ * @fires {CustomEvent<void>} close - Fired when the menu closed on its own, from a click outside, `Escape`, or another menu opening. `open` is already `false` by the time it arrives; reset the flag you bind to `open` here, or the next click on the button finds it still set and asks a closed menu to close.
103
103
  * @stable
104
104
  */
105
105
  export declare class ObcSystemMenu extends LitElement {
106
- /** Let the browser close this menu on its own: on a click outside it, on `Escape`, or when another menu opens. Leave it off to keep showing and hiding the menu yourself. */
106
+ /** Lets the menu close itself on a click outside it, on `Escape`, or when another menu opens. The page is covered while the menu is open, so the closing click reaches nothing there; only the top bar's strip stays clickable, which is how its buttons switch or close menus in one click. Wire it with three bindings: set `softDismiss`, bind `open` to a flag you keep, and reset that flag on `close`. The button that opens the menu needs no wiring of its own: flip the flag from its click, or from the button event of `obc-top-bar` or `obc-integration-bar`. The user menu's *Soft Dismiss From A Bar Button* story shows that wiring, the brilliance menu's *Soft Dismiss* story the one for a button you hold yourself (`bindPopoverTrigger`), and the vue demo runs its top bar this way. Leave it off to show and hide the menu yourself. */
107
107
  softDismiss: boolean;
108
108
  /**
109
- * Whether the menu is showing.
109
+ * Whether the menu is showing. Bind it to a flag you keep rather than toggling it from the menu's own value: when the menu closes itself, `open` is written back to `false` before the button's click reaches your handler, so `menu.open = !menu.open` gets out of step. Reset the flag on `close`.
110
110
  *
111
111
  * Available when `softDismiss==true`.
112
112
  */