@ecopages/radiant-ui 0.1.0-beta.1 → 0.1.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/ui/menu-button/index.js +1 -1
- package/dist/components/ui/menu-button/index.js.map +4 -4
- package/dist/components/ui/menu-button/menu-button.css +6 -1
- package/dist/components/ui/menu-button/menu-button.script.d.ts +7 -7
- package/dist/components/ui/menubar/index.js +1 -1
- package/dist/components/ui/menubar/index.js.map +2 -2
- package/dist/components/ui/menubar/menubar.css +1 -1
- package/dist/components/ui/shared/floating-position.d.ts +37 -0
- package/dist/components/ui/shared/floating-position.test.d.ts +1 -0
- package/dist/components/ui/shared/placement.d.ts +2 -0
- package/dist/components/ui/tooltip/index.js +1 -1
- package/dist/components/ui/tooltip/index.js.map +4 -4
- package/dist/components/ui/tooltip/tooltip.css +6 -1
- package/dist/components/ui/tooltip/tooltip.script.d.ts +6 -6
- package/dist/index.js +1 -1
- package/dist/index.js.map +4 -4
- package/dist/styles/styles.css +9 -3
- package/package.json +4 -7
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{RadiantElement as
|
|
1
|
+
import{RadiantElement as O,bound as D,customElement as _,event as v,onEvent as l,onUpdated as L,prop as y,query as R}from"@ecopages/radiant";var A={top:"bottom",bottom:"top",left:"right",right:"left"},S=8;function F(e){let t=e.indexOf("-");if(t===-1)return{side:e,align:"center"};let n=e.slice(0,t),r=e.slice(t+1);return{side:n,align:r==="start"||r==="end"?r:"center"}}function h(e){return e==="top"||e==="bottom"?"y":"x"}function g(e,t,n,r,i){let{width:o,height:a}=t,s=0,u=0;switch(n){case"top":u=e.top-a-i;break;case"bottom":u=e.bottom+i;break;case"left":s=e.left-o-i;break;case"right":s=e.right+i;break}return h(n)==="y"?r==="start"?s=e.left:r==="end"?s=e.right-o:s=e.left+(e.width-o)/2:r==="start"?u=e.top:r==="end"?u=e.bottom-a:u=e.top+(e.height-a)/2,{x:s,y:u}}function C(e,t,n,r){let{padding:i,width:o,height:a}=r;return n==="top"?Math.max(0,i-e.y):n==="bottom"?Math.max(0,e.y+t.height-(a-i)):n==="left"?Math.max(0,i-e.x):Math.max(0,e.x+t.width-(o-i))}function f(e,t,n,r){let{padding:i,width:o,height:a}=r;return n==="top"?e.top-i-t.height:n==="bottom"?a-i-t.height-e.bottom:n==="left"?e.left-i-t.width:o-i-t.width-e.right}function I(e,t,n,r){let{padding:i,width:o,height:a}=r;if(h(n)==="y"){let u=Math.max(i,o-i-t.width);return{x:Math.min(Math.max(e.x,i),u),y:e.y}}let s=Math.max(i,a-i-t.height);return{x:e.x,y:Math.min(Math.max(e.y,i),s)}}function P(e,t,n,r,i={width:typeof window>"u"?0:window.innerWidth,height:typeof window>"u"?0:window.innerHeight,padding:S}){let{side:o,align:a}=F(n),s=g(e,t,o,a,r),u=C(s,t,o,i),m=o,c=s;if(u>0){let d=A[o],x=g(e,t,d,a,r),M=f(e,t,o,i);f(e,t,d,i)>M&&(m=d,c=x)}return I(c,t,m,i)}function k(e,t,n,r){let i=P(e.getBoundingClientRect(),{width:t.offsetWidth,height:t.offsetHeight},n,r);Object.assign(t.style,{position:"fixed",left:`${i.x}px`,top:`${i.y}px`,right:"auto",bottom:"auto",visibility:"visible"})}function b({anchor:e,floating:t,getPlacement:n,gap:r}){let i=()=>{t.hidden||k(e,t,n(),r)};i(),window.addEventListener("resize",i),window.addEventListener("scroll",i,!0);let o=new ResizeObserver(i);return o.observe(e),o.observe(t),()=>{window.removeEventListener("resize",i),window.removeEventListener("scroll",i,!0),o.disconnect()}}var V=6,p=@_("rui-menu-button")class extends O{@y({type:Boolean,reflect:!0,defaultValue:!1})open;@y({type:String,defaultValue:"bottom-start"})placement;@R({ref:"trigger"})triggerTarget;@R({ref:"menu"})menuTarget;@v({name:"rui-change",bubbles:!0,composed:!0})changeEvent;@v({name:"rui-close",bubbles:!0,composed:!0})closeEvent;menuId=`rui-menu-${Math.random().toString(36).slice(2,9)}`;cleanupFloating=null;pendingFocus=null;connectedCallback(){super.connectedCallback(),queueMicrotask(()=>this.syncOpenState())}disconnectedCallback(){this.teardownFloating(),super.disconnectedCallback()}getItems(){return Array.from(this.menuTarget?.querySelectorAll('[role="menuitem"]')??[]).filter(t=>t.getAttribute("aria-disabled")!=="true")}teardownFloating(){this.cleanupFloating?.(),this.cleanupFloating=null}attachFloating(){!this.triggerTarget||!this.menuTarget||(this.teardownFloating(),this.cleanupFloating=b({anchor:this.triggerTarget,floating:this.menuTarget,getPlacement:()=>this.placement,gap:V}))}@D@L(["open","placement"])syncOpenState(){if(!this.triggerTarget||!this.menuTarget)return;this.menuTarget.id=this.menuId,this.triggerTarget.setAttribute("aria-controls",this.menuId),this.triggerTarget.setAttribute("aria-haspopup","menu"),this.triggerTarget.setAttribute("aria-expanded",String(this.open)),this.menuTarget.hidden=!this.open,this.open?this.attachFloating():this.teardownFloating();let t=this.pendingFocus;if(this.pendingFocus=null,!!t){if(t==="first"&&this.getItems()[0]?.focus(),t==="last"){let n=this.getItems();n[n.length-1]?.focus()}t==="trigger"&&this.triggerTarget?.focus()}}setOpen(t,n=null){let r=this.open;this.pendingFocus=n,this.open=t,r&&!t&&this.closeEvent.emit(),r===t&&this.syncOpenState()}@l({document:!0,type:"click"})onDocumentClick(t){if(!this.open)return;let n=t.target;this.triggerTarget?.contains(n)||this.menuTarget?.contains(n)||this.setOpen(!1)}@l({ref:"trigger",type:"click"})onTriggerClick(){this.setOpen(!this.open,this.open?null:"first")}@l({ref:"trigger",type:"keydown"})onTriggerKeydown(t){switch(t.key){case"ArrowDown":case"Enter":case" ":t.preventDefault(),this.setOpen(!0,"first");break;case"ArrowUp":t.preventDefault(),this.setOpen(!0,"last");break;default:break}}@l({ref:"menu",type:"keydown"})onMenuKeydown(t){let n=this.getItems(),r=document.activeElement,i=r?n.indexOf(r):-1;switch(t.key){case"ArrowDown":{t.preventDefault(),n[(i+1)%n.length]?.focus();break}case"ArrowUp":{t.preventDefault(),n[(i-1+n.length)%n.length]?.focus();break}case"Home":t.preventDefault(),n[0]?.focus();break;case"End":t.preventDefault(),n[n.length-1]?.focus();break;case"Escape":t.preventDefault(),this.setOpen(!1,"trigger");break;case"Tab":this.setOpen(!1);break;case"Enter":case" ":r&&n.includes(r)&&(t.preventDefault(),this.activateItem(r));break;default:break}}@l({selector:'[role="menuitem"]',type:"click"})onItemClick(t){let n=t.target.closest('[role="menuitem"]');!n||!this.contains(n)||this.activateItem(n)}activateItem(t){let n=t.getAttribute("data-value")||t.textContent?.trim()||"";this.changeEvent.emit({value:n}),this.setOpen(!1,"trigger")}render(){return React.createElement("div",{class:"rui-menu-button"},React.createElement("button",{type:"button","data-ref":"trigger",class:"rui-button rui-button--primary rui-button--md rui-menu-button__trigger","aria-haspopup":"menu","aria-expanded":"false"},React.createElement("slot",{name:"trigger"}),React.createElement("span",{class:"rui-menu-button__chevron","aria-hidden":"true"})),React.createElement("div",{"data-ref":"menu",class:"rui-menu-button__menu rui-floating",role:"menu",hidden:!0},React.createElement("slot",null)))}};var B=Symbol.for("@ecopages/storybook-radiant.viewElement"),T=Symbol.for("@ecopages/storybook-radiant.scriptModule"),E=Symbol.for("@ecopages/storybook-radiant.scriptExport");function H(e,t){e[B]=t;let n=t[T];n&&(e[T]=n,e[E]=t[E]??t.name)}function w(e,t){let n=(r=>t(r));return H(n,e),n}import"./menu-button.css";var N=w(p,({slot:e,open:t,placement:n,trigger:r,items:i})=>React.createElement("rui-menu-button",{slot:e,open:t,placement:n},React.createElement("span",{slot:"trigger"},r),i.map(o=>React.createElement("button",{type:"button",class:"rui-menu-button__item",role:"menuitem","data-value":o.value,"aria-disabled":o.disabled?"true":void 0,tabindex:-1},o.label))));export{N as RuiMenuButton,p as RuiMenuButtonElement};
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["../../../../src/components/ui/menu-button/menu-button.script.tsx", "../../../../src/lib/radiant-view.ts", "../../../../src/components/ui/menu-button/menu-button.tsx"],
|
|
4
|
-
"sourcesContent": ["import { RadiantElement, bound, customElement, event, onEvent, onUpdated, prop, query } from '@ecopages/radiant';\nimport type { EventEmitter } from '@ecopages/radiant/tools/event-emitter';\nimport { type Placement, autoUpdate, computePosition, flip, offset, shift } from '@floating-ui/dom';\n\nexport type RuiMenuButtonProps = {\n\t/** Whether the menu starts open. Default: `false`. */\n\topen?: boolean;\n\t/** Floating-ui placement for the menu. Default: `bottom-start`. */\n\tplacement?: Placement;\n};\n\nexport type RuiMenuButtonSelectDetail = {\n\tvalue: string;\n};\n\n/**\n * `<rui-menu-button>` \u2014 a button that opens a menu of actions.\n *\n * Implements the WAI-ARIA APG Menu Button pattern together with the Menu\n * keyboard model: the trigger exposes `aria-haspopup=\"menu\"` and\n * `aria-expanded`, and the popup has `role=\"menu\"` with `role=\"menuitem\"`\n * children.\n *\n * @see https://www.w3.org/WAI/ARIA/apg/patterns/menu-button/\n * @see https://www.w3.org/WAI/ARIA/apg/patterns/menubar/\n *\n * Keyboard interaction (trigger):\n * - `Enter` / `Space` / `ArrowDown`: open and focus the first item\n * - `ArrowUp`: open and focus the last item\n *\n * Keyboard interaction (open menu):\n * - `ArrowDown` / `ArrowUp`: move between items (wraps)\n * - `Home` / `End`: first / last item\n * - `Enter` / `Space`: activate the focused item and close\n * - `Escape`: close and return focus to the trigger\n *\n * @element rui-menu-button\n * @slot trigger - Label for the menu button.\n * @slot - Menu items (`role=\"menuitem\"`), typically buttons or anchors.\n * @fires rui-change - Emitted when a menu item is activated; `detail.value` is the item's `data-value` or text.\n * @fires rui-close - Emitted when the menu closes.\n */\n@customElement('rui-menu-button')\nexport class RuiMenuButton extends RadiantElement {\n\t@prop({ type: Boolean, reflect: true, defaultValue: false }) open: boolean;\n\t@prop({ type: String, defaultValue: 'bottom-start' }) placement: Placement;\n\n\t@query({ ref: 'trigger' }) triggerTarget: HTMLButtonElement;\n\t@query({ ref: 'menu' }) menuTarget: HTMLElement;\n\n\t@event({ name: 'rui-change', bubbles: true, composed: true })\n\tchangeEvent: EventEmitter<RuiMenuButtonSelectDetail>;\n\n\t@event({ name: 'rui-close', bubbles: true, composed: true })\n\tcloseEvent: EventEmitter<void>;\n\n\tprivate cleanup: ReturnType<typeof autoUpdate> | null = null;\n\tprivate menuId = `rui-menu-${Math.random().toString(36).slice(2, 9)}`;\n\n\toverride connectedCallback(): void {\n\t\tsuper.connectedCallback();\n\t\tqueueMicrotask(() => this.syncOpenState());\n\t}\n\n\toverride disconnectedCallback(): void {\n\t\tthis.teardownFloating();\n\t\tsuper.disconnectedCallback();\n\t}\n\n\tprivate getItems(): HTMLElement[] {\n\t\treturn Array.from(this.menuTarget?.querySelectorAll<HTMLElement>('[role=\"menuitem\"]') ?? []).filter(\n\t\t\t(item) => item.getAttribute('aria-disabled') !== 'true',\n\t\t);\n\t}\n\n\tprivate teardownFloating(): void {\n\t\tthis.cleanup?.();\n\t\tthis.cleanup = null;\n\t}\n\n\t@bound\n\tupdatePosition(): void {\n\t\tif (!this.triggerTarget || !this.menuTarget || !this.open) return;\n\t\tcomputePosition(this.triggerTarget, this.menuTarget, {\n\t\t\tplacement: this.placement,\n\t\t\tmiddleware: [offset(6), flip(), shift({ padding: 8 })],\n\t\t}).then(({ x, y }) => {\n\t\t\tObject.assign(this.menuTarget.style, {\n\t\t\t\tleft: `${x}px`,\n\t\t\t\ttop: `${y}px`,\n\t\t\t});\n\t\t});\n\t}\n\n\tprivate pendingFocus: 'first' | 'last' | 'trigger' | null = null;\n\n\t@bound\n\t@onUpdated(['open', 'placement'])\n\tsyncOpenState(): void {\n\t\tif (!this.triggerTarget || !this.menuTarget) return;\n\n\t\tthis.menuTarget.id = this.menuId;\n\t\tthis.triggerTarget.setAttribute('aria-controls', this.menuId);\n\t\tthis.triggerTarget.setAttribute('aria-haspopup', 'menu');\n\t\tthis.triggerTarget.setAttribute('aria-expanded', String(this.open));\n\t\tthis.menuTarget.hidden = !this.open;\n\n\t\tif (this.open) {\n\t\t\tthis.teardownFloating();\n\t\t\tthis.cleanup = autoUpdate(this.triggerTarget, this.menuTarget, this.updatePosition);\n\t\t\tthis.updatePosition();\n\t\t} else {\n\t\t\tthis.teardownFloating();\n\t\t}\n\n\t\tconst focus = this.pendingFocus;\n\t\tthis.pendingFocus = null;\n\t\tif (!focus) return;\n\n\t\tif (focus === 'first') this.getItems()[0]?.focus();\n\t\tif (focus === 'last') {\n\t\t\tconst items = this.getItems();\n\t\t\titems[items.length - 1]?.focus();\n\t\t}\n\t\tif (focus === 'trigger') this.triggerTarget?.focus();\n\t}\n\n\tprivate setOpen(next: boolean, focus: 'first' | 'last' | 'trigger' | null = null): void {\n\t\tconst wasOpen = this.open;\n\t\tthis.pendingFocus = focus;\n\t\tthis.open = next;\n\t\tif (wasOpen && !next) this.closeEvent.emit();\n\t\tif (wasOpen === next) this.syncOpenState();\n\t}\n\n\t@onEvent({ document: true, type: 'click' })\n\tonDocumentClick(event: MouseEvent): void {\n\t\tif (!this.open) return;\n\t\tconst target = event.target as Node;\n\t\tif (this.triggerTarget?.contains(target) || this.menuTarget?.contains(target)) return;\n\t\tthis.setOpen(false);\n\t}\n\n\t@onEvent({ ref: 'trigger', type: 'click' })\n\tonTriggerClick(): void {\n\t\tthis.setOpen(!this.open, this.open ? null : 'first');\n\t}\n\n\t@onEvent({ ref: 'trigger', type: 'keydown' })\n\tonTriggerKeydown(event: KeyboardEvent): void {\n\t\tswitch (event.key) {\n\t\t\tcase 'ArrowDown':\n\t\t\tcase 'Enter':\n\t\t\tcase ' ':\n\t\t\t\tevent.preventDefault();\n\t\t\t\tthis.setOpen(true, 'first');\n\t\t\t\tbreak;\n\t\t\tcase 'ArrowUp':\n\t\t\t\tevent.preventDefault();\n\t\t\t\tthis.setOpen(true, 'last');\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\t@onEvent({ ref: 'menu', type: 'keydown' })\n\tonMenuKeydown(event: KeyboardEvent): void {\n\t\tconst items = this.getItems();\n\t\tconst current = document.activeElement as HTMLElement | null;\n\t\tconst index = current ? items.indexOf(current) : -1;\n\n\t\tswitch (event.key) {\n\t\t\tcase 'ArrowDown': {\n\t\t\t\tevent.preventDefault();\n\t\t\t\tconst next = items[(index + 1) % items.length];\n\t\t\t\tnext?.focus();\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 'ArrowUp': {\n\t\t\t\tevent.preventDefault();\n\t\t\t\tconst prev = items[(index - 1 + items.length) % items.length];\n\t\t\t\tprev?.focus();\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 'Home':\n\t\t\t\tevent.preventDefault();\n\t\t\t\titems[0]?.focus();\n\t\t\t\tbreak;\n\t\t\tcase 'End':\n\t\t\t\tevent.preventDefault();\n\t\t\t\titems[items.length - 1]?.focus();\n\t\t\t\tbreak;\n\t\t\tcase 'Escape':\n\t\t\t\tevent.preventDefault();\n\t\t\t\tthis.setOpen(false, 'trigger');\n\t\t\t\tbreak;\n\t\t\tcase 'Tab':\n\t\t\t\tthis.setOpen(false);\n\t\t\t\tbreak;\n\t\t\tcase 'Enter':\n\t\t\tcase ' ':\n\t\t\t\tif (current && items.includes(current)) {\n\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\tthis.activateItem(current);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\t@onEvent({ selector: '[role=\"menuitem\"]', type: 'click' })\n\tonItemClick(event: Event): void {\n\t\tconst item = (event.target as HTMLElement).closest('[role=\"menuitem\"]') as HTMLElement | null;\n\t\tif (!item || !this.contains(item)) return;\n\t\tthis.activateItem(item);\n\t}\n\n\tprivate activateItem(item: HTMLElement): void {\n\t\tconst value = item.getAttribute('data-value') || item.textContent?.trim() || '';\n\t\tthis.changeEvent.emit({ value });\n\t\tthis.setOpen(false, 'trigger');\n\t}\n\n\toverride render() {\n\t\treturn (\n\t\t\t<div class=\"rui-menu-button\">\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tdata-ref=\"trigger\"\n\t\t\t\t\tclass=\"rui-button rui-button--primary rui-button--md rui-menu-button__trigger\"\n\t\t\t\t\taria-haspopup=\"menu\"\n\t\t\t\t\taria-expanded=\"false\"\n\t\t\t\t>\n\t\t\t\t\t<slot name=\"trigger\"></slot>\n\t\t\t\t\t<span class=\"rui-menu-button__chevron\" aria-hidden=\"true\"></span>\n\t\t\t\t</button>\n\t\t\t\t<div data-ref=\"menu\" class=\"rui-menu-button__menu\" role=\"menu\" hidden>\n\t\t\t\t\t<slot></slot>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t);\n\t}\n}\n", "import type { JsxRenderable } from '@ecopages/jsx';\n\nconst RADIANT_VIEW_ELEMENT = Symbol.for('@ecopages/storybook-radiant.viewElement');\nconst RADIANT_SCRIPT_MODULE = Symbol.for('@ecopages/storybook-radiant.scriptModule');\nconst RADIANT_SCRIPT_EXPORT = Symbol.for('@ecopages/storybook-radiant.scriptExport');\n\nexport type RadiantViewComponent<TArgs = unknown> = ((args: TArgs) => JsxRenderable) & {\n\t[RADIANT_VIEW_ELEMENT]?: CustomElementConstructor;\n\t[RADIANT_SCRIPT_MODULE]?: string;\n\t[RADIANT_SCRIPT_EXPORT]?: string;\n};\n\nfunction linkViewToElement<TArgs>(\n\tview: RadiantViewComponent<TArgs>,\n\telement: CustomElementConstructor & {\n\t\t[RADIANT_SCRIPT_MODULE]?: string;\n\t\t[RADIANT_SCRIPT_EXPORT]?: string;\n\t},\n): void {\n\tview[RADIANT_VIEW_ELEMENT] = element;\n\n\tconst ssrModule = element[RADIANT_SCRIPT_MODULE];\n\tif (ssrModule) {\n\t\tview[RADIANT_SCRIPT_MODULE] = ssrModule;\n\t\tview[RADIANT_SCRIPT_EXPORT] = element[RADIANT_SCRIPT_EXPORT] ?? element.name;\n\t}\n}\n\n/** Link a JSX view export to its RadiantElement class (same symbols as the Storybook framework). */\nexport function defineRadiantView<TArgs>(\n\telement: CustomElementConstructor & {\n\t\t[RADIANT_SCRIPT_MODULE]?: string;\n\t\t[RADIANT_SCRIPT_EXPORT]?: string;\n\t},\n\trender: (args: TArgs) => JsxRenderable,\n): RadiantViewComponent<TArgs> {\n\tconst view = ((args: TArgs) => render(args)) as RadiantViewComponent<TArgs>;\n\tlinkViewToElement(view, element);\n\treturn view;\n}\n\n/** Re-copy SSR metadata from the linked element (e.g. after HMR). */\nexport function syncRadiantViewMetadata(view: RadiantViewComponent): void {\n\tconst element = view[RADIANT_VIEW_ELEMENT];\n\tif (element) {\n\t\tlinkViewToElement(view, element);\n\t}\n}\n", "import type { JsxRenderable } from '@ecopages/jsx';\nimport type { RadiantSlotProps } from '../../../types';\nimport { defineRadiantView } from '../../../lib/radiant-view';\nimport type { RuiMenuButtonProps } from './menu-button.script';\nimport { RuiMenuButton as RuiMenuButtonElement } from './menu-button.script';\nimport './menu-button.css';\n\nexport type RuiMenuItem = {\n\tvalue: string;\n\tlabel: JsxRenderable;\n\tdisabled?: boolean;\n};\n\nexport const RuiMenuButton = defineRadiantView(\n\tRuiMenuButtonElement,\n\t({\n\t\tslot,\n\t\topen,\n\t\tplacement,\n\t\ttrigger,\n\t\titems,\n\t}: RuiMenuButtonProps &\n\t\tRadiantSlotProps & {\n\t\t\ttrigger: JsxRenderable;\n\t\t\titems: RuiMenuItem[];\n\t\t}) => (\n\t\t<rui-menu-button slot={slot} open={open} placement={placement}>\n\t\t\t<span slot=\"trigger\">{trigger}</span>\n\t\t\t{items.map((item) => (\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclass=\"rui-menu-button__item\"\n\t\t\t\t\trole=\"menuitem\"\n\t\t\t\t\tdata-value={item.value}\n\t\t\t\t\taria-disabled={item.disabled ? 'true' : undefined}\n\t\t\t\t\ttabindex={-1}\n\t\t\t\t>\n\t\t\t\t\t{item.label}\n\t\t\t\t</button>\n\t\t\t))}\n\t\t</rui-menu-button>\n\t),\n);\n"],
|
|
5
|
-
"mappings": "AAAA,OAAS,kBAAAA,EAAgB,SAAAC,EAAO,iBAAAC,EAAe,SAAAC,EAAO,WAAAC,EAAS,aAAAC,EAAW,QAAAC,EAAM,SAAAC,MAAa,
|
|
6
|
-
"names": ["RadiantElement", "bound", "customElement", "event", "onEvent", "onUpdated", "prop", "query", "
|
|
3
|
+
"sources": ["../../../../src/components/ui/menu-button/menu-button.script.tsx", "../../../../src/components/ui/shared/floating-position.ts", "../../../../src/lib/radiant-view.ts", "../../../../src/components/ui/menu-button/menu-button.tsx"],
|
|
4
|
+
"sourcesContent": ["import { RadiantElement, bound, customElement, event, onEvent, onUpdated, prop, query } from '@ecopages/radiant';\nimport type { EventEmitter } from '@ecopages/radiant/tools/event-emitter';\nimport { attachFloating } from '../shared/floating-position';\nimport type { RuiPlacement } from '../shared/placement';\n\nexport type RuiMenuButtonProps = {\n\t/** Whether the menu starts open. Default: `false`. */\n\topen?: boolean;\n\t/** Placement of the menu surface relative to its trigger. Default: `bottom-start`. */\n\tplacement?: RuiPlacement;\n};\n\nexport type RuiMenuButtonSelectDetail = {\n\tvalue: string;\n};\n\nconst MENU_GAP = 6;\n\n/**\n * `<rui-menu-button>` \u2014 a button that opens a menu of actions.\n *\n * Implements the WAI-ARIA APG Menu Button pattern together with the Menu\n * keyboard model: the trigger exposes `aria-haspopup=\"menu\"` and\n * `aria-expanded`, and the popup has `role=\"menu\"` with `role=\"menuitem\"`\n * children.\n *\n * @see https://www.w3.org/WAI/ARIA/apg/patterns/menu-button/\n * @see https://www.w3.org/WAI/ARIA/apg/patterns/menubar/\n *\n * Keyboard interaction (trigger):\n * - `Enter` / `Space` / `ArrowDown`: open and focus the first item\n * - `ArrowUp`: open and focus the last item\n *\n * Keyboard interaction (open menu):\n * - `ArrowDown` / `ArrowUp`: move between items (wraps)\n * - `Home` / `End`: first / last item\n * - `Enter` / `Space`: activate the focused item and close\n * - `Escape`: close and return focus to the trigger\n *\n * @element rui-menu-button\n * @slot trigger - Label for the menu button.\n * @slot - Menu items (`role=\"menuitem\"`), typically buttons or anchors.\n * @fires rui-change - Emitted when a menu item is activated; `detail.value` is the item's `data-value` or text.\n * @fires rui-close - Emitted when the menu closes.\n */\n@customElement('rui-menu-button')\nexport class RuiMenuButton extends RadiantElement {\n\t@prop({ type: Boolean, reflect: true, defaultValue: false }) open: boolean;\n\t@prop({ type: String, defaultValue: 'bottom-start' }) placement: RuiPlacement;\n\n\t@query({ ref: 'trigger' }) triggerTarget: HTMLButtonElement;\n\t@query({ ref: 'menu' }) menuTarget: HTMLElement;\n\n\t@event({ name: 'rui-change', bubbles: true, composed: true })\n\tchangeEvent: EventEmitter<RuiMenuButtonSelectDetail>;\n\n\t@event({ name: 'rui-close', bubbles: true, composed: true })\n\tcloseEvent: EventEmitter<void>;\n\n\tprivate menuId = `rui-menu-${Math.random().toString(36).slice(2, 9)}`;\n\tprivate cleanupFloating: (() => void) | null = null;\n\tprivate pendingFocus: 'first' | 'last' | 'trigger' | null = null;\n\n\toverride connectedCallback(): void {\n\t\tsuper.connectedCallback();\n\t\tqueueMicrotask(() => this.syncOpenState());\n\t}\n\n\toverride disconnectedCallback(): void {\n\t\tthis.teardownFloating();\n\t\tsuper.disconnectedCallback();\n\t}\n\n\tprivate getItems(): HTMLElement[] {\n\t\treturn Array.from(this.menuTarget?.querySelectorAll<HTMLElement>('[role=\"menuitem\"]') ?? []).filter(\n\t\t\t(item) => item.getAttribute('aria-disabled') !== 'true',\n\t\t);\n\t}\n\n\tprivate teardownFloating(): void {\n\t\tthis.cleanupFloating?.();\n\t\tthis.cleanupFloating = null;\n\t}\n\n\tprivate attachFloating(): void {\n\t\tif (!this.triggerTarget || !this.menuTarget) return;\n\t\tthis.teardownFloating();\n\t\tthis.cleanupFloating = attachFloating({\n\t\t\tanchor: this.triggerTarget,\n\t\t\tfloating: this.menuTarget,\n\t\t\tgetPlacement: () => this.placement,\n\t\t\tgap: MENU_GAP,\n\t\t});\n\t}\n\n\t@bound\n\t@onUpdated(['open', 'placement'])\n\tsyncOpenState(): void {\n\t\tif (!this.triggerTarget || !this.menuTarget) return;\n\n\t\tthis.menuTarget.id = this.menuId;\n\t\tthis.triggerTarget.setAttribute('aria-controls', this.menuId);\n\t\tthis.triggerTarget.setAttribute('aria-haspopup', 'menu');\n\t\tthis.triggerTarget.setAttribute('aria-expanded', String(this.open));\n\t\tthis.menuTarget.hidden = !this.open;\n\n\t\tif (this.open) this.attachFloating();\n\t\telse this.teardownFloating();\n\n\t\tconst focus = this.pendingFocus;\n\t\tthis.pendingFocus = null;\n\t\tif (!focus) return;\n\n\t\tif (focus === 'first') this.getItems()[0]?.focus();\n\t\tif (focus === 'last') {\n\t\t\tconst items = this.getItems();\n\t\t\titems[items.length - 1]?.focus();\n\t\t}\n\t\tif (focus === 'trigger') this.triggerTarget?.focus();\n\t}\n\n\tprivate setOpen(next: boolean, focus: 'first' | 'last' | 'trigger' | null = null): void {\n\t\tconst wasOpen = this.open;\n\t\tthis.pendingFocus = focus;\n\t\tthis.open = next;\n\t\tif (wasOpen && !next) this.closeEvent.emit();\n\t\tif (wasOpen === next) this.syncOpenState();\n\t}\n\n\t@onEvent({ document: true, type: 'click' })\n\tonDocumentClick(event: MouseEvent): void {\n\t\tif (!this.open) return;\n\t\tconst target = event.target as Node;\n\t\tif (this.triggerTarget?.contains(target) || this.menuTarget?.contains(target)) return;\n\t\tthis.setOpen(false);\n\t}\n\n\t@onEvent({ ref: 'trigger', type: 'click' })\n\tonTriggerClick(): void {\n\t\tthis.setOpen(!this.open, this.open ? null : 'first');\n\t}\n\n\t@onEvent({ ref: 'trigger', type: 'keydown' })\n\tonTriggerKeydown(event: KeyboardEvent): void {\n\t\tswitch (event.key) {\n\t\t\tcase 'ArrowDown':\n\t\t\tcase 'Enter':\n\t\t\tcase ' ':\n\t\t\t\tevent.preventDefault();\n\t\t\t\tthis.setOpen(true, 'first');\n\t\t\t\tbreak;\n\t\t\tcase 'ArrowUp':\n\t\t\t\tevent.preventDefault();\n\t\t\t\tthis.setOpen(true, 'last');\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\t@onEvent({ ref: 'menu', type: 'keydown' })\n\tonMenuKeydown(event: KeyboardEvent): void {\n\t\tconst items = this.getItems();\n\t\tconst current = document.activeElement as HTMLElement | null;\n\t\tconst index = current ? items.indexOf(current) : -1;\n\n\t\tswitch (event.key) {\n\t\t\tcase 'ArrowDown': {\n\t\t\t\tevent.preventDefault();\n\t\t\t\tconst next = items[(index + 1) % items.length];\n\t\t\t\tnext?.focus();\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 'ArrowUp': {\n\t\t\t\tevent.preventDefault();\n\t\t\t\tconst prev = items[(index - 1 + items.length) % items.length];\n\t\t\t\tprev?.focus();\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 'Home':\n\t\t\t\tevent.preventDefault();\n\t\t\t\titems[0]?.focus();\n\t\t\t\tbreak;\n\t\t\tcase 'End':\n\t\t\t\tevent.preventDefault();\n\t\t\t\titems[items.length - 1]?.focus();\n\t\t\t\tbreak;\n\t\t\tcase 'Escape':\n\t\t\t\tevent.preventDefault();\n\t\t\t\tthis.setOpen(false, 'trigger');\n\t\t\t\tbreak;\n\t\t\tcase 'Tab':\n\t\t\t\tthis.setOpen(false);\n\t\t\t\tbreak;\n\t\t\tcase 'Enter':\n\t\t\tcase ' ':\n\t\t\t\tif (current && items.includes(current)) {\n\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\tthis.activateItem(current);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\t@onEvent({ selector: '[role=\"menuitem\"]', type: 'click' })\n\tonItemClick(event: Event): void {\n\t\tconst item = (event.target as HTMLElement).closest('[role=\"menuitem\"]') as HTMLElement | null;\n\t\tif (!item || !this.contains(item)) return;\n\t\tthis.activateItem(item);\n\t}\n\n\tprivate activateItem(item: HTMLElement): void {\n\t\tconst value = item.getAttribute('data-value') || item.textContent?.trim() || '';\n\t\tthis.changeEvent.emit({ value });\n\t\tthis.setOpen(false, 'trigger');\n\t}\n\n\toverride render() {\n\t\treturn (\n\t\t\t<div class=\"rui-menu-button\">\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tdata-ref=\"trigger\"\n\t\t\t\t\tclass=\"rui-button rui-button--primary rui-button--md rui-menu-button__trigger\"\n\t\t\t\t\taria-haspopup=\"menu\"\n\t\t\t\t\taria-expanded=\"false\"\n\t\t\t\t>\n\t\t\t\t\t<slot name=\"trigger\"></slot>\n\t\t\t\t\t<span class=\"rui-menu-button__chevron\" aria-hidden=\"true\"></span>\n\t\t\t\t</button>\n\t\t\t\t<div data-ref=\"menu\" class=\"rui-menu-button__menu rui-floating\" role=\"menu\" hidden>\n\t\t\t\t\t<slot></slot>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t);\n\t}\n}\n", "import type { RuiPlacement } from './placement';\n\ntype Side = 'top' | 'right' | 'bottom' | 'left';\ntype Align = 'start' | 'center' | 'end';\n\nexport type FloatingSize = { width: number; height: number };\nexport type FloatingCoords = { x: number; y: number };\nexport type FloatingViewport = { width: number; height: number; padding: number };\n\nconst OPPOSITE: Record<Side, Side> = {\n\ttop: 'bottom',\n\tbottom: 'top',\n\tleft: 'right',\n\tright: 'left',\n};\n\nconst DEFAULT_VIEWPORT_PADDING = 8;\n\nfunction parsePlacement(placement: RuiPlacement): { side: Side; align: Align } {\n\tconst dash = placement.indexOf('-');\n\tif (dash === -1) return { side: placement as Side, align: 'center' };\n\tconst side = placement.slice(0, dash) as Side;\n\tconst align = placement.slice(dash + 1);\n\treturn { side, align: align === 'start' || align === 'end' ? align : 'center' };\n}\n\nfunction primaryAxis(side: Side): 'x' | 'y' {\n\treturn side === 'top' || side === 'bottom' ? 'y' : 'x';\n}\n\nfunction coordsFor(anchor: DOMRect, size: FloatingSize, side: Side, align: Align, gap: number): FloatingCoords {\n\tconst { width, height } = size;\n\tlet x = 0;\n\tlet y = 0;\n\n\tswitch (side) {\n\t\tcase 'top':\n\t\t\ty = anchor.top - height - gap;\n\t\t\tbreak;\n\t\tcase 'bottom':\n\t\t\ty = anchor.bottom + gap;\n\t\t\tbreak;\n\t\tcase 'left':\n\t\t\tx = anchor.left - width - gap;\n\t\t\tbreak;\n\t\tcase 'right':\n\t\t\tx = anchor.right + gap;\n\t\t\tbreak;\n\t}\n\n\tif (primaryAxis(side) === 'y') {\n\t\tif (align === 'start') x = anchor.left;\n\t\telse if (align === 'end') x = anchor.right - width;\n\t\telse x = anchor.left + (anchor.width - width) / 2;\n\t} else {\n\t\tif (align === 'start') y = anchor.top;\n\t\telse if (align === 'end') y = anchor.bottom - height;\n\t\telse y = anchor.top + (anchor.height - height) / 2;\n\t}\n\n\treturn { x, y };\n}\n\nfunction overflowOnPrimary(coords: FloatingCoords, size: FloatingSize, side: Side, viewport: FloatingViewport): number {\n\tconst { padding, width: vw, height: vh } = viewport;\n\tif (side === 'top') return Math.max(0, padding - coords.y);\n\tif (side === 'bottom') return Math.max(0, coords.y + size.height - (vh - padding));\n\tif (side === 'left') return Math.max(0, padding - coords.x);\n\treturn Math.max(0, coords.x + size.width - (vw - padding));\n}\n\nfunction freeSpaceOnSide(anchor: DOMRect, size: FloatingSize, side: Side, viewport: FloatingViewport): number {\n\tconst { padding, width: vw, height: vh } = viewport;\n\tif (side === 'top') return anchor.top - padding - size.height;\n\tif (side === 'bottom') return vh - padding - size.height - anchor.bottom;\n\tif (side === 'left') return anchor.left - padding - size.width;\n\treturn vw - padding - size.width - anchor.right;\n}\n\nfunction clampCrossAxis(\n\tcoords: FloatingCoords,\n\tsize: FloatingSize,\n\tside: Side,\n\tviewport: FloatingViewport,\n): FloatingCoords {\n\tconst { padding, width: vw, height: vh } = viewport;\n\tif (primaryAxis(side) === 'y') {\n\t\tconst maxX = Math.max(padding, vw - padding - size.width);\n\t\treturn { x: Math.min(Math.max(coords.x, padding), maxX), y: coords.y };\n\t}\n\tconst maxY = Math.max(padding, vh - padding - size.height);\n\treturn { x: coords.x, y: Math.min(Math.max(coords.y, padding), maxY) };\n}\n\n/**\n * Computes fixed-position coords for a floating surface.\n *\n * @remarks\n * Prefers `placement`, then flips on the primary axis when the opposite side\n * has more free space. Clamps only the cross-axis so the floating surface\n * never slides onto its anchor.\n */\nexport function computeFloatingCoords(\n\tanchor: DOMRect,\n\tsize: FloatingSize,\n\tplacement: RuiPlacement,\n\tgap: number,\n\tviewport: FloatingViewport = {\n\t\twidth: typeof window === 'undefined' ? 0 : window.innerWidth,\n\t\theight: typeof window === 'undefined' ? 0 : window.innerHeight,\n\t\tpadding: DEFAULT_VIEWPORT_PADDING,\n\t},\n): FloatingCoords {\n\tconst { side: preferred, align } = parsePlacement(placement);\n\tconst preferredCoords = coordsFor(anchor, size, preferred, align, gap);\n\tconst preferredOverflow = overflowOnPrimary(preferredCoords, size, preferred, viewport);\n\n\tlet side = preferred;\n\tlet coords = preferredCoords;\n\n\tif (preferredOverflow > 0) {\n\t\tconst flipped = OPPOSITE[preferred];\n\t\tconst flippedCoords = coordsFor(anchor, size, flipped, align, gap);\n\t\tconst preferredFree = freeSpaceOnSide(anchor, size, preferred, viewport);\n\t\tconst flippedFree = freeSpaceOnSide(anchor, size, flipped, viewport);\n\t\tif (flippedFree > preferredFree) {\n\t\t\tside = flipped;\n\t\t\tcoords = flippedCoords;\n\t\t}\n\t}\n\n\treturn clampCrossAxis(coords, size, side, viewport);\n}\n\n/** Applies fixed coords to a floating element. */\nexport function applyFloatingPosition(\n\tanchor: HTMLElement,\n\tfloating: HTMLElement,\n\tplacement: RuiPlacement,\n\tgap: number,\n): void {\n\tconst coords = computeFloatingCoords(\n\t\tanchor.getBoundingClientRect(),\n\t\t{ width: floating.offsetWidth, height: floating.offsetHeight },\n\t\tplacement,\n\t\tgap,\n\t);\n\tObject.assign(floating.style, {\n\t\tposition: 'fixed',\n\t\tleft: `${coords.x}px`,\n\t\ttop: `${coords.y}px`,\n\t\tright: 'auto',\n\t\tbottom: 'auto',\n\t\tvisibility: 'visible',\n\t});\n}\n\nexport type AttachFloatingOptions = {\n\tanchor: HTMLElement;\n\tfloating: HTMLElement;\n\tgetPlacement: () => RuiPlacement;\n\tgap: number;\n};\n\n/**\n * Positions on attach and keeps the floating element updated on scroll/resize.\n *\n * @returns Cleanup that removes listeners and observers.\n */\nexport function attachFloating({ anchor, floating, getPlacement, gap }: AttachFloatingOptions): () => void {\n\tconst update = (): void => {\n\t\tif (floating.hidden) return;\n\t\tapplyFloatingPosition(anchor, floating, getPlacement(), gap);\n\t};\n\n\tupdate();\n\n\twindow.addEventListener('resize', update);\n\twindow.addEventListener('scroll', update, true);\n\n\tconst observer = new ResizeObserver(update);\n\tobserver.observe(anchor);\n\tobserver.observe(floating);\n\n\treturn () => {\n\t\twindow.removeEventListener('resize', update);\n\t\twindow.removeEventListener('scroll', update, true);\n\t\tobserver.disconnect();\n\t};\n}\n", "import type { JsxRenderable } from '@ecopages/jsx';\n\nconst RADIANT_VIEW_ELEMENT = Symbol.for('@ecopages/storybook-radiant.viewElement');\nconst RADIANT_SCRIPT_MODULE = Symbol.for('@ecopages/storybook-radiant.scriptModule');\nconst RADIANT_SCRIPT_EXPORT = Symbol.for('@ecopages/storybook-radiant.scriptExport');\n\nexport type RadiantViewComponent<TArgs = unknown> = ((args: TArgs) => JsxRenderable) & {\n\t[RADIANT_VIEW_ELEMENT]?: CustomElementConstructor;\n\t[RADIANT_SCRIPT_MODULE]?: string;\n\t[RADIANT_SCRIPT_EXPORT]?: string;\n};\n\nfunction linkViewToElement<TArgs>(\n\tview: RadiantViewComponent<TArgs>,\n\telement: CustomElementConstructor & {\n\t\t[RADIANT_SCRIPT_MODULE]?: string;\n\t\t[RADIANT_SCRIPT_EXPORT]?: string;\n\t},\n): void {\n\tview[RADIANT_VIEW_ELEMENT] = element;\n\n\tconst ssrModule = element[RADIANT_SCRIPT_MODULE];\n\tif (ssrModule) {\n\t\tview[RADIANT_SCRIPT_MODULE] = ssrModule;\n\t\tview[RADIANT_SCRIPT_EXPORT] = element[RADIANT_SCRIPT_EXPORT] ?? element.name;\n\t}\n}\n\n/** Link a JSX view export to its RadiantElement class (same symbols as the Storybook framework). */\nexport function defineRadiantView<TArgs>(\n\telement: CustomElementConstructor & {\n\t\t[RADIANT_SCRIPT_MODULE]?: string;\n\t\t[RADIANT_SCRIPT_EXPORT]?: string;\n\t},\n\trender: (args: TArgs) => JsxRenderable,\n): RadiantViewComponent<TArgs> {\n\tconst view = ((args: TArgs) => render(args)) as RadiantViewComponent<TArgs>;\n\tlinkViewToElement(view, element);\n\treturn view;\n}\n\n/** Re-copy SSR metadata from the linked element (e.g. after HMR). */\nexport function syncRadiantViewMetadata(view: RadiantViewComponent): void {\n\tconst element = view[RADIANT_VIEW_ELEMENT];\n\tif (element) {\n\t\tlinkViewToElement(view, element);\n\t}\n}\n", "import type { JsxRenderable } from '@ecopages/jsx';\nimport type { RadiantSlotProps } from '../../../types';\nimport { defineRadiantView } from '../../../lib/radiant-view';\nimport type { RuiMenuButtonProps } from './menu-button.script';\nimport { RuiMenuButton as RuiMenuButtonElement } from './menu-button.script';\nimport './menu-button.css';\n\nexport type RuiMenuItem = {\n\tvalue: string;\n\tlabel: JsxRenderable;\n\tdisabled?: boolean;\n};\n\nexport const RuiMenuButton = defineRadiantView(\n\tRuiMenuButtonElement,\n\t({\n\t\tslot,\n\t\topen,\n\t\tplacement,\n\t\ttrigger,\n\t\titems,\n\t}: RuiMenuButtonProps &\n\t\tRadiantSlotProps & {\n\t\t\ttrigger: JsxRenderable;\n\t\t\titems: RuiMenuItem[];\n\t\t}) => (\n\t\t<rui-menu-button slot={slot} open={open} placement={placement}>\n\t\t\t<span slot=\"trigger\">{trigger}</span>\n\t\t\t{items.map((item) => (\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclass=\"rui-menu-button__item\"\n\t\t\t\t\trole=\"menuitem\"\n\t\t\t\t\tdata-value={item.value}\n\t\t\t\t\taria-disabled={item.disabled ? 'true' : undefined}\n\t\t\t\t\ttabindex={-1}\n\t\t\t\t>\n\t\t\t\t\t{item.label}\n\t\t\t\t</button>\n\t\t\t))}\n\t\t</rui-menu-button>\n\t),\n);\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["RadiantElement", "bound", "customElement", "event", "onEvent", "onUpdated", "prop", "query", "OPPOSITE", "DEFAULT_VIEWPORT_PADDING", "parsePlacement", "placement", "dash", "side", "align", "primaryAxis", "coordsFor", "anchor", "size", "gap", "width", "height", "x", "y", "overflowOnPrimary", "coords", "viewport", "padding", "vw", "vh", "freeSpaceOnSide", "clampCrossAxis", "maxX", "maxY", "computeFloatingCoords", "preferred", "preferredCoords", "preferredOverflow", "flipped", "flippedCoords", "preferredFree", "applyFloatingPosition", "floating", "attachFloating", "getPlacement", "update", "observer", "MENU_GAP", "RuiMenuButton", "customElement", "RadiantElement", "prop", "query", "event", "item", "attachFloating", "bound", "onUpdated", "focus", "items", "next", "wasOpen", "onEvent", "target", "current", "index", "value", "RADIANT_VIEW_ELEMENT", "RADIANT_SCRIPT_MODULE", "RADIANT_SCRIPT_EXPORT", "linkViewToElement", "view", "element", "ssrModule", "defineRadiantView", "render", "args", "RuiMenuButton", "defineRadiantView", "slot", "open", "placement", "trigger", "items", "item"]
|
|
7
7
|
}
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */
|
|
2
2
|
@layer properties;
|
|
3
|
+
@layer components {
|
|
4
|
+
.rui-floating {
|
|
5
|
+
position: fixed;
|
|
6
|
+
visibility: hidden;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
3
9
|
rui-menu-button {
|
|
4
10
|
display: inline-flex;
|
|
5
11
|
}
|
|
@@ -23,7 +29,6 @@ rui-menu-button {
|
|
|
23
29
|
height: 0;
|
|
24
30
|
}
|
|
25
31
|
.rui-menu-button__menu {
|
|
26
|
-
position: absolute;
|
|
27
32
|
z-index: var(--z-index-popover, var(--z-popover));
|
|
28
33
|
min-width: calc(var(--spacing, 0.25rem) * 40);
|
|
29
34
|
border-radius: var(--radius-container, var(--radius-container));
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { RadiantElement } from '@ecopages/radiant';
|
|
2
2
|
import type { EventEmitter } from '@ecopages/radiant/tools/event-emitter';
|
|
3
|
-
import {
|
|
3
|
+
import type { RuiPlacement } from '../shared/placement';
|
|
4
4
|
export type RuiMenuButtonProps = {
|
|
5
5
|
/** Whether the menu starts open. Default: `false`. */
|
|
6
6
|
open?: boolean;
|
|
7
|
-
/**
|
|
8
|
-
placement?:
|
|
7
|
+
/** Placement of the menu surface relative to its trigger. Default: `bottom-start`. */
|
|
8
|
+
placement?: RuiPlacement;
|
|
9
9
|
};
|
|
10
10
|
export type RuiMenuButtonSelectDetail = {
|
|
11
11
|
value: string;
|
|
@@ -39,19 +39,19 @@ export type RuiMenuButtonSelectDetail = {
|
|
|
39
39
|
*/
|
|
40
40
|
export declare class RuiMenuButton extends RadiantElement {
|
|
41
41
|
open: boolean;
|
|
42
|
-
placement:
|
|
42
|
+
placement: RuiPlacement;
|
|
43
43
|
triggerTarget: HTMLButtonElement;
|
|
44
44
|
menuTarget: HTMLElement;
|
|
45
45
|
changeEvent: EventEmitter<RuiMenuButtonSelectDetail>;
|
|
46
46
|
closeEvent: EventEmitter<void>;
|
|
47
|
-
private cleanup;
|
|
48
47
|
private menuId;
|
|
48
|
+
private cleanupFloating;
|
|
49
|
+
private pendingFocus;
|
|
49
50
|
connectedCallback(): void;
|
|
50
51
|
disconnectedCallback(): void;
|
|
51
52
|
private getItems;
|
|
52
53
|
private teardownFloating;
|
|
53
|
-
|
|
54
|
-
private pendingFocus;
|
|
54
|
+
private attachFloating;
|
|
55
55
|
syncOpenState(): void;
|
|
56
56
|
private setOpen;
|
|
57
57
|
onDocumentClick(event: MouseEvent): void;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{RadiantElement as R,customElement as E,event as T,onEvent as u,prop as y}from"@ecopages/radiant";function p(r,e){r.forEach((t,n)=>{t.tabIndex=n===e?0:-1})}function M(r,e){if(!r.length)return null;let t=Math.max(0,Math.min(r.length-1,e));return p(r,t),r[t]?.focus(),r[t]??null}function h(r,e){return r==="Home"?"first":r==="End"?"last":e==="horizontal"?r==="ArrowRight"?"next":r==="ArrowLeft"?"prev":null:r==="ArrowDown"?"next":r==="ArrowUp"?"prev":null}function d(r){let{items:e,current:t,key:n,orientation:o="horizontal",wrap:l=!0}=r;if(!e.length||!t)return{handled:!1};let a=e.indexOf(t);if(a<0)return{handled:!1};let s=h(n,o);if(!s)return{handled:!1};let i=a;s==="first"?i=0:s==="last"?i=e.length-1:s==="next"?i=l?(a+1)%e.length:Math.min(e.length-1,a+1):i=l?(a-1+e.length)%e.length:Math.max(0,a-1);let c=M(e,i);return c?{handled:!0,index:i,item:c}:{handled:!1}}var m=@E("rui-menubar")class extends R{@y({type:String,defaultValue:""})label;@T({name:"rui-change",bubbles:!0,composed:!0})changeEvent;resolvedAriaLabel=this.$.label.map(e=>e||void 0);openRoot=null;getTopItems(){return Array.from(this.querySelectorAll('[data-ref="menubar"] > [data-ref="menubar-root"] > [role="menuitem"]'))}getMenuFor(e){return e.closest('[data-ref="menubar-root"]')?.querySelector(':scope > [role="menu"]')??null}getMenuItems(e){return Array.from(e.querySelectorAll(':scope > [role="menuitem"]')).filter(t=>t.getAttribute("aria-disabled")!=="true")}connectedCallback(){super.connectedCallback(),queueMicrotask(()=>p(this.getTopItems(),0))}closeOpenMenu(e=!1){if(!this.openRoot)return;let t=this.openRoot.querySelector(':scope > [role="menuitem"]'),n=this.openRoot.querySelector(':scope > [role="menu"]');t&&t.setAttribute("aria-expanded","false"),n&&(n.hidden=!0),this.openRoot=null,e&&t&&t.focus()}openMenu(e,t="first"){let n=e.closest('[data-ref="menubar-root"]'),o=this.getMenuFor(e);if(!(!n||!o)&&(this.openRoot&&this.openRoot!==n&&this.closeOpenMenu(),e.setAttribute("aria-expanded","true"),o.hidden=!1,this.openRoot=n,t==="first")){let l=this.getMenuItems(o);p(l,0),l[0]?.focus()}}@u({document:!0,type:"click"})onDocumentClick(e){this.openRoot&&(this.contains(e.target)||this.closeOpenMenu())}@u({selector:'[data-ref="menubar"] > [data-ref="menubar-root"] > [role="menuitem"]',type:"keydown"})onTopKeydown(e){let t=this.getTopItems(),n=e.target.closest('[role="menuitem"]');if(!n||!t.includes(n))return;if(e.key==="ArrowDown"||e.key==="Enter"||e.key===" "){this.getMenuFor(n)&&(e.preventDefault(),this.openMenu(n,"first"));return}if(e.key==="Escape"){e.preventDefault(),this.closeOpenMenu(!0);return}let o=d({items:t,current:n,key:e.key,orientation:"horizontal"});o.handled&&(e.preventDefault(),this.openRoot&&(this.getMenuFor(o.item)?this.openMenu(o.item,"first"):this.closeOpenMenu()))}@u({selector:'[data-ref="menubar"] > [data-ref="menubar-root"] > [role="menuitem"]',type:"click"})onTopClick(e){let t=e.target.closest('[role="menuitem"]');if(!t||!this.getTopItems().includes(t)
|
|
1
|
+
import{RadiantElement as R,customElement as E,event as T,onEvent as u,prop as y}from"@ecopages/radiant";function p(r,e){r.forEach((t,n)=>{t.tabIndex=n===e?0:-1})}function M(r,e){if(!r.length)return null;let t=Math.max(0,Math.min(r.length-1,e));return p(r,t),r[t]?.focus(),r[t]??null}function h(r,e){return r==="Home"?"first":r==="End"?"last":e==="horizontal"?r==="ArrowRight"?"next":r==="ArrowLeft"?"prev":null:r==="ArrowDown"?"next":r==="ArrowUp"?"prev":null}function d(r){let{items:e,current:t,key:n,orientation:o="horizontal",wrap:l=!0}=r;if(!e.length||!t)return{handled:!1};let a=e.indexOf(t);if(a<0)return{handled:!1};let s=h(n,o);if(!s)return{handled:!1};let i=a;s==="first"?i=0:s==="last"?i=e.length-1:s==="next"?i=l?(a+1)%e.length:Math.min(e.length-1,a+1):i=l?(a-1+e.length)%e.length:Math.max(0,a-1);let c=M(e,i);return c?{handled:!0,index:i,item:c}:{handled:!1}}var m=@E("rui-menubar")class extends R{@y({type:String,defaultValue:""})label;@T({name:"rui-change",bubbles:!0,composed:!0})changeEvent;resolvedAriaLabel=this.$.label.map(e=>e||void 0);openRoot=null;getTopItems(){return Array.from(this.querySelectorAll('[data-ref="menubar"] > [data-ref="menubar-root"] > [role="menuitem"]'))}getMenuFor(e){return e.closest('[data-ref="menubar-root"]')?.querySelector(':scope > [role="menu"]')??null}getMenuItems(e){return Array.from(e.querySelectorAll(':scope > [role="menuitem"]')).filter(t=>t.getAttribute("aria-disabled")!=="true")}connectedCallback(){super.connectedCallback(),queueMicrotask(()=>p(this.getTopItems(),0))}closeOpenMenu(e=!1){if(!this.openRoot)return;let t=this.openRoot.querySelector(':scope > [role="menuitem"]'),n=this.openRoot.querySelector(':scope > [role="menu"]');t&&t.setAttribute("aria-expanded","false"),n&&(n.hidden=!0),this.openRoot=null,e&&t&&t.focus()}openMenu(e,t="first"){let n=e.closest('[data-ref="menubar-root"]'),o=this.getMenuFor(e);if(!(!n||!o)&&(this.openRoot&&this.openRoot!==n&&this.closeOpenMenu(),e.setAttribute("aria-expanded","true"),o.hidden=!1,this.openRoot=n,t==="first")){let l=this.getMenuItems(o);p(l,0),l[0]?.focus()}}@u({document:!0,type:"click"})onDocumentClick(e){this.openRoot&&(this.contains(e.target)||this.closeOpenMenu())}@u({selector:'[data-ref="menubar"] > [data-ref="menubar-root"] > [role="menuitem"]',type:"keydown"})onTopKeydown(e){let t=this.getTopItems(),n=e.target.closest('[role="menuitem"]');if(!n||!t.includes(n))return;if(e.key==="ArrowDown"||e.key==="Enter"||e.key===" "){this.getMenuFor(n)&&(e.preventDefault(),this.openMenu(n,"first"));return}if(e.key==="Escape"){e.preventDefault(),this.closeOpenMenu(!0);return}let o=d({items:t,current:n,key:e.key,orientation:"horizontal"});o.handled&&(e.preventDefault(),this.openRoot&&(this.getMenuFor(o.item)?this.openMenu(o.item,"first"):this.closeOpenMenu()))}@u({selector:'[data-ref="menubar"] > [data-ref="menubar-root"] > [role="menuitem"]',type:"click"})onTopClick(e){let t=e.target.closest('[role="menuitem"]');if(!t||!this.getTopItems().includes(t))return;if(!this.getMenuFor(t)){this.closeOpenMenu();return}t.getAttribute("aria-expanded")==="true"?this.closeOpenMenu():this.openMenu(t,"first")}@u({selector:'[role="menu"] > [role="menuitem"]',type:"keydown"})onMenuKeydown(e){let t=e.target.closest('[role="menu"]'),n=e.target.closest('[role="menuitem"]');if(!t||!n||!this.contains(t))return;let o=this.getMenuItems(t);if(e.key==="Escape"){e.preventDefault(),this.closeOpenMenu(!0);return}if(e.key==="Enter"||e.key===" "){e.preventDefault(),this.activateItem(n);return}if(e.key==="ArrowLeft"||e.key==="ArrowRight"){let a=this.getTopItems(),s=t.parentElement?.querySelector(':scope > [role="menuitem"]');if(!s)return;e.preventDefault();let i=d({items:a,current:s,key:e.key,orientation:"horizontal"});i.handled&&this.getMenuFor(i.item)&&this.openMenu(i.item,"first");return}d({items:o,current:n,key:e.key,orientation:"vertical"}).handled&&e.preventDefault()}@u({selector:'[role="menu"] > [role="menuitem"]',type:"click"})onMenuItemClick(e){let t=e.target.closest('[role="menuitem"]');!t||!this.contains(t)||t.parentElement?.getAttribute("role")==="menu"&&this.activateItem(t)}activateItem(e){let t=e.getAttribute("data-value")||e.textContent?.trim()||"";this.changeEvent.emit({value:t}),this.closeOpenMenu(!0)}render(){return React.createElement("div",{class:"rui-menubar","data-ref":"menubar",role:"menubar","aria-label":this.resolvedAriaLabel},React.createElement("slot",null))}};var v=Symbol.for("@ecopages/storybook-radiant.viewElement"),f=Symbol.for("@ecopages/storybook-radiant.scriptModule"),b=Symbol.for("@ecopages/storybook-radiant.scriptExport");function x(r,e){r[v]=e;let t=e[f];t&&(r[f]=t,r[b]=e[b]??e.name)}function g(r,e){let t=(n=>e(n));return x(t,r),t}import"./menubar.css";var A=g(m,({slot:r,label:e,items:t})=>React.createElement("rui-menubar",{slot:r,label:e},t.map(n=>React.createElement("div",{class:"rui-menubar__root","data-ref":"menubar-root"},React.createElement("button",{type:"button",class:"rui-menubar__item",role:"menuitem",tabindex:-1,"aria-haspopup":n.items?.length?"true":void 0,"aria-expanded":n.items?.length?"false":void 0},n.label),n.items?.length?React.createElement("div",{class:"rui-menubar__menu",role:"menu",hidden:!0},n.items.map(o=>React.createElement("button",{type:"button",class:"rui-menubar__menu-item",role:"menuitem","data-value":o.id,tabindex:-1},o.label))):null))));export{A as RuiMenubar,m as RuiMenubarElement};
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/components/ui/menubar/menubar.script.tsx", "../../../../src/lib/roving-tabindex.ts", "../../../../src/lib/radiant-view.ts", "../../../../src/components/ui/menubar/menubar.tsx"],
|
|
4
|
-
"sourcesContent": ["import { RadiantElement, customElement, event, onEvent, prop } from '@ecopages/radiant';\nimport type { EventEmitter } from '@ecopages/radiant/tools/event-emitter';\nimport { applyRovingTabindex, navigateRovingTabindex } from '../../../lib/roving-tabindex';\n\nexport type RuiMenubarProps = {\n\tlabel?: string;\n};\n\nexport type RuiMenubarChangeDetail = { value: string };\n\ntype RuiMenubarBindings = {\n\tlabel: string;\n};\n\n/**\n * `<rui-menubar>` \u2014 a horizontal menubar of menus.\n *\n * Implements the APG Menubar pattern: top-level items use `role=\"menuitem\"` with\n * `aria-haspopup` / `aria-expanded` when they own a nested `[role=\"menu\"]`. Arrow\n * keys move across the bar; Enter/Space/ArrowDown open the popup; Escape closes it.\n *\n * Expected markup (also produced by the JSX view helper):\n * ```html\n * <div class=\"rui-menubar__root\" data-ref=\"menubar-root\">\n * <button role=\"menuitem\" aria-haspopup=\"true\" aria-expanded=\"false\">File</button>\n * <div role=\"menu\" hidden>\n * <button role=\"menuitem\" data-value=\"new\">New</button>\n * </div>\n * </div>\n * ```\n *\n * @see https://www.w3.org/WAI/ARIA/apg/patterns/menubar/\n * @element rui-menubar\n * @fires rui-change\n */\n@customElement('rui-menubar')\nexport class RuiMenubar extends RadiantElement<RuiMenubarBindings> {\n\t@prop({ type: String, defaultValue: '' }) label: string;\n\n\t@event({ name: 'rui-change', bubbles: true, composed: true })\n\tchangeEvent: EventEmitter<RuiMenubarChangeDetail>;\n\n\tprivate readonly resolvedAriaLabel = this.$.label.map((label) => label || undefined);\n\n\tprivate openRoot: HTMLElement | null = null;\n\n\tprivate getTopItems(): HTMLElement[] {\n\t\treturn Array.from(\n\t\t\tthis.querySelectorAll<HTMLElement>('[data-ref=\"menubar\"] > [data-ref=\"menubar-root\"] > [role=\"menuitem\"]'),\n\t\t);\n\t}\n\n\tprivate getMenuFor(topItem: HTMLElement): HTMLElement | null {\n\t\tconst root = topItem.closest('[data-ref=\"menubar-root\"]');\n\t\treturn root?.querySelector<HTMLElement>(':scope > [role=\"menu\"]') ?? null;\n\t}\n\n\tprivate getMenuItems(menu: HTMLElement): HTMLElement[] {\n\t\treturn Array.from(menu.querySelectorAll<HTMLElement>(':scope > [role=\"menuitem\"]')).filter(\n\t\t\t(item) => item.getAttribute('aria-disabled') !== 'true',\n\t\t);\n\t}\n\n\toverride connectedCallback(): void {\n\t\tsuper.connectedCallback();\n\t\tqueueMicrotask(() => applyRovingTabindex(this.getTopItems(), 0));\n\t}\n\n\tprivate closeOpenMenu(returnFocus = false): void {\n\t\tif (!this.openRoot) return;\n\t\tconst top = this.openRoot.querySelector<HTMLElement>(':scope > [role=\"menuitem\"]');\n\t\tconst menu = this.openRoot.querySelector<HTMLElement>(':scope > [role=\"menu\"]');\n\t\tif (top) top.setAttribute('aria-expanded', 'false');\n\t\tif (menu) menu.hidden = true;\n\t\tthis.openRoot = null;\n\t\tif (returnFocus && top) top.focus();\n\t}\n\n\tprivate openMenu(topItem: HTMLElement, focus: 'first' | null = 'first'): void {\n\t\tconst root = topItem.closest('[data-ref=\"menubar-root\"]') as HTMLElement | null;\n\t\tconst menu = this.getMenuFor(topItem);\n\t\tif (!root || !menu) return;\n\n\t\tif (this.openRoot && this.openRoot !== root) this.closeOpenMenu();\n\n\t\ttopItem.setAttribute('aria-expanded', 'true');\n\t\tmenu.hidden = false;\n\t\tthis.openRoot = root;\n\n\t\tif (focus === 'first') {\n\t\t\tconst items = this.getMenuItems(menu);\n\t\t\tapplyRovingTabindex(items, 0);\n\t\t\titems[0]?.focus();\n\t\t}\n\t}\n\n\t@onEvent({ document: true, type: 'click' })\n\tonDocumentClick(event: MouseEvent): void {\n\t\tif (!this.openRoot) return;\n\t\tif (!this.contains(event.target as Node)) this.closeOpenMenu();\n\t}\n\n\t@onEvent({ selector: '[data-ref=\"menubar\"] > [data-ref=\"menubar-root\"] > [role=\"menuitem\"]', type: 'keydown' })\n\tonTopKeydown(event: KeyboardEvent): void {\n\t\tconst items = this.getTopItems();\n\t\tconst current = (event.target as HTMLElement).closest('[role=\"menuitem\"]') as HTMLElement | null;\n\t\tif (!current || !items.includes(current)) return;\n\n\t\tif (event.key === 'ArrowDown' || event.key === 'Enter' || event.key === ' ') {\n\t\t\tif (this.getMenuFor(current)) {\n\t\t\t\tevent.preventDefault();\n\t\t\t\tthis.openMenu(current, 'first');\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tif (event.key === 'Escape') {\n\t\t\tevent.preventDefault();\n\t\t\tthis.closeOpenMenu(true);\n\t\t\treturn;\n\t\t}\n\n\t\tconst result = navigateRovingTabindex({\n\t\t\titems,\n\t\t\tcurrent,\n\t\t\tkey: event.key,\n\t\t\torientation: 'horizontal',\n\t\t});\n\t\tif (!result.handled) return;\n\t\tevent.preventDefault();\n\n\t\t// APG: when a menu is open, moving across the bar opens the newly focused menu.\n\t\tif (this.openRoot) {\n\t\t\tif (this.getMenuFor(result.item)) this.openMenu(result.item, 'first');\n\t\t\telse this.closeOpenMenu();\n\t\t}\n\t}\n\n\t@onEvent({ selector: '[data-ref=\"menubar\"] > [data-ref=\"menubar-root\"] > [role=\"menuitem\"]', type: 'click' })\n\tonTopClick(event: Event): void {\n\t\tconst current = (event.target as HTMLElement).closest('[role=\"menuitem\"]') as HTMLElement | null;\n\t\tif (!current || !this.getTopItems().includes(current)) return;\n\t\tif (!this.getMenuFor(current)) return;\n\n\t\tconst expanded = current.getAttribute('aria-expanded') === 'true';\n\t\tif (expanded) this.closeOpenMenu();\n\t\telse this.openMenu(current, 'first');\n\t}\n\n\t@onEvent({ selector: '[role=\"menu\"] > [role=\"menuitem\"]', type: 'keydown' })\n\tonMenuKeydown(event: KeyboardEvent): void {\n\t\tconst menu = (event.target as HTMLElement).closest('[role=\"menu\"]') as HTMLElement | null;\n\t\tconst current = (event.target as HTMLElement).closest('[role=\"menuitem\"]') as HTMLElement | null;\n\t\tif (!menu || !current || !this.contains(menu)) return;\n\n\t\tconst items = this.getMenuItems(menu);\n\n\t\tif (event.key === 'Escape') {\n\t\t\tevent.preventDefault();\n\t\t\tthis.closeOpenMenu(true);\n\t\t\treturn;\n\t\t}\n\n\t\tif (event.key === 'Enter' || event.key === ' ') {\n\t\t\tevent.preventDefault();\n\t\t\tthis.activateItem(current);\n\t\t\treturn;\n\t\t}\n\n\t\tif (event.key === 'ArrowLeft' || event.key === 'ArrowRight') {\n\t\t\tconst tops = this.getTopItems();\n\t\t\tconst top = menu.parentElement?.querySelector<HTMLElement>(':scope > [role=\"menuitem\"]');\n\t\t\tif (!top) return;\n\t\t\tevent.preventDefault();\n\t\t\tconst result = navigateRovingTabindex({\n\t\t\t\titems: tops,\n\t\t\t\tcurrent: top,\n\t\t\t\tkey: event.key,\n\t\t\t\torientation: 'horizontal',\n\t\t\t});\n\t\t\tif (result.handled && this.getMenuFor(result.item)) this.openMenu(result.item, 'first');\n\t\t\treturn;\n\t\t}\n\n\t\tconst result = navigateRovingTabindex({\n\t\t\titems,\n\t\t\tcurrent,\n\t\t\tkey: event.key,\n\t\t\torientation: 'vertical',\n\t\t});\n\t\tif (result.handled) event.preventDefault();\n\t}\n\n\t@onEvent({ selector: '[role=\"menu\"] > [role=\"menuitem\"]', type: 'click' })\n\tonMenuItemClick(event: Event): void {\n\t\tconst item = (event.target as HTMLElement).closest('[role=\"menuitem\"]') as HTMLElement | null;\n\t\tif (!item || !this.contains(item)) return;\n\t\t// Ignore top-level items (they are not direct children of role=menu in our markup).\n\t\tif (item.parentElement?.getAttribute('role') !== 'menu') return;\n\t\tthis.activateItem(item);\n\t}\n\n\tprivate activateItem(item: HTMLElement): void {\n\t\tconst value = item.getAttribute('data-value') || item.textContent?.trim() || '';\n\t\tthis.changeEvent.emit({ value });\n\t\tthis.closeOpenMenu(true);\n\t}\n\n\toverride render() {\n\t\treturn (\n\t\t\t<div class=\"rui-menubar\" data-ref=\"menubar\" role=\"menubar\" aria-label={this.resolvedAriaLabel}>\n\t\t\t\t<slot></slot>\n\t\t\t</div>\n\t\t);\n\t}\n}\n", "export type RovingOrientation = 'horizontal' | 'vertical';\n\nexport type RovingNavResult = { handled: false } | { handled: true; index: number; item: HTMLElement };\n\n/**\n * Apply the APG roving-tabindex pattern: one item is tabbable, the rest are `-1`.\n */\nexport function applyRovingTabindex(items: HTMLElement[], activeIndex: number): void {\n\titems.forEach((item, index) => {\n\t\titem.tabIndex = index === activeIndex ? 0 : -1;\n\t});\n}\n\n/**\n * Focus `items[index]` and update tabindexes. Returns the focused item, or null.\n */\nexport function focusRovingItem(items: HTMLElement[], index: number): HTMLElement | null {\n\tif (!items.length) return null;\n\tconst next = Math.max(0, Math.min(items.length - 1, index));\n\tapplyRovingTabindex(items, next);\n\titems[next]?.focus();\n\treturn items[next] ?? null;\n}\n\nfunction deltaForKey(key: string, orientation: RovingOrientation): 'prev' | 'next' | 'first' | 'last' | null {\n\tif (key === 'Home') return 'first';\n\tif (key === 'End') return 'last';\n\n\tif (orientation === 'horizontal') {\n\t\tif (key === 'ArrowRight') return 'next';\n\t\tif (key === 'ArrowLeft') return 'prev';\n\t\treturn null;\n\t}\n\n\tif (key === 'ArrowDown') return 'next';\n\tif (key === 'ArrowUp') return 'prev';\n\treturn null;\n}\n\n/**\n * Handle arrow / Home / End navigation for a flat roving-tabindex collection.\n * Does not activate the item \u2014 callers decide whether to select on move.\n */\nexport function navigateRovingTabindex(options: {\n\titems: HTMLElement[];\n\tcurrent: HTMLElement | null;\n\tkey: string;\n\torientation?: RovingOrientation;\n\twrap?: boolean;\n}): RovingNavResult {\n\tconst { items, current, key, orientation = 'horizontal', wrap = true } = options;\n\tif (!items.length || !current) return { handled: false };\n\n\tconst index = items.indexOf(current);\n\tif (index < 0) return { handled: false };\n\n\tconst delta = deltaForKey(key, orientation);\n\tif (!delta) return { handled: false };\n\n\tlet next = index;\n\tif (delta === 'first') next = 0;\n\telse if (delta === 'last') next = items.length - 1;\n\telse if (delta === 'next') next = wrap ? (index + 1) % items.length : Math.min(items.length - 1, index + 1);\n\telse next = wrap ? (index - 1 + items.length) % items.length : Math.max(0, index - 1);\n\n\tconst item = focusRovingItem(items, next);\n\tif (!item) return { handled: false };\n\treturn { handled: true, index: next, item };\n}\n", "import type { JsxRenderable } from '@ecopages/jsx';\n\nconst RADIANT_VIEW_ELEMENT = Symbol.for('@ecopages/storybook-radiant.viewElement');\nconst RADIANT_SCRIPT_MODULE = Symbol.for('@ecopages/storybook-radiant.scriptModule');\nconst RADIANT_SCRIPT_EXPORT = Symbol.for('@ecopages/storybook-radiant.scriptExport');\n\nexport type RadiantViewComponent<TArgs = unknown> = ((args: TArgs) => JsxRenderable) & {\n\t[RADIANT_VIEW_ELEMENT]?: CustomElementConstructor;\n\t[RADIANT_SCRIPT_MODULE]?: string;\n\t[RADIANT_SCRIPT_EXPORT]?: string;\n};\n\nfunction linkViewToElement<TArgs>(\n\tview: RadiantViewComponent<TArgs>,\n\telement: CustomElementConstructor & {\n\t\t[RADIANT_SCRIPT_MODULE]?: string;\n\t\t[RADIANT_SCRIPT_EXPORT]?: string;\n\t},\n): void {\n\tview[RADIANT_VIEW_ELEMENT] = element;\n\n\tconst ssrModule = element[RADIANT_SCRIPT_MODULE];\n\tif (ssrModule) {\n\t\tview[RADIANT_SCRIPT_MODULE] = ssrModule;\n\t\tview[RADIANT_SCRIPT_EXPORT] = element[RADIANT_SCRIPT_EXPORT] ?? element.name;\n\t}\n}\n\n/** Link a JSX view export to its RadiantElement class (same symbols as the Storybook framework). */\nexport function defineRadiantView<TArgs>(\n\telement: CustomElementConstructor & {\n\t\t[RADIANT_SCRIPT_MODULE]?: string;\n\t\t[RADIANT_SCRIPT_EXPORT]?: string;\n\t},\n\trender: (args: TArgs) => JsxRenderable,\n): RadiantViewComponent<TArgs> {\n\tconst view = ((args: TArgs) => render(args)) as RadiantViewComponent<TArgs>;\n\tlinkViewToElement(view, element);\n\treturn view;\n}\n\n/** Re-copy SSR metadata from the linked element (e.g. after HMR). */\nexport function syncRadiantViewMetadata(view: RadiantViewComponent): void {\n\tconst element = view[RADIANT_VIEW_ELEMENT];\n\tif (element) {\n\t\tlinkViewToElement(view, element);\n\t}\n}\n", "import type { JsxRenderable } from '@ecopages/jsx';\nimport type { RadiantSlotProps } from '../../../types';\nimport { defineRadiantView } from '../../../lib/radiant-view';\nimport type { RuiMenubarProps } from './menubar.script';\nimport { RuiMenubar as RuiMenubarElement } from './menubar.script';\nimport './menubar.css';\n\nexport type RuiMenubarItem = {\n\tid: string;\n\tlabel: JsxRenderable;\n\t/** Popup actions. When present, the top item opens a `role=\"menu\"`. */\n\titems?: Array<{ id: string; label: JsxRenderable }>;\n};\n\nexport const RuiMenubar = defineRadiantView(\n\tRuiMenubarElement,\n\t({ slot, label, items }: RuiMenubarProps & RadiantSlotProps & { items: RuiMenubarItem[] }) => (\n\t\t<rui-menubar slot={slot} label={label}>\n\t\t\t{items.map((item) => (\n\t\t\t\t<div class=\"rui-menubar__root\" data-ref=\"menubar-root\">\n\t\t\t\t\t<button\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tclass=\"rui-menubar__item\"\n\t\t\t\t\t\trole=\"menuitem\"\n\t\t\t\t\t\ttabindex={-1}\n\t\t\t\t\t\taria-haspopup={item.items?.length ? 'true' : undefined}\n\t\t\t\t\t\taria-expanded={item.items?.length ? 'false' : undefined}\n\t\t\t\t\t>\n\t\t\t\t\t\t{item.label}\n\t\t\t\t\t</button>\n\t\t\t\t\t{item.items?.length ? (\n\t\t\t\t\t\t<div class=\"rui-menubar__menu\" role=\"menu\" hidden>\n\t\t\t\t\t\t\t{item.items.map((child) => (\n\t\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\t\tclass=\"rui-menubar__menu-item\"\n\t\t\t\t\t\t\t\t\trole=\"menuitem\"\n\t\t\t\t\t\t\t\t\tdata-value={child.id}\n\t\t\t\t\t\t\t\t\ttabindex={-1}\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t{child.label}\n\t\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t) : null}\n\t\t\t\t</div>\n\t\t\t))}\n\t\t</rui-menubar>\n\t),\n);\n"],
|
|
5
|
-
"mappings": "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
|
|
4
|
+
"sourcesContent": ["import { RadiantElement, customElement, event, onEvent, prop } from '@ecopages/radiant';\nimport type { EventEmitter } from '@ecopages/radiant/tools/event-emitter';\nimport { applyRovingTabindex, navigateRovingTabindex } from '../../../lib/roving-tabindex';\n\nexport type RuiMenubarProps = {\n\tlabel?: string;\n};\n\nexport type RuiMenubarChangeDetail = { value: string };\n\ntype RuiMenubarBindings = {\n\tlabel: string;\n};\n\n/**\n * `<rui-menubar>` \u2014 a horizontal menubar of menus.\n *\n * Implements the APG Menubar pattern: top-level items use `role=\"menuitem\"` with\n * `aria-haspopup` / `aria-expanded` when they own a nested `[role=\"menu\"]`. Arrow\n * keys move across the bar; Enter/Space/ArrowDown open the popup; Escape closes it.\n *\n * Expected markup (also produced by the JSX view helper):\n * ```html\n * <div class=\"rui-menubar__root\" data-ref=\"menubar-root\">\n * <button role=\"menuitem\" aria-haspopup=\"true\" aria-expanded=\"false\">File</button>\n * <div role=\"menu\" hidden>\n * <button role=\"menuitem\" data-value=\"new\">New</button>\n * </div>\n * </div>\n * ```\n *\n * @see https://www.w3.org/WAI/ARIA/apg/patterns/menubar/\n * @element rui-menubar\n * @fires rui-change\n */\n@customElement('rui-menubar')\nexport class RuiMenubar extends RadiantElement<RuiMenubarBindings> {\n\t@prop({ type: String, defaultValue: '' }) label: string;\n\n\t@event({ name: 'rui-change', bubbles: true, composed: true })\n\tchangeEvent: EventEmitter<RuiMenubarChangeDetail>;\n\n\tprivate readonly resolvedAriaLabel = this.$.label.map((label) => label || undefined);\n\n\tprivate openRoot: HTMLElement | null = null;\n\n\tprivate getTopItems(): HTMLElement[] {\n\t\treturn Array.from(\n\t\t\tthis.querySelectorAll<HTMLElement>('[data-ref=\"menubar\"] > [data-ref=\"menubar-root\"] > [role=\"menuitem\"]'),\n\t\t);\n\t}\n\n\tprivate getMenuFor(topItem: HTMLElement): HTMLElement | null {\n\t\tconst root = topItem.closest('[data-ref=\"menubar-root\"]');\n\t\treturn root?.querySelector<HTMLElement>(':scope > [role=\"menu\"]') ?? null;\n\t}\n\n\tprivate getMenuItems(menu: HTMLElement): HTMLElement[] {\n\t\treturn Array.from(menu.querySelectorAll<HTMLElement>(':scope > [role=\"menuitem\"]')).filter(\n\t\t\t(item) => item.getAttribute('aria-disabled') !== 'true',\n\t\t);\n\t}\n\n\toverride connectedCallback(): void {\n\t\tsuper.connectedCallback();\n\t\tqueueMicrotask(() => applyRovingTabindex(this.getTopItems(), 0));\n\t}\n\n\tprivate closeOpenMenu(returnFocus = false): void {\n\t\tif (!this.openRoot) return;\n\t\tconst top = this.openRoot.querySelector<HTMLElement>(':scope > [role=\"menuitem\"]');\n\t\tconst menu = this.openRoot.querySelector<HTMLElement>(':scope > [role=\"menu\"]');\n\t\tif (top) top.setAttribute('aria-expanded', 'false');\n\t\tif (menu) menu.hidden = true;\n\t\tthis.openRoot = null;\n\t\tif (returnFocus && top) top.focus();\n\t}\n\n\tprivate openMenu(topItem: HTMLElement, focus: 'first' | null = 'first'): void {\n\t\tconst root = topItem.closest('[data-ref=\"menubar-root\"]') as HTMLElement | null;\n\t\tconst menu = this.getMenuFor(topItem);\n\t\tif (!root || !menu) return;\n\n\t\tif (this.openRoot && this.openRoot !== root) this.closeOpenMenu();\n\n\t\ttopItem.setAttribute('aria-expanded', 'true');\n\t\tmenu.hidden = false;\n\t\tthis.openRoot = root;\n\n\t\tif (focus === 'first') {\n\t\t\tconst items = this.getMenuItems(menu);\n\t\t\tapplyRovingTabindex(items, 0);\n\t\t\titems[0]?.focus();\n\t\t}\n\t}\n\n\t@onEvent({ document: true, type: 'click' })\n\tonDocumentClick(event: MouseEvent): void {\n\t\tif (!this.openRoot) return;\n\t\tif (!this.contains(event.target as Node)) this.closeOpenMenu();\n\t}\n\n\t@onEvent({ selector: '[data-ref=\"menubar\"] > [data-ref=\"menubar-root\"] > [role=\"menuitem\"]', type: 'keydown' })\n\tonTopKeydown(event: KeyboardEvent): void {\n\t\tconst items = this.getTopItems();\n\t\tconst current = (event.target as HTMLElement).closest('[role=\"menuitem\"]') as HTMLElement | null;\n\t\tif (!current || !items.includes(current)) return;\n\n\t\tif (event.key === 'ArrowDown' || event.key === 'Enter' || event.key === ' ') {\n\t\t\tif (this.getMenuFor(current)) {\n\t\t\t\tevent.preventDefault();\n\t\t\t\tthis.openMenu(current, 'first');\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tif (event.key === 'Escape') {\n\t\t\tevent.preventDefault();\n\t\t\tthis.closeOpenMenu(true);\n\t\t\treturn;\n\t\t}\n\n\t\tconst result = navigateRovingTabindex({\n\t\t\titems,\n\t\t\tcurrent,\n\t\t\tkey: event.key,\n\t\t\torientation: 'horizontal',\n\t\t});\n\t\tif (!result.handled) return;\n\t\tevent.preventDefault();\n\n\t\t// APG: when a menu is open, moving across the bar opens the newly focused menu.\n\t\tif (this.openRoot) {\n\t\t\tif (this.getMenuFor(result.item)) this.openMenu(result.item, 'first');\n\t\t\telse this.closeOpenMenu();\n\t\t}\n\t}\n\n\t@onEvent({ selector: '[data-ref=\"menubar\"] > [data-ref=\"menubar-root\"] > [role=\"menuitem\"]', type: 'click' })\n\tonTopClick(event: Event): void {\n\t\tconst current = (event.target as HTMLElement).closest('[role=\"menuitem\"]') as HTMLElement | null;\n\t\tif (!current || !this.getTopItems().includes(current)) return;\n\n\t\tif (!this.getMenuFor(current)) {\n\t\t\tthis.closeOpenMenu();\n\t\t\treturn;\n\t\t}\n\n\t\tconst expanded = current.getAttribute('aria-expanded') === 'true';\n\t\tif (expanded) this.closeOpenMenu();\n\t\telse this.openMenu(current, 'first');\n\t}\n\n\t@onEvent({ selector: '[role=\"menu\"] > [role=\"menuitem\"]', type: 'keydown' })\n\tonMenuKeydown(event: KeyboardEvent): void {\n\t\tconst menu = (event.target as HTMLElement).closest('[role=\"menu\"]') as HTMLElement | null;\n\t\tconst current = (event.target as HTMLElement).closest('[role=\"menuitem\"]') as HTMLElement | null;\n\t\tif (!menu || !current || !this.contains(menu)) return;\n\n\t\tconst items = this.getMenuItems(menu);\n\n\t\tif (event.key === 'Escape') {\n\t\t\tevent.preventDefault();\n\t\t\tthis.closeOpenMenu(true);\n\t\t\treturn;\n\t\t}\n\n\t\tif (event.key === 'Enter' || event.key === ' ') {\n\t\t\tevent.preventDefault();\n\t\t\tthis.activateItem(current);\n\t\t\treturn;\n\t\t}\n\n\t\tif (event.key === 'ArrowLeft' || event.key === 'ArrowRight') {\n\t\t\tconst tops = this.getTopItems();\n\t\t\tconst top = menu.parentElement?.querySelector<HTMLElement>(':scope > [role=\"menuitem\"]');\n\t\t\tif (!top) return;\n\t\t\tevent.preventDefault();\n\t\t\tconst result = navigateRovingTabindex({\n\t\t\t\titems: tops,\n\t\t\t\tcurrent: top,\n\t\t\t\tkey: event.key,\n\t\t\t\torientation: 'horizontal',\n\t\t\t});\n\t\t\tif (result.handled && this.getMenuFor(result.item)) this.openMenu(result.item, 'first');\n\t\t\treturn;\n\t\t}\n\n\t\tconst result = navigateRovingTabindex({\n\t\t\titems,\n\t\t\tcurrent,\n\t\t\tkey: event.key,\n\t\t\torientation: 'vertical',\n\t\t});\n\t\tif (result.handled) event.preventDefault();\n\t}\n\n\t@onEvent({ selector: '[role=\"menu\"] > [role=\"menuitem\"]', type: 'click' })\n\tonMenuItemClick(event: Event): void {\n\t\tconst item = (event.target as HTMLElement).closest('[role=\"menuitem\"]') as HTMLElement | null;\n\t\tif (!item || !this.contains(item)) return;\n\t\t// Ignore top-level items (they are not direct children of role=menu in our markup).\n\t\tif (item.parentElement?.getAttribute('role') !== 'menu') return;\n\t\tthis.activateItem(item);\n\t}\n\n\tprivate activateItem(item: HTMLElement): void {\n\t\tconst value = item.getAttribute('data-value') || item.textContent?.trim() || '';\n\t\tthis.changeEvent.emit({ value });\n\t\tthis.closeOpenMenu(true);\n\t}\n\n\toverride render() {\n\t\treturn (\n\t\t\t<div class=\"rui-menubar\" data-ref=\"menubar\" role=\"menubar\" aria-label={this.resolvedAriaLabel}>\n\t\t\t\t<slot></slot>\n\t\t\t</div>\n\t\t);\n\t}\n}\n", "export type RovingOrientation = 'horizontal' | 'vertical';\n\nexport type RovingNavResult = { handled: false } | { handled: true; index: number; item: HTMLElement };\n\n/**\n * Apply the APG roving-tabindex pattern: one item is tabbable, the rest are `-1`.\n */\nexport function applyRovingTabindex(items: HTMLElement[], activeIndex: number): void {\n\titems.forEach((item, index) => {\n\t\titem.tabIndex = index === activeIndex ? 0 : -1;\n\t});\n}\n\n/**\n * Focus `items[index]` and update tabindexes. Returns the focused item, or null.\n */\nexport function focusRovingItem(items: HTMLElement[], index: number): HTMLElement | null {\n\tif (!items.length) return null;\n\tconst next = Math.max(0, Math.min(items.length - 1, index));\n\tapplyRovingTabindex(items, next);\n\titems[next]?.focus();\n\treturn items[next] ?? null;\n}\n\nfunction deltaForKey(key: string, orientation: RovingOrientation): 'prev' | 'next' | 'first' | 'last' | null {\n\tif (key === 'Home') return 'first';\n\tif (key === 'End') return 'last';\n\n\tif (orientation === 'horizontal') {\n\t\tif (key === 'ArrowRight') return 'next';\n\t\tif (key === 'ArrowLeft') return 'prev';\n\t\treturn null;\n\t}\n\n\tif (key === 'ArrowDown') return 'next';\n\tif (key === 'ArrowUp') return 'prev';\n\treturn null;\n}\n\n/**\n * Handle arrow / Home / End navigation for a flat roving-tabindex collection.\n * Does not activate the item \u2014 callers decide whether to select on move.\n */\nexport function navigateRovingTabindex(options: {\n\titems: HTMLElement[];\n\tcurrent: HTMLElement | null;\n\tkey: string;\n\torientation?: RovingOrientation;\n\twrap?: boolean;\n}): RovingNavResult {\n\tconst { items, current, key, orientation = 'horizontal', wrap = true } = options;\n\tif (!items.length || !current) return { handled: false };\n\n\tconst index = items.indexOf(current);\n\tif (index < 0) return { handled: false };\n\n\tconst delta = deltaForKey(key, orientation);\n\tif (!delta) return { handled: false };\n\n\tlet next = index;\n\tif (delta === 'first') next = 0;\n\telse if (delta === 'last') next = items.length - 1;\n\telse if (delta === 'next') next = wrap ? (index + 1) % items.length : Math.min(items.length - 1, index + 1);\n\telse next = wrap ? (index - 1 + items.length) % items.length : Math.max(0, index - 1);\n\n\tconst item = focusRovingItem(items, next);\n\tif (!item) return { handled: false };\n\treturn { handled: true, index: next, item };\n}\n", "import type { JsxRenderable } from '@ecopages/jsx';\n\nconst RADIANT_VIEW_ELEMENT = Symbol.for('@ecopages/storybook-radiant.viewElement');\nconst RADIANT_SCRIPT_MODULE = Symbol.for('@ecopages/storybook-radiant.scriptModule');\nconst RADIANT_SCRIPT_EXPORT = Symbol.for('@ecopages/storybook-radiant.scriptExport');\n\nexport type RadiantViewComponent<TArgs = unknown> = ((args: TArgs) => JsxRenderable) & {\n\t[RADIANT_VIEW_ELEMENT]?: CustomElementConstructor;\n\t[RADIANT_SCRIPT_MODULE]?: string;\n\t[RADIANT_SCRIPT_EXPORT]?: string;\n};\n\nfunction linkViewToElement<TArgs>(\n\tview: RadiantViewComponent<TArgs>,\n\telement: CustomElementConstructor & {\n\t\t[RADIANT_SCRIPT_MODULE]?: string;\n\t\t[RADIANT_SCRIPT_EXPORT]?: string;\n\t},\n): void {\n\tview[RADIANT_VIEW_ELEMENT] = element;\n\n\tconst ssrModule = element[RADIANT_SCRIPT_MODULE];\n\tif (ssrModule) {\n\t\tview[RADIANT_SCRIPT_MODULE] = ssrModule;\n\t\tview[RADIANT_SCRIPT_EXPORT] = element[RADIANT_SCRIPT_EXPORT] ?? element.name;\n\t}\n}\n\n/** Link a JSX view export to its RadiantElement class (same symbols as the Storybook framework). */\nexport function defineRadiantView<TArgs>(\n\telement: CustomElementConstructor & {\n\t\t[RADIANT_SCRIPT_MODULE]?: string;\n\t\t[RADIANT_SCRIPT_EXPORT]?: string;\n\t},\n\trender: (args: TArgs) => JsxRenderable,\n): RadiantViewComponent<TArgs> {\n\tconst view = ((args: TArgs) => render(args)) as RadiantViewComponent<TArgs>;\n\tlinkViewToElement(view, element);\n\treturn view;\n}\n\n/** Re-copy SSR metadata from the linked element (e.g. after HMR). */\nexport function syncRadiantViewMetadata(view: RadiantViewComponent): void {\n\tconst element = view[RADIANT_VIEW_ELEMENT];\n\tif (element) {\n\t\tlinkViewToElement(view, element);\n\t}\n}\n", "import type { JsxRenderable } from '@ecopages/jsx';\nimport type { RadiantSlotProps } from '../../../types';\nimport { defineRadiantView } from '../../../lib/radiant-view';\nimport type { RuiMenubarProps } from './menubar.script';\nimport { RuiMenubar as RuiMenubarElement } from './menubar.script';\nimport './menubar.css';\n\nexport type RuiMenubarItem = {\n\tid: string;\n\tlabel: JsxRenderable;\n\t/** Popup actions. When present, the top item opens a `role=\"menu\"`. */\n\titems?: Array<{ id: string; label: JsxRenderable }>;\n};\n\nexport const RuiMenubar = defineRadiantView(\n\tRuiMenubarElement,\n\t({ slot, label, items }: RuiMenubarProps & RadiantSlotProps & { items: RuiMenubarItem[] }) => (\n\t\t<rui-menubar slot={slot} label={label}>\n\t\t\t{items.map((item) => (\n\t\t\t\t<div class=\"rui-menubar__root\" data-ref=\"menubar-root\">\n\t\t\t\t\t<button\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tclass=\"rui-menubar__item\"\n\t\t\t\t\t\trole=\"menuitem\"\n\t\t\t\t\t\ttabindex={-1}\n\t\t\t\t\t\taria-haspopup={item.items?.length ? 'true' : undefined}\n\t\t\t\t\t\taria-expanded={item.items?.length ? 'false' : undefined}\n\t\t\t\t\t>\n\t\t\t\t\t\t{item.label}\n\t\t\t\t\t</button>\n\t\t\t\t\t{item.items?.length ? (\n\t\t\t\t\t\t<div class=\"rui-menubar__menu\" role=\"menu\" hidden>\n\t\t\t\t\t\t\t{item.items.map((child) => (\n\t\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\t\tclass=\"rui-menubar__menu-item\"\n\t\t\t\t\t\t\t\t\trole=\"menuitem\"\n\t\t\t\t\t\t\t\t\tdata-value={child.id}\n\t\t\t\t\t\t\t\t\ttabindex={-1}\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t{child.label}\n\t\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t) : null}\n\t\t\t\t</div>\n\t\t\t))}\n\t\t</rui-menubar>\n\t),\n);\n"],
|
|
5
|
+
"mappings": "AAAA,OAAS,kBAAAA,EAAgB,iBAAAC,EAAe,SAAAC,EAAO,WAAAC,EAAS,QAAAC,MAAY,oBCO7D,SAASC,EAAoBC,EAAsBC,EAA2B,CACpFD,EAAM,QAAQ,CAACE,EAAMC,IAAU,CAC9BD,EAAK,SAAWC,IAAUF,EAAc,EAAI,EAC7C,CAAC,CACF,CAKO,SAASG,EAAgBJ,EAAsBG,EAAmC,CACxF,GAAI,CAACH,EAAM,OAAQ,OAAO,KAC1B,IAAMK,EAAO,KAAK,IAAI,EAAG,KAAK,IAAIL,EAAM,OAAS,EAAGG,CAAK,CAAC,EAC1D,OAAAJ,EAAoBC,EAAOK,CAAI,EAC/BL,EAAMK,CAAI,GAAG,MAAM,EACZL,EAAMK,CAAI,GAAK,IACvB,CAEA,SAASC,EAAYC,EAAaC,EAA2E,CAC5G,OAAID,IAAQ,OAAe,QACvBA,IAAQ,MAAc,OAEtBC,IAAgB,aACfD,IAAQ,aAAqB,OAC7BA,IAAQ,YAAoB,OACzB,KAGJA,IAAQ,YAAoB,OAC5BA,IAAQ,UAAkB,OACvB,IACR,CAMO,SAASE,EAAuBC,EAMnB,CACnB,GAAM,CAAE,MAAAV,EAAO,QAAAW,EAAS,IAAAJ,EAAK,YAAAC,EAAc,aAAc,KAAAI,EAAO,EAAK,EAAIF,EACzE,GAAI,CAACV,EAAM,QAAU,CAACW,EAAS,MAAO,CAAE,QAAS,EAAM,EAEvD,IAAMR,EAAQH,EAAM,QAAQW,CAAO,EACnC,GAAIR,EAAQ,EAAG,MAAO,CAAE,QAAS,EAAM,EAEvC,IAAMU,EAAQP,EAAYC,EAAKC,CAAW,EAC1C,GAAI,CAACK,EAAO,MAAO,CAAE,QAAS,EAAM,EAEpC,IAAIR,EAAOF,EACPU,IAAU,QAASR,EAAO,EACrBQ,IAAU,OAAQR,EAAOL,EAAM,OAAS,EACxCa,IAAU,OAAQR,EAAOO,GAAQT,EAAQ,GAAKH,EAAM,OAAS,KAAK,IAAIA,EAAM,OAAS,EAAGG,EAAQ,CAAC,EACrGE,EAAOO,GAAQT,EAAQ,EAAIH,EAAM,QAAUA,EAAM,OAAS,KAAK,IAAI,EAAGG,EAAQ,CAAC,EAEpF,IAAMD,EAAOE,EAAgBJ,EAAOK,CAAI,EACxC,OAAKH,EACE,CAAE,QAAS,GAAM,MAAOG,EAAM,KAAAH,CAAK,EADxB,CAAE,QAAS,EAAM,CAEpC,CDhCO,IAAMY,EADb,CAACC,EAAc,aAAa,CACrB,cAAyBC,CAAmC,CAClE,CAACC,EAAK,CAAE,KAAM,OAAQ,aAAc,EAAG,CAAC,CAAE,MAE1C,CAACC,EAAM,CAAE,KAAM,aAAc,QAAS,GAAM,SAAU,EAAK,CAAC,CAC5D,YAEiB,kBAAoB,KAAK,EAAE,MAAM,IAAKC,GAAUA,GAAS,MAAS,EAE3E,SAA+B,KAE/B,aAA6B,CACpC,OAAO,MAAM,KACZ,KAAK,iBAA8B,sEAAsE,CAC1G,CACD,CAEQ,WAAWC,EAA0C,CAE5D,OADaA,EAAQ,QAAQ,2BAA2B,GAC3C,cAA2B,wBAAwB,GAAK,IACtE,CAEQ,aAAaC,EAAkC,CACtD,OAAO,MAAM,KAAKA,EAAK,iBAA8B,4BAA4B,CAAC,EAAE,OAClFC,GAASA,EAAK,aAAa,eAAe,IAAM,MAClD,CACD,CAES,mBAA0B,CAClC,MAAM,kBAAkB,EACxB,eAAe,IAAMC,EAAoB,KAAK,YAAY,EAAG,CAAC,CAAC,CAChE,CAEQ,cAAcC,EAAc,GAAa,CAChD,GAAI,CAAC,KAAK,SAAU,OACpB,IAAMC,EAAM,KAAK,SAAS,cAA2B,4BAA4B,EAC3EJ,EAAO,KAAK,SAAS,cAA2B,wBAAwB,EAC1EI,GAAKA,EAAI,aAAa,gBAAiB,OAAO,EAC9CJ,IAAMA,EAAK,OAAS,IACxB,KAAK,SAAW,KACZG,GAAeC,GAAKA,EAAI,MAAM,CACnC,CAEQ,SAASL,EAAsBM,EAAwB,QAAe,CAC7E,IAAMC,EAAOP,EAAQ,QAAQ,2BAA2B,EAClDC,EAAO,KAAK,WAAWD,CAAO,EACpC,GAAI,GAACO,GAAQ,CAACN,KAEV,KAAK,UAAY,KAAK,WAAaM,GAAM,KAAK,cAAc,EAEhEP,EAAQ,aAAa,gBAAiB,MAAM,EAC5CC,EAAK,OAAS,GACd,KAAK,SAAWM,EAEZD,IAAU,SAAS,CACtB,IAAME,EAAQ,KAAK,aAAaP,CAAI,EACpCE,EAAoBK,EAAO,CAAC,EAC5BA,EAAM,CAAC,GAAG,MAAM,CACjB,CACD,CAEA,CAACC,EAAQ,CAAE,SAAU,GAAM,KAAM,OAAQ,CAAC,CAC1C,gBAAgBX,EAAyB,CACnC,KAAK,WACL,KAAK,SAASA,EAAM,MAAc,GAAG,KAAK,cAAc,EAC9D,CAEA,CAACW,EAAQ,CAAE,SAAU,uEAAwE,KAAM,SAAU,CAAC,CAC9G,aAAaX,EAA4B,CACxC,IAAMU,EAAQ,KAAK,YAAY,EACzBE,EAAWZ,EAAM,OAAuB,QAAQ,mBAAmB,EACzE,GAAI,CAACY,GAAW,CAACF,EAAM,SAASE,CAAO,EAAG,OAE1C,GAAIZ,EAAM,MAAQ,aAAeA,EAAM,MAAQ,SAAWA,EAAM,MAAQ,IAAK,CACxE,KAAK,WAAWY,CAAO,IAC1BZ,EAAM,eAAe,EACrB,KAAK,SAASY,EAAS,OAAO,GAE/B,MACD,CAEA,GAAIZ,EAAM,MAAQ,SAAU,CAC3BA,EAAM,eAAe,EACrB,KAAK,cAAc,EAAI,EACvB,MACD,CAEA,IAAMa,EAASC,EAAuB,CACrC,MAAAJ,EACA,QAAAE,EACA,IAAKZ,EAAM,IACX,YAAa,YACd,CAAC,EACIa,EAAO,UACZb,EAAM,eAAe,EAGjB,KAAK,WACJ,KAAK,WAAWa,EAAO,IAAI,EAAG,KAAK,SAASA,EAAO,KAAM,OAAO,EAC/D,KAAK,cAAc,GAE1B,CAEA,CAACF,EAAQ,CAAE,SAAU,uEAAwE,KAAM,OAAQ,CAAC,CAC5G,WAAWX,EAAoB,CAC9B,IAAMY,EAAWZ,EAAM,OAAuB,QAAQ,mBAAmB,EACzE,GAAI,CAACY,GAAW,CAAC,KAAK,YAAY,EAAE,SAASA,CAAO,EAAG,OAEvD,GAAI,CAAC,KAAK,WAAWA,CAAO,EAAG,CAC9B,KAAK,cAAc,EACnB,MACD,CAEiBA,EAAQ,aAAa,eAAe,IAAM,OAC7C,KAAK,cAAc,EAC5B,KAAK,SAASA,EAAS,OAAO,CACpC,CAEA,CAACD,EAAQ,CAAE,SAAU,oCAAqC,KAAM,SAAU,CAAC,CAC3E,cAAcX,EAA4B,CACzC,IAAMG,EAAQH,EAAM,OAAuB,QAAQ,eAAe,EAC5DY,EAAWZ,EAAM,OAAuB,QAAQ,mBAAmB,EACzE,GAAI,CAACG,GAAQ,CAACS,GAAW,CAAC,KAAK,SAAST,CAAI,EAAG,OAE/C,IAAMO,EAAQ,KAAK,aAAaP,CAAI,EAEpC,GAAIH,EAAM,MAAQ,SAAU,CAC3BA,EAAM,eAAe,EACrB,KAAK,cAAc,EAAI,EACvB,MACD,CAEA,GAAIA,EAAM,MAAQ,SAAWA,EAAM,MAAQ,IAAK,CAC/CA,EAAM,eAAe,EACrB,KAAK,aAAaY,CAAO,EACzB,MACD,CAEA,GAAIZ,EAAM,MAAQ,aAAeA,EAAM,MAAQ,aAAc,CAC5D,IAAMe,EAAO,KAAK,YAAY,EACxBR,EAAMJ,EAAK,eAAe,cAA2B,4BAA4B,EACvF,GAAI,CAACI,EAAK,OACVP,EAAM,eAAe,EACrB,IAAMa,EAASC,EAAuB,CACrC,MAAOC,EACP,QAASR,EACT,IAAKP,EAAM,IACX,YAAa,YACd,CAAC,EACGa,EAAO,SAAW,KAAK,WAAWA,EAAO,IAAI,GAAG,KAAK,SAASA,EAAO,KAAM,OAAO,EACtF,MACD,CAEeC,EAAuB,CACrC,MAAAJ,EACA,QAAAE,EACA,IAAKZ,EAAM,IACX,YAAa,UACd,CAAC,EACU,SAASA,EAAM,eAAe,CAC1C,CAEA,CAACW,EAAQ,CAAE,SAAU,oCAAqC,KAAM,OAAQ,CAAC,CACzE,gBAAgBX,EAAoB,CACnC,IAAMI,EAAQJ,EAAM,OAAuB,QAAQ,mBAAmB,EAClE,CAACI,GAAQ,CAAC,KAAK,SAASA,CAAI,GAE5BA,EAAK,eAAe,aAAa,MAAM,IAAM,QACjD,KAAK,aAAaA,CAAI,CACvB,CAEQ,aAAaA,EAAyB,CAC7C,IAAMY,EAAQZ,EAAK,aAAa,YAAY,GAAKA,EAAK,aAAa,KAAK,GAAK,GAC7E,KAAK,YAAY,KAAK,CAAE,MAAAY,CAAM,CAAC,EAC/B,KAAK,cAAc,EAAI,CACxB,CAES,QAAS,CACjB,OACC,oBAAC,OAAI,MAAM,cAAc,WAAS,UAAU,KAAK,UAAU,aAAY,KAAK,mBAC3E,oBAAC,WAAK,CACP,CAEF,CACD,EEzNA,IAAMC,EAAuB,OAAO,IAAI,yCAAyC,EAC3EC,EAAwB,OAAO,IAAI,0CAA0C,EAC7EC,EAAwB,OAAO,IAAI,0CAA0C,EAQnF,SAASC,EACRC,EACAC,EAIO,CACPD,EAAKJ,CAAoB,EAAIK,EAE7B,IAAMC,EAAYD,EAAQJ,CAAqB,EAC3CK,IACHF,EAAKH,CAAqB,EAAIK,EAC9BF,EAAKF,CAAqB,EAAIG,EAAQH,CAAqB,GAAKG,EAAQ,KAE1E,CAGO,SAASE,EACfF,EAIAG,EAC8B,CAC9B,IAAMJ,GAASK,GAAgBD,EAAOC,CAAI,GAC1C,OAAAN,EAAkBC,EAAMC,CAAO,EACxBD,CACR,CClCA,MAAO,gBASA,IAAMM,EAAaC,EACzBD,EACA,CAAC,CAAE,KAAAE,EAAM,MAAAC,EAAO,MAAAC,CAAM,IACrB,oBAAC,eAAY,KAAMF,EAAM,MAAOC,GAC9BC,EAAM,IAAKC,GACX,oBAAC,OAAI,MAAM,oBAAoB,WAAS,gBACvC,oBAAC,UACA,KAAK,SACL,MAAM,oBACN,KAAK,WACL,SAAU,GACV,gBAAeA,EAAK,OAAO,OAAS,OAAS,OAC7C,gBAAeA,EAAK,OAAO,OAAS,QAAU,QAE7CA,EAAK,KACP,EACCA,EAAK,OAAO,OACZ,oBAAC,OAAI,MAAM,oBAAoB,KAAK,OAAO,OAAM,IAC/CA,EAAK,MAAM,IAAKC,GAChB,oBAAC,UACA,KAAK,SACL,MAAM,yBACN,KAAK,WACL,aAAYA,EAAM,GAClB,SAAU,IAETA,EAAM,KACR,CACA,CACF,EACG,IACL,CACA,CACF,CAEF",
|
|
6
6
|
"names": ["RadiantElement", "customElement", "event", "onEvent", "prop", "applyRovingTabindex", "items", "activeIndex", "item", "index", "focusRovingItem", "next", "deltaForKey", "key", "orientation", "navigateRovingTabindex", "options", "current", "wrap", "delta", "RuiMenubar", "customElement", "RadiantElement", "prop", "event", "label", "topItem", "menu", "item", "applyRovingTabindex", "returnFocus", "top", "focus", "root", "items", "onEvent", "current", "result", "navigateRovingTabindex", "tops", "value", "RADIANT_VIEW_ELEMENT", "RADIANT_SCRIPT_MODULE", "RADIANT_SCRIPT_EXPORT", "linkViewToElement", "view", "element", "ssrModule", "defineRadiantView", "render", "args", "RuiMenubar", "defineRadiantView", "slot", "label", "items", "item", "child"]
|
|
7
7
|
}
|
|
@@ -66,7 +66,7 @@ rui-menubar {
|
|
|
66
66
|
top: 100%;
|
|
67
67
|
left: 0px;
|
|
68
68
|
z-index: var(--z-index-dropdown, var(--z-dropdown));
|
|
69
|
-
margin-top: var(--spacing
|
|
69
|
+
margin-top: calc(var(--spacing, 0.25rem) * 2.5);
|
|
70
70
|
min-width: calc(var(--spacing, 0.25rem) * 40);
|
|
71
71
|
border-radius: var(--radius-container, var(--radius-container));
|
|
72
72
|
border-style: var(--tw-border-style);
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { RuiPlacement } from './placement';
|
|
2
|
+
export type FloatingSize = {
|
|
3
|
+
width: number;
|
|
4
|
+
height: number;
|
|
5
|
+
};
|
|
6
|
+
export type FloatingCoords = {
|
|
7
|
+
x: number;
|
|
8
|
+
y: number;
|
|
9
|
+
};
|
|
10
|
+
export type FloatingViewport = {
|
|
11
|
+
width: number;
|
|
12
|
+
height: number;
|
|
13
|
+
padding: number;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Computes fixed-position coords for a floating surface.
|
|
17
|
+
*
|
|
18
|
+
* @remarks
|
|
19
|
+
* Prefers `placement`, then flips on the primary axis when the opposite side
|
|
20
|
+
* has more free space. Clamps only the cross-axis so the floating surface
|
|
21
|
+
* never slides onto its anchor.
|
|
22
|
+
*/
|
|
23
|
+
export declare function computeFloatingCoords(anchor: DOMRect, size: FloatingSize, placement: RuiPlacement, gap: number, viewport?: FloatingViewport): FloatingCoords;
|
|
24
|
+
/** Applies fixed coords to a floating element. */
|
|
25
|
+
export declare function applyFloatingPosition(anchor: HTMLElement, floating: HTMLElement, placement: RuiPlacement, gap: number): void;
|
|
26
|
+
export type AttachFloatingOptions = {
|
|
27
|
+
anchor: HTMLElement;
|
|
28
|
+
floating: HTMLElement;
|
|
29
|
+
getPlacement: () => RuiPlacement;
|
|
30
|
+
gap: number;
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Positions on attach and keeps the floating element updated on scroll/resize.
|
|
34
|
+
*
|
|
35
|
+
* @returns Cleanup that removes listeners and observers.
|
|
36
|
+
*/
|
|
37
|
+
export declare function attachFloating({ anchor, floating, getPlacement, gap }: AttachFloatingOptions): () => void;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{RadiantElement as
|
|
1
|
+
import{RadiantElement as I,bound as y,customElement as L,onEvent as h,onUpdated as D,prop as c,query as V}from"@ecopages/radiant";var F={top:"bottom",bottom:"top",left:"right",right:"left"},S=8;function C(t){let e=t.indexOf("-");if(e===-1)return{side:t,align:"center"};let o=t.slice(0,e),n=t.slice(e+1);return{side:o,align:n==="start"||n==="end"?n:"center"}}function b(t){return t==="top"||t==="bottom"?"y":"x"}function f(t,e,o,n,i){let{width:r,height:s}=e,l=0,a=0;switch(o){case"top":a=t.top-s-i;break;case"bottom":a=t.bottom+i;break;case"left":l=t.left-r-i;break;case"right":l=t.right+i;break}return b(o)==="y"?n==="start"?l=t.left:n==="end"?l=t.right-r:l=t.left+(t.width-r)/2:n==="start"?a=t.top:n==="end"?a=t.bottom-s:a=t.top+(t.height-s)/2,{x:l,y:a}}function M(t,e,o,n){let{padding:i,width:r,height:s}=n;return o==="top"?Math.max(0,i-t.y):o==="bottom"?Math.max(0,t.y+e.height-(s-i)):o==="left"?Math.max(0,i-t.x):Math.max(0,t.x+e.width-(r-i))}function T(t,e,o,n){let{padding:i,width:r,height:s}=n;return o==="top"?t.top-i-e.height:o==="bottom"?s-i-e.height-t.bottom:o==="left"?t.left-i-e.width:r-i-e.width-t.right}function _(t,e,o,n){let{padding:i,width:r,height:s}=n;if(b(o)==="y"){let a=Math.max(i,r-i-e.width);return{x:Math.min(Math.max(t.x,i),a),y:t.y}}let l=Math.max(i,s-i-e.height);return{x:t.x,y:Math.min(Math.max(t.y,i),l)}}function O(t,e,o,n,i={width:typeof window>"u"?0:window.innerWidth,height:typeof window>"u"?0:window.innerHeight,padding:S}){let{side:r,align:s}=C(o),l=f(t,e,r,s,n),a=M(l,e,r,i),m=r,g=l;if(a>0){let p=F[r],A=f(t,e,p,s,n),P=T(t,e,r,i);T(t,e,p,i)>P&&(m=p,g=A)}return _(g,e,m,i)}function u(t,e,o,n){let i=O(t.getBoundingClientRect(),{width:e.offsetWidth,height:e.offsetHeight},o,n);Object.assign(e.style,{position:"fixed",left:`${i.x}px`,top:`${i.y}px`,right:"auto",bottom:"auto",visibility:"visible"})}function v({anchor:t,floating:e,getPlacement:o,gap:n}){let i=()=>{e.hidden||u(t,e,o(),n)};i(),window.addEventListener("resize",i),window.addEventListener("scroll",i,!0);let r=new ResizeObserver(i);return r.observe(t),r.observe(e),()=>{window.removeEventListener("resize",i),window.removeEventListener("scroll",i,!0),r.disconnect()}}var w=8,d=@L("rui-tooltip")class extends I{@c({type:String,defaultValue:""})content;@c({type:String,defaultValue:"top"})placement;@c({type:Number,defaultValue:200})delay;@V({ref:"tooltip"})tooltipTarget;open=!1;showTimer=null;hideTimer=null;tooltipId=`rui-tooltip-${Math.random().toString(36).slice(2,9)}`;describedEl=null;cleanupFloating=null;connectedCallback(){super.connectedCallback(),this.addEventListener("pointerenter",this.onPointerEnter),this.addEventListener("pointerleave",this.onPointerLeave),queueMicrotask(()=>this.wireTrigger())}disconnectedCallback(){this.clearTimers(),this.teardownFloating(),this.removeEventListener("pointerenter",this.onPointerEnter),this.removeEventListener("pointerleave",this.onPointerLeave),this.describedEl?.removeAttribute("aria-describedby"),super.disconnectedCallback()}clearTimers(){this.showTimer&&clearTimeout(this.showTimer),this.hideTimer&&clearTimeout(this.hideTimer),this.showTimer=null,this.hideTimer=null}teardownFloating(){this.cleanupFloating?.(),this.cleanupFloating=null}getAnchor(){return this.querySelector('button, a[href], input, select, textarea, [tabindex]:not([tabindex="-1"])')??this}wireTrigger(){this.describedEl?.removeAttribute("aria-describedby"),this.describedEl=this.getAnchor(),this.describedEl.setAttribute("aria-describedby",this.tooltipId)}attachFloating(){this.tooltipTarget&&(this.teardownFloating(),this.cleanupFloating=v({anchor:this.getAnchor(),floating:this.tooltipTarget,getPlacement:()=>this.placement,gap:w}))}@D(["content","placement"])onDescribedPropsUpdated(){this.wireTrigger(),this.open&&this.tooltipTarget&&u(this.getAnchor(),this.tooltipTarget,this.placement,w)}setOpen(e){this.clearTimers(),this.open=e,this.tooltipTarget&&(this.tooltipTarget.hidden=!e,e?this.attachFloating():this.teardownFloating())}scheduleShow(){if(this.clearTimers(),this.delay<=0){this.setOpen(!0);return}this.showTimer=setTimeout(()=>this.setOpen(!0),this.delay)}scheduleHide(){this.clearTimers(),this.hideTimer=setTimeout(()=>this.setOpen(!1),80)}@h({document:!0,type:"keydown"})onDocumentKeydown(e){!this.open||e.key!=="Escape"||this.setOpen(!1)}@h({type:"focusin",selector:".rui-tooltip__trigger"})onFocusIn(){this.scheduleShow()}@h({type:"focusout",selector:".rui-tooltip__trigger"})onFocusOut(e){let o=e.relatedTarget;o&&this.contains(o)||this.scheduleHide()}@y onPointerEnter(){this.scheduleShow()}@y onPointerLeave(){this.scheduleHide()}render(){return React.createElement("span",{class:"rui-tooltip"},React.createElement("span",{class:"rui-tooltip__trigger"},React.createElement("slot",null)),React.createElement("span",{"data-ref":"tooltip",id:this.tooltipId,class:"rui-tooltip__content rui-floating",role:"tooltip",hidden:!0},this.$.content))}};var k=Symbol.for("@ecopages/storybook-radiant.viewElement"),R=Symbol.for("@ecopages/storybook-radiant.scriptModule"),E=Symbol.for("@ecopages/storybook-radiant.scriptExport");function N(t,e){t[k]=e;let o=e[R];o&&(t[R]=o,t[E]=e[E]??e.name)}function x(t,e){let o=(n=>e(n));return N(o,t),o}import"./tooltip.css";var H=x(d,({slot:t,content:e,placement:o,delay:n,children:i})=>React.createElement("rui-tooltip",{slot:t,content:e,placement:o,delay:n},i));export{H as RuiTooltip,d as RuiTooltipElement};
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["../../../../src/components/ui/tooltip/tooltip.script.tsx", "../../../../src/lib/radiant-view.ts", "../../../../src/components/ui/tooltip/tooltip.tsx"],
|
|
4
|
-
"sourcesContent": ["import { RadiantElement, bound, customElement, onEvent, onUpdated, prop, query } from '@ecopages/radiant';\nimport { type Placement, autoUpdate, computePosition, flip, offset, shift } from '@floating-ui/dom';\n\nexport type RuiTooltipProps = {\n\t/** Accessible description shown in the tooltip. */\n\tcontent?: string;\n\t/** Floating-ui placement. Default: `top`. */\n\tplacement?: Placement;\n\t/** Delay in ms before showing on hover/focus. Default: `200`. */\n\tdelay?: number;\n};\n\ntype RuiTooltipBindings = {\n\tcontent: string;\n};\n\n/**\n * `<rui-tooltip>` \u2014 a popup that describes a trigger on hover or focus.\n *\n * Implements the WAI-ARIA APG Tooltip pattern: the popup has `role=\"tooltip\"`\n * and the focusable trigger references it with `aria-describedby`. The tooltip\n * itself never receives focus.\n *\n * Event wiring:\n * - Bubbling events (`focusin` / `focusout`, document `keydown`) use `@onEvent`.\n * - `pointerenter` / `pointerleave` are bound directly on the host \u2014 they do not\n * bubble, so `@onEvent` delegation cannot see them on the slotted trigger.\n *\n * @see https://www.w3.org/WAI/ARIA/apg/patterns/tooltip/\n *\n * @element rui-tooltip\n * @slot - The trigger element (typically a button or focusable control).\n */\n@customElement('rui-tooltip')\nexport class RuiTooltip extends RadiantElement<RuiTooltipBindings> {\n\t@prop({ type: String, defaultValue: '' }) content: string;\n\t@prop({ type: String, defaultValue: 'top' }) placement: Placement;\n\t@prop({ type: Number, defaultValue: 200 }) delay: number;\n\n\t@query({ ref: 'tooltip' }) tooltipTarget: HTMLElement;\n\n\t/**\n\t * Plain (non-reactive) field, not a JSX binding target: `hidden` is toggled\n\t * imperatively in `setOpen()` rather than bound, since it flips on every\n\t * hover/focus in/out \u2014 high-frequency enough that a plain synchronous DOM\n\t * write is preferable to routing it through the reactive binding system.\n\t */\n\tprivate open = false;\n\n\tprivate showTimer: ReturnType<typeof setTimeout> | null = null;\n\tprivate hideTimer: ReturnType<typeof setTimeout> | null = null;\n\tprivate cleanup: ReturnType<typeof autoUpdate> | null = null;\n\tprivate tooltipId = `rui-tooltip-${Math.random().toString(36).slice(2, 9)}`;\n\tprivate describedEl: HTMLElement | null = null;\n\n\t/**\n\t * Pointer enter/leave must be host listeners: those events do not bubble, and\n\t * `@onEvent` only supports delegated (bubbling) listeners.\n\t */\n\toverride connectedCallback(): void {\n\t\tsuper.connectedCallback();\n\t\tthis.addEventListener('pointerenter', this.onPointerEnter);\n\t\tthis.addEventListener('pointerleave', this.onPointerLeave);\n\t\tqueueMicrotask(() => this.wireTrigger());\n\t}\n\n\toverride disconnectedCallback(): void {\n\t\tthis.clearTimers();\n\t\tthis.teardownFloating();\n\t\tthis.removeEventListener('pointerenter', this.onPointerEnter);\n\t\tthis.removeEventListener('pointerleave', this.onPointerLeave);\n\t\tthis.describedEl?.removeAttribute('aria-describedby');\n\t\tsuper.disconnectedCallback();\n\t}\n\n\tprivate clearTimers(): void {\n\t\tif (this.showTimer) clearTimeout(this.showTimer);\n\t\tif (this.hideTimer) clearTimeout(this.hideTimer);\n\t\tthis.showTimer = null;\n\t\tthis.hideTimer = null;\n\t}\n\n\tprivate teardownFloating(): void {\n\t\tthis.cleanup?.();\n\t\tthis.cleanup = null;\n\t}\n\n\tprivate getAnchor(): HTMLElement {\n\t\treturn (\n\t\t\t(this.querySelector(\n\t\t\t\t'button, a[href], input, select, textarea, [tabindex]:not([tabindex=\"-1\"])',\n\t\t\t) as HTMLElement | null) ?? this\n\t\t);\n\t}\n\n\tprivate wireTrigger(): void {\n\t\tthis.describedEl?.removeAttribute('aria-describedby');\n\t\tthis.describedEl = this.getAnchor();\n\t\tthis.describedEl.setAttribute('aria-describedby', this.tooltipId);\n\t}\n\n\t@bound\n\tupdatePosition(): void {\n\t\tif (!this.tooltipTarget || !this.open) return;\n\t\tconst anchor = this.getAnchor();\n\t\tcomputePosition(anchor, this.tooltipTarget, {\n\t\t\tplacement: this.placement,\n\t\t\tmiddleware: [offset(8), flip(), shift({ padding: 8 })],\n\t\t}).then(({ x, y }) => {\n\t\t\tObject.assign(this.tooltipTarget.style, {\n\t\t\t\tleft: `${x}px`,\n\t\t\t\ttop: `${y}px`,\n\t\t\t});\n\t\t});\n\t}\n\n\t@onUpdated(['content', 'placement'])\n\tonDescribedPropsUpdated(): void {\n\t\tthis.wireTrigger();\n\t\tif (this.open) this.updatePosition();\n\t}\n\n\tprivate setOpen(next: boolean): void {\n\t\tthis.clearTimers();\n\t\tthis.open = next;\n\t\tif (!this.tooltipTarget) return;\n\t\tthis.tooltipTarget.hidden = !next;\n\n\t\tif (next) {\n\t\t\tthis.teardownFloating();\n\t\t\tconst anchor = this.getAnchor();\n\t\t\tthis.cleanup = autoUpdate(anchor, this.tooltipTarget, this.updatePosition);\n\t\t\tthis.updatePosition();\n\t\t} else {\n\t\t\tthis.teardownFloating();\n\t\t}\n\t}\n\n\tprivate scheduleShow(): void {\n\t\tthis.clearTimers();\n\t\tif (this.delay <= 0) {\n\t\t\tthis.setOpen(true);\n\t\t\treturn;\n\t\t}\n\t\tthis.showTimer = setTimeout(() => this.setOpen(true), this.delay);\n\t}\n\n\tprivate scheduleHide(): void {\n\t\tthis.clearTimers();\n\t\tthis.hideTimer = setTimeout(() => this.setOpen(false), 80);\n\t}\n\n\t@onEvent({ document: true, type: 'keydown' })\n\tonDocumentKeydown(event: KeyboardEvent): void {\n\t\tif (!this.open || event.key !== 'Escape') return;\n\t\tthis.setOpen(false);\n\t}\n\n\t@onEvent({ type: 'focusin', selector: '.rui-tooltip__trigger' })\n\tonFocusIn(): void {\n\t\tthis.scheduleShow();\n\t}\n\n\t@onEvent({ type: 'focusout', selector: '.rui-tooltip__trigger' })\n\tonFocusOut(event: FocusEvent): void {\n\t\tconst next = event.relatedTarget as Node | null;\n\t\tif (next && this.contains(next)) return;\n\t\tthis.scheduleHide();\n\t}\n\n\t@bound\n\tonPointerEnter(): void {\n\t\tthis.scheduleShow();\n\t}\n\n\t@bound\n\tonPointerLeave(): void {\n\t\tthis.scheduleHide();\n\t}\n\n\toverride render() {\n\t\treturn (\n\t\t\t<span class=\"rui-tooltip\">\n\t\t\t\t<span class=\"rui-tooltip__trigger\">\n\t\t\t\t\t<slot></slot>\n\t\t\t\t</span>\n\t\t\t\t<span data-ref=\"tooltip\" id={this.tooltipId} class=\"rui-tooltip__content\" role=\"tooltip\" hidden>\n\t\t\t\t\t{this.$.content}\n\t\t\t\t</span>\n\t\t\t</span>\n\t\t);\n\t}\n}\n", "import type { JsxRenderable } from '@ecopages/jsx';\n\nconst RADIANT_VIEW_ELEMENT = Symbol.for('@ecopages/storybook-radiant.viewElement');\nconst RADIANT_SCRIPT_MODULE = Symbol.for('@ecopages/storybook-radiant.scriptModule');\nconst RADIANT_SCRIPT_EXPORT = Symbol.for('@ecopages/storybook-radiant.scriptExport');\n\nexport type RadiantViewComponent<TArgs = unknown> = ((args: TArgs) => JsxRenderable) & {\n\t[RADIANT_VIEW_ELEMENT]?: CustomElementConstructor;\n\t[RADIANT_SCRIPT_MODULE]?: string;\n\t[RADIANT_SCRIPT_EXPORT]?: string;\n};\n\nfunction linkViewToElement<TArgs>(\n\tview: RadiantViewComponent<TArgs>,\n\telement: CustomElementConstructor & {\n\t\t[RADIANT_SCRIPT_MODULE]?: string;\n\t\t[RADIANT_SCRIPT_EXPORT]?: string;\n\t},\n): void {\n\tview[RADIANT_VIEW_ELEMENT] = element;\n\n\tconst ssrModule = element[RADIANT_SCRIPT_MODULE];\n\tif (ssrModule) {\n\t\tview[RADIANT_SCRIPT_MODULE] = ssrModule;\n\t\tview[RADIANT_SCRIPT_EXPORT] = element[RADIANT_SCRIPT_EXPORT] ?? element.name;\n\t}\n}\n\n/** Link a JSX view export to its RadiantElement class (same symbols as the Storybook framework). */\nexport function defineRadiantView<TArgs>(\n\telement: CustomElementConstructor & {\n\t\t[RADIANT_SCRIPT_MODULE]?: string;\n\t\t[RADIANT_SCRIPT_EXPORT]?: string;\n\t},\n\trender: (args: TArgs) => JsxRenderable,\n): RadiantViewComponent<TArgs> {\n\tconst view = ((args: TArgs) => render(args)) as RadiantViewComponent<TArgs>;\n\tlinkViewToElement(view, element);\n\treturn view;\n}\n\n/** Re-copy SSR metadata from the linked element (e.g. after HMR). */\nexport function syncRadiantViewMetadata(view: RadiantViewComponent): void {\n\tconst element = view[RADIANT_VIEW_ELEMENT];\n\tif (element) {\n\t\tlinkViewToElement(view, element);\n\t}\n}\n", "import type { WithChildren, RadiantSlotProps } from '../../../types';\nimport { defineRadiantView } from '../../../lib/radiant-view';\nimport type { RuiTooltipProps } from './tooltip.script';\nimport { RuiTooltip as RuiTooltipElement } from './tooltip.script';\nimport './tooltip.css';\n\nexport const RuiTooltip = defineRadiantView(\n\tRuiTooltipElement,\n\t({ slot, content, placement, delay, children }: WithChildren<RuiTooltipProps & RadiantSlotProps>) => (\n\t\t<rui-tooltip slot={slot} content={content} placement={placement} delay={delay}>\n\t\t\t{children}\n\t\t</rui-tooltip>\n\t),\n);\n"],
|
|
5
|
-
"mappings": "AAAA,OAAS,kBAAAA,EAAgB,SAAAC,EAAO,iBAAAC,EAAe,WAAAC,EAAS,aAAAC,EAAW,QAAAC,EAAM,SAAAC,MAAa,
|
|
6
|
-
"names": ["RadiantElement", "bound", "customElement", "onEvent", "onUpdated", "prop", "query", "
|
|
3
|
+
"sources": ["../../../../src/components/ui/tooltip/tooltip.script.tsx", "../../../../src/components/ui/shared/floating-position.ts", "../../../../src/lib/radiant-view.ts", "../../../../src/components/ui/tooltip/tooltip.tsx"],
|
|
4
|
+
"sourcesContent": ["import { RadiantElement, bound, customElement, onEvent, onUpdated, prop, query } from '@ecopages/radiant';\nimport { applyFloatingPosition, attachFloating } from '../shared/floating-position';\nimport type { RuiPlacement } from '../shared/placement';\n\nexport type RuiTooltipProps = {\n\t/** Accessible description shown in the tooltip. */\n\tcontent?: string;\n\t/** Placement of the tooltip surface relative to its anchor. Default: `top`. */\n\tplacement?: RuiPlacement;\n\t/** Delay in ms before showing on hover/focus. Default: `200`. */\n\tdelay?: number;\n};\n\ntype RuiTooltipBindings = {\n\tcontent: string;\n};\n\nconst TOOLTIP_GAP = 8;\n\n/**\n * `<rui-tooltip>` \u2014 a popup that describes a trigger on hover or focus.\n *\n * Implements the WAI-ARIA APG Tooltip pattern: the popup has `role=\"tooltip\"`\n * and the focusable trigger references it with `aria-describedby`. The tooltip\n * itself never receives focus.\n *\n * Event wiring:\n * - Bubbling events (`focusin` / `focusout`, document `keydown`) use `@onEvent`.\n * - `pointerenter` / `pointerleave` are bound directly on the host \u2014 they do not\n * bubble, so `@onEvent` delegation cannot see them on the slotted trigger.\n *\n * @see https://www.w3.org/WAI/ARIA/apg/patterns/tooltip/\n *\n * @element rui-tooltip\n * @slot - The trigger element (typically a button or focusable control).\n */\n@customElement('rui-tooltip')\nexport class RuiTooltip extends RadiantElement<RuiTooltipBindings> {\n\t@prop({ type: String, defaultValue: '' }) content: string;\n\t@prop({ type: String, defaultValue: 'top' }) placement: RuiPlacement;\n\t@prop({ type: Number, defaultValue: 200 }) delay: number;\n\n\t@query({ ref: 'tooltip' }) tooltipTarget: HTMLElement;\n\n\t/**\n\t * Plain (non-reactive) field, not a JSX binding target: `hidden` is toggled\n\t * imperatively in `setOpen()` rather than bound, since it flips on every\n\t * hover/focus in/out \u2014 high-frequency enough that a plain synchronous DOM\n\t * write is preferable to routing it through the reactive binding system.\n\t */\n\tprivate open = false;\n\n\tprivate showTimer: ReturnType<typeof setTimeout> | null = null;\n\tprivate hideTimer: ReturnType<typeof setTimeout> | null = null;\n\tprivate tooltipId = `rui-tooltip-${Math.random().toString(36).slice(2, 9)}`;\n\tprivate describedEl: HTMLElement | null = null;\n\tprivate cleanupFloating: (() => void) | null = null;\n\n\t/**\n\t * Pointer enter/leave must be host listeners: those events do not bubble, and\n\t * `@onEvent` only supports delegated (bubbling) listeners.\n\t */\n\toverride connectedCallback(): void {\n\t\tsuper.connectedCallback();\n\t\tthis.addEventListener('pointerenter', this.onPointerEnter);\n\t\tthis.addEventListener('pointerleave', this.onPointerLeave);\n\t\tqueueMicrotask(() => this.wireTrigger());\n\t}\n\n\toverride disconnectedCallback(): void {\n\t\tthis.clearTimers();\n\t\tthis.teardownFloating();\n\t\tthis.removeEventListener('pointerenter', this.onPointerEnter);\n\t\tthis.removeEventListener('pointerleave', this.onPointerLeave);\n\t\tthis.describedEl?.removeAttribute('aria-describedby');\n\t\tsuper.disconnectedCallback();\n\t}\n\n\tprivate clearTimers(): void {\n\t\tif (this.showTimer) clearTimeout(this.showTimer);\n\t\tif (this.hideTimer) clearTimeout(this.hideTimer);\n\t\tthis.showTimer = null;\n\t\tthis.hideTimer = null;\n\t}\n\n\tprivate teardownFloating(): void {\n\t\tthis.cleanupFloating?.();\n\t\tthis.cleanupFloating = null;\n\t}\n\n\tprivate getAnchor(): HTMLElement {\n\t\treturn (\n\t\t\t(this.querySelector(\n\t\t\t\t'button, a[href], input, select, textarea, [tabindex]:not([tabindex=\"-1\"])',\n\t\t\t) as HTMLElement | null) ?? this\n\t\t);\n\t}\n\n\tprivate wireTrigger(): void {\n\t\tthis.describedEl?.removeAttribute('aria-describedby');\n\t\tthis.describedEl = this.getAnchor();\n\t\tthis.describedEl.setAttribute('aria-describedby', this.tooltipId);\n\t}\n\n\tprivate attachFloating(): void {\n\t\tif (!this.tooltipTarget) return;\n\t\tthis.teardownFloating();\n\t\tthis.cleanupFloating = attachFloating({\n\t\t\tanchor: this.getAnchor(),\n\t\t\tfloating: this.tooltipTarget,\n\t\t\tgetPlacement: () => this.placement,\n\t\t\tgap: TOOLTIP_GAP,\n\t\t});\n\t}\n\n\t@onUpdated(['content', 'placement'])\n\tonDescribedPropsUpdated(): void {\n\t\tthis.wireTrigger();\n\t\tif (this.open && this.tooltipTarget) {\n\t\t\tapplyFloatingPosition(this.getAnchor(), this.tooltipTarget, this.placement, TOOLTIP_GAP);\n\t\t}\n\t}\n\n\tprivate setOpen(next: boolean): void {\n\t\tthis.clearTimers();\n\t\tthis.open = next;\n\t\tif (!this.tooltipTarget) return;\n\t\tthis.tooltipTarget.hidden = !next;\n\n\t\tif (next) this.attachFloating();\n\t\telse this.teardownFloating();\n\t}\n\n\tprivate scheduleShow(): void {\n\t\tthis.clearTimers();\n\t\tif (this.delay <= 0) {\n\t\t\tthis.setOpen(true);\n\t\t\treturn;\n\t\t}\n\t\tthis.showTimer = setTimeout(() => this.setOpen(true), this.delay);\n\t}\n\n\tprivate scheduleHide(): void {\n\t\tthis.clearTimers();\n\t\tthis.hideTimer = setTimeout(() => this.setOpen(false), 80);\n\t}\n\n\t@onEvent({ document: true, type: 'keydown' })\n\tonDocumentKeydown(event: KeyboardEvent): void {\n\t\tif (!this.open || event.key !== 'Escape') return;\n\t\tthis.setOpen(false);\n\t}\n\n\t@onEvent({ type: 'focusin', selector: '.rui-tooltip__trigger' })\n\tonFocusIn(): void {\n\t\tthis.scheduleShow();\n\t}\n\n\t@onEvent({ type: 'focusout', selector: '.rui-tooltip__trigger' })\n\tonFocusOut(event: FocusEvent): void {\n\t\tconst next = event.relatedTarget as Node | null;\n\t\tif (next && this.contains(next)) return;\n\t\tthis.scheduleHide();\n\t}\n\n\t@bound\n\tonPointerEnter(): void {\n\t\tthis.scheduleShow();\n\t}\n\n\t@bound\n\tonPointerLeave(): void {\n\t\tthis.scheduleHide();\n\t}\n\n\toverride render() {\n\t\treturn (\n\t\t\t<span class=\"rui-tooltip\">\n\t\t\t\t<span class=\"rui-tooltip__trigger\">\n\t\t\t\t\t<slot></slot>\n\t\t\t\t</span>\n\t\t\t\t<span\n\t\t\t\t\tdata-ref=\"tooltip\"\n\t\t\t\t\tid={this.tooltipId}\n\t\t\t\t\tclass=\"rui-tooltip__content rui-floating\"\n\t\t\t\t\trole=\"tooltip\"\n\t\t\t\t\thidden\n\t\t\t\t>\n\t\t\t\t\t{this.$.content}\n\t\t\t\t</span>\n\t\t\t</span>\n\t\t);\n\t}\n}\n", "import type { RuiPlacement } from './placement';\n\ntype Side = 'top' | 'right' | 'bottom' | 'left';\ntype Align = 'start' | 'center' | 'end';\n\nexport type FloatingSize = { width: number; height: number };\nexport type FloatingCoords = { x: number; y: number };\nexport type FloatingViewport = { width: number; height: number; padding: number };\n\nconst OPPOSITE: Record<Side, Side> = {\n\ttop: 'bottom',\n\tbottom: 'top',\n\tleft: 'right',\n\tright: 'left',\n};\n\nconst DEFAULT_VIEWPORT_PADDING = 8;\n\nfunction parsePlacement(placement: RuiPlacement): { side: Side; align: Align } {\n\tconst dash = placement.indexOf('-');\n\tif (dash === -1) return { side: placement as Side, align: 'center' };\n\tconst side = placement.slice(0, dash) as Side;\n\tconst align = placement.slice(dash + 1);\n\treturn { side, align: align === 'start' || align === 'end' ? align : 'center' };\n}\n\nfunction primaryAxis(side: Side): 'x' | 'y' {\n\treturn side === 'top' || side === 'bottom' ? 'y' : 'x';\n}\n\nfunction coordsFor(anchor: DOMRect, size: FloatingSize, side: Side, align: Align, gap: number): FloatingCoords {\n\tconst { width, height } = size;\n\tlet x = 0;\n\tlet y = 0;\n\n\tswitch (side) {\n\t\tcase 'top':\n\t\t\ty = anchor.top - height - gap;\n\t\t\tbreak;\n\t\tcase 'bottom':\n\t\t\ty = anchor.bottom + gap;\n\t\t\tbreak;\n\t\tcase 'left':\n\t\t\tx = anchor.left - width - gap;\n\t\t\tbreak;\n\t\tcase 'right':\n\t\t\tx = anchor.right + gap;\n\t\t\tbreak;\n\t}\n\n\tif (primaryAxis(side) === 'y') {\n\t\tif (align === 'start') x = anchor.left;\n\t\telse if (align === 'end') x = anchor.right - width;\n\t\telse x = anchor.left + (anchor.width - width) / 2;\n\t} else {\n\t\tif (align === 'start') y = anchor.top;\n\t\telse if (align === 'end') y = anchor.bottom - height;\n\t\telse y = anchor.top + (anchor.height - height) / 2;\n\t}\n\n\treturn { x, y };\n}\n\nfunction overflowOnPrimary(coords: FloatingCoords, size: FloatingSize, side: Side, viewport: FloatingViewport): number {\n\tconst { padding, width: vw, height: vh } = viewport;\n\tif (side === 'top') return Math.max(0, padding - coords.y);\n\tif (side === 'bottom') return Math.max(0, coords.y + size.height - (vh - padding));\n\tif (side === 'left') return Math.max(0, padding - coords.x);\n\treturn Math.max(0, coords.x + size.width - (vw - padding));\n}\n\nfunction freeSpaceOnSide(anchor: DOMRect, size: FloatingSize, side: Side, viewport: FloatingViewport): number {\n\tconst { padding, width: vw, height: vh } = viewport;\n\tif (side === 'top') return anchor.top - padding - size.height;\n\tif (side === 'bottom') return vh - padding - size.height - anchor.bottom;\n\tif (side === 'left') return anchor.left - padding - size.width;\n\treturn vw - padding - size.width - anchor.right;\n}\n\nfunction clampCrossAxis(\n\tcoords: FloatingCoords,\n\tsize: FloatingSize,\n\tside: Side,\n\tviewport: FloatingViewport,\n): FloatingCoords {\n\tconst { padding, width: vw, height: vh } = viewport;\n\tif (primaryAxis(side) === 'y') {\n\t\tconst maxX = Math.max(padding, vw - padding - size.width);\n\t\treturn { x: Math.min(Math.max(coords.x, padding), maxX), y: coords.y };\n\t}\n\tconst maxY = Math.max(padding, vh - padding - size.height);\n\treturn { x: coords.x, y: Math.min(Math.max(coords.y, padding), maxY) };\n}\n\n/**\n * Computes fixed-position coords for a floating surface.\n *\n * @remarks\n * Prefers `placement`, then flips on the primary axis when the opposite side\n * has more free space. Clamps only the cross-axis so the floating surface\n * never slides onto its anchor.\n */\nexport function computeFloatingCoords(\n\tanchor: DOMRect,\n\tsize: FloatingSize,\n\tplacement: RuiPlacement,\n\tgap: number,\n\tviewport: FloatingViewport = {\n\t\twidth: typeof window === 'undefined' ? 0 : window.innerWidth,\n\t\theight: typeof window === 'undefined' ? 0 : window.innerHeight,\n\t\tpadding: DEFAULT_VIEWPORT_PADDING,\n\t},\n): FloatingCoords {\n\tconst { side: preferred, align } = parsePlacement(placement);\n\tconst preferredCoords = coordsFor(anchor, size, preferred, align, gap);\n\tconst preferredOverflow = overflowOnPrimary(preferredCoords, size, preferred, viewport);\n\n\tlet side = preferred;\n\tlet coords = preferredCoords;\n\n\tif (preferredOverflow > 0) {\n\t\tconst flipped = OPPOSITE[preferred];\n\t\tconst flippedCoords = coordsFor(anchor, size, flipped, align, gap);\n\t\tconst preferredFree = freeSpaceOnSide(anchor, size, preferred, viewport);\n\t\tconst flippedFree = freeSpaceOnSide(anchor, size, flipped, viewport);\n\t\tif (flippedFree > preferredFree) {\n\t\t\tside = flipped;\n\t\t\tcoords = flippedCoords;\n\t\t}\n\t}\n\n\treturn clampCrossAxis(coords, size, side, viewport);\n}\n\n/** Applies fixed coords to a floating element. */\nexport function applyFloatingPosition(\n\tanchor: HTMLElement,\n\tfloating: HTMLElement,\n\tplacement: RuiPlacement,\n\tgap: number,\n): void {\n\tconst coords = computeFloatingCoords(\n\t\tanchor.getBoundingClientRect(),\n\t\t{ width: floating.offsetWidth, height: floating.offsetHeight },\n\t\tplacement,\n\t\tgap,\n\t);\n\tObject.assign(floating.style, {\n\t\tposition: 'fixed',\n\t\tleft: `${coords.x}px`,\n\t\ttop: `${coords.y}px`,\n\t\tright: 'auto',\n\t\tbottom: 'auto',\n\t\tvisibility: 'visible',\n\t});\n}\n\nexport type AttachFloatingOptions = {\n\tanchor: HTMLElement;\n\tfloating: HTMLElement;\n\tgetPlacement: () => RuiPlacement;\n\tgap: number;\n};\n\n/**\n * Positions on attach and keeps the floating element updated on scroll/resize.\n *\n * @returns Cleanup that removes listeners and observers.\n */\nexport function attachFloating({ anchor, floating, getPlacement, gap }: AttachFloatingOptions): () => void {\n\tconst update = (): void => {\n\t\tif (floating.hidden) return;\n\t\tapplyFloatingPosition(anchor, floating, getPlacement(), gap);\n\t};\n\n\tupdate();\n\n\twindow.addEventListener('resize', update);\n\twindow.addEventListener('scroll', update, true);\n\n\tconst observer = new ResizeObserver(update);\n\tobserver.observe(anchor);\n\tobserver.observe(floating);\n\n\treturn () => {\n\t\twindow.removeEventListener('resize', update);\n\t\twindow.removeEventListener('scroll', update, true);\n\t\tobserver.disconnect();\n\t};\n}\n", "import type { JsxRenderable } from '@ecopages/jsx';\n\nconst RADIANT_VIEW_ELEMENT = Symbol.for('@ecopages/storybook-radiant.viewElement');\nconst RADIANT_SCRIPT_MODULE = Symbol.for('@ecopages/storybook-radiant.scriptModule');\nconst RADIANT_SCRIPT_EXPORT = Symbol.for('@ecopages/storybook-radiant.scriptExport');\n\nexport type RadiantViewComponent<TArgs = unknown> = ((args: TArgs) => JsxRenderable) & {\n\t[RADIANT_VIEW_ELEMENT]?: CustomElementConstructor;\n\t[RADIANT_SCRIPT_MODULE]?: string;\n\t[RADIANT_SCRIPT_EXPORT]?: string;\n};\n\nfunction linkViewToElement<TArgs>(\n\tview: RadiantViewComponent<TArgs>,\n\telement: CustomElementConstructor & {\n\t\t[RADIANT_SCRIPT_MODULE]?: string;\n\t\t[RADIANT_SCRIPT_EXPORT]?: string;\n\t},\n): void {\n\tview[RADIANT_VIEW_ELEMENT] = element;\n\n\tconst ssrModule = element[RADIANT_SCRIPT_MODULE];\n\tif (ssrModule) {\n\t\tview[RADIANT_SCRIPT_MODULE] = ssrModule;\n\t\tview[RADIANT_SCRIPT_EXPORT] = element[RADIANT_SCRIPT_EXPORT] ?? element.name;\n\t}\n}\n\n/** Link a JSX view export to its RadiantElement class (same symbols as the Storybook framework). */\nexport function defineRadiantView<TArgs>(\n\telement: CustomElementConstructor & {\n\t\t[RADIANT_SCRIPT_MODULE]?: string;\n\t\t[RADIANT_SCRIPT_EXPORT]?: string;\n\t},\n\trender: (args: TArgs) => JsxRenderable,\n): RadiantViewComponent<TArgs> {\n\tconst view = ((args: TArgs) => render(args)) as RadiantViewComponent<TArgs>;\n\tlinkViewToElement(view, element);\n\treturn view;\n}\n\n/** Re-copy SSR metadata from the linked element (e.g. after HMR). */\nexport function syncRadiantViewMetadata(view: RadiantViewComponent): void {\n\tconst element = view[RADIANT_VIEW_ELEMENT];\n\tif (element) {\n\t\tlinkViewToElement(view, element);\n\t}\n}\n", "import type { WithChildren, RadiantSlotProps } from '../../../types';\nimport { defineRadiantView } from '../../../lib/radiant-view';\nimport type { RuiTooltipProps } from './tooltip.script';\nimport { RuiTooltip as RuiTooltipElement } from './tooltip.script';\nimport './tooltip.css';\n\nexport const RuiTooltip = defineRadiantView(\n\tRuiTooltipElement,\n\t({ slot, content, placement, delay, children }: WithChildren<RuiTooltipProps & RadiantSlotProps>) => (\n\t\t<rui-tooltip slot={slot} content={content} placement={placement} delay={delay}>\n\t\t\t{children}\n\t\t</rui-tooltip>\n\t),\n);\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["RadiantElement", "bound", "customElement", "onEvent", "onUpdated", "prop", "query", "OPPOSITE", "DEFAULT_VIEWPORT_PADDING", "parsePlacement", "placement", "dash", "side", "align", "primaryAxis", "coordsFor", "anchor", "size", "gap", "width", "height", "x", "y", "overflowOnPrimary", "coords", "viewport", "padding", "vw", "vh", "freeSpaceOnSide", "clampCrossAxis", "maxX", "maxY", "computeFloatingCoords", "preferred", "preferredCoords", "preferredOverflow", "flipped", "flippedCoords", "preferredFree", "applyFloatingPosition", "floating", "attachFloating", "getPlacement", "update", "observer", "TOOLTIP_GAP", "RuiTooltip", "customElement", "RadiantElement", "prop", "query", "attachFloating", "onUpdated", "applyFloatingPosition", "next", "onEvent", "event", "bound", "RADIANT_VIEW_ELEMENT", "RADIANT_SCRIPT_MODULE", "RADIANT_SCRIPT_EXPORT", "linkViewToElement", "view", "element", "ssrModule", "defineRadiantView", "render", "args", "RuiTooltip", "defineRadiantView", "slot", "content", "placement", "delay", "children"]
|
|
7
7
|
}
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */
|
|
2
2
|
@layer properties;
|
|
3
|
+
@layer components {
|
|
4
|
+
.rui-floating {
|
|
5
|
+
position: fixed;
|
|
6
|
+
visibility: hidden;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
3
9
|
rui-tooltip {
|
|
4
10
|
display: inline-flex;
|
|
5
11
|
}
|
|
@@ -12,7 +18,6 @@ rui-tooltip {
|
|
|
12
18
|
display: inline-flex;
|
|
13
19
|
}
|
|
14
20
|
.rui-tooltip__content {
|
|
15
|
-
position: absolute;
|
|
16
21
|
z-index: var(--z-index-overlay, var(--z-overlay));
|
|
17
22
|
max-width: var(--container-xs, 20rem);
|
|
18
23
|
border-radius: var(--radius-container, var(--radius-container));
|