luxen-ui 0.2.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cdn/chunks/decorate.js +1 -1
- package/cdn/chunks/decorate.js.map +1 -1
- package/cdn/custom-elements.json +185 -112
- package/cdn/elements/avatar/avatar.js +1 -1
- package/cdn/elements/avatar/avatar.js.map +1 -1
- package/cdn/elements/badge/badge.js +1 -1
- package/cdn/elements/carousel/carousel.d.ts +9 -1
- package/cdn/elements/carousel/carousel.d.ts.map +1 -1
- package/cdn/elements/carousel/carousel.js +21 -20
- package/cdn/elements/carousel/carousel.js.map +1 -1
- package/cdn/elements/dialog/dialog.js +1 -1
- package/cdn/elements/dialog/dialog.styles.js +1 -1
- package/cdn/elements/dialog/dialog.styles.js.map +1 -1
- package/cdn/elements/divider/divider.js +1 -1
- package/cdn/elements/drawer/drawer.js +1 -1
- package/cdn/elements/dropdown/dropdown.d.ts +5 -2
- package/cdn/elements/dropdown/dropdown.d.ts.map +1 -1
- package/cdn/elements/dropdown/dropdown.js +6 -3
- package/cdn/elements/dropdown/dropdown.js.map +1 -1
- package/cdn/elements/dropdown-item/dropdown-item.js +1 -1
- package/cdn/elements/dropdown-item/dropdown-item.js.map +1 -1
- package/cdn/elements/icon/icon.js +1 -1
- package/cdn/elements/input-otp/input-otp.d.ts +8 -2
- package/cdn/elements/input-otp/input-otp.d.ts.map +1 -1
- package/cdn/elements/input-otp/input-otp.js +1 -1
- package/cdn/elements/input-otp/input-otp.js.map +1 -1
- package/cdn/elements/input-stepper/input-stepper.js +1 -1
- package/cdn/elements/popover/popover.js +1 -1
- package/cdn/elements/popover/popover.js.map +1 -1
- package/cdn/elements/rating/rating.js +1 -1
- package/cdn/elements/tabs/tabs.js +1 -1
- package/cdn/elements/toast/toast.js +1 -1
- package/cdn/elements/toast/toast.js.map +1 -1
- package/cdn/elements/tooltip/tooltip.d.ts +3 -3
- package/cdn/elements/tooltip/tooltip.js +1 -1
- package/cdn/elements/tooltip/tooltip.js.map +1 -1
- package/cdn/elements/tree/tree.js +1 -1
- package/cdn/elements/tree/tree.js.map +1 -1
- package/cdn/elements/tree-item/tree-item.js +1 -1
- package/cdn/elements/tree-item/tree-item.js.map +1 -1
- package/cdn/shared/luxen-form-associated-element.js +1 -1
- package/cdn/styles/elements/divider.css +7 -0
- package/cdn/styles/elements/input-otp.css +63 -29
- package/cdn/styles/elements/select.css +3 -3
- package/cdn/styles/index.css +10 -0
- package/dist/css/elements/divider.css +7 -0
- package/dist/css/elements/input-otp.css +63 -29
- package/dist/css/elements/select.css +3 -3
- package/dist/css/index.css +10 -0
- package/dist/custom-elements.json +185 -112
- package/dist/elements/avatar/avatar.css +13 -7
- package/dist/elements/carousel/carousel.css +7 -0
- package/dist/elements/carousel/carousel.d.ts +9 -1
- package/dist/elements/carousel/carousel.d.ts.map +1 -1
- package/dist/elements/carousel/carousel.js +71 -37
- package/dist/elements/dialog/dialog.css +10 -0
- package/dist/elements/dropdown/dropdown.css +14 -3
- package/dist/elements/dropdown/dropdown.d.ts +5 -2
- package/dist/elements/dropdown/dropdown.d.ts.map +1 -1
- package/dist/elements/dropdown/dropdown.js +19 -7
- package/dist/elements/input-otp/input-otp.d.ts +8 -2
- package/dist/elements/input-otp/input-otp.d.ts.map +1 -1
- package/dist/elements/input-otp/input-otp.js +14 -5
- package/dist/elements/tooltip/tooltip.css +15 -7
- package/dist/elements/tooltip/tooltip.d.ts +3 -3
- package/dist/elements/tooltip/tooltip.js +3 -3
- package/dist/skills/luxen-ui/references/dialog.md +76 -0
- package/dist/skills/luxen-ui/references/drawer.md +8 -0
- package/dist/skills/luxen-ui/references/select.md +1 -1
- package/package.json +1 -1
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{cls as e,tagName as t,uniqueId as n}from"../../registry.js";import{LuxenElement as r}from"../../shared/luxen-element.js";import{
|
|
1
|
+
import{cls as e,tagName as t,uniqueId as n}from"../../registry.js";import{LuxenElement as r}from"../../shared/luxen-element.js";import{i,t as a}from"../../chunks/decorate.js";var o=class extends HTMLElement{},s=class extends r{constructor(...e){super(...e),this.placement=`top-end`,this.duration=5e3,this.variant=``,this._timers=new WeakMap,this._positionCache=new WeakMap,this._documentHidden=!1,this._onVisibilityChange=()=>{this._documentHidden=document.hidden;let e=this.querySelectorAll(`:scope > ${t(`toast-item`)}`);if(document.hidden)for(let t of e)this._pauseTimer(t);else for(let t of e)this._resumeTimer(t)},this._onKeyDown=e=>{if(e.key!==`Escape`||!this.matches(`:popover-open`))return;let n=this.querySelector(`:scope > ${t(`toast-item`)}:last-of-type`);n&&(e.preventDefault(),this._hideToast(n))}}createRenderRoot(){return this}connectedCallback(){super.connectedCallback(),this.popover=`manual`,this.setAttribute(`role`,`log`),this.setAttribute(`aria-live`,`polite`),this.setAttribute(`aria-relevant`,`additions`),document.addEventListener(`keydown`,this._onKeyDown),document.addEventListener(`visibilitychange`,this._onVisibilityChange)}disconnectedCallback(){super.disconnectedCallback(),document.removeEventListener(`keydown`,this._onKeyDown),document.removeEventListener(`visibilitychange`,this._onVisibilityChange)}toast(r){let{heading:i,variant:a=this.variant,duration:o=this.duration}=r,s=n(`toast`),c=document.createElement(t(`toast-item`));if(a&&c.setAttribute(`variant`,a),c.setAttribute(`role`,a===`danger`?`alert`:`status`),c.setAttribute(`aria-atomic`,`true`),r.icon){let t=document.createElement(`iconify-icon`);t.setAttribute(`icon`,r.icon),t.setAttribute(`width`,`20`),t.setAttribute(`height`,`20`),t.setAttribute(`aria-hidden`,`true`),t.className=e(`toast-icon`),c.appendChild(t)}else{let t=document.createElement(`div`);t.className=e(`toast-accent`),t.setAttribute(`aria-hidden`,`true`),c.appendChild(t)}let l=document.createElement(`div`);l.className=e(`toast-content`);let u=document.createElement(`div`);if(i){let t=document.createElement(`div`);t.id=`${s}-heading`,t.className=`${e(`toast-heading`)} font-medium`,t.textContent=i,u.appendChild(t),c.setAttribute(`aria-labelledby`,`${s}-heading`)}let d=document.createElement(`div`);d.id=`${s}-message`,d.className=e(`toast-message`),`html`in r&&r.html?d.setHTML(r.html):d.textContent=r.text??``,u.appendChild(d),c.setAttribute(i?`aria-describedby`:`aria-labelledby`,`${s}-message`),l.appendChild(u),c.appendChild(l);let f=document.createElement(`button`);if(f.type=`button`,f.className=e(`close`),f.setAttribute(`data-appearance`,`ring`),f.setAttribute(`aria-label`,`Close`),f.addEventListener(`click`,()=>this._hideToast(c)),c.appendChild(f),r.timer&&o>0&&isFinite(o)){let t=document.createElement(`div`);t.className=e(`toast-timer`),t.setAttribute(`aria-hidden`,`true`),t.style.setProperty(`--_timer-duration`,`${o}ms`),c.appendChild(t)}if(this._capturePositions(),this.placement.includes(`bottom`)?this.appendChild(c):this.prepend(c),this.matches(`:popover-open`)||this.showPopover(),!this.emit(`show`,{cancelable:!0,detail:{toast:c}}))return c.remove(),c;requestAnimationFrame(()=>{c.setAttribute(`showing`,``),this._animatePositions();let e=t=>{t.target===c&&(c.removeEventListener(`transitionend`,e),this.emit(`after-show`,{detail:{toast:c}}))};c.addEventListener(`transitionend`,e)}),o>0&&isFinite(o)&&this._startTimer(c,o),c.addEventListener(`pointerenter`,()=>this._pauseTimer(c)),c.addEventListener(`pointerleave`,()=>this._resumeTimer(c)),c.addEventListener(`focusin`,()=>this._pauseTimer(c)),c.addEventListener(`focusout`,()=>this._resumeTimer(c));let p=null,m=!1;c.addEventListener(`pointerdown`,e=>{e.button===0&&(e.target.closest(`button, a, [role="button"]`)||(p={x:e.clientX,y:e.clientY},m=!1,c.setPointerCapture(e.pointerId),c.style.transition=`none`))});let h=e=>this.placement.endsWith(`-start`)?Math.min(e,0):this.placement.endsWith(`-end`)?Math.max(e,0):e;return c.addEventListener(`pointermove`,e=>{if(!p)return;let t=h(e.clientX-p.x),n=e.clientY-p.y,r=e.pointerType===`touch`?10:2;if(!(!m&&Math.abs(t)<r)){if(!m&&Math.abs(n)>Math.abs(t)){p=null;return}m=!0,this._pauseTimer(c),c.style.transform=`translateX(${t}px)`,c.style.opacity=`${1-Math.abs(t)/300}`}}),c.addEventListener(`pointerup`,e=>{if(!p)return;let t=h(e.clientX-p.x);if(p=null,c.style.transition=``,m&&Math.abs(t)>50){let e=t>0?1:-1;c.style.transform=`translateX(${e*120}%)`,c.style.opacity=`0`,c.addEventListener(`transitionend`,()=>this._hideToast(c),{once:!0})}else c.style.transform=``,c.style.opacity=``,m&&this._resumeTimer(c);m=!1}),c}_startTimer(e,t){let n=window.setTimeout(()=>this._hideToast(e),t);this._timers.set(e,{remaining:t,startTime:performance.now(),timeoutId:n,paused:!1})}_pauseTimer(t){let n=this._timers.get(t);if(!n||n.paused)return;clearTimeout(n.timeoutId),n.remaining-=performance.now()-n.startTime,n.paused=!0;let r=t.querySelector(`.${e(`toast-timer`)}`);if(r)for(let e of r.getAnimations())e.pause()}_resumeTimer(t){let n=this._timers.get(t);if(!n||!n.paused||n.remaining<=0||this._documentHidden)return;n.startTime=performance.now(),n.timeoutId=window.setTimeout(()=>this._hideToast(t),n.remaining),n.paused=!1;let r=t.querySelector(`.${e(`toast-timer`)}`);if(r)for(let e of r.getAnimations())e.play()}_hideToast(e){let n=this._timers.get(e);if(n&&(clearTimeout(n.timeoutId),this._timers.delete(e)),!e.hasAttribute(`showing`)||!this.emit(`hide`,{cancelable:!0,detail:{toast:e}}))return;this._capturePositions(),e.removeAttribute(`showing`);let r=()=>{e.remove(),this._animatePositions(),this.emit(`after-hide`,{detail:{toast:e}}),this.querySelectorAll(`:scope > ${t(`toast-item`)}`).length===0&&this.matches(`:popover-open`)&&this.hidePopover()};if(parseFloat(getComputedStyle(e).transitionDuration)===0)r();else{let t=n=>{n.target===e&&(e.removeEventListener(`transitionend`,t),r())};e.addEventListener(`transitionend`,t)}}_capturePositions(){let e=this.querySelectorAll(`:scope > ${t(`toast-item`)}`);for(let t of e)this._positionCache.set(t,t.getBoundingClientRect())}_animatePositions(){if(window.matchMedia(`(prefers-reduced-motion: reduce)`).matches)return;let e=this.querySelectorAll(`:scope > ${t(`toast-item`)}`);for(let t of e){let e=this._positionCache.get(t);if(!e)continue;let n=t.getBoundingClientRect(),r=e.top-n.top;Math.abs(r)<1||t.animate([{transform:`translateY(${r}px)`},{transform:`translateY(0)`}],{duration:200,easing:`cubic-bezier(0.2, 0, 0, 1)`})}}};a([i({reflect:!0})],s.prototype,`placement`,void 0),a([i({type:Number,reflect:!0})],s.prototype,`duration`,void 0),a([i({reflect:!0})],s.prototype,`variant`,void 0);export{s as LuxenToast,o as LuxenToastItem};
|
|
2
2
|
//# sourceMappingURL=toast.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"toast.js","names":[],"sources":["../../../src/html/elements/toast/toast.ts"],"sourcesContent":["/* eslint-disable no-shadow -- the local `toast` HTMLElement intentionally\n shadows the module-level `toast()` export; renaming would touch ~50 lines\n without behavioral benefit. */\nimport { LuxenElement } from '../../shared/luxen-element';\nimport { property } from 'lit/decorators.js';\nimport { tagName, cls, uniqueId } from '../../registry';\n\ndeclare global {\n interface Element {\n /** @see https://developer.mozilla.org/en-US/docs/Web/API/Element/setHTML */\n setHTML(input: string, options?: Record<string, unknown>): void;\n }\n}\n\n/** Minimal custom element for toast items — no render, no shadow DOM. */\nexport class LuxenToastItem extends HTMLElement {}\n\ninterface ToastOptionsBase {\n /** Optional heading text displayed above the message. */\n heading?: string;\n /** Override the element's variant for this toast. */\n variant?: string;\n /** Override auto-dismiss duration (ms) for this toast. 0 = no auto-dismiss. */\n duration?: number;\n /** Iconify icon name (e.g. 'lucide:check'). Replaces the accent bar. */\n icon?: string;\n /** Show a countdown progress bar at the bottom. Default `false`. */\n timer?: boolean;\n}\n\nexport type ToastOptions =\n | (ToastOptionsBase & { /** The message text to display. */ text: string; html?: never })\n | (ToastOptionsBase & {\n text?: never;\n /** HTML content (sanitized via the Sanitizer API). */ html: string;\n });\n\ninterface TimerState {\n remaining: number;\n startTime: number;\n timeoutId: number;\n paused: boolean;\n}\n\n/**\n * @summary A toast notification container that generates toast items internally.\n * @customElement l-toast\n *\n * @event show - Emitted when a toast begins to show. Cancelable.\n * @event after-show - Emitted after the show animation completes.\n * @event hide - Emitted when a toast begins to hide. Cancelable.\n * @event after-hide - Emitted after the hide animation completes and toast is removed.\n *\n * @cssproperty --gap - Gap between stacked toast items.\n * @cssproperty --width - Width of the toast stack.\n * @cssproperty --show-duration - Duration of the show animation.\n * @cssproperty --hide-duration - Duration of the hide animation.\n */\nexport class LuxenToast extends LuxenElement {\n /** Use light DOM — no Shadow DOM. */\n override createRenderRoot() {\n return this;\n }\n\n /** Position of the toast stack on the screen. */\n @property({ reflect: true }) placement:\n | 'top-start'\n | 'top-center'\n | 'top-end'\n | 'bottom-start'\n | 'bottom-center'\n | 'bottom-end' = 'top-end';\n\n /** Default auto-dismiss delay in milliseconds. 0 disables auto-dismiss. */\n @property({ type: Number, reflect: true }) duration = 5000;\n\n /** Default variant for toast items: info, success, warning, danger. */\n @property({ reflect: true }) variant = '';\n\n private _timers = new WeakMap<HTMLElement, TimerState>();\n private _positionCache = new WeakMap<Element, DOMRect>();\n private _documentHidden = false;\n\n override connectedCallback() {\n super.connectedCallback();\n this.popover = 'manual';\n this.setAttribute('role', 'log');\n this.setAttribute('aria-live', 'polite');\n this.setAttribute('aria-relevant', 'additions');\n\n document.addEventListener('keydown', this._onKeyDown);\n document.addEventListener('visibilitychange', this._onVisibilityChange);\n }\n\n override disconnectedCallback() {\n super.disconnectedCallback();\n document.removeEventListener('keydown', this._onKeyDown);\n document.removeEventListener('visibilitychange', this._onVisibilityChange);\n }\n\n /** Create and show a toast notification. */\n toast(options: ToastOptions): HTMLElement {\n const { heading, variant = this.variant, duration = this.duration } = options;\n\n // Build toast DOM\n const uid = uniqueId('toast');\n const toast = document.createElement(tagName('toast-item'));\n if (variant) toast.setAttribute('variant', variant);\n toast.setAttribute('role', variant === 'danger' ? 'alert' : 'status');\n toast.setAttribute('aria-atomic', 'true');\n\n if (options.icon) {\n const icon = document.createElement('iconify-icon');\n icon.setAttribute('icon', options.icon);\n icon.setAttribute('width', '20');\n icon.setAttribute('height', '20');\n icon.setAttribute('aria-hidden', 'true');\n icon.className = cls('toast-icon');\n toast.appendChild(icon);\n } else {\n const accent = document.createElement('div');\n accent.className = cls('toast-accent');\n accent.setAttribute('aria-hidden', 'true');\n toast.appendChild(accent);\n }\n\n const content = document.createElement('div');\n content.className = cls('toast-content');\n\n const textWrap = document.createElement('div');\n\n if (heading) {\n const headingEl = document.createElement('div');\n headingEl.id = `${uid}-heading`;\n headingEl.className = `${cls('toast-heading')} font-medium`;\n headingEl.textContent = heading;\n textWrap.appendChild(headingEl);\n toast.setAttribute('aria-labelledby', `${uid}-heading`);\n }\n\n const messageEl = document.createElement('div');\n messageEl.id = `${uid}-message`;\n messageEl.className = cls('toast-message');\n if ('html' in options && options.html) {\n messageEl.setHTML(options.html);\n } else {\n messageEl.textContent = options.text ?? '';\n }\n textWrap.appendChild(messageEl);\n toast.setAttribute(heading ? 'aria-describedby' : 'aria-labelledby', `${uid}-message`);\n\n content.appendChild(textWrap);\n toast.appendChild(content);\n\n const closeBtn = document.createElement('button');\n closeBtn.type = 'button';\n closeBtn.className = cls('close');\n closeBtn.setAttribute('data-appearance', 'ring');\n closeBtn.setAttribute('aria-label', 'Close');\n closeBtn.addEventListener('click', () => this._hideToast(toast));\n toast.appendChild(closeBtn);\n\n // Timer bar (opt-in)\n if (options.timer && duration > 0 && isFinite(duration)) {\n const timerBar = document.createElement('div');\n timerBar.className = cls('toast-timer');\n timerBar.setAttribute('aria-hidden', 'true');\n timerBar.style.setProperty('--_timer-duration', `${duration}ms`);\n toast.appendChild(timerBar);\n }\n\n // FLIP: capture existing positions\n this._capturePositions();\n\n // Insert into container\n if (this.placement.includes('bottom')) {\n this.appendChild(toast);\n } else {\n this.prepend(toast);\n }\n\n // Show popover if not already open\n if (!this.matches(':popover-open')) {\n this.showPopover();\n }\n\n // Dispatch show\n if (!this.emit('show', { cancelable: true, detail: { toast } })) {\n toast.remove();\n return toast;\n }\n\n // Trigger show animation on next frame\n requestAnimationFrame(() => {\n toast.setAttribute('showing', '');\n this._animatePositions();\n\n const onShown = (e: TransitionEvent) => {\n if (e.target !== toast) return;\n toast.removeEventListener('transitionend', onShown);\n this.emit('after-show', { detail: { toast } });\n };\n toast.addEventListener('transitionend', onShown);\n });\n\n // Auto-dismiss timer (0 or Infinity = persistent)\n if (duration > 0 && isFinite(duration)) {\n this._startTimer(toast, duration);\n }\n\n // Pause/resume timer on hover and focus\n toast.addEventListener('pointerenter', () => this._pauseTimer(toast));\n toast.addEventListener('pointerleave', () => this._resumeTimer(toast));\n toast.addEventListener('focusin', () => this._pauseTimer(toast));\n toast.addEventListener('focusout', () => this._resumeTimer(toast));\n\n // ── Swipe-to-dismiss ──────────────────────────────────────────────────────\n\n let swipeStart: { x: number; y: number } | null = null;\n let swiping = false;\n\n toast.addEventListener('pointerdown', (e: PointerEvent) => {\n if (e.button !== 0) return;\n if ((e.target as Element).closest('button, a, [role=\"button\"]')) return;\n swipeStart = { x: e.clientX, y: e.clientY };\n swiping = false;\n toast.setPointerCapture(e.pointerId);\n toast.style.transition = 'none';\n });\n\n // Only allow swiping away from the screen edge, never inward\n const clampDelta = (dx: number) =>\n this.placement.endsWith('-start')\n ? Math.min(dx, 0)\n : this.placement.endsWith('-end')\n ? Math.max(dx, 0)\n : dx;\n\n toast.addEventListener('pointermove', (e: PointerEvent) => {\n if (!swipeStart) return;\n const deltaX = clampDelta(e.clientX - swipeStart.x);\n const deltaY = e.clientY - swipeStart.y;\n\n const buffer = e.pointerType === 'touch' ? 10 : 2;\n if (!swiping && Math.abs(deltaX) < buffer) return;\n\n // If vertical movement dominates, cancel swipe tracking\n if (!swiping && Math.abs(deltaY) > Math.abs(deltaX)) {\n swipeStart = null;\n return;\n }\n\n swiping = true;\n this._pauseTimer(toast);\n toast.style.transform = `translateX(${deltaX}px)`;\n toast.style.opacity = `${1 - Math.abs(deltaX) / 300}`;\n });\n\n toast.addEventListener('pointerup', (e: PointerEvent) => {\n if (!swipeStart) return;\n const deltaX = clampDelta(e.clientX - swipeStart.x);\n swipeStart = null;\n toast.style.transition = '';\n\n if (swiping && Math.abs(deltaX) > 50) {\n const dir = deltaX > 0 ? 1 : -1;\n toast.style.transform = `translateX(${dir * 120}%)`;\n toast.style.opacity = '0';\n toast.addEventListener('transitionend', () => this._hideToast(toast), { once: true });\n } else {\n toast.style.transform = '';\n toast.style.opacity = '';\n if (swiping) this._resumeTimer(toast);\n }\n swiping = false;\n });\n\n return toast;\n }\n\n // ── Timer management ──────────────────────────────────────────────────────────\n\n private _startTimer(toast: HTMLElement, duration: number) {\n const timeoutId = window.setTimeout(() => this._hideToast(toast), duration);\n this._timers.set(toast, {\n remaining: duration,\n startTime: performance.now(),\n timeoutId,\n paused: false,\n });\n }\n\n private _pauseTimer(toast: HTMLElement) {\n const timer = this._timers.get(toast);\n if (!timer || timer.paused) return;\n clearTimeout(timer.timeoutId);\n timer.remaining -= performance.now() - timer.startTime;\n timer.paused = true;\n\n // Use the Web Animations API to pause the countdown bar. CSS `animation-play-state`\n // cannot be used here because browsers defer style recalculations while the tab is\n // hidden, so the animation runs to completion before the style change takes effect.\n const timerBar = toast.querySelector(`.${cls('toast-timer')}`);\n if (timerBar) for (const anim of timerBar.getAnimations()) anim.pause();\n }\n\n private _resumeTimer(toast: HTMLElement) {\n const timer = this._timers.get(toast);\n if (!timer || !timer.paused || timer.remaining <= 0 || this._documentHidden) return;\n timer.startTime = performance.now();\n timer.timeoutId = window.setTimeout(() => this._hideToast(toast), timer.remaining);\n timer.paused = false;\n\n const timerBar = toast.querySelector(`.${cls('toast-timer')}`);\n if (timerBar) for (const anim of timerBar.getAnimations()) anim.play();\n }\n\n // ── Hide a toast ──────────────────────────────────────────────────────────────\n\n private _hideToast(toast: HTMLElement) {\n // Clear timer\n const timer = this._timers.get(toast);\n if (timer) {\n clearTimeout(timer.timeoutId);\n this._timers.delete(toast);\n }\n\n // Prevent double-hide\n if (!toast.hasAttribute('showing')) return;\n\n // Dispatch hide\n if (!this.emit('hide', { cancelable: true, detail: { toast } })) return;\n\n this._capturePositions();\n\n // Remove showing class to trigger exit transition\n toast.removeAttribute('showing');\n\n const remove = () => {\n toast.remove();\n this._animatePositions();\n\n this.emit('after-hide', { detail: { toast } });\n\n // Close popover if no toasts remain\n const remaining = this.querySelectorAll(`:scope > ${tagName('toast-item')}`);\n if (remaining.length === 0 && this.matches(':popover-open')) {\n this.hidePopover();\n }\n };\n\n // Handle zero-duration transitions (reduced motion)\n const duration = parseFloat(getComputedStyle(toast).transitionDuration);\n if (duration === 0) {\n remove();\n } else {\n const onEnd = (e: TransitionEvent) => {\n if (e.target !== toast) return;\n toast.removeEventListener('transitionend', onEnd);\n remove();\n };\n toast.addEventListener('transitionend', onEnd);\n }\n }\n\n // ── FLIP animation for reordering ─────────────────────────────────────────────\n\n private _capturePositions() {\n const items = this.querySelectorAll(`:scope > ${tagName('toast-item')}`);\n for (const child of items) {\n this._positionCache.set(child, child.getBoundingClientRect());\n }\n }\n\n private _animatePositions() {\n if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;\n\n const items = this.querySelectorAll(`:scope > ${tagName('toast-item')}`);\n for (const child of items) {\n const oldRect = this._positionCache.get(child);\n if (!oldRect) continue;\n\n const newRect = child.getBoundingClientRect();\n const deltaY = oldRect.top - newRect.top;\n\n if (Math.abs(deltaY) < 1) continue;\n\n (child as HTMLElement).animate(\n [{ transform: `translateY(${deltaY}px)` }, { transform: 'translateY(0)' }],\n { duration: 200, easing: 'cubic-bezier(0.2, 0, 0, 1)' },\n );\n }\n }\n\n // ── Document visibility ──────────────────────────────────────────────────────\n\n private _onVisibilityChange = () => {\n this._documentHidden = document.hidden;\n const items = this.querySelectorAll<HTMLElement>(`:scope > ${tagName('toast-item')}`);\n if (document.hidden) {\n for (const item of items) this._pauseTimer(item);\n } else {\n for (const item of items) this._resumeTimer(item);\n }\n };\n\n // ── Keyboard ──────────────────────────────────────────────────────────────────\n\n private _onKeyDown = (e: KeyboardEvent) => {\n if (e.key !== 'Escape') return;\n if (!this.matches(':popover-open')) return;\n\n const last = this.querySelector<HTMLElement>(`:scope > ${tagName('toast-item')}:last-of-type`);\n if (last) {\n e.preventDefault();\n this._hideToast(last);\n }\n };\n}\n\n// ── Standalone function ───────────────────────────────────────────────────────\n\nlet _defaultInstance: LuxenToast | null = null;\n\nfunction _getDefault(): LuxenToast {\n if (!_defaultInstance || !_defaultInstance.isConnected) {\n _defaultInstance =\n document.querySelector(tagName('toast')) ??\n (document.createElement(tagName('toast')) as LuxenToast);\n if (!_defaultInstance.isConnected) {\n document.body.appendChild(_defaultInstance);\n }\n }\n return _defaultInstance;\n}\n\n/** Show a toast notification. Auto-creates `<l-toast>` if none exists in the DOM. */\nexport function toast(options: ToastOptions): HTMLElement {\n return _getDefault().toast(options);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"toast.js","names":[],"sources":["../../../src/html/elements/toast/toast.ts"],"sourcesContent":["/* eslint-disable no-shadow -- the local `toast` HTMLElement intentionally\n shadows the module-level `toast()` export; renaming would touch ~50 lines\n without behavioral benefit. */\nimport { LuxenElement } from '../../shared/luxen-element';\nimport { property } from 'lit/decorators.js';\nimport { tagName, cls, uniqueId } from '../../registry';\n\ndeclare global {\n interface Element {\n /** @see https://developer.mozilla.org/en-US/docs/Web/API/Element/setHTML */\n setHTML(input: string, options?: Record<string, unknown>): void;\n }\n}\n\n/** Minimal custom element for toast items — no render, no shadow DOM. */\nexport class LuxenToastItem extends HTMLElement {}\n\ninterface ToastOptionsBase {\n /** Optional heading text displayed above the message. */\n heading?: string;\n /** Override the element's variant for this toast. */\n variant?: string;\n /** Override auto-dismiss duration (ms) for this toast. 0 = no auto-dismiss. */\n duration?: number;\n /** Iconify icon name (e.g. 'lucide:check'). Replaces the accent bar. */\n icon?: string;\n /** Show a countdown progress bar at the bottom. Default `false`. */\n timer?: boolean;\n}\n\nexport type ToastOptions =\n | (ToastOptionsBase & { /** The message text to display. */ text: string; html?: never })\n | (ToastOptionsBase & {\n text?: never;\n /** HTML content (sanitized via the Sanitizer API). */ html: string;\n });\n\ninterface TimerState {\n remaining: number;\n startTime: number;\n timeoutId: number;\n paused: boolean;\n}\n\n/**\n * @summary A toast notification container that generates toast items internally.\n * @customElement l-toast\n *\n * @event show - Emitted when a toast begins to show. Cancelable.\n * @event after-show - Emitted after the show animation completes.\n * @event hide - Emitted when a toast begins to hide. Cancelable.\n * @event after-hide - Emitted after the hide animation completes and toast is removed.\n *\n * @cssproperty --gap - Gap between stacked toast items.\n * @cssproperty --width - Width of the toast stack.\n * @cssproperty --show-duration - Duration of the show animation.\n * @cssproperty --hide-duration - Duration of the hide animation.\n */\nexport class LuxenToast extends LuxenElement {\n /** Use light DOM — no Shadow DOM. */\n override createRenderRoot() {\n return this;\n }\n\n /** Position of the toast stack on the screen. */\n @property({ reflect: true }) placement:\n | 'top-start'\n | 'top-center'\n | 'top-end'\n | 'bottom-start'\n | 'bottom-center'\n | 'bottom-end' = 'top-end';\n\n /** Default auto-dismiss delay in milliseconds. 0 disables auto-dismiss. */\n @property({ type: Number, reflect: true }) duration = 5000;\n\n /** Default variant for toast items: info, success, warning, danger. */\n @property({ reflect: true }) variant = '';\n\n private _timers = new WeakMap<HTMLElement, TimerState>();\n private _positionCache = new WeakMap<Element, DOMRect>();\n private _documentHidden = false;\n\n override connectedCallback() {\n super.connectedCallback();\n this.popover = 'manual';\n this.setAttribute('role', 'log');\n this.setAttribute('aria-live', 'polite');\n this.setAttribute('aria-relevant', 'additions');\n\n document.addEventListener('keydown', this._onKeyDown);\n document.addEventListener('visibilitychange', this._onVisibilityChange);\n }\n\n override disconnectedCallback() {\n super.disconnectedCallback();\n document.removeEventListener('keydown', this._onKeyDown);\n document.removeEventListener('visibilitychange', this._onVisibilityChange);\n }\n\n /** Create and show a toast notification. */\n toast(options: ToastOptions): HTMLElement {\n const { heading, variant = this.variant, duration = this.duration } = options;\n\n // Build toast DOM\n const uid = uniqueId('toast');\n const toast = document.createElement(tagName('toast-item'));\n if (variant) toast.setAttribute('variant', variant);\n toast.setAttribute('role', variant === 'danger' ? 'alert' : 'status');\n toast.setAttribute('aria-atomic', 'true');\n\n if (options.icon) {\n const icon = document.createElement('iconify-icon');\n icon.setAttribute('icon', options.icon);\n icon.setAttribute('width', '20');\n icon.setAttribute('height', '20');\n icon.setAttribute('aria-hidden', 'true');\n icon.className = cls('toast-icon');\n toast.appendChild(icon);\n } else {\n const accent = document.createElement('div');\n accent.className = cls('toast-accent');\n accent.setAttribute('aria-hidden', 'true');\n toast.appendChild(accent);\n }\n\n const content = document.createElement('div');\n content.className = cls('toast-content');\n\n const textWrap = document.createElement('div');\n\n if (heading) {\n const headingEl = document.createElement('div');\n headingEl.id = `${uid}-heading`;\n headingEl.className = `${cls('toast-heading')} font-medium`;\n headingEl.textContent = heading;\n textWrap.appendChild(headingEl);\n toast.setAttribute('aria-labelledby', `${uid}-heading`);\n }\n\n const messageEl = document.createElement('div');\n messageEl.id = `${uid}-message`;\n messageEl.className = cls('toast-message');\n if ('html' in options && options.html) {\n messageEl.setHTML(options.html);\n } else {\n messageEl.textContent = options.text ?? '';\n }\n textWrap.appendChild(messageEl);\n toast.setAttribute(heading ? 'aria-describedby' : 'aria-labelledby', `${uid}-message`);\n\n content.appendChild(textWrap);\n toast.appendChild(content);\n\n const closeBtn = document.createElement('button');\n closeBtn.type = 'button';\n closeBtn.className = cls('close');\n closeBtn.setAttribute('data-appearance', 'ring');\n closeBtn.setAttribute('aria-label', 'Close');\n closeBtn.addEventListener('click', () => this._hideToast(toast));\n toast.appendChild(closeBtn);\n\n // Timer bar (opt-in)\n if (options.timer && duration > 0 && isFinite(duration)) {\n const timerBar = document.createElement('div');\n timerBar.className = cls('toast-timer');\n timerBar.setAttribute('aria-hidden', 'true');\n timerBar.style.setProperty('--_timer-duration', `${duration}ms`);\n toast.appendChild(timerBar);\n }\n\n // FLIP: capture existing positions\n this._capturePositions();\n\n // Insert into container\n if (this.placement.includes('bottom')) {\n this.appendChild(toast);\n } else {\n this.prepend(toast);\n }\n\n // Show popover if not already open\n if (!this.matches(':popover-open')) {\n this.showPopover();\n }\n\n // Dispatch show\n if (!this.emit('show', { cancelable: true, detail: { toast } })) {\n toast.remove();\n return toast;\n }\n\n // Trigger show animation on next frame\n requestAnimationFrame(() => {\n toast.setAttribute('showing', '');\n this._animatePositions();\n\n const onShown = (e: TransitionEvent) => {\n if (e.target !== toast) return;\n toast.removeEventListener('transitionend', onShown);\n this.emit('after-show', { detail: { toast } });\n };\n toast.addEventListener('transitionend', onShown);\n });\n\n // Auto-dismiss timer (0 or Infinity = persistent)\n if (duration > 0 && isFinite(duration)) {\n this._startTimer(toast, duration);\n }\n\n // Pause/resume timer on hover and focus\n toast.addEventListener('pointerenter', () => this._pauseTimer(toast));\n toast.addEventListener('pointerleave', () => this._resumeTimer(toast));\n toast.addEventListener('focusin', () => this._pauseTimer(toast));\n toast.addEventListener('focusout', () => this._resumeTimer(toast));\n\n // ── Swipe-to-dismiss ──────────────────────────────────────────────────────\n\n let swipeStart: { x: number; y: number } | null = null;\n let swiping = false;\n\n toast.addEventListener('pointerdown', (e: PointerEvent) => {\n if (e.button !== 0) return;\n if ((e.target as Element).closest('button, a, [role=\"button\"]')) return;\n swipeStart = { x: e.clientX, y: e.clientY };\n swiping = false;\n toast.setPointerCapture(e.pointerId);\n toast.style.transition = 'none';\n });\n\n // Only allow swiping away from the screen edge, never inward\n const clampDelta = (dx: number) =>\n this.placement.endsWith('-start')\n ? Math.min(dx, 0)\n : this.placement.endsWith('-end')\n ? Math.max(dx, 0)\n : dx;\n\n toast.addEventListener('pointermove', (e: PointerEvent) => {\n if (!swipeStart) return;\n const deltaX = clampDelta(e.clientX - swipeStart.x);\n const deltaY = e.clientY - swipeStart.y;\n\n const buffer = e.pointerType === 'touch' ? 10 : 2;\n if (!swiping && Math.abs(deltaX) < buffer) return;\n\n // If vertical movement dominates, cancel swipe tracking\n if (!swiping && Math.abs(deltaY) > Math.abs(deltaX)) {\n swipeStart = null;\n return;\n }\n\n swiping = true;\n this._pauseTimer(toast);\n toast.style.transform = `translateX(${deltaX}px)`;\n toast.style.opacity = `${1 - Math.abs(deltaX) / 300}`;\n });\n\n toast.addEventListener('pointerup', (e: PointerEvent) => {\n if (!swipeStart) return;\n const deltaX = clampDelta(e.clientX - swipeStart.x);\n swipeStart = null;\n toast.style.transition = '';\n\n if (swiping && Math.abs(deltaX) > 50) {\n const dir = deltaX > 0 ? 1 : -1;\n toast.style.transform = `translateX(${dir * 120}%)`;\n toast.style.opacity = '0';\n toast.addEventListener('transitionend', () => this._hideToast(toast), { once: true });\n } else {\n toast.style.transform = '';\n toast.style.opacity = '';\n if (swiping) this._resumeTimer(toast);\n }\n swiping = false;\n });\n\n return toast;\n }\n\n // ── Timer management ──────────────────────────────────────────────────────────\n\n private _startTimer(toast: HTMLElement, duration: number) {\n const timeoutId = window.setTimeout(() => this._hideToast(toast), duration);\n this._timers.set(toast, {\n remaining: duration,\n startTime: performance.now(),\n timeoutId,\n paused: false,\n });\n }\n\n private _pauseTimer(toast: HTMLElement) {\n const timer = this._timers.get(toast);\n if (!timer || timer.paused) return;\n clearTimeout(timer.timeoutId);\n timer.remaining -= performance.now() - timer.startTime;\n timer.paused = true;\n\n // Use the Web Animations API to pause the countdown bar. CSS `animation-play-state`\n // cannot be used here because browsers defer style recalculations while the tab is\n // hidden, so the animation runs to completion before the style change takes effect.\n const timerBar = toast.querySelector(`.${cls('toast-timer')}`);\n if (timerBar) for (const anim of timerBar.getAnimations()) anim.pause();\n }\n\n private _resumeTimer(toast: HTMLElement) {\n const timer = this._timers.get(toast);\n if (!timer || !timer.paused || timer.remaining <= 0 || this._documentHidden) return;\n timer.startTime = performance.now();\n timer.timeoutId = window.setTimeout(() => this._hideToast(toast), timer.remaining);\n timer.paused = false;\n\n const timerBar = toast.querySelector(`.${cls('toast-timer')}`);\n if (timerBar) for (const anim of timerBar.getAnimations()) anim.play();\n }\n\n // ── Hide a toast ──────────────────────────────────────────────────────────────\n\n private _hideToast(toast: HTMLElement) {\n // Clear timer\n const timer = this._timers.get(toast);\n if (timer) {\n clearTimeout(timer.timeoutId);\n this._timers.delete(toast);\n }\n\n // Prevent double-hide\n if (!toast.hasAttribute('showing')) return;\n\n // Dispatch hide\n if (!this.emit('hide', { cancelable: true, detail: { toast } })) return;\n\n this._capturePositions();\n\n // Remove showing class to trigger exit transition\n toast.removeAttribute('showing');\n\n const remove = () => {\n toast.remove();\n this._animatePositions();\n\n this.emit('after-hide', { detail: { toast } });\n\n // Close popover if no toasts remain\n const remaining = this.querySelectorAll(`:scope > ${tagName('toast-item')}`);\n if (remaining.length === 0 && this.matches(':popover-open')) {\n this.hidePopover();\n }\n };\n\n // Handle zero-duration transitions (reduced motion)\n const duration = parseFloat(getComputedStyle(toast).transitionDuration);\n if (duration === 0) {\n remove();\n } else {\n const onEnd = (e: TransitionEvent) => {\n if (e.target !== toast) return;\n toast.removeEventListener('transitionend', onEnd);\n remove();\n };\n toast.addEventListener('transitionend', onEnd);\n }\n }\n\n // ── FLIP animation for reordering ─────────────────────────────────────────────\n\n private _capturePositions() {\n const items = this.querySelectorAll(`:scope > ${tagName('toast-item')}`);\n for (const child of items) {\n this._positionCache.set(child, child.getBoundingClientRect());\n }\n }\n\n private _animatePositions() {\n if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;\n\n const items = this.querySelectorAll(`:scope > ${tagName('toast-item')}`);\n for (const child of items) {\n const oldRect = this._positionCache.get(child);\n if (!oldRect) continue;\n\n const newRect = child.getBoundingClientRect();\n const deltaY = oldRect.top - newRect.top;\n\n if (Math.abs(deltaY) < 1) continue;\n\n (child as HTMLElement).animate(\n [{ transform: `translateY(${deltaY}px)` }, { transform: 'translateY(0)' }],\n { duration: 200, easing: 'cubic-bezier(0.2, 0, 0, 1)' },\n );\n }\n }\n\n // ── Document visibility ──────────────────────────────────────────────────────\n\n private _onVisibilityChange = () => {\n this._documentHidden = document.hidden;\n const items = this.querySelectorAll<HTMLElement>(`:scope > ${tagName('toast-item')}`);\n if (document.hidden) {\n for (const item of items) this._pauseTimer(item);\n } else {\n for (const item of items) this._resumeTimer(item);\n }\n };\n\n // ── Keyboard ──────────────────────────────────────────────────────────────────\n\n private _onKeyDown = (e: KeyboardEvent) => {\n if (e.key !== 'Escape') return;\n if (!this.matches(':popover-open')) return;\n\n const last = this.querySelector<HTMLElement>(`:scope > ${tagName('toast-item')}:last-of-type`);\n if (last) {\n e.preventDefault();\n this._hideToast(last);\n }\n };\n}\n\n// ── Standalone function ───────────────────────────────────────────────────────\n\nlet _defaultInstance: LuxenToast | null = null;\n\nfunction _getDefault(): LuxenToast {\n if (!_defaultInstance || !_defaultInstance.isConnected) {\n _defaultInstance =\n document.querySelector(tagName('toast')) ??\n (document.createElement(tagName('toast')) as LuxenToast);\n if (!_defaultInstance.isConnected) {\n document.body.appendChild(_defaultInstance);\n }\n }\n return _defaultInstance;\n}\n\n/** Show a toast notification. Auto-creates `<l-toast>` if none exists in the DOM. */\nexport function toast(options: ToastOptions): HTMLElement {\n return _getDefault().toast(options);\n}\n"],"mappings":"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"}
|
|
@@ -10,9 +10,9 @@ import { Placement } from '@floating-ui/dom';
|
|
|
10
10
|
* @csspart body - The tooltip popover container.
|
|
11
11
|
* @csspart arrow - The directional arrow element.
|
|
12
12
|
*
|
|
13
|
-
* @cssproperty --background - Background color. Default: dark in light mode, light in dark mode.
|
|
14
|
-
* @cssproperty --color - Text color.
|
|
15
|
-
* @cssproperty --radius - Border radius. Default `4px`.
|
|
13
|
+
* @cssproperty --background-color - Background color. Default: dark in light mode, light in dark mode.
|
|
14
|
+
* @cssproperty --text-color - Text color. If unset, auto-derived from `--background-color` luminance.
|
|
15
|
+
* @cssproperty --border-radius - Border radius. Default `4px`.
|
|
16
16
|
* @cssproperty --max-width - Maximum width. Default `180px`.
|
|
17
17
|
* @cssproperty --arrow-size - Arrow size. Default `6px`.
|
|
18
18
|
* @cssproperty --show-duration - Show animation duration. Default `150ms`.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{uniqueId as e}from"../../registry.js";import{c as t,i as n,n as r}from"../../chunks/lit.js";import{LuxenElement as i}from"../../shared/luxen-element.js";import{a,t as o}from"../../chunks/decorate.js";import s from"../../shared/styles/host.styles.js";import{PopoverController as c}from"../../shared/controllers/popover.js";var l=t(`:host{--background:var(--l-color-bg-fill-brand,var(--lightningcss-light,#1f2937)var(--lightningcss-dark,#f9fafb));--
|
|
1
|
+
import{uniqueId as e}from"../../registry.js";import{c as t,i as n,n as r}from"../../chunks/lit.js";import{LuxenElement as i}from"../../shared/luxen-element.js";import{i as a,t as o}from"../../chunks/decorate.js";import s from"../../shared/styles/host.styles.js";import{PopoverController as c}from"../../shared/controllers/popover.js";var l=t(`:host{--background-color:var(--l-color-bg-fill-brand,var(--lightningcss-light,#1f2937)var(--lightningcss-dark,#f9fafb));--border-radius:4px;--max-width:180px;--arrow-size:6px;--show-duration:.15s;--hide-duration:.15s;display:contents}[popover]{inset:unset;box-sizing:border-box;width:max-content;max-width:var(--max-width);border-radius:var(--border-radius);background:var(--background-color);color:var(--text-color,oklch(from var(--background-color) calc(.59 - .41 * sign(l - .5)) 0 0));filter:drop-shadow(0 1px 2px #00000029);pointer-events:none;border:0;padding:4px 8px;font-size:.8125rem;line-height:1.4;overflow:visible}@supports (color:contrast-color(red vs black, white)){[popover]{color:var(--text-color,contrast-color(var(--background-color) vs #111827, #fff to AA))}}i{width:var(--arrow-size);height:var(--arrow-size);background:var(--background-color);display:block;position:absolute;transform:rotate(45deg)}`),u=class extends i{constructor(...t){super(...t),this._tooltipId=e(`tooltip`),this._floating=new c(this,{getTriggerElement:()=>this._trigger,getFloatingElement:()=>this._popover,getArrowElement:()=>this._arrowEl}),this.#e=``,this.#t=`top`,this.#n=8,this.#r=!1,this.#i=!1,this.#a=`hover focus`,this._onPointerEnter=()=>{this._hasTrigger(`hover`)&&(this._floating.cleanupSafePolygon(),this.show())},this._onPointerLeave=e=>{!this._hasTrigger(`hover`)||!this.open||this._floating.handlePointerLeave(e,()=>this.hide())},this._onFocusIn=()=>{this._hasTrigger(`focus`)&&this.show()},this._onFocusOut=()=>{this._hasTrigger(`focus`)&&this.hide()},this._onClick=()=>{this._hasTrigger(`click`)&&this.toggle()},this._onKeyDown=e=>{this.open&&e.key===`Escape`&&(e.stopPropagation(),this.hide())}}static{this.styles=[s,l]}#e;get for(){return this.#e}set for(e){this.#e=e}#t;get placement(){return this.#t}set placement(e){this.#t=e}#n;get distance(){return this.#n}set distance(e){this.#n=e}#r;get open(){return this.#r}set open(e){this.#r=e}#i;get withoutArrow(){return this.#i}set withoutArrow(e){this.#i=e}#a;get trigger(){return this.#a}set trigger(e){this.#a=e}_hasTrigger(e){return this.trigger.split(` `).includes(e)}get _trigger(){return this.for?this.getRootNode().getElementById(this.for):null}get _popover(){return this.shadowRoot.querySelector(`[popover]`)}get _arrowEl(){return this.withoutArrow?null:this.shadowRoot.querySelector(`i`)}_getDuration(e){let t=parseFloat(getComputedStyle(this).getPropertyValue(e));return Number.isNaN(t)?150:t}connectedCallback(){super.connectedCallback(),requestAnimationFrame(()=>this._addTriggerListeners())}disconnectedCallback(){super.disconnectedCallback(),this._removeTriggerListeners()}updated(e){e.has(`open`)&&this._handleOpenChange(),e.has(`for`)&&(this._removeTriggerListeners(e.get(`for`)),this._addTriggerListeners())}show(){this.open||=!0}hide(){this.open&&=!1}toggle(){this.open=!this.open}async _handleOpenChange(){let e=this._popover;if(!e)return;let t={placement:this.placement,distance:this.distance};if(this.open){if(e.showPopover(),await this._floating.updatePosition(t),!this.open)return;await this._floating.animateShow(e,this._getDuration(`--show-duration`)),this._floating.startPositioning(t),this._trigger?.setAttribute(`aria-describedby`,this._tooltipId)}else this._floating.stopPositioning(),this._floating.cleanupSafePolygon(),this._trigger?.removeAttribute(`aria-describedby`),await this._floating.animateHide(e,this._getDuration(`--hide-duration`)),e.matches(`:popover-open`)&&e.hidePopover()}_addTriggerListeners(){this._floating.addTriggerListeners({onPointerEnter:this._onPointerEnter,onPointerLeave:this._onPointerLeave,onFocusIn:this._onFocusIn,onFocusOut:this._onFocusOut,onClick:this._onClick,onKeyDown:this._onKeyDown})}_removeTriggerListeners(e){let t=e?this.getRootNode().getElementById(e):void 0;this._floating.removeTriggerListeners(t)}render(){return n`
|
|
2
2
|
<div
|
|
3
3
|
id=${this._tooltipId}
|
|
4
4
|
popover="manual"
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tooltip.js","names":[],"sources":["../../../src/html/elements/tooltip/tooltip.css?inline","../../../src/html/elements/tooltip/tooltip.ts"],"sourcesContent":[":host {\n --background: var(--l-color-bg-fill-brand, light-dark(#1f2937, #f9fafb));\n --color: light-dark(#fff, #111827);\n --radius: 4px;\n --max-width: 180px;\n --arrow-size: 6px;\n --show-duration: 150ms;\n --hide-duration: 150ms;\n\n display: contents;\n}\n\n[popover] {\n inset: unset;\n overflow: visible;\n box-sizing: border-box;\n width: max-content;\n max-width: var(--max-width);\n padding: 4px 8px;\n border: 0;\n border-radius: var(--radius);\n background: var(--background);\n color: var(--color);\n font-size: 0.8125rem;\n line-height: 1.4;\n filter: drop-shadow(0 1px 2px rgb(0 0 0 / 16%));\n pointer-events: none;\n}\n\ni {\n position: absolute;\n display: block;\n width: var(--arrow-size);\n height: var(--arrow-size);\n background: var(--background);\n transform: rotate(45deg);\n}\n","import { html, nothing, unsafeCSS, type PropertyValues } from 'lit';\nimport { LuxenElement } from '../../shared/luxen-element';\nimport { property } from 'lit/decorators.js';\nimport type { Placement } from '@floating-ui/dom';\nimport { PopoverController } from '../../shared/controllers/popover';\nimport { uniqueId } from '../../registry';\nimport hostStyles from '../../shared/styles/host.styles';\nimport rawStyles from './tooltip.css?inline';\n\nconst styles = unsafeCSS(rawStyles);\n\n/**\n * @summary A tooltip that displays contextual text on hover or focus.\n * @customElement l-tooltip\n *\n * @slot - Tooltip content (text or rich HTML).\n *\n * @csspart body - The tooltip popover container.\n * @csspart arrow - The directional arrow element.\n *\n * @cssproperty --background - Background color. Default: dark in light mode, light in dark mode.\n * @cssproperty --color - Text color.\n * @cssproperty --radius - Border radius. Default `4px`.\n * @cssproperty --max-width - Maximum width. Default `180px`.\n * @cssproperty --arrow-size - Arrow size. Default `6px`.\n * @cssproperty --show-duration - Show animation duration. Default `150ms`.\n * @cssproperty --hide-duration - Hide animation duration. Default `150ms`.\n */\nexport class LuxenTooltip extends LuxenElement {\n static override styles = [hostStyles, styles];\n\n private _tooltipId = uniqueId('tooltip');\n\n private _floating = new PopoverController(this, {\n getTriggerElement: () => this._trigger,\n getFloatingElement: () => this._popover,\n getArrowElement: () => this._arrowEl,\n });\n\n /** The HTML id of the element triggering the tooltip. */\n @property()\n accessor for = '';\n\n /** The preferred placement of the tooltip. */\n @property()\n accessor placement: Placement = 'top';\n\n /** The distance in pixels from the target element. */\n @property({ type: Number })\n accessor distance = 8;\n\n /** Whether or not the tooltip is visible. */\n @property({ type: Boolean, reflect: true })\n accessor open = false;\n\n /** Hide the directional arrow. */\n @property({ type: Boolean, reflect: true, attribute: 'without-arrow' })\n accessor withoutArrow = false;\n\n /** Space-separated list of trigger modes: `hover`, `focus`, `click`, `manual`. */\n @property()\n accessor trigger = 'hover focus';\n\n private _hasTrigger(type: string) {\n return this.trigger.split(' ').includes(type);\n }\n\n private get _trigger(): HTMLElement | null {\n return this.for ? (this.getRootNode() as Document | ShadowRoot).getElementById(this.for) : null;\n }\n\n private get _popover(): HTMLElement {\n return this.shadowRoot!.querySelector('[popover]')!;\n }\n\n private get _arrowEl(): HTMLElement | null {\n return this.withoutArrow ? null : this.shadowRoot!.querySelector('i');\n }\n\n private _getDuration(prop: '--show-duration' | '--hide-duration'): number {\n const parsed = parseFloat(getComputedStyle(this).getPropertyValue(prop));\n return Number.isNaN(parsed) ? 150 : parsed;\n }\n\n override connectedCallback() {\n super.connectedCallback();\n requestAnimationFrame(() => this._addTriggerListeners());\n }\n\n override disconnectedCallback() {\n super.disconnectedCallback();\n this._removeTriggerListeners();\n }\n\n override updated(changed: PropertyValues<this>) {\n if (changed.has('open')) {\n this._handleOpenChange();\n }\n if (changed.has('for')) {\n this._removeTriggerListeners(changed.get('for') as string);\n this._addTriggerListeners();\n }\n }\n\n show() {\n if (!this.open) this.open = true;\n }\n\n hide() {\n if (this.open) this.open = false;\n }\n\n toggle() {\n this.open = !this.open;\n }\n\n private async _handleOpenChange() {\n const popover = this._popover;\n if (!popover) return;\n\n const posOpts = { placement: this.placement, distance: this.distance };\n\n if (this.open) {\n popover.showPopover();\n await this._floating.updatePosition(posOpts);\n if (!this.open) return;\n await this._floating.animateShow(popover, this._getDuration('--show-duration'));\n this._floating.startPositioning(posOpts);\n this._trigger?.setAttribute('aria-describedby', this._tooltipId);\n } else {\n this._floating.stopPositioning();\n this._floating.cleanupSafePolygon();\n this._trigger?.removeAttribute('aria-describedby');\n await this._floating.animateHide(popover, this._getDuration('--hide-duration'));\n if (popover.matches(':popover-open')) popover.hidePopover();\n }\n }\n\n // --- Trigger event handlers ---\n\n private _onPointerEnter = () => {\n if (!this._hasTrigger('hover')) return;\n this._floating.cleanupSafePolygon();\n this.show();\n };\n\n private _onPointerLeave = (e: PointerEvent) => {\n if (!this._hasTrigger('hover') || !this.open) return;\n this._floating.handlePointerLeave(e, () => this.hide());\n };\n\n private _onFocusIn = () => {\n if (this._hasTrigger('focus')) this.show();\n };\n private _onFocusOut = () => {\n if (this._hasTrigger('focus')) this.hide();\n };\n private _onClick = () => {\n if (this._hasTrigger('click')) this.toggle();\n };\n\n private _onKeyDown = (e: KeyboardEvent) => {\n if (this.open && e.key === 'Escape') {\n e.stopPropagation();\n this.hide();\n }\n };\n\n private _addTriggerListeners() {\n this._floating.addTriggerListeners({\n onPointerEnter: this._onPointerEnter,\n onPointerLeave: this._onPointerLeave,\n onFocusIn: this._onFocusIn,\n onFocusOut: this._onFocusOut,\n onClick: this._onClick,\n onKeyDown: this._onKeyDown,\n });\n }\n\n private _removeTriggerListeners(forId?: string) {\n const trigger = forId\n ? (this.getRootNode() as Document | ShadowRoot).getElementById(forId)\n : undefined;\n this._floating.removeTriggerListeners(trigger);\n }\n\n override render() {\n return html`\n <div\n id=${this._tooltipId}\n popover=\"manual\"\n role=\"tooltip\"\n part=\"body\"\n >\n ${this.withoutArrow\n ? nothing\n : html`\n <i\n part=\"arrow\"\n role=\"presentation\"\n ></i>\n `}\n <slot></slot>\n </div>\n `;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"tooltip.js","names":[],"sources":["../../../src/html/elements/tooltip/tooltip.css?inline","../../../src/html/elements/tooltip/tooltip.ts"],"sourcesContent":[":host {\n --background-color: var(--l-color-bg-fill-brand, light-dark(#1f2937, #f9fafb));\n --border-radius: 4px;\n --max-width: 180px;\n --arrow-size: 6px;\n --show-duration: 150ms;\n --hide-duration: 150ms;\n\n display: contents;\n}\n\n[popover] {\n inset: unset;\n overflow: visible;\n box-sizing: border-box;\n width: max-content;\n max-width: var(--max-width);\n padding: 4px 8px;\n border: 0;\n border-radius: var(--border-radius);\n background: var(--background-color);\n color: var(\n --text-color,\n oklch(from var(--background-color) calc(0.59 - 0.41 * sign(l - 0.5)) 0 0)\n );\n font-size: 0.8125rem;\n line-height: 1.4;\n filter: drop-shadow(0 1px 2px rgb(0 0 0 / 16%));\n pointer-events: none;\n}\n\n@supports (color: contrast-color(red vs black, white)) {\n [popover] {\n color: var(--text-color, contrast-color(var(--background-color) vs #111827, #fff to AA));\n }\n}\n\ni {\n position: absolute;\n display: block;\n width: var(--arrow-size);\n height: var(--arrow-size);\n background: var(--background-color);\n transform: rotate(45deg);\n}\n","import { html, nothing, unsafeCSS, type PropertyValues } from 'lit';\nimport { LuxenElement } from '../../shared/luxen-element';\nimport { property } from 'lit/decorators.js';\nimport type { Placement } from '@floating-ui/dom';\nimport { PopoverController } from '../../shared/controllers/popover';\nimport { uniqueId } from '../../registry';\nimport hostStyles from '../../shared/styles/host.styles';\nimport rawStyles from './tooltip.css?inline';\n\nconst styles = unsafeCSS(rawStyles);\n\n/**\n * @summary A tooltip that displays contextual text on hover or focus.\n * @customElement l-tooltip\n *\n * @slot - Tooltip content (text or rich HTML).\n *\n * @csspart body - The tooltip popover container.\n * @csspart arrow - The directional arrow element.\n *\n * @cssproperty --background-color - Background color. Default: dark in light mode, light in dark mode.\n * @cssproperty --text-color - Text color. If unset, auto-derived from `--background-color` luminance.\n * @cssproperty --border-radius - Border radius. Default `4px`.\n * @cssproperty --max-width - Maximum width. Default `180px`.\n * @cssproperty --arrow-size - Arrow size. Default `6px`.\n * @cssproperty --show-duration - Show animation duration. Default `150ms`.\n * @cssproperty --hide-duration - Hide animation duration. Default `150ms`.\n */\nexport class LuxenTooltip extends LuxenElement {\n static override styles = [hostStyles, styles];\n\n private _tooltipId = uniqueId('tooltip');\n\n private _floating = new PopoverController(this, {\n getTriggerElement: () => this._trigger,\n getFloatingElement: () => this._popover,\n getArrowElement: () => this._arrowEl,\n });\n\n /** The HTML id of the element triggering the tooltip. */\n @property()\n accessor for = '';\n\n /** The preferred placement of the tooltip. */\n @property()\n accessor placement: Placement = 'top';\n\n /** The distance in pixels from the target element. */\n @property({ type: Number })\n accessor distance = 8;\n\n /** Whether or not the tooltip is visible. */\n @property({ type: Boolean, reflect: true })\n accessor open = false;\n\n /** Hide the directional arrow. */\n @property({ type: Boolean, reflect: true, attribute: 'without-arrow' })\n accessor withoutArrow = false;\n\n /** Space-separated list of trigger modes: `hover`, `focus`, `click`, `manual`. */\n @property()\n accessor trigger = 'hover focus';\n\n private _hasTrigger(type: string) {\n return this.trigger.split(' ').includes(type);\n }\n\n private get _trigger(): HTMLElement | null {\n return this.for ? (this.getRootNode() as Document | ShadowRoot).getElementById(this.for) : null;\n }\n\n private get _popover(): HTMLElement {\n return this.shadowRoot!.querySelector('[popover]')!;\n }\n\n private get _arrowEl(): HTMLElement | null {\n return this.withoutArrow ? null : this.shadowRoot!.querySelector('i');\n }\n\n private _getDuration(prop: '--show-duration' | '--hide-duration'): number {\n const parsed = parseFloat(getComputedStyle(this).getPropertyValue(prop));\n return Number.isNaN(parsed) ? 150 : parsed;\n }\n\n override connectedCallback() {\n super.connectedCallback();\n requestAnimationFrame(() => this._addTriggerListeners());\n }\n\n override disconnectedCallback() {\n super.disconnectedCallback();\n this._removeTriggerListeners();\n }\n\n override updated(changed: PropertyValues<this>) {\n if (changed.has('open')) {\n this._handleOpenChange();\n }\n if (changed.has('for')) {\n this._removeTriggerListeners(changed.get('for') as string);\n this._addTriggerListeners();\n }\n }\n\n show() {\n if (!this.open) this.open = true;\n }\n\n hide() {\n if (this.open) this.open = false;\n }\n\n toggle() {\n this.open = !this.open;\n }\n\n private async _handleOpenChange() {\n const popover = this._popover;\n if (!popover) return;\n\n const posOpts = { placement: this.placement, distance: this.distance };\n\n if (this.open) {\n popover.showPopover();\n await this._floating.updatePosition(posOpts);\n if (!this.open) return;\n await this._floating.animateShow(popover, this._getDuration('--show-duration'));\n this._floating.startPositioning(posOpts);\n this._trigger?.setAttribute('aria-describedby', this._tooltipId);\n } else {\n this._floating.stopPositioning();\n this._floating.cleanupSafePolygon();\n this._trigger?.removeAttribute('aria-describedby');\n await this._floating.animateHide(popover, this._getDuration('--hide-duration'));\n if (popover.matches(':popover-open')) popover.hidePopover();\n }\n }\n\n // --- Trigger event handlers ---\n\n private _onPointerEnter = () => {\n if (!this._hasTrigger('hover')) return;\n this._floating.cleanupSafePolygon();\n this.show();\n };\n\n private _onPointerLeave = (e: PointerEvent) => {\n if (!this._hasTrigger('hover') || !this.open) return;\n this._floating.handlePointerLeave(e, () => this.hide());\n };\n\n private _onFocusIn = () => {\n if (this._hasTrigger('focus')) this.show();\n };\n private _onFocusOut = () => {\n if (this._hasTrigger('focus')) this.hide();\n };\n private _onClick = () => {\n if (this._hasTrigger('click')) this.toggle();\n };\n\n private _onKeyDown = (e: KeyboardEvent) => {\n if (this.open && e.key === 'Escape') {\n e.stopPropagation();\n this.hide();\n }\n };\n\n private _addTriggerListeners() {\n this._floating.addTriggerListeners({\n onPointerEnter: this._onPointerEnter,\n onPointerLeave: this._onPointerLeave,\n onFocusIn: this._onFocusIn,\n onFocusOut: this._onFocusOut,\n onClick: this._onClick,\n onKeyDown: this._onKeyDown,\n });\n }\n\n private _removeTriggerListeners(forId?: string) {\n const trigger = forId\n ? (this.getRootNode() as Document | ShadowRoot).getElementById(forId)\n : undefined;\n this._floating.removeTriggerListeners(trigger);\n }\n\n override render() {\n return html`\n <div\n id=${this._tooltipId}\n popover=\"manual\"\n role=\"tooltip\"\n part=\"body\"\n >\n ${this.withoutArrow\n ? nothing\n : html`\n <i\n part=\"arrow\"\n role=\"presentation\"\n ></i>\n `}\n <slot></slot>\n </div>\n `;\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{tagName as e}from"../../registry.js";import{c as t,i as n}from"../../chunks/lit.js";import{LuxenElement as r}from"../../shared/luxen-element.js";import{
|
|
1
|
+
import{tagName as e}from"../../registry.js";import{c as t,i as n}from"../../chunks/lit.js";import{LuxenElement as r}from"../../shared/luxen-element.js";import{i,t as a}from"../../chunks/decorate.js";import o from"../../shared/styles/host.styles.js";var s=t(`:host{--indent-size:1rem;--indent-guide-width:1px;--indent-guide-style:solid;--indent-guide-color:var(--l-color-border-interactive,var(--lightningcss-light,#d1d5db)var(--lightningcss-dark,#3a4048));--row-height:1.75rem;--row-padding-inline:.25rem;--chevron-size:1.125rem;--item-gap:.375rem;color:var(--l-color-text-primary,CanvasText);font-family:inherit;line-height:1.5;display:block}.tree{outline:none;display:block}.tree:focus-visible{outline:2px solid var(--l-focus-ring,Highlight);outline-offset:2px;border-radius:.375rem}`),c=class extends r{constructor(...t){super(...t),this._lastFocusedItem=null,this.#e=`single`,this.#t=!1,this._onItemToggle=e=>{let{item:t,checked:n}=e.detail;this._selectItem(t,n)},this._onClick=e=>{let t=this._itemFromEvent(e);if(!t||t.disabled)return;let n=e.composedPath();if(n.some(e=>e instanceof HTMLInputElement&&e.type===`checkbox`))return;let r=new Set([`BUTTON`,`A`,`INPUT`,`SELECT`,`TEXTAREA`]),i=new Set([`button`,`link`,`menuitem`,`menuitemcheckbox`]);if(n.some(e=>{if(!(e instanceof HTMLElement)||e===t||e.getAttribute?.(`part`)===`expand-button`||e instanceof HTMLInputElement&&e.type===`checkbox`)return!1;if(r.has(e.tagName))return!0;let n=e.getAttribute?.(`role`);return n!==null&&i.has(n)}))return;let a=n.some(e=>e instanceof HTMLElement&&e.getAttribute?.(`part`)===`expand-button`);if(this._focusItem(t),a){t.toggle();return}switch(this.selection){case`single`:this._setSingleSelection(t),t.isLeaf()||t.toggle();break;case`leaf`:t.isLeaf()?this._setSingleSelection(t):t.toggle();break;case`multiple`:this._selectItem(t,!t.selected);break;case`none`:t.toggle();break}},this._onFocusIn=t=>{let n=t.target;if(n instanceof HTMLElement){let t=n.closest(e(`tree-item`));t&&(this._lastFocusedItem=t)}},this._onKeyDown=t=>{let n=this._lastFocusedItem??this._visibleItems()[0];if(!n)return;let r=this._visibleItems(),i=r.indexOf(n);switch(t.key){case`ArrowDown`:{t.preventDefault();let e=r[Math.min(i+1,r.length-1)];e&&this._focusItem(e);break}case`ArrowUp`:{t.preventDefault();let e=r[Math.max(i-1,0)];e&&this._focusItem(e);break}case`ArrowRight`:if(t.preventDefault(),!n.isLeaf()&&!n.expanded)n.expanded=!0;else if(n.expanded){let e=n.getChildrenItems()[0];e&&this._focusItem(e)}break;case`ArrowLeft`:if(t.preventDefault(),n.expanded&&!n.isLeaf())n.expanded=!1;else{let t=n.parentElement?.closest(e(`tree-item`));t&&this._focusItem(t)}break;case`Home`:t.preventDefault(),r[0]&&this._focusItem(r[0]);break;case`End`:{t.preventDefault();let e=r[r.length-1];e&&this._focusItem(e);break}case`Enter`:case` `:t.preventDefault(),this._handleRowActivate(n);break;case`*`:{t.preventDefault();let r=(n.parentElement?Array.from(n.parentElement.children):[]).filter(t=>t.tagName===e(`tree-item`).toUpperCase());for(let e of r)e.isLeaf()||(e.expanded=!0);break}}}}static{this.styles=[o,s]}#e;get selection(){return this.#e}set selection(e){this.#e=e}#t;get independent(){return this.#t}set independent(e){this.#t=e}connectedCallback(){super.connectedCallback(),this._mutationObserver=new MutationObserver(()=>this._syncAll()),this._mutationObserver.observe(this,{childList:!0,subtree:!0}),this.addEventListener(`l-tree-item-toggle`,this._onItemToggle),queueMicrotask(()=>this._syncAll())}disconnectedCallback(){super.disconnectedCallback(),this._mutationObserver?.disconnect(),this.removeEventListener(`l-tree-item-toggle`,this._onItemToggle)}updated(e){(e.has(`selection`)||e.has(`independent`))&&this._syncAll()}getAllItems({includeDisabled:t=!0}={}){let n=e(`tree-item`);return Array.from(this.querySelectorAll(n)).filter(e=>t||!e.disabled)}getSelection(){return this.getAllItems().filter(e=>e.selected)}expandAll(){for(let e of this.getAllItems())e.isLeaf()||(e.expanded=!0)}collapseAll(){for(let e of this.getAllItems())e.expanded=!1}_syncAll(){let e=this.selection===`multiple`,t=this._rootItems();for(let n of t)this._syncSubtree(n,0,e);this._updateBranchStates(),this._ensureTabStop()}_syncSubtree(e,t,n){e.depth=t,e.showCheckbox=n&&this._canShowCheckboxOn(e);for(let r of e.getChildrenItems())this._syncSubtree(r,t+1,n)}_canShowCheckboxOn(e){return this.selection===`multiple`}_rootItems(){let t=e(`tree-item`).toUpperCase();return Array.from(this.children).filter(e=>e.tagName===t)}_ensureTabStop(){let e=this._visibleItems();if(e.length!==0&&!e.some(e=>e.tabIndex===0)){for(let t of e)t.tabIndex=-1;e[0].tabIndex=0}}_visibleItems(){let e=[],t=n=>{for(let r of n)e.push(r),r.expanded&&t(r.getChildrenItems())};return t(this._rootItems()),e}_handleRowActivate(e){if(!e.disabled)switch(this.selection){case`single`:this._setSingleSelection(e);break;case`leaf`:e.isLeaf()?this._setSingleSelection(e):e.toggle();break;case`multiple`:this._selectItem(e,!e.selected);break;case`none`:e.toggle();break}}_setSingleSelection(e){for(let t of this.getAllItems())t!==e&&t.selected&&(t.selected=!1);e.selected=!0,this._emitSelectionChange()}_selectItem(e,t){e.disabled||(e.selected=t,this.selection===`multiple`&&!this.independent&&this._setSubtreeSelection(e,t),e.indeterminate=!1,this._updateBranchStates(),this._emitSelectionChange())}_setSubtreeSelection(e,t){for(let n of e.getChildrenItems())n.disabled||(n.selected=t,n.indeterminate=!1,this._setSubtreeSelection(n,t))}_updateBranchStates(){if(this.selection!==`multiple`||this.independent){for(let e of this.getAllItems())e.indeterminate=!1;return}let e=t=>{let n=t.getChildrenItems({includeDisabled:!1});if(n.length===0)return{all:t.selected,any:t.selected};let r=!0,i=!1;for(let t of n){let n=e(t);n.all||(r=!1),n.any&&(i=!0)}return t.selected=r,t.indeterminate=!r&&i,{all:r&&(t.getChildrenItems().length>0?r:t.selected),any:i}};for(let t of this._rootItems())e(t)}_emitSelectionChange(){this.emit(`selection-change`,{detail:{selection:this.getSelection()}})}_itemFromEvent(t){let n=e(`tree-item`),r=t.composedPath();for(let e of r)if(e instanceof HTMLElement&&e.matches?.(n))return e;return null}_focusItem(e){let t=this._visibleItems();for(let e of t)e.tabIndex=-1;e.tabIndex=0,e.focus(),this._lastFocusedItem=e}render(){return n`
|
|
2
2
|
<div
|
|
3
3
|
class="tree"
|
|
4
4
|
part="base"
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tree.js","names":[],"sources":["../../../src/html/elements/tree/tree.css?inline","../../../src/html/elements/tree/tree.ts"],"sourcesContent":[":host {\n --indent-size: 1rem;\n --indent-guide-width: 1px;\n --indent-guide-style: solid;\n --indent-guide-color: var(--l-color-border-interactive, light-dark(#d1d5db, #3a4048));\n --row-height: 1.75rem;\n --row-padding-inline: 0.25rem;\n --chevron-size: 1.125rem;\n --item-gap: 0.375rem;\n\n display: block;\n color: var(--l-color-text-primary, CanvasText);\n font-family: inherit;\n line-height: 1.5;\n}\n\n.tree {\n display: block;\n outline: none;\n}\n\n.tree:focus-visible {\n outline: 2px solid var(--l-focus-ring, Highlight);\n outline-offset: 2px;\n border-radius: 0.375rem;\n}\n","import { html, unsafeCSS, type PropertyValues } from 'lit';\nimport { property } from 'lit/decorators.js';\nimport { LuxenElement } from '../../shared/luxen-element';\nimport { tagName } from '../../registry';\nimport type { LuxenTreeItem } from '../tree-item/tree-item';\nimport hostStyles from '../../shared/styles/host.styles';\nimport rawStyles from './tree.css?inline';\n\nconst styles = unsafeCSS(rawStyles);\n\nexport type TreeSelection = 'single' | 'multiple' | 'leaf' | 'none';\n\n/**\n * A hierarchical tree view composed of `<l-tree-item>` children.\n *\n * @slot - One or more `l-tree-item` elements.\n *\n * @csspart base - The root tree container.\n *\n * @cssproperty --indent-size - Horizontal indent per depth level. Default `1rem`.\n * @cssproperty --indent-guide-width - Thickness of the vertical guide line between a parent and its children. Default `1px`. Set to `0` to hide guides.\n * @cssproperty --indent-guide-style - Line style of the guide (`solid`, `dashed`, `dotted`, `double`…). Default `solid`.\n * @cssproperty --indent-guide-color - Color of the guide line.\n * @cssproperty --row-height - Minimum row height. Default `1.75rem`.\n * @cssproperty --row-padding-inline - Inner inline padding of the row; also drives the content slot left indent and the indent guide column. Default `0.25rem`.\n * @cssproperty --chevron-size - Size of the expand/collapse chevron box. Default `1.125rem`.\n * @cssproperty --item-gap - Horizontal gap between chevron, prefix, label and suffix on the row; also drives the content slot left indent. Default `0.375rem`.\n *\n * @event selection-change - Fired when the selected items change. Detail: `{ selection: LuxenTreeItem[] }`.\n */\nexport class LuxenTree extends LuxenElement {\n static override styles = [hostStyles, styles];\n\n private _mutationObserver?: MutationObserver;\n private _lastFocusedItem: LuxenTreeItem | null = null;\n\n /**\n * Selection behaviour:\n * - `single` (default): at most one item selected via `aria-selected`.\n * - `multiple`: any number of items selected. Checkboxes are rendered.\n * - `leaf`: only leaf items can be selected (single). Branches just toggle.\n * - `none`: purely navigable, no selection state.\n */\n @property({ reflect: true })\n accessor selection: TreeSelection = 'single';\n\n /**\n * When set with `selection=\"multiple\"`, parent and children selection are decoupled:\n * toggling a parent does NOT toggle its descendants and vice versa.\n * Without it, selection cascades both ways and branches may become indeterminate.\n */\n @property({ type: Boolean, reflect: true })\n accessor independent = false;\n\n override connectedCallback() {\n super.connectedCallback();\n this._mutationObserver = new MutationObserver(() => this._syncAll());\n this._mutationObserver.observe(this, { childList: true, subtree: true });\n this.addEventListener('l-tree-item-toggle', this._onItemToggle as EventListener);\n\n // Defer sync to let light DOM upgrade.\n queueMicrotask(() => this._syncAll());\n }\n\n override disconnectedCallback() {\n super.disconnectedCallback();\n this._mutationObserver?.disconnect();\n this.removeEventListener('l-tree-item-toggle', this._onItemToggle as EventListener);\n }\n\n override updated(changed: PropertyValues<this>) {\n if (changed.has('selection') || changed.has('independent')) {\n this._syncAll();\n }\n }\n\n // --- Public API ---\n\n /** Returns all items in document (flat) order, including nested ones. */\n getAllItems({ includeDisabled = true } = {}): LuxenTreeItem[] {\n const tag = tagName('tree-item');\n return Array.from(this.querySelectorAll<LuxenTreeItem>(tag)).filter(\n (item) => includeDisabled || !item.disabled,\n );\n }\n\n /** Returns currently selected items. */\n getSelection(): LuxenTreeItem[] {\n return this.getAllItems().filter((i) => i.selected);\n }\n\n /** Expands every item that has children. */\n expandAll() {\n for (const item of this.getAllItems()) {\n if (!item.isLeaf()) item.expanded = true;\n }\n }\n\n /** Collapses every item. */\n collapseAll() {\n for (const item of this.getAllItems()) {\n item.expanded = false;\n }\n }\n\n // --- Sync / ARIA / depth / checkbox visibility ---\n\n private _syncAll() {\n const showCheckbox = this.selection === 'multiple';\n const roots = this._rootItems();\n for (const root of roots) {\n this._syncSubtree(root, 0, showCheckbox);\n }\n this._updateBranchStates();\n // Ensure at least one item is tabbable.\n this._ensureTabStop();\n }\n\n private _syncSubtree(item: LuxenTreeItem, depth: number, showCheckbox: boolean) {\n item.depth = depth;\n item.showCheckbox = showCheckbox && this._canShowCheckboxOn(item);\n for (const child of item.getChildrenItems()) {\n this._syncSubtree(child, depth + 1, showCheckbox);\n }\n }\n\n private _canShowCheckboxOn(_item: LuxenTreeItem): boolean {\n if (this.selection !== 'multiple') return false;\n // In cascade mode, branches get a checkbox too so you can bulk-toggle children.\n // In leaf-only selection, hidden here because selection !== 'multiple'.\n return true;\n }\n\n private _rootItems(): LuxenTreeItem[] {\n const tag = tagName('tree-item').toUpperCase();\n return (Array.from(this.children) as LuxenTreeItem[]).filter((el) => el.tagName === tag);\n }\n\n private _ensureTabStop() {\n const items = this._visibleItems();\n if (items.length === 0) return;\n const hasTabStop = items.some((i) => i.tabIndex === 0);\n if (!hasTabStop) {\n for (const i of items) i.tabIndex = -1;\n items[0].tabIndex = 0;\n }\n }\n\n /** Items currently visible (parent chain all expanded). */\n private _visibleItems(): LuxenTreeItem[] {\n const out: LuxenTreeItem[] = [];\n const walk = (items: LuxenTreeItem[]) => {\n for (const i of items) {\n out.push(i);\n if (i.expanded) walk(i.getChildrenItems());\n }\n };\n walk(this._rootItems());\n return out;\n }\n\n // --- Selection handling ---\n\n private _onItemToggle = (event: CustomEvent<{ item: LuxenTreeItem; checked: boolean }>) => {\n const { item, checked } = event.detail;\n this._selectItem(item, checked);\n };\n\n private _handleRowActivate(item: LuxenTreeItem) {\n if (item.disabled) return;\n\n switch (this.selection) {\n case 'single':\n this._setSingleSelection(item);\n break;\n case 'leaf':\n if (item.isLeaf()) this._setSingleSelection(item);\n else item.toggle();\n break;\n case 'multiple':\n this._selectItem(item, !item.selected);\n break;\n case 'none':\n item.toggle();\n break;\n }\n }\n\n private _setSingleSelection(item: LuxenTreeItem) {\n for (const i of this.getAllItems()) {\n if (i !== item && i.selected) i.selected = false;\n }\n item.selected = true;\n this._emitSelectionChange();\n }\n\n private _selectItem(item: LuxenTreeItem, value: boolean) {\n if (item.disabled) return;\n item.selected = value;\n\n if (this.selection === 'multiple' && !this.independent) {\n // Cascade DOWN: toggling a branch toggles all descendants.\n this._setSubtreeSelection(item, value);\n }\n\n item.indeterminate = false;\n this._updateBranchStates();\n this._emitSelectionChange();\n }\n\n private _setSubtreeSelection(item: LuxenTreeItem, value: boolean) {\n for (const child of item.getChildrenItems()) {\n if (child.disabled) continue;\n child.selected = value;\n child.indeterminate = false;\n this._setSubtreeSelection(child, value);\n }\n }\n\n /** Propagate child state UP to parents (indeterminate / auto-checked). */\n private _updateBranchStates() {\n if (this.selection !== 'multiple' || this.independent) {\n // In independent or non-multiple modes, clear any indeterminate flags.\n for (const i of this.getAllItems()) i.indeterminate = false;\n return;\n }\n\n const recompute = (item: LuxenTreeItem): { all: boolean; any: boolean } => {\n const children = item.getChildrenItems({ includeDisabled: false });\n if (children.length === 0) {\n return { all: item.selected, any: item.selected };\n }\n\n let all = true;\n let any = false;\n for (const child of children) {\n const state = recompute(child);\n if (!state.all) all = false;\n if (state.any) any = true;\n }\n\n item.selected = all;\n item.indeterminate = !all && any;\n return { all: all && (item.getChildrenItems().length > 0 ? all : item.selected), any };\n };\n\n for (const root of this._rootItems()) recompute(root);\n }\n\n private _emitSelectionChange() {\n this.emit('selection-change', { detail: { selection: this.getSelection() } });\n }\n\n // --- Keyboard / focus ---\n\n private _onClick = (event: MouseEvent) => {\n const item = this._itemFromEvent(event);\n if (!item || item.disabled) return;\n\n const path = event.composedPath();\n const onCheckbox = path.some((n) => n instanceof HTMLInputElement && n.type === 'checkbox');\n if (onCheckbox) return; // handled via change event\n\n // Clicks on consumer-provided interactive elements (buttons, links, form\n // controls, menu items…) must not toggle the row — the consumer owns that\n // interaction. Works regardless of which slot the element was placed in.\n const INTERACTIVE_TAGS = new Set(['BUTTON', 'A', 'INPUT', 'SELECT', 'TEXTAREA']);\n const INTERACTIVE_ROLES = new Set(['button', 'link', 'menuitem', 'menuitemcheckbox']);\n const onInteractive = path.some((n) => {\n if (!(n instanceof HTMLElement) || n === item) return false;\n if (n.getAttribute?.('part') === 'expand-button') return false;\n if (n instanceof HTMLInputElement && n.type === 'checkbox') return false;\n if (INTERACTIVE_TAGS.has(n.tagName)) return true;\n const role = n.getAttribute?.('role');\n return role !== null && INTERACTIVE_ROLES.has(role);\n });\n if (onInteractive) return;\n\n const onExpand = path.some(\n (n) => n instanceof HTMLElement && n.getAttribute?.('part') === 'expand-button',\n );\n\n this._focusItem(item);\n\n if (onExpand) {\n item.toggle();\n return;\n }\n\n // Row click (label area): mode-dependent behaviour.\n switch (this.selection) {\n case 'single':\n this._setSingleSelection(item);\n if (!item.isLeaf()) item.toggle();\n break;\n case 'leaf':\n if (item.isLeaf()) this._setSingleSelection(item);\n else item.toggle();\n break;\n case 'multiple':\n // The whole row acts like a <label> for the checkbox: clicking anywhere\n // on it toggles selection. Use the chevron to expand/collapse branches.\n this._selectItem(item, !item.selected);\n break;\n case 'none':\n item.toggle();\n break;\n }\n };\n\n private _itemFromEvent(event: Event): LuxenTreeItem | null {\n const tag = tagName('tree-item');\n const path = event.composedPath();\n for (const node of path) {\n if (node instanceof HTMLElement && node.matches?.(tag)) {\n return node as LuxenTreeItem;\n }\n }\n return null;\n }\n\n private _focusItem(item: LuxenTreeItem) {\n const visible = this._visibleItems();\n for (const i of visible) i.tabIndex = -1;\n item.tabIndex = 0;\n item.focus();\n this._lastFocusedItem = item;\n }\n\n private _onFocusIn = (event: FocusEvent) => {\n const target = event.target;\n if (target instanceof HTMLElement) {\n const item = target.closest(tagName('tree-item')) as LuxenTreeItem | null;\n if (item) this._lastFocusedItem = item;\n }\n };\n\n private _onKeyDown = (event: KeyboardEvent) => {\n const current = this._lastFocusedItem ?? this._visibleItems()[0];\n if (!current) return;\n\n const visible = this._visibleItems();\n const index = visible.indexOf(current);\n\n switch (event.key) {\n case 'ArrowDown': {\n event.preventDefault();\n const next = visible[Math.min(index + 1, visible.length - 1)];\n if (next) this._focusItem(next);\n break;\n }\n case 'ArrowUp': {\n event.preventDefault();\n const prev = visible[Math.max(index - 1, 0)];\n if (prev) this._focusItem(prev);\n break;\n }\n case 'ArrowRight': {\n event.preventDefault();\n if (!current.isLeaf() && !current.expanded) {\n current.expanded = true;\n } else if (current.expanded) {\n const first = current.getChildrenItems()[0];\n if (first) this._focusItem(first);\n }\n break;\n }\n case 'ArrowLeft': {\n event.preventDefault();\n if (current.expanded && !current.isLeaf()) {\n current.expanded = false;\n } else {\n const parent = current.parentElement?.closest(\n tagName('tree-item'),\n ) as LuxenTreeItem | null;\n if (parent) this._focusItem(parent);\n }\n break;\n }\n case 'Home': {\n event.preventDefault();\n if (visible[0]) this._focusItem(visible[0]);\n break;\n }\n case 'End': {\n event.preventDefault();\n const last = visible[visible.length - 1];\n if (last) this._focusItem(last);\n break;\n }\n case 'Enter':\n case ' ': {\n event.preventDefault();\n this._handleRowActivate(current);\n break;\n }\n case '*': {\n event.preventDefault();\n // Expand all siblings of the current item.\n const siblings = (\n current.parentElement\n ? (Array.from(current.parentElement.children) as LuxenTreeItem[])\n : []\n ).filter((el) => el.tagName === tagName('tree-item').toUpperCase());\n for (const sib of siblings) {\n if (!sib.isLeaf()) sib.expanded = true;\n }\n break;\n }\n }\n };\n\n override render() {\n return html`\n <div\n class=\"tree\"\n part=\"base\"\n role=\"tree\"\n aria-multiselectable=${this.selection === 'multiple' ? 'true' : 'false'}\n @click=${this._onClick}\n @keydown=${this._onKeyDown}\n @focusin=${this._onFocusIn}\n >\n <slot></slot>\n </div>\n `;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"tree.js","names":[],"sources":["../../../src/html/elements/tree/tree.css?inline","../../../src/html/elements/tree/tree.ts"],"sourcesContent":[":host {\n --indent-size: 1rem;\n --indent-guide-width: 1px;\n --indent-guide-style: solid;\n --indent-guide-color: var(--l-color-border-interactive, light-dark(#d1d5db, #3a4048));\n --row-height: 1.75rem;\n --row-padding-inline: 0.25rem;\n --chevron-size: 1.125rem;\n --item-gap: 0.375rem;\n\n display: block;\n color: var(--l-color-text-primary, CanvasText);\n font-family: inherit;\n line-height: 1.5;\n}\n\n.tree {\n display: block;\n outline: none;\n}\n\n.tree:focus-visible {\n outline: 2px solid var(--l-focus-ring, Highlight);\n outline-offset: 2px;\n border-radius: 0.375rem;\n}\n","import { html, unsafeCSS, type PropertyValues } from 'lit';\nimport { property } from 'lit/decorators.js';\nimport { LuxenElement } from '../../shared/luxen-element';\nimport { tagName } from '../../registry';\nimport type { LuxenTreeItem } from '../tree-item/tree-item';\nimport hostStyles from '../../shared/styles/host.styles';\nimport rawStyles from './tree.css?inline';\n\nconst styles = unsafeCSS(rawStyles);\n\nexport type TreeSelection = 'single' | 'multiple' | 'leaf' | 'none';\n\n/**\n * A hierarchical tree view composed of `<l-tree-item>` children.\n *\n * @slot - One or more `l-tree-item` elements.\n *\n * @csspart base - The root tree container.\n *\n * @cssproperty --indent-size - Horizontal indent per depth level. Default `1rem`.\n * @cssproperty --indent-guide-width - Thickness of the vertical guide line between a parent and its children. Default `1px`. Set to `0` to hide guides.\n * @cssproperty --indent-guide-style - Line style of the guide (`solid`, `dashed`, `dotted`, `double`…). Default `solid`.\n * @cssproperty --indent-guide-color - Color of the guide line.\n * @cssproperty --row-height - Minimum row height. Default `1.75rem`.\n * @cssproperty --row-padding-inline - Inner inline padding of the row; also drives the content slot left indent and the indent guide column. Default `0.25rem`.\n * @cssproperty --chevron-size - Size of the expand/collapse chevron box. Default `1.125rem`.\n * @cssproperty --item-gap - Horizontal gap between chevron, prefix, label and suffix on the row; also drives the content slot left indent. Default `0.375rem`.\n *\n * @event selection-change - Fired when the selected items change. Detail: `{ selection: LuxenTreeItem[] }`.\n */\nexport class LuxenTree extends LuxenElement {\n static override styles = [hostStyles, styles];\n\n private _mutationObserver?: MutationObserver;\n private _lastFocusedItem: LuxenTreeItem | null = null;\n\n /**\n * Selection behaviour:\n * - `single` (default): at most one item selected via `aria-selected`.\n * - `multiple`: any number of items selected. Checkboxes are rendered.\n * - `leaf`: only leaf items can be selected (single). Branches just toggle.\n * - `none`: purely navigable, no selection state.\n */\n @property({ reflect: true })\n accessor selection: TreeSelection = 'single';\n\n /**\n * When set with `selection=\"multiple\"`, parent and children selection are decoupled:\n * toggling a parent does NOT toggle its descendants and vice versa.\n * Without it, selection cascades both ways and branches may become indeterminate.\n */\n @property({ type: Boolean, reflect: true })\n accessor independent = false;\n\n override connectedCallback() {\n super.connectedCallback();\n this._mutationObserver = new MutationObserver(() => this._syncAll());\n this._mutationObserver.observe(this, { childList: true, subtree: true });\n this.addEventListener('l-tree-item-toggle', this._onItemToggle as EventListener);\n\n // Defer sync to let light DOM upgrade.\n queueMicrotask(() => this._syncAll());\n }\n\n override disconnectedCallback() {\n super.disconnectedCallback();\n this._mutationObserver?.disconnect();\n this.removeEventListener('l-tree-item-toggle', this._onItemToggle as EventListener);\n }\n\n override updated(changed: PropertyValues<this>) {\n if (changed.has('selection') || changed.has('independent')) {\n this._syncAll();\n }\n }\n\n // --- Public API ---\n\n /** Returns all items in document (flat) order, including nested ones. */\n getAllItems({ includeDisabled = true } = {}): LuxenTreeItem[] {\n const tag = tagName('tree-item');\n return Array.from(this.querySelectorAll<LuxenTreeItem>(tag)).filter(\n (item) => includeDisabled || !item.disabled,\n );\n }\n\n /** Returns currently selected items. */\n getSelection(): LuxenTreeItem[] {\n return this.getAllItems().filter((i) => i.selected);\n }\n\n /** Expands every item that has children. */\n expandAll() {\n for (const item of this.getAllItems()) {\n if (!item.isLeaf()) item.expanded = true;\n }\n }\n\n /** Collapses every item. */\n collapseAll() {\n for (const item of this.getAllItems()) {\n item.expanded = false;\n }\n }\n\n // --- Sync / ARIA / depth / checkbox visibility ---\n\n private _syncAll() {\n const showCheckbox = this.selection === 'multiple';\n const roots = this._rootItems();\n for (const root of roots) {\n this._syncSubtree(root, 0, showCheckbox);\n }\n this._updateBranchStates();\n // Ensure at least one item is tabbable.\n this._ensureTabStop();\n }\n\n private _syncSubtree(item: LuxenTreeItem, depth: number, showCheckbox: boolean) {\n item.depth = depth;\n item.showCheckbox = showCheckbox && this._canShowCheckboxOn(item);\n for (const child of item.getChildrenItems()) {\n this._syncSubtree(child, depth + 1, showCheckbox);\n }\n }\n\n private _canShowCheckboxOn(_item: LuxenTreeItem): boolean {\n if (this.selection !== 'multiple') return false;\n // In cascade mode, branches get a checkbox too so you can bulk-toggle children.\n // In leaf-only selection, hidden here because selection !== 'multiple'.\n return true;\n }\n\n private _rootItems(): LuxenTreeItem[] {\n const tag = tagName('tree-item').toUpperCase();\n return (Array.from(this.children) as LuxenTreeItem[]).filter((el) => el.tagName === tag);\n }\n\n private _ensureTabStop() {\n const items = this._visibleItems();\n if (items.length === 0) return;\n const hasTabStop = items.some((i) => i.tabIndex === 0);\n if (!hasTabStop) {\n for (const i of items) i.tabIndex = -1;\n items[0].tabIndex = 0;\n }\n }\n\n /** Items currently visible (parent chain all expanded). */\n private _visibleItems(): LuxenTreeItem[] {\n const out: LuxenTreeItem[] = [];\n const walk = (items: LuxenTreeItem[]) => {\n for (const i of items) {\n out.push(i);\n if (i.expanded) walk(i.getChildrenItems());\n }\n };\n walk(this._rootItems());\n return out;\n }\n\n // --- Selection handling ---\n\n private _onItemToggle = (event: CustomEvent<{ item: LuxenTreeItem; checked: boolean }>) => {\n const { item, checked } = event.detail;\n this._selectItem(item, checked);\n };\n\n private _handleRowActivate(item: LuxenTreeItem) {\n if (item.disabled) return;\n\n switch (this.selection) {\n case 'single':\n this._setSingleSelection(item);\n break;\n case 'leaf':\n if (item.isLeaf()) this._setSingleSelection(item);\n else item.toggle();\n break;\n case 'multiple':\n this._selectItem(item, !item.selected);\n break;\n case 'none':\n item.toggle();\n break;\n }\n }\n\n private _setSingleSelection(item: LuxenTreeItem) {\n for (const i of this.getAllItems()) {\n if (i !== item && i.selected) i.selected = false;\n }\n item.selected = true;\n this._emitSelectionChange();\n }\n\n private _selectItem(item: LuxenTreeItem, value: boolean) {\n if (item.disabled) return;\n item.selected = value;\n\n if (this.selection === 'multiple' && !this.independent) {\n // Cascade DOWN: toggling a branch toggles all descendants.\n this._setSubtreeSelection(item, value);\n }\n\n item.indeterminate = false;\n this._updateBranchStates();\n this._emitSelectionChange();\n }\n\n private _setSubtreeSelection(item: LuxenTreeItem, value: boolean) {\n for (const child of item.getChildrenItems()) {\n if (child.disabled) continue;\n child.selected = value;\n child.indeterminate = false;\n this._setSubtreeSelection(child, value);\n }\n }\n\n /** Propagate child state UP to parents (indeterminate / auto-checked). */\n private _updateBranchStates() {\n if (this.selection !== 'multiple' || this.independent) {\n // In independent or non-multiple modes, clear any indeterminate flags.\n for (const i of this.getAllItems()) i.indeterminate = false;\n return;\n }\n\n const recompute = (item: LuxenTreeItem): { all: boolean; any: boolean } => {\n const children = item.getChildrenItems({ includeDisabled: false });\n if (children.length === 0) {\n return { all: item.selected, any: item.selected };\n }\n\n let all = true;\n let any = false;\n for (const child of children) {\n const state = recompute(child);\n if (!state.all) all = false;\n if (state.any) any = true;\n }\n\n item.selected = all;\n item.indeterminate = !all && any;\n return { all: all && (item.getChildrenItems().length > 0 ? all : item.selected), any };\n };\n\n for (const root of this._rootItems()) recompute(root);\n }\n\n private _emitSelectionChange() {\n this.emit('selection-change', { detail: { selection: this.getSelection() } });\n }\n\n // --- Keyboard / focus ---\n\n private _onClick = (event: MouseEvent) => {\n const item = this._itemFromEvent(event);\n if (!item || item.disabled) return;\n\n const path = event.composedPath();\n const onCheckbox = path.some((n) => n instanceof HTMLInputElement && n.type === 'checkbox');\n if (onCheckbox) return; // handled via change event\n\n // Clicks on consumer-provided interactive elements (buttons, links, form\n // controls, menu items…) must not toggle the row — the consumer owns that\n // interaction. Works regardless of which slot the element was placed in.\n const INTERACTIVE_TAGS = new Set(['BUTTON', 'A', 'INPUT', 'SELECT', 'TEXTAREA']);\n const INTERACTIVE_ROLES = new Set(['button', 'link', 'menuitem', 'menuitemcheckbox']);\n const onInteractive = path.some((n) => {\n if (!(n instanceof HTMLElement) || n === item) return false;\n if (n.getAttribute?.('part') === 'expand-button') return false;\n if (n instanceof HTMLInputElement && n.type === 'checkbox') return false;\n if (INTERACTIVE_TAGS.has(n.tagName)) return true;\n const role = n.getAttribute?.('role');\n return role !== null && INTERACTIVE_ROLES.has(role);\n });\n if (onInteractive) return;\n\n const onExpand = path.some(\n (n) => n instanceof HTMLElement && n.getAttribute?.('part') === 'expand-button',\n );\n\n this._focusItem(item);\n\n if (onExpand) {\n item.toggle();\n return;\n }\n\n // Row click (label area): mode-dependent behaviour.\n switch (this.selection) {\n case 'single':\n this._setSingleSelection(item);\n if (!item.isLeaf()) item.toggle();\n break;\n case 'leaf':\n if (item.isLeaf()) this._setSingleSelection(item);\n else item.toggle();\n break;\n case 'multiple':\n // The whole row acts like a <label> for the checkbox: clicking anywhere\n // on it toggles selection. Use the chevron to expand/collapse branches.\n this._selectItem(item, !item.selected);\n break;\n case 'none':\n item.toggle();\n break;\n }\n };\n\n private _itemFromEvent(event: Event): LuxenTreeItem | null {\n const tag = tagName('tree-item');\n const path = event.composedPath();\n for (const node of path) {\n if (node instanceof HTMLElement && node.matches?.(tag)) {\n return node as LuxenTreeItem;\n }\n }\n return null;\n }\n\n private _focusItem(item: LuxenTreeItem) {\n const visible = this._visibleItems();\n for (const i of visible) i.tabIndex = -1;\n item.tabIndex = 0;\n item.focus();\n this._lastFocusedItem = item;\n }\n\n private _onFocusIn = (event: FocusEvent) => {\n const target = event.target;\n if (target instanceof HTMLElement) {\n const item = target.closest(tagName('tree-item')) as LuxenTreeItem | null;\n if (item) this._lastFocusedItem = item;\n }\n };\n\n private _onKeyDown = (event: KeyboardEvent) => {\n const current = this._lastFocusedItem ?? this._visibleItems()[0];\n if (!current) return;\n\n const visible = this._visibleItems();\n const index = visible.indexOf(current);\n\n switch (event.key) {\n case 'ArrowDown': {\n event.preventDefault();\n const next = visible[Math.min(index + 1, visible.length - 1)];\n if (next) this._focusItem(next);\n break;\n }\n case 'ArrowUp': {\n event.preventDefault();\n const prev = visible[Math.max(index - 1, 0)];\n if (prev) this._focusItem(prev);\n break;\n }\n case 'ArrowRight': {\n event.preventDefault();\n if (!current.isLeaf() && !current.expanded) {\n current.expanded = true;\n } else if (current.expanded) {\n const first = current.getChildrenItems()[0];\n if (first) this._focusItem(first);\n }\n break;\n }\n case 'ArrowLeft': {\n event.preventDefault();\n if (current.expanded && !current.isLeaf()) {\n current.expanded = false;\n } else {\n const parent = current.parentElement?.closest(\n tagName('tree-item'),\n ) as LuxenTreeItem | null;\n if (parent) this._focusItem(parent);\n }\n break;\n }\n case 'Home': {\n event.preventDefault();\n if (visible[0]) this._focusItem(visible[0]);\n break;\n }\n case 'End': {\n event.preventDefault();\n const last = visible[visible.length - 1];\n if (last) this._focusItem(last);\n break;\n }\n case 'Enter':\n case ' ': {\n event.preventDefault();\n this._handleRowActivate(current);\n break;\n }\n case '*': {\n event.preventDefault();\n // Expand all siblings of the current item.\n const siblings = (\n current.parentElement\n ? (Array.from(current.parentElement.children) as LuxenTreeItem[])\n : []\n ).filter((el) => el.tagName === tagName('tree-item').toUpperCase());\n for (const sib of siblings) {\n if (!sib.isLeaf()) sib.expanded = true;\n }\n break;\n }\n }\n };\n\n override render() {\n return html`\n <div\n class=\"tree\"\n part=\"base\"\n role=\"tree\"\n aria-multiselectable=${this.selection === 'multiple' ? 'true' : 'false'}\n @click=${this._onClick}\n @keydown=${this._onKeyDown}\n @focusin=${this._onFocusIn}\n >\n <slot></slot>\n </div>\n `;\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{tagName as e}from"../../registry.js";import{c as t,i as n}from"../../chunks/lit.js";import{LuxenElement as r}from"../../shared/luxen-element.js";import{
|
|
1
|
+
import{tagName as e}from"../../registry.js";import{c as t,i as n}from"../../chunks/lit.js";import{LuxenElement as r}from"../../shared/luxen-element.js";import{i,t as a}from"../../chunks/decorate.js";import o from"../../shared/styles/host.styles.js";var s=t(`:host{color:var(--l-color-text-primary,CanvasText);font-size:.875rem;line-height:1.5;display:block}:host([disabled]){opacity:.4}:host([disabled]) .item{cursor:not-allowed}.item{align-items:center;gap:var(--item-gap);min-block-size:var(--row-height);padding-inline:var(--row-padding-inline);cursor:pointer;-webkit-user-select:none;user-select:none;border-radius:.375rem;padding-inline-start:calc(var(--indent-size) * var(--_depth,0) + var(--row-padding-inline));transition:background-color .12s,color .12s;display:flex;position:relative}.item:focus-visible,:host(:focus-visible) .item{outline:2px solid var(--l-focus-ring,Highlight);outline-offset:1px}@media (hover:hover){:host(:not([disabled])) .item:hover{background-color:var(--l-color-bg-state-hover)}}:host([selected]:not([disabled])) .item{background-color:var(--l-color-bg-state-selected)}.expand{inline-size:var(--chevron-size);block-size:var(--chevron-size);color:var(--l-color-text-secondary,CanvasText);cursor:pointer;border-radius:3px;flex:none;place-items:center;display:grid}:host(:not(:state(has-children)):not([lazy])) .expand>svg{display:none}.expand svg{width:100%;height:100%;display:block}.checkbox{accent-color:var(--l-color-bg-fill-brand,Highlight);cursor:pointer;flex:none;block-size:1rem;inline-size:1rem;margin:0}:host(:not(:state(checkbox))) .checkbox{display:none}.label{text-overflow:ellipsis;white-space:nowrap;flex:1;align-items:center;gap:.375rem;min-inline-size:0;display:flex;overflow:hidden}.label ::slotted(*){min-inline-size:0}.branch{position:relative}.content{padding-inline-start:calc(var(--indent-size) * var(--_depth,0) + var(--row-padding-inline) + var(--chevron-size) + var(--item-gap));padding-inline-end:var(--row-padding-inline);display:block}:host(:state(has-children):not([expanded])) .content,.children{display:none}:host([expanded]) .children{display:block}.branch:before{content:"";border-inline-start:var(--indent-guide-width) var(--indent-guide-style) var(--indent-guide-color);pointer-events:none;inline-size:0;position:absolute;inset-block:0;inset-inline-start:calc(var(--indent-size) * var(--_depth,0) + var(--row-padding-inline) + (var(--chevron-size) / 2) - (var(--indent-guide-width) / 2))}:host(:not([expanded])) .branch:before,:host(:not(:state(has-children))) .branch:before{display:none}.spinner{border:2px solid var(--l-color-border,currentColor);border-block-start-color:#0000;border-radius:50%;block-size:.875rem;inline-size:.875rem;animation:.7s linear infinite spin}@media (prefers-reduced-motion:reduce){.spinner{animation:none}}@keyframes spin{to{transform:rotate(360deg)}}`),c=class extends r{constructor(...e){super(...e),this._internals=this.attachInternals(),this.#e=!1,this.#t=!1,this.#n=!1,this.#r=!1,this.#i=!1,this.#a=!1,this._showCheckbox=!1,this._depth=0,this._hasChildren=!1,this._onCheckboxChange=e=>{e.stopPropagation();let t=e.target;this.dispatchEvent(new CustomEvent(`l-tree-item-toggle`,{bubbles:!0,composed:!0,detail:{item:this,checked:t.checked}}))}}static{this.styles=[o,s]}#e;get expanded(){return this.#e}set expanded(e){this.#e=e}#t;get selected(){return this.#t}set selected(e){this.#t=e}#n;get indeterminate(){return this.#n}set indeterminate(e){this.#n=e}#r;get disabled(){return this.#r}set disabled(e){this.#r=e}#i;get lazy(){return this.#i}set lazy(e){this.#i=e}#a;get loading(){return this.#a}set loading(e){this.#a=e}set showCheckbox(e){this._showCheckbox=e,this._setState(`checkbox`,e)}get showCheckbox(){return this._showCheckbox}set depth(e){this._depth=e,this.style.setProperty(`--_depth`,String(e))}get depth(){return this._depth}get hasChildren(){return this._hasChildren}getChildrenItems({includeDisabled:t=!0}={}){let n=e(`tree-item`).toUpperCase();return Array.from(this.children).filter(e=>e.tagName===n&&(t||!e.disabled))}isLeaf(){return!this.lazy&&this.getChildrenItems().length===0}getTextLabel(){let e=this.shadowRoot?.querySelector(`slot:not([name])`);return e?e.assignedNodes({flatten:!0}).map(e=>e.textContent??``).join(``).trim():(this.textContent??``).trim()}connectedCallback(){super.connectedCallback(),this._internals.role=`treeitem`,this._childObserver=new MutationObserver(()=>this._syncChildren()),this._childObserver.observe(this,{childList:!0}),this._syncChildren()}disconnectedCallback(){super.disconnectedCallback(),this._childObserver?.disconnect()}updated(e){e.has(`expanded`)&&(this._internals.ariaExpanded=this.isLeaf()?null:String(this.expanded),this.emit(this.expanded?`expand`:`collapse`)),e.has(`selected`)&&(this._internals.ariaSelected=String(this.selected)),e.has(`disabled`)&&(this._internals.ariaDisabled=this.disabled?`true`:null)}_setState(e,t){this._internals.states&&(t?this._internals.states.add(e):this._internals.states.delete(e))}_syncChildren(){let t=e(`tree-item`).toUpperCase(),n=0;for(let e of Array.from(this.children))e.tagName===t&&(n++,e.slot!==`children`&&(e.slot=`children`));this._hasChildren=n>0,this._setState(`has-children`,this._hasChildren),!this._hasChildren&&!this.lazy&&this.expanded&&(this.expanded=!1),this._internals.ariaExpanded=this.isLeaf()?null:String(this.expanded)}toggle(){if(this.isLeaf()&&!this.lazy)return;let e=!this.expanded;e&&this.lazy&&this.emit(`lazy-load`),this.expanded=e}render(){return n`
|
|
2
2
|
<div
|
|
3
3
|
class="item"
|
|
4
4
|
part="base"
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tree-item.js","names":[],"sources":["../../../src/html/elements/tree-item/tree-item.css?inline","../../../src/html/elements/tree-item/tree-item.ts"],"sourcesContent":[":host {\n display: block;\n color: var(--l-color-text-primary, CanvasText);\n font-size: 0.875rem;\n line-height: 1.5;\n}\n\n:host([disabled]) {\n opacity: 0.4;\n}\n\n:host([disabled]) .item {\n cursor: not-allowed;\n}\n\n.item {\n display: flex;\n align-items: center;\n gap: var(--item-gap);\n min-block-size: var(--row-height);\n padding-inline: var(--row-padding-inline);\n padding-inline-start: calc(var(--indent-size) * var(--_depth, 0) + var(--row-padding-inline));\n border-radius: 0.375rem;\n cursor: pointer;\n user-select: none;\n transition:\n background-color 120ms ease,\n color 120ms ease;\n position: relative;\n}\n\n.item:focus-visible,\n:host(:focus-visible) .item {\n outline: 2px solid var(--l-focus-ring, Highlight);\n outline-offset: 1px;\n}\n\n@media (hover: hover) {\n :host(:not([disabled])) .item:hover {\n background-color: var(--l-color-bg-state-hover);\n }\n}\n\n:host([selected]:not([disabled])) .item {\n background-color: var(--l-color-bg-state-selected);\n}\n\n.expand {\n inline-size: var(--chevron-size);\n block-size: var(--chevron-size);\n display: grid;\n place-items: center;\n flex: none;\n color: var(--l-color-text-secondary, CanvasText);\n border-radius: 3px;\n cursor: pointer;\n}\n\n/* Hide the DEFAULT fallback chevron SVG on a leaf — slotted content\n (user-provided icon, avatar, etc.) remains visible. */\n:host(:not(:state(has-children)):not([lazy])) .expand > svg {\n display: none;\n}\n\n.expand svg {\n width: 100%;\n height: 100%;\n display: block;\n}\n\n.checkbox {\n margin: 0;\n flex: none;\n accent-color: var(--l-color-bg-fill-brand, Highlight);\n cursor: pointer;\n inline-size: 1rem;\n block-size: 1rem;\n}\n\n/* Checkbox is only rendered when the parent tree enables multi-selection */\n:host(:not(:state(checkbox))) .checkbox {\n display: none;\n}\n\n.label {\n flex: 1;\n min-inline-size: 0;\n display: flex;\n align-items: center;\n gap: 0.375rem;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.label ::slotted(*) {\n min-inline-size: 0;\n}\n\n/* Wrapper around the content slot + children — anchor for the indent guide. */\n.branch {\n position: relative;\n}\n\n/* Content slot — block area between the row and the children.\n Aligned under the label text: same left offset as the row's label\n (depth indent + row padding + chevron + gap). Visible for leaves (no\n children) and for expanded branches; hidden when a branch is collapsed\n (mirrors the children visibility). */\n.content {\n display: block;\n padding-inline-start: calc(\n var(--indent-size) * var(--_depth, 0) + var(--row-padding-inline) + var(--chevron-size) +\n var(--item-gap)\n );\n padding-inline-end: var(--row-padding-inline);\n}\n\n:host(:state(has-children):not([expanded])) .content {\n display: none;\n}\n\n.children {\n display: none;\n}\n\n:host([expanded]) .children {\n display: block;\n}\n\n/* Vertical indent guide — spans the content + children block, starting\n right below the row so it never overlaps the chevron/avatar.\n The guide's visual centre sits exactly on the parent chevron's centre. */\n.branch::before {\n content: '';\n position: absolute;\n inset-block: 0;\n inset-inline-start: calc(\n var(--indent-size) * var(--_depth, 0) + var(--row-padding-inline) + (var(--chevron-size) / 2) -\n (var(--indent-guide-width) / 2)\n );\n inline-size: 0;\n border-inline-start: var(--indent-guide-width) var(--indent-guide-style) var(--indent-guide-color);\n pointer-events: none;\n}\n\n/* Only render the guide for open branches that have children. */\n:host(:not([expanded])) .branch::before,\n:host(:not(:state(has-children))) .branch::before {\n display: none;\n}\n\n.spinner {\n inline-size: 0.875rem;\n block-size: 0.875rem;\n border-radius: 50%;\n border: 2px solid var(--l-color-border, currentColor);\n border-block-start-color: transparent;\n animation: spin 700ms linear infinite;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .spinner {\n animation: none;\n }\n}\n\n@keyframes spin {\n to {\n transform: rotate(360deg);\n }\n}\n","import { html, unsafeCSS, type PropertyValues } from 'lit';\nimport { property } from 'lit/decorators.js';\nimport { LuxenElement } from '../../shared/luxen-element';\nimport { tagName } from '../../registry';\nimport hostStyles from '../../shared/styles/host.styles';\nimport rawStyles from './tree-item.css?inline';\n\nconst styles = unsafeCSS(rawStyles);\n\n/**\n * A node inside `<l-tree>`. Nested `<l-tree-item>` children become sub-nodes.\n *\n * @slot - Label content (kept to a single row).\n * @slot prefix - Leading content before the label (e.g. icon).\n * @slot suffix - Trailing content.\n * @slot expand-icon - Icon shown when the item is collapsed.\n * @slot collapse-icon - Icon shown when the item is expanded.\n * @slot content - Block content that belongs to the item but not to its header row (e.g. comment body, action bar). Hidden when a branch is collapsed.\n *\n * @csspart base - The item row.\n * @csspart expand-button - The chevron toggle area.\n * @csspart checkbox - The native checkbox input.\n * @csspart label - The label container.\n * @csspart branch - Wrapper around the content and children slots; carries the indent guide.\n * @csspart content - The content slot wrapper (block area between the row and the children).\n * @csspart children - The nested items container.\n *\n * @cssproperty [--_depth] - Internal depth index driving indentation. Set by `<l-tree>`.\n *\n * Layout tokens (`--chevron-size`, `--row-height`, `--row-padding-inline`, `--item-gap`) live on `<l-tree>` and cascade down — see its CSS custom properties.\n *\n * @event expand - Fired when the item is expanded.\n * @event collapse - Fired when the item is collapsed.\n * @event lazy-load - Fired when a lazy item is expanded for the first time. Consumers should append children and set `lazy=false`.\n */\nexport class LuxenTreeItem extends LuxenElement {\n static override styles = [hostStyles, styles];\n\n private _internals = this.attachInternals();\n private _childObserver?: MutationObserver;\n\n /** Whether the item is expanded. */\n @property({ type: Boolean, reflect: true })\n accessor expanded = false;\n\n /** Whether the item is selected. */\n @property({ type: Boolean, reflect: true })\n accessor selected = false;\n\n /** Whether the checkbox is indeterminate (some descendants selected). */\n @property({ type: Boolean, reflect: true })\n accessor indeterminate = false;\n\n /** Whether the item is disabled. */\n @property({ type: Boolean, reflect: true })\n accessor disabled = false;\n\n /** Marks this item as having children that will be loaded on first expand. */\n @property({ type: Boolean, reflect: true })\n accessor lazy = false;\n\n /** Whether the item is currently loading (shows a spinner). */\n @property({ type: Boolean, reflect: true })\n accessor loading = false;\n\n /** Set by `<l-tree>`: whether a checkbox is shown. */\n set showCheckbox(value: boolean) {\n this._showCheckbox = value;\n this._setState('checkbox', value);\n }\n get showCheckbox(): boolean {\n return this._showCheckbox;\n }\n private _showCheckbox = false;\n\n /** Set by `<l-tree>`: depth of the item in the tree (0 = root). */\n set depth(value: number) {\n this._depth = value;\n this.style.setProperty('--_depth', String(value));\n }\n get depth(): number {\n return this._depth;\n }\n private _depth = 0;\n\n /** Whether this item has nested tree-item children. */\n get hasChildren(): boolean {\n return this._hasChildren;\n }\n private _hasChildren = false;\n\n /** Returns the child `<l-tree-item>` elements directly under this one. */\n getChildrenItems({ includeDisabled = true } = {}): LuxenTreeItem[] {\n const childTag = tagName('tree-item').toUpperCase();\n return (Array.from(this.children) as LuxenTreeItem[]).filter(\n (el) => el.tagName === childTag && (includeDisabled || !el.disabled),\n );\n }\n\n /** Returns true if this item has no expandable children. */\n isLeaf(): boolean {\n return !this.lazy && this.getChildrenItems().length === 0;\n }\n\n /** Returns the text label of this item. */\n getTextLabel(): string {\n const slot = this.shadowRoot?.querySelector<HTMLSlotElement>('slot:not([name])');\n if (!slot) return (this.textContent ?? '').trim();\n return slot\n .assignedNodes({ flatten: true })\n .map((n) => n.textContent ?? '')\n .join('')\n .trim();\n }\n\n override connectedCallback() {\n super.connectedCallback();\n this._internals.role = 'treeitem';\n this._childObserver = new MutationObserver(() => this._syncChildren());\n this._childObserver.observe(this, { childList: true });\n this._syncChildren();\n }\n\n override disconnectedCallback() {\n super.disconnectedCallback();\n this._childObserver?.disconnect();\n }\n\n override updated(changed: PropertyValues<this>) {\n if (changed.has('expanded')) {\n this._internals.ariaExpanded = this.isLeaf() ? null : String(this.expanded);\n this.emit(this.expanded ? 'expand' : 'collapse');\n }\n\n if (changed.has('selected')) {\n this._internals.ariaSelected = String(this.selected);\n }\n\n if (changed.has('disabled')) {\n this._internals.ariaDisabled = this.disabled ? 'true' : null;\n }\n }\n\n private _setState(name: string, on: boolean) {\n if (!this._internals.states) return;\n if (on) this._internals.states.add(name);\n else this._internals.states.delete(name);\n }\n\n private _syncChildren() {\n // Auto-slot nested tree-items into the `children` slot so they render in the group container,\n // while label text/elements remain in the default slot.\n const childTag = tagName('tree-item').toUpperCase();\n let count = 0;\n for (const child of Array.from(this.children) as HTMLElement[]) {\n if (child.tagName === childTag) {\n count++;\n if (child.slot !== 'children') child.slot = 'children';\n }\n }\n this._hasChildren = count > 0;\n this._setState('has-children', this._hasChildren);\n if (!this._hasChildren && !this.lazy && this.expanded) {\n this.expanded = false;\n }\n this._internals.ariaExpanded = this.isLeaf() ? null : String(this.expanded);\n }\n\n /** Toggle expand state. Emits `lazy-load` the first time a lazy item opens. */\n toggle() {\n if (this.isLeaf() && !this.lazy) return;\n const next = !this.expanded;\n if (next && this.lazy) {\n this.emit('lazy-load');\n }\n this.expanded = next;\n }\n\n private _onCheckboxChange = (event: Event) => {\n event.stopPropagation();\n const input = event.target as HTMLInputElement;\n this.dispatchEvent(\n new CustomEvent('l-tree-item-toggle', {\n bubbles: true,\n composed: true,\n detail: { item: this, checked: input.checked },\n }),\n );\n };\n\n override render() {\n return html`\n <div\n class=\"item\"\n part=\"base\"\n >\n <span\n class=\"expand\"\n part=\"expand-button\"\n aria-hidden=\"true\"\n >\n ${this.loading\n ? html`<span\n class=\"spinner\"\n role=\"status\"\n ></span>`\n : this.expanded || this.isLeaf()\n ? html`<slot name=\"collapse-icon\">\n <svg\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n >\n <path\n d=\"M3.5 6l4.5 5 4.5-5\"\n stroke=\"currentColor\"\n stroke-width=\"1.5\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n </slot>`\n : html`<slot name=\"expand-icon\">\n <svg\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n >\n <path\n d=\"M6 3.5l5 4.5-5 4.5\"\n stroke=\"currentColor\"\n stroke-width=\"1.5\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n </slot>`}\n </span>\n\n <input\n class=\"checkbox\"\n part=\"checkbox\"\n type=\"checkbox\"\n tabindex=\"-1\"\n .checked=${this.selected}\n .indeterminate=${this.indeterminate}\n ?disabled=${this.disabled}\n @click=${(e: Event) => e.stopPropagation()}\n @change=${this._onCheckboxChange}\n />\n\n <slot name=\"prefix\"></slot>\n <span\n class=\"label\"\n part=\"label\"\n ><slot></slot\n ></span>\n <slot name=\"suffix\"></slot>\n </div>\n\n <div\n class=\"branch\"\n part=\"branch\"\n >\n <div\n class=\"content\"\n part=\"content\"\n >\n <slot name=\"content\"></slot>\n </div>\n <div\n class=\"children\"\n part=\"children\"\n role=\"group\"\n >\n <slot name=\"children\"></slot>\n </div>\n </div>\n `;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"tree-item.js","names":[],"sources":["../../../src/html/elements/tree-item/tree-item.css?inline","../../../src/html/elements/tree-item/tree-item.ts"],"sourcesContent":[":host {\n display: block;\n color: var(--l-color-text-primary, CanvasText);\n font-size: 0.875rem;\n line-height: 1.5;\n}\n\n:host([disabled]) {\n opacity: 0.4;\n}\n\n:host([disabled]) .item {\n cursor: not-allowed;\n}\n\n.item {\n display: flex;\n align-items: center;\n gap: var(--item-gap);\n min-block-size: var(--row-height);\n padding-inline: var(--row-padding-inline);\n padding-inline-start: calc(var(--indent-size) * var(--_depth, 0) + var(--row-padding-inline));\n border-radius: 0.375rem;\n cursor: pointer;\n user-select: none;\n transition:\n background-color 120ms ease,\n color 120ms ease;\n position: relative;\n}\n\n.item:focus-visible,\n:host(:focus-visible) .item {\n outline: 2px solid var(--l-focus-ring, Highlight);\n outline-offset: 1px;\n}\n\n@media (hover: hover) {\n :host(:not([disabled])) .item:hover {\n background-color: var(--l-color-bg-state-hover);\n }\n}\n\n:host([selected]:not([disabled])) .item {\n background-color: var(--l-color-bg-state-selected);\n}\n\n.expand {\n inline-size: var(--chevron-size);\n block-size: var(--chevron-size);\n display: grid;\n place-items: center;\n flex: none;\n color: var(--l-color-text-secondary, CanvasText);\n border-radius: 3px;\n cursor: pointer;\n}\n\n/* Hide the DEFAULT fallback chevron SVG on a leaf — slotted content\n (user-provided icon, avatar, etc.) remains visible. */\n:host(:not(:state(has-children)):not([lazy])) .expand > svg {\n display: none;\n}\n\n.expand svg {\n width: 100%;\n height: 100%;\n display: block;\n}\n\n.checkbox {\n margin: 0;\n flex: none;\n accent-color: var(--l-color-bg-fill-brand, Highlight);\n cursor: pointer;\n inline-size: 1rem;\n block-size: 1rem;\n}\n\n/* Checkbox is only rendered when the parent tree enables multi-selection */\n:host(:not(:state(checkbox))) .checkbox {\n display: none;\n}\n\n.label {\n flex: 1;\n min-inline-size: 0;\n display: flex;\n align-items: center;\n gap: 0.375rem;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.label ::slotted(*) {\n min-inline-size: 0;\n}\n\n/* Wrapper around the content slot + children — anchor for the indent guide. */\n.branch {\n position: relative;\n}\n\n/* Content slot — block area between the row and the children.\n Aligned under the label text: same left offset as the row's label\n (depth indent + row padding + chevron + gap). Visible for leaves (no\n children) and for expanded branches; hidden when a branch is collapsed\n (mirrors the children visibility). */\n.content {\n display: block;\n padding-inline-start: calc(\n var(--indent-size) * var(--_depth, 0) + var(--row-padding-inline) + var(--chevron-size) +\n var(--item-gap)\n );\n padding-inline-end: var(--row-padding-inline);\n}\n\n:host(:state(has-children):not([expanded])) .content {\n display: none;\n}\n\n.children {\n display: none;\n}\n\n:host([expanded]) .children {\n display: block;\n}\n\n/* Vertical indent guide — spans the content + children block, starting\n right below the row so it never overlaps the chevron/avatar.\n The guide's visual centre sits exactly on the parent chevron's centre. */\n.branch::before {\n content: '';\n position: absolute;\n inset-block: 0;\n inset-inline-start: calc(\n var(--indent-size) * var(--_depth, 0) + var(--row-padding-inline) + (var(--chevron-size) / 2) -\n (var(--indent-guide-width) / 2)\n );\n inline-size: 0;\n border-inline-start: var(--indent-guide-width) var(--indent-guide-style) var(--indent-guide-color);\n pointer-events: none;\n}\n\n/* Only render the guide for open branches that have children. */\n:host(:not([expanded])) .branch::before,\n:host(:not(:state(has-children))) .branch::before {\n display: none;\n}\n\n.spinner {\n inline-size: 0.875rem;\n block-size: 0.875rem;\n border-radius: 50%;\n border: 2px solid var(--l-color-border, currentColor);\n border-block-start-color: transparent;\n animation: spin 700ms linear infinite;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .spinner {\n animation: none;\n }\n}\n\n@keyframes spin {\n to {\n transform: rotate(360deg);\n }\n}\n","import { html, unsafeCSS, type PropertyValues } from 'lit';\nimport { property } from 'lit/decorators.js';\nimport { LuxenElement } from '../../shared/luxen-element';\nimport { tagName } from '../../registry';\nimport hostStyles from '../../shared/styles/host.styles';\nimport rawStyles from './tree-item.css?inline';\n\nconst styles = unsafeCSS(rawStyles);\n\n/**\n * A node inside `<l-tree>`. Nested `<l-tree-item>` children become sub-nodes.\n *\n * @slot - Label content (kept to a single row).\n * @slot prefix - Leading content before the label (e.g. icon).\n * @slot suffix - Trailing content.\n * @slot expand-icon - Icon shown when the item is collapsed.\n * @slot collapse-icon - Icon shown when the item is expanded.\n * @slot content - Block content that belongs to the item but not to its header row (e.g. comment body, action bar). Hidden when a branch is collapsed.\n *\n * @csspart base - The item row.\n * @csspart expand-button - The chevron toggle area.\n * @csspart checkbox - The native checkbox input.\n * @csspart label - The label container.\n * @csspart branch - Wrapper around the content and children slots; carries the indent guide.\n * @csspart content - The content slot wrapper (block area between the row and the children).\n * @csspart children - The nested items container.\n *\n * @cssproperty [--_depth] - Internal depth index driving indentation. Set by `<l-tree>`.\n *\n * Layout tokens (`--chevron-size`, `--row-height`, `--row-padding-inline`, `--item-gap`) live on `<l-tree>` and cascade down — see its CSS custom properties.\n *\n * @event expand - Fired when the item is expanded.\n * @event collapse - Fired when the item is collapsed.\n * @event lazy-load - Fired when a lazy item is expanded for the first time. Consumers should append children and set `lazy=false`.\n */\nexport class LuxenTreeItem extends LuxenElement {\n static override styles = [hostStyles, styles];\n\n private _internals = this.attachInternals();\n private _childObserver?: MutationObserver;\n\n /** Whether the item is expanded. */\n @property({ type: Boolean, reflect: true })\n accessor expanded = false;\n\n /** Whether the item is selected. */\n @property({ type: Boolean, reflect: true })\n accessor selected = false;\n\n /** Whether the checkbox is indeterminate (some descendants selected). */\n @property({ type: Boolean, reflect: true })\n accessor indeterminate = false;\n\n /** Whether the item is disabled. */\n @property({ type: Boolean, reflect: true })\n accessor disabled = false;\n\n /** Marks this item as having children that will be loaded on first expand. */\n @property({ type: Boolean, reflect: true })\n accessor lazy = false;\n\n /** Whether the item is currently loading (shows a spinner). */\n @property({ type: Boolean, reflect: true })\n accessor loading = false;\n\n /** Set by `<l-tree>`: whether a checkbox is shown. */\n set showCheckbox(value: boolean) {\n this._showCheckbox = value;\n this._setState('checkbox', value);\n }\n get showCheckbox(): boolean {\n return this._showCheckbox;\n }\n private _showCheckbox = false;\n\n /** Set by `<l-tree>`: depth of the item in the tree (0 = root). */\n set depth(value: number) {\n this._depth = value;\n this.style.setProperty('--_depth', String(value));\n }\n get depth(): number {\n return this._depth;\n }\n private _depth = 0;\n\n /** Whether this item has nested tree-item children. */\n get hasChildren(): boolean {\n return this._hasChildren;\n }\n private _hasChildren = false;\n\n /** Returns the child `<l-tree-item>` elements directly under this one. */\n getChildrenItems({ includeDisabled = true } = {}): LuxenTreeItem[] {\n const childTag = tagName('tree-item').toUpperCase();\n return (Array.from(this.children) as LuxenTreeItem[]).filter(\n (el) => el.tagName === childTag && (includeDisabled || !el.disabled),\n );\n }\n\n /** Returns true if this item has no expandable children. */\n isLeaf(): boolean {\n return !this.lazy && this.getChildrenItems().length === 0;\n }\n\n /** Returns the text label of this item. */\n getTextLabel(): string {\n const slot = this.shadowRoot?.querySelector<HTMLSlotElement>('slot:not([name])');\n if (!slot) return (this.textContent ?? '').trim();\n return slot\n .assignedNodes({ flatten: true })\n .map((n) => n.textContent ?? '')\n .join('')\n .trim();\n }\n\n override connectedCallback() {\n super.connectedCallback();\n this._internals.role = 'treeitem';\n this._childObserver = new MutationObserver(() => this._syncChildren());\n this._childObserver.observe(this, { childList: true });\n this._syncChildren();\n }\n\n override disconnectedCallback() {\n super.disconnectedCallback();\n this._childObserver?.disconnect();\n }\n\n override updated(changed: PropertyValues<this>) {\n if (changed.has('expanded')) {\n this._internals.ariaExpanded = this.isLeaf() ? null : String(this.expanded);\n this.emit(this.expanded ? 'expand' : 'collapse');\n }\n\n if (changed.has('selected')) {\n this._internals.ariaSelected = String(this.selected);\n }\n\n if (changed.has('disabled')) {\n this._internals.ariaDisabled = this.disabled ? 'true' : null;\n }\n }\n\n private _setState(name: string, on: boolean) {\n if (!this._internals.states) return;\n if (on) this._internals.states.add(name);\n else this._internals.states.delete(name);\n }\n\n private _syncChildren() {\n // Auto-slot nested tree-items into the `children` slot so they render in the group container,\n // while label text/elements remain in the default slot.\n const childTag = tagName('tree-item').toUpperCase();\n let count = 0;\n for (const child of Array.from(this.children) as HTMLElement[]) {\n if (child.tagName === childTag) {\n count++;\n if (child.slot !== 'children') child.slot = 'children';\n }\n }\n this._hasChildren = count > 0;\n this._setState('has-children', this._hasChildren);\n if (!this._hasChildren && !this.lazy && this.expanded) {\n this.expanded = false;\n }\n this._internals.ariaExpanded = this.isLeaf() ? null : String(this.expanded);\n }\n\n /** Toggle expand state. Emits `lazy-load` the first time a lazy item opens. */\n toggle() {\n if (this.isLeaf() && !this.lazy) return;\n const next = !this.expanded;\n if (next && this.lazy) {\n this.emit('lazy-load');\n }\n this.expanded = next;\n }\n\n private _onCheckboxChange = (event: Event) => {\n event.stopPropagation();\n const input = event.target as HTMLInputElement;\n this.dispatchEvent(\n new CustomEvent('l-tree-item-toggle', {\n bubbles: true,\n composed: true,\n detail: { item: this, checked: input.checked },\n }),\n );\n };\n\n override render() {\n return html`\n <div\n class=\"item\"\n part=\"base\"\n >\n <span\n class=\"expand\"\n part=\"expand-button\"\n aria-hidden=\"true\"\n >\n ${this.loading\n ? html`<span\n class=\"spinner\"\n role=\"status\"\n ></span>`\n : this.expanded || this.isLeaf()\n ? html`<slot name=\"collapse-icon\">\n <svg\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n >\n <path\n d=\"M3.5 6l4.5 5 4.5-5\"\n stroke=\"currentColor\"\n stroke-width=\"1.5\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n </slot>`\n : html`<slot name=\"expand-icon\">\n <svg\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n >\n <path\n d=\"M6 3.5l5 4.5-5 4.5\"\n stroke=\"currentColor\"\n stroke-width=\"1.5\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n </slot>`}\n </span>\n\n <input\n class=\"checkbox\"\n part=\"checkbox\"\n type=\"checkbox\"\n tabindex=\"-1\"\n .checked=${this.selected}\n .indeterminate=${this.indeterminate}\n ?disabled=${this.disabled}\n @click=${(e: Event) => e.stopPropagation()}\n @change=${this._onCheckboxChange}\n />\n\n <slot name=\"prefix\"></slot>\n <span\n class=\"label\"\n part=\"label\"\n ><slot></slot\n ></span>\n <slot name=\"suffix\"></slot>\n </div>\n\n <div\n class=\"branch\"\n part=\"branch\"\n >\n <div\n class=\"content\"\n part=\"content\"\n >\n <slot name=\"content\"></slot>\n </div>\n <div\n class=\"children\"\n part=\"children\"\n role=\"group\"\n >\n <slot name=\"children\"></slot>\n </div>\n </div>\n `;\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{LuxenElement as e}from"./luxen-element.js";import{
|
|
1
|
+
import{LuxenElement as e}from"./luxen-element.js";import{i as t,t as n}from"../chunks/decorate.js";var r=class extends e{static{this.formAssociated=!0}constructor(){super(),this.name=``,this.disabled=!1,this.required=!1,this._defaultFormValue=``,this.hasInteracted=!1,this._internals=this.attachInternals()}get form(){return this._internals.form}get formLabels(){return this._internals.labels}get validationTarget(){}_syncFormValue(e){this._internals.setFormValue(e)}checkValidity(){return this._internals.checkValidity()}reportValidity(){return this.hasInteracted=!0,this._updateCustomStates(),this._internals.reportValidity()}setCustomValidity(e){e?this._internals.setValidity({customError:!0},e,this.validationTarget):this._internals.setValidity({}),this._updateCustomStates()}setValidity(e={},t=``,n){this._internals.setValidity(e,t,n??this.validationTarget),this._updateCustomStates()}get validity(){return this._internals.validity}get validationMessage(){return this._internals.validationMessage}get willValidate(){return this._internals.willValidate}_updateCustomStates(){let e=this._internals.states,t=this._internals.validity.valid;this._toggleState(e,`required`,this.required),this._toggleState(e,`optional`,!this.required),this._toggleState(e,`valid`,t),this._toggleState(e,`invalid`,!t),this._toggleState(e,`user-valid`,t&&this.hasInteracted),this._toggleState(e,`user-invalid`,!t&&this.hasInteracted)}_toggleState(e,t,n){n?e.add(t):e.delete(t)}formAssociatedCallback(e){}formDisabledCallback(e){this.disabled=e}formResetCallback(){this.hasInteracted=!1,this._syncFormValue(this._defaultFormValue),this._updateCustomStates()}formStateRestoreCallback(e,t){this._syncFormValue(e)}};n([t({reflect:!0})],r.prototype,`name`,void 0),n([t({type:Boolean,reflect:!0})],r.prototype,`disabled`,void 0),n([t({type:Boolean,reflect:!0})],r.prototype,`required`,void 0);export{r as LuxenFormAssociatedElement};
|
|
2
2
|
//# sourceMappingURL=luxen-form-associated-element.js.map
|
|
@@ -52,6 +52,13 @@
|
|
|
52
52
|
padding-inline: var(--spacing-2, 0.5rem);
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
/* Compact form when slotted into <l-dropdown>: bleed to panel edges, tighten spacing. */
|
|
56
|
+
l-dropdown l-divider:not([orientation='vertical']) {
|
|
57
|
+
margin-block: var(--padding, 0.25rem);
|
|
58
|
+
margin-inline: calc(var(--padding, 0.25rem) * -1);
|
|
59
|
+
width: auto;
|
|
60
|
+
}
|
|
61
|
+
|
|
55
62
|
/*
|
|
56
63
|
▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬
|
|
57
64
|
vertical
|