Brunobkr/llama.cpp_AlgMor24_github
ΩFFFΣLLIa • llama.cpp • AlgMor24 ██████╗ ███████╗███████╗███████╗██╗ ██╗ ██╗ █████╗ ██╔═══██╗██╔════╝██╔════╝██╔════╝██║ ██║ ██║██╔══██╗ ██║ ██║█████╗ █████╗ █████╗ ██║ ██║ ██║███████║ ██║ ██║██╔══╝ ██╔══╝ ██╔══╝ ██║ ██║ ██║██╔══██║ ╚██████╔╝██║ ██║ ███████╗███████╗███████╗██║██║ ██║ ╚═════╝ ╚═╝ ╚═╝ ╚══════╝╚══════╝╚══════╝╚═╝╚═╝ ╚═╝ High-Performance LLM / VLM Inference & Autonomous Agentic Ecosystem… See the full description on the dataset page: https://huggingface.co/datasets/Brunobkr/llama.cpp_AlgMor24_github.
03.1k
1/**2 * Based on Radix UI's Navigation Menu3 * https://www.radix-ui.com/docs/primitives/components/navigation-menu4 */5import { afterSleep, afterTick, attachRef, DOMContext, getWindow, simpleBox, boxWith, } from "svelte-toolbelt";6import { Context, useDebounce, watch } from "runed";7import { untrack } from "svelte";8import { SvelteMap } from "svelte/reactivity";9import { useId } from "../../shared/index.js";10import { createBitsAttrs, boolToStr, boolToEmptyStrOrUndef, getDataOpenClosed, } from "../../internal/attrs.js";11import { noop } from "../../internal/noop.js";12import { getTabbableCandidates } from "../../internal/focus.js";13import { kbd } from "../../internal/kbd.js";14import { CustomEventDispatcher } from "../../internal/events.js";15import { useArrowNavigation } from "../../internal/use-arrow-navigation.js";16import { boxAutoReset } from "../../internal/box-auto-reset.svelte.js";17import { isElement } from "../../internal/is.js";18import { RovingFocusGroup } from "../../internal/roving-focus-group.js";19import { SvelteResizeObserver } from "../../internal/svelte-resize-observer.svelte.js";20const navigationMenuAttrs = createBitsAttrs({21 component: "navigation-menu",22 parts: [23 "root",24 "sub",25 "item",26 "list",27 "trigger",28 "content",29 "link",30 "viewport",31 "menu",32 "indicator",33 ],34});35const NavigationMenuProviderContext = new Context("NavigationMenu.Root");36export const NavigationMenuItemContext = new Context("NavigationMenu.Item");37const NavigationMenuListContext = new Context("NavigationMenu.List");38const NavigationMenuContentContext = new Context("NavigationMenu.Content");39const NavigationMenuSubContext = new Context("NavigationMenu.Sub");40class NavigationMenuProviderState {41 static create(opts) {42 return NavigationMenuProviderContext.set(new NavigationMenuProviderState(opts));43 }44 opts;45 indicatorTrackRef = simpleBox(null);46 viewportRef = simpleBox(null);47 viewportContent = new SvelteMap();48 onTriggerEnter;49 onTriggerLeave = noop;50 onContentEnter = noop;51 onContentLeave = noop;52 onItemSelect;53 onItemDismiss;54 activeItem = null;55 prevActiveItem = null;56 constructor(opts) {57 this.opts = opts;58 this.onTriggerEnter = opts.onTriggerEnter;59 this.onTriggerLeave = opts.onTriggerLeave ?? noop;60 this.onContentEnter = opts.onContentEnter ?? noop;61 this.onContentLeave = opts.onContentLeave ?? noop;62 this.onItemDismiss = opts.onItemDismiss;63 this.onItemSelect = opts.onItemSelect;64 }65 setActiveItem = (item) => {66 this.prevActiveItem = this.activeItem;67 this.activeItem = item;68 };69}70export class NavigationMenuRootState {71 static create(opts) {72 return new NavigationMenuRootState(opts);73 }74 opts;75 attachment;76 provider;77 previousValue = simpleBox("");78 isDelaySkipped;79 #derivedDelay = $derived.by(() => {80 const isOpen = this.opts?.value?.current !== "";81 if (isOpen || this.isDelaySkipped.current) {82 // 150 for user to switch trigger or move into content view83 return 150;84 }85 else {86 return this.opts.delayDuration.current;87 }88 });89 constructor(opts) {90 this.opts = opts;91 this.attachment = attachRef(this.opts.ref);92 this.isDelaySkipped = boxAutoReset(false, {93 afterMs: this.opts.skipDelayDuration.current,94 getWindow: () => getWindow(opts.ref.current),95 });96 this.provider = NavigationMenuProviderState.create({97 value: this.opts.value,98 previousValue: this.previousValue,99 dir: this.opts.dir,100 orientation: this.opts.orientation,101 rootNavigationMenuRef: this.opts.ref,102 isRootMenu: true,103 onTriggerEnter: (itemValue, itemState) => {104 this.#onTriggerEnter(itemValue, itemState);105 },106 onTriggerLeave: this.#onTriggerLeave,107 onContentEnter: this.#onContentEnter,108 onContentLeave: this.#onContentLeave,109 onItemSelect: this.#onItemSelect,110 onItemDismiss: this.#onItemDismiss,111 });112 }113 #debouncedFn = useDebounce((val, itemState) => {114 // passing `undefined` meant to reset the debounce timer115 if (typeof val === "string") {116 this.setValue(val, itemState);117 }118 }, () => this.#derivedDelay);119 #onTriggerEnter = (itemValue, itemState) => {120 this.#debouncedFn(itemValue, itemState);121 };122 #onTriggerLeave = () => {123 this.isDelaySkipped.current = false;124 this.#debouncedFn("", null);125 };126 #onContentEnter = () => {127 this.#debouncedFn(undefined, null);128 };129 #onContentLeave = () => {130 if (this.provider.activeItem &&131 this.provider.activeItem.opts.openOnHover.current === false) {132 return;133 }134 this.#debouncedFn("", null);135 };136 #onItemSelect = (itemValue, itemState) => {137 this.setValue(itemValue, itemState);138 };139 #onItemDismiss = () => {140 this.setValue("", null);141 };142 setValue = (newValue, itemState) => {143 this.previousValue.current = this.opts.value.current;144 this.opts.value.current = newValue;145 this.provider.setActiveItem(itemState);146 // When all menus are closed, we want to reset previousValue to prevent147 // weird transitions from old positions when opening fresh148 if (newValue === "") {149 this.previousValue.current = "";150 }151 };152 props = $derived.by(() => ({153 id: this.opts.id.current,154 "data-orientation": this.opts.orientation.current,155 dir: this.opts.dir.current,156 [navigationMenuAttrs.root]: "",157 [navigationMenuAttrs.menu]: "",158 ...this.attachment,159 }));160}161export class NavigationMenuSubState {162 static create(opts) {163 return new NavigationMenuSubState(opts, NavigationMenuProviderContext.get());164 }165 opts;166 context;167 previousValue = simpleBox("");168 subProvider;169 attachment;170 constructor(opts, context) {171 this.opts = opts;172 this.context = context;173 this.attachment = attachRef(this.opts.ref);174 this.subProvider = NavigationMenuProviderState.create({175 isRootMenu: false,176 value: this.opts.value,177 dir: this.context.opts.dir,178 orientation: this.opts.orientation,179 rootNavigationMenuRef: this.opts.ref,180 onTriggerEnter: this.setValue,181 onItemSelect: this.setValue,182 onItemDismiss: () => this.setValue("", null),183 previousValue: this.previousValue,184 });185 }186 setValue = (newValue, itemState) => {187 this.previousValue.current = this.opts.value.current;188 this.opts.value.current = newValue;189 this.subProvider.setActiveItem(itemState);190 // When all menus are closed, we want to reset previousValue to prevent191 // weird transitions from old positions when opening fresh192 if (newValue === "") {193 this.previousValue.current = "";194 }195 };196 props = $derived.by(() => ({197 id: this.opts.id.current,198 "data-orientation": this.opts.orientation.current,199 [navigationMenuAttrs.sub]: "",200 [navigationMenuAttrs.menu]: "",201 ...this.attachment,202 }));203}204export class NavigationMenuListState {205 static create(opts) {206 return NavigationMenuListContext.set(new NavigationMenuListState(opts, NavigationMenuProviderContext.get()));207 }208 wrapperId = simpleBox(useId());209 wrapperRef = simpleBox(null);210 opts;211 context;212 attachment;213 wrapperAttachment = attachRef(this.wrapperRef, (v) => (this.context.indicatorTrackRef.current = v));214 listTriggers = $state.raw([]);215 rovingFocusGroup;216 wrapperMounted = $state(false);217 constructor(opts, context) {218 this.opts = opts;219 this.context = context;220 this.attachment = attachRef(this.opts.ref);221 this.rovingFocusGroup = new RovingFocusGroup({222 rootNode: opts.ref,223 candidateSelector: `${navigationMenuAttrs.selector("trigger")}:not([data-disabled]), ${navigationMenuAttrs.selector("link")}:not([data-disabled])`,224 loop: boxWith(() => false),225 orientation: this.context.opts.orientation,226 });227 }228 registerTrigger(trigger) {229 if (trigger)230 this.listTriggers.push(trigger);231 return () => {232 this.listTriggers = this.listTriggers.filter((t) => t.id !== trigger.id);233 };234 }235 wrapperProps = $derived.by(() => ({236 id: this.wrapperId.current,237 ...this.wrapperAttachment,238 }));239 props = $derived.by(() => ({240 id: this.opts.id.current,241 "data-orientation": this.context.opts.orientation.current,242 [navigationMenuAttrs.list]: "",243 ...this.attachment,244 }));245}246export class NavigationMenuItemState {247 static create(opts) {248 return NavigationMenuItemContext.set(new NavigationMenuItemState(opts, NavigationMenuListContext.get()));249 }250 opts;251 attachment;252 listContext;253 contentNode = $state(null);254 triggerNode = $state(null);255 focusProxyNode = $state(null);256 restoreContentTabOrder = noop;257 wasEscapeClose = false;258 contentId = $derived.by(() => this.contentNode?.id);259 triggerId = $derived.by(() => this.triggerNode?.id);260 contentChildren = simpleBox(undefined);261 contentChild = simpleBox(undefined);262 contentProps = simpleBox({});263 domContext;264 constructor(opts, listContext) {265 this.opts = opts;266 this.listContext = listContext;267 this.domContext = new DOMContext(opts.ref);268 this.attachment = attachRef(this.opts.ref);269 }270 #handleContentEntry = (side = "start") => {271 if (!this.contentNode)272 return;273 this.restoreContentTabOrder();274 const candidates = getTabbableCandidates(this.contentNode);275 if (candidates.length)276 focusFirst(side === "start" ? candidates : candidates.reverse(), () => this.domContext.getActiveElement());277 };278 #handleContentExit = () => {279 if (!this.contentNode)280 return;281 const candidates = getTabbableCandidates(this.contentNode);282 if (candidates.length)283 this.restoreContentTabOrder = removeFromTabOrder(candidates);284 };285 onEntryKeydown = this.#handleContentEntry;286 onFocusProxyEnter = this.#handleContentEntry;287 onRootContentClose = this.#handleContentExit;288 onContentFocusOutside = this.#handleContentExit;289 props = $derived.by(() => ({290 id: this.opts.id.current,291 [navigationMenuAttrs.item]: "",292 ...this.attachment,293 }));294}295export class NavigationMenuTriggerState {296 static create(opts) {297 return new NavigationMenuTriggerState(opts, {298 provider: NavigationMenuProviderContext.get(),299 item: NavigationMenuItemContext.get(),300 list: NavigationMenuListContext.get(),301 sub: NavigationMenuSubContext.getOr(null),302 });303 }304 opts;305 attachment;306 focusProxyId = simpleBox(useId());307 focusProxyRef = simpleBox(null);308 focusProxyAttachment = attachRef(this.focusProxyRef, (v) => (this.itemContext.focusProxyNode = v));309 context;310 itemContext;311 listContext;312 hasPointerMoveOpened = simpleBox(false);313 wasClickClose = false;314 focusProxyMounted = $state(false);315 open = $derived.by(() => this.itemContext.opts.value.current === this.context.opts.value.current);316 constructor(opts, context) {317 this.opts = opts;318 this.attachment = attachRef(this.opts.ref, (v) => (this.itemContext.triggerNode = v));319 this.hasPointerMoveOpened = boxAutoReset(false, {320 afterMs: 300,321 getWindow: () => getWindow(opts.ref.current),322 });323 this.context = context.provider;324 this.itemContext = context.item;325 this.listContext = context.list;326 watch(() => this.opts.ref.current, () => {327 const node = this.opts.ref.current;328 if (!node)329 return;330 return this.listContext.registerTrigger(node);331 });332 }333 onpointerenter = (_) => {334 this.wasClickClose = false;335 this.itemContext.wasEscapeClose = false;336 };337 onpointermove = whenMouse(() => {338 if (this.opts.disabled.current ||339 this.wasClickClose ||340 this.itemContext.wasEscapeClose ||341 this.hasPointerMoveOpened.current ||342 !this.itemContext.opts.openOnHover.current) {343 return;344 }345 this.context.onTriggerEnter(this.itemContext.opts.value.current, this.itemContext);346 this.hasPointerMoveOpened.current = true;347 });348 onpointerleave = whenMouse(() => {349 if (this.opts.disabled.current || !this.itemContext.opts.openOnHover.current)350 return;351 this.context.onTriggerLeave();352 this.hasPointerMoveOpened.current = false;353 });354 onclick = () => {355 // if opened via pointer move, we prevent the click event356 if (this.hasPointerMoveOpened.current)357 return;358 const shouldClose = this.open &&359 (!this.itemContext.opts.openOnHover.current || this.context.opts.isRootMenu);360 if (shouldClose) {361 this.context.onItemSelect("", null);362 }363 else if (!this.open) {364 this.context.onItemSelect(this.itemContext.opts.value.current, this.itemContext);365 }366 this.wasClickClose = shouldClose;367 };368 onkeydown = (e) => {369 const verticalEntryKey = this.context.opts.dir.current === "rtl" ? kbd.ARROW_LEFT : kbd.ARROW_RIGHT;370 const entryKey = { horizontal: kbd.ARROW_DOWN, vertical: verticalEntryKey }[this.context.opts.orientation.current];371 if (this.open && e.key === entryKey) {372 this.itemContext.onEntryKeydown();373 // prevent focus group from handling the event374 e.preventDefault();375 return;376 }377 this.itemContext.listContext.rovingFocusGroup.handleKeydown(this.opts.ref.current, e);378 };379 focusProxyOnFocus = (e) => {380 const content = this.itemContext.contentNode;381 const prevFocusedElement = e.relatedTarget;382 const wasTriggerFocused = this.opts.ref.current && prevFocusedElement === this.opts.ref.current;383 const wasFocusFromContent = content?.contains(prevFocusedElement);384 if (wasTriggerFocused || !wasFocusFromContent) {385 this.itemContext.onFocusProxyEnter(wasTriggerFocused ? "start" : "end");386 }387 };388 props = $derived.by(() => ({389 id: this.opts.id.current,390 disabled: this.opts.disabled.current,391 "data-disabled": boolToEmptyStrOrUndef(Boolean(this.opts.disabled.current)),392 "data-state": getDataOpenClosed(this.open),393 "data-value": this.itemContext.opts.value.current,394 "aria-expanded": boolToStr(this.open),395 "aria-controls": this.itemContext.contentId,396 [navigationMenuAttrs.trigger]: "",397 onpointermove: this.onpointermove,398 onpointerleave: this.onpointerleave,399 onpointerenter: this.onpointerenter,400 onclick: this.onclick,401 onkeydown: this.onkeydown,402 ...this.attachment,403 }));404 focusProxyProps = $derived.by(() => ({405 id: this.focusProxyId.current,406 tabindex: 0,407 onfocus: this.focusProxyOnFocus,408 ...this.focusProxyAttachment,409 }));410}411const LINK_SELECT_EVENT = new CustomEventDispatcher("bitsLinkSelect", {412 bubbles: true,413 cancelable: true,414});415const ROOT_CONTENT_DISMISS_EVENT = new CustomEventDispatcher("bitsRootContentDismiss", {416 cancelable: true,417 bubbles: true,418});419export class NavigationMenuLinkState {420 static create(opts) {421 return new NavigationMenuLinkState(opts, {422 provider: NavigationMenuProviderContext.get(),423 item: NavigationMenuItemContext.get(),424 });425 }426 opts;427 context;428 attachment;429 isFocused = $state(false);430 constructor(opts, context) {431 this.opts = opts;432 this.context = context;433 this.attachment = attachRef(this.opts.ref);434 }435 onclick = (e) => {436 const currTarget = e.currentTarget;437 LINK_SELECT_EVENT.listen(currTarget, (e) => this.opts.onSelect.current(e), { once: true });438 const linkSelectEvent = LINK_SELECT_EVENT.dispatch(currTarget);439 if (!linkSelectEvent.defaultPrevented && !e.metaKey) {440 ROOT_CONTENT_DISMISS_EVENT.dispatch(currTarget);441 }442 };443 onkeydown = (e) => {444 if (this.context.item.contentNode)445 return;446 this.context.item.listContext.rovingFocusGroup.handleKeydown(this.opts.ref.current, e);447 };448 onfocus = (_) => {449 this.isFocused = true;450 };451 onblur = (_) => {452 this.isFocused = false;453 };454 #handlePointerDismiss = () => {455 // only close submenu if this link is not inside the currently open submenu content456 const currentlyOpenValue = this.context.provider.opts.value.current;457 const isInsideOpenSubmenu = this.context.item.opts.value.current === currentlyOpenValue;458 const activeItem = this.context.item.listContext.context.activeItem;459 if (activeItem && !activeItem.opts.openOnHover.current)460 return;461 if (currentlyOpenValue && !isInsideOpenSubmenu) {462 this.context.provider.onItemDismiss();463 }464 };465 onpointerenter = () => {466 this.#handlePointerDismiss();467 };468 onpointermove = whenMouse(() => {469 this.#handlePointerDismiss();470 });471 props = $derived.by(() => ({472 id: this.opts.id.current,473 "data-active": this.opts.active.current ? "" : undefined,474 "aria-current": this.opts.active.current ? "page" : undefined,475 "data-focused": this.isFocused ? "" : undefined,476 onclick: this.onclick,477 onkeydown: this.onkeydown,478 onfocus: this.onfocus,479 onblur: this.onblur,480 onpointerenter: this.onpointerenter,481 onpointermove: this.onpointermove,482 [navigationMenuAttrs.link]: "",483 ...this.attachment,484 }));485}486export class NavigationMenuIndicatorState {487 static create() {488 return new NavigationMenuIndicatorState(NavigationMenuProviderContext.get());489 }490 context;491 isVisible = $derived.by(() => Boolean(this.context.opts.value.current));492 constructor(context) {493 this.context = context;494 }495}496export class NavigationMenuIndicatorImplState {497 static create(opts) {498 return new NavigationMenuIndicatorImplState(opts, {499 provider: NavigationMenuProviderContext.get(),500 list: NavigationMenuListContext.get(),501 });502 }503 opts;504 attachment;505 context;506 listContext;507 position = $state.raw(null);508 isHorizontal = $derived.by(() => this.context.opts.orientation.current === "horizontal");509 isVisible = $derived.by(() => !!this.context.opts.value.current);510 activeTrigger = $derived.by(() => {511 const items = this.listContext.listTriggers;512 const triggerNode = items.find((item) => item.getAttribute("data-value") === this.context.opts.value.current);513 return triggerNode ?? null;514 });515 shouldRender = $derived.by(() => this.position !== null);516 constructor(opts, context) {517 this.opts = opts;518 this.context = context.provider;519 this.listContext = context.list;520 this.attachment = attachRef(this.opts.ref);521 new SvelteResizeObserver(() => this.activeTrigger, this.handlePositionChange);522 new SvelteResizeObserver(() => this.context.indicatorTrackRef.current, this.handlePositionChange);523 }524 handlePositionChange = () => {525 if (!this.activeTrigger)526 return;527 this.position = {528 size: this.isHorizontal529 ? this.activeTrigger.offsetWidth530 : this.activeTrigger.offsetHeight,531 offset: this.isHorizontal532 ? this.activeTrigger.offsetLeft533 : this.activeTrigger.offsetTop,534 };535 };536 props = $derived.by(() => ({537 id: this.opts.id.current,538 "data-state": this.isVisible ? "visible" : "hidden",539 "data-orientation": this.context.opts.orientation.current,540 style: {541 position: "absolute",542 ...(this.isHorizontal543 ? {544 left: 0,545 width: `${this.position?.size}px`,546 transform: `translateX(${this.position?.offset}px)`,547 }548 : {549 top: 0,550 height: `${this.position?.size}px`,551 transform: `translateY(${this.position?.offset}px)`,552 }),553 },554 [navigationMenuAttrs.indicator]: "",555 ...this.attachment,556 }));557}558export class NavigationMenuContentState {559 static create(opts) {560 return NavigationMenuContentContext.set(new NavigationMenuContentState(opts, {561 provider: NavigationMenuProviderContext.get(),562 item: NavigationMenuItemContext.get(),563 list: NavigationMenuListContext.get(),564 }));565 }566 opts;567 context;568 itemContext;569 listContext;570 attachment;571 mounted = $state(false);572 open = $derived.by(() => this.itemContext.opts.value.current === this.context.opts.value.current);573 value = $derived.by(() => this.itemContext.opts.value.current);574 // We persist the last active content value as the viewport may be animating out575 // and we want the content to remain mounted for the lifecycle of the viewport.576 isLastActiveValue = $derived.by(() => {577 if (this.context.viewportRef.current) {578 if (!this.context.opts.value.current && this.context.opts.previousValue.current) {579 return (this.context.opts.previousValue.current === this.itemContext.opts.value.current);580 }581 }582 return false;583 });584 constructor(opts, context) {585 this.opts = opts;586 this.context = context.provider;587 this.itemContext = context.item;588 this.listContext = context.list;589 this.attachment = attachRef(this.opts.ref, (v) => (this.itemContext.contentNode = v));590 }591 onpointerenter = (_) => {592 this.context.onContentEnter();593 };594 onpointerleave = whenMouse(() => {595 if (!this.itemContext.opts.openOnHover.current)596 return;597 this.context.onContentLeave();598 });599 props = $derived.by(() => ({600 id: this.opts.id.current,601 onpointerenter: this.onpointerenter,602 onpointerleave: this.onpointerleave,603 ...this.attachment,604 }));605}606export class NavigationMenuContentImplState {607 static create(opts, itemState) {608 return new NavigationMenuContentImplState(opts, itemState ?? NavigationMenuItemContext.get());609 }610 opts;611 itemContext;612 context;613 listContext;614 attachment;615 prevMotionAttribute = $state(null);616 motionAttribute = $derived.by(() => {617 const items = this.listContext.listTriggers;618 const values = items.map((item) => item.getAttribute("data-value")).filter(Boolean);619 if (this.context.opts.dir.current === "rtl")620 values.reverse();621 const index = values.indexOf(this.context.opts.value.current);622 const prevIndex = values.indexOf(this.context.opts.previousValue.current);623 const isSelected = this.itemContext.opts.value.current === this.context.opts.value.current;624 const wasSelected = prevIndex === values.indexOf(this.itemContext.opts.value.current);625 // When all menus are closed, we want to reset motion state to prevent residual animations626 if (!this.context.opts.value.current && !this.context.opts.previousValue.current) {627 untrack(() => (this.prevMotionAttribute = null));628 return null;629 }630 // We only want to update selected and the last selected content631 // this avoids animations being interrupted outside of that range632 if (!isSelected && !wasSelected)633 return untrack(() => this.prevMotionAttribute);634 const attribute = (() => {635 // Don't provide a direction on the initial open636 if (index !== prevIndex) {637 // If we're moving to this item from another638 if (isSelected && prevIndex !== -1)639 return index > prevIndex ? "from-end" : "from-start";640 // If we're leaving this item for another641 if (wasSelected && index !== -1)642 return index > prevIndex ? "to-start" : "to-end";643 }644 // Otherwise we're entering from closed or leaving the list645 // entirely and should not animate in any direction646 return null;647 })();648 untrack(() => (this.prevMotionAttribute = attribute));649 return attribute;650 });651 domContext;652 constructor(opts, itemContext) {653 this.opts = opts;654 this.attachment = attachRef(this.opts.ref);655 this.itemContext = itemContext;656 this.listContext = itemContext.listContext;657 this.context = itemContext.listContext.context;658 this.domContext = new DOMContext(opts.ref);659 watch([660 () => this.itemContext.opts.value.current,661 () => this.itemContext.triggerNode,662 () => this.opts.ref.current,663 ], () => {664 const content = this.opts.ref.current;665 if (!(content && this.context.opts.isRootMenu))666 return;667 const handleClose = () => {668 this.context.onItemDismiss();669 this.itemContext.onRootContentClose();670 if (content.contains(this.domContext.getActiveElement())) {671 this.itemContext.triggerNode?.focus();672 }673 };674 const removeListener = ROOT_CONTENT_DISMISS_EVENT.listen(content, handleClose);675 return () => {676 removeListener();677 };678 });679 }680 onFocusOutside = (e) => {681 this.itemContext.onContentFocusOutside();682 const target = e.target;683 // only dismiss content when focus moves outside of the menu684 if (this.context.opts.rootNavigationMenuRef.current?.contains(target)) {685 e.preventDefault();686 return;687 }688 this.context.onItemDismiss();689 };690 onInteractOutside = (e) => {691 const target = e.target;692 const isTrigger = this.listContext.listTriggers.some((trigger) => trigger.contains(target));693 const isRootViewport = this.context.opts.isRootMenu && this.context.viewportRef.current?.contains(target);694 if (!this.context.opts.isRootMenu && !isTrigger) {695 this.context.onItemDismiss();696 return;697 }698 if (isTrigger || isRootViewport) {699 e.preventDefault();700 return;701 }702 if (!this.itemContext.opts.openOnHover.current) {703 this.context.onItemSelect("", null);704 }705 };706 onkeydown = (e) => {707 // prevent parent menus handling sub-menu keydown events708 const target = e.target;709 if (!isElement(target))710 return;711 if (target.closest(navigationMenuAttrs.selector("menu")) !==712 this.context.opts.rootNavigationMenuRef.current)713 return;714 const isMetaKey = e.altKey || e.ctrlKey || e.metaKey;715 const isTabKey = e.key === kbd.TAB && !isMetaKey;716 const candidates = getTabbableCandidates(e.currentTarget);717 if (isTabKey) {718 const focusedElement = this.domContext.getActiveElement();719 const index = candidates.findIndex((candidate) => candidate === focusedElement);720 const isMovingBackwards = e.shiftKey;721 const nextCandidates = isMovingBackwards722 ? candidates.slice(0, index).reverse()723 : candidates.slice(index + 1, candidates.length);724 if (focusFirst(nextCandidates, () => this.domContext.getActiveElement())) {725 // prevent browser tab keydown because we've handled focus726 e.preventDefault();727 return;728 }729 else {730 // If we can't focus that means we're at the edges731 // so focus the proxy and let browser handle732 // tab/shift+tab keypress on the proxy instead733 handleProxyFocus(this.itemContext.focusProxyNode);734 return;735 }736 }737 let activeEl = this.domContext.getActiveElement();738 if (this.itemContext.contentNode) {739 const focusedNode = this.itemContext.contentNode.querySelector("[data-focused]");740 if (focusedNode) {741 activeEl = focusedNode;742 }743 }744 if (activeEl === this.itemContext.triggerNode)745 return;746 const newSelectedElement = useArrowNavigation(e, activeEl, undefined, {747 itemsArray: candidates,748 candidateSelector: navigationMenuAttrs.selector("link"),749 loop: false,750 enableIgnoredElement: true,751 });752 newSelectedElement?.focus();753 };754 onEscapeKeydown = (_) => {755 this.context.onItemDismiss();756 this.itemContext.triggerNode?.focus();757 // prevent the dropdown from reopening after the escape key has been pressed758 this.itemContext.wasEscapeClose = true;759 };760 props = $derived.by(() => ({761 id: this.opts.id.current,762 "aria-labelledby": this.itemContext.triggerId,763 "data-motion": this.motionAttribute ?? undefined,764 "data-orientation": this.context.opts.orientation.current,765 "data-state": getDataOpenClosed(this.context.opts.value.current === this.itemContext.opts.value.current),766 onkeydown: this.onkeydown,767 [navigationMenuAttrs.content]: "",768 ...this.attachment,769 }));770}771export class NavigationMenuViewportState {772 static create(opts) {773 return new NavigationMenuViewportState(opts, NavigationMenuProviderContext.get());774 }775 opts;776 context;777 attachment;778 open = $derived.by(() => !!this.context.opts.value.current);779 viewportWidth = $derived.by(() => (this.size ? `${this.size.width}px` : undefined));780 viewportHeight = $derived.by(() => (this.size ? `${this.size.height}px` : undefined));781 activeContentValue = $derived.by(() => this.context.opts.value.current);782 size = $state(null);783 contentNode = $state(null);784 mounted = $state(false);785 constructor(opts, context) {786 this.opts = opts;787 this.context = context;788 this.attachment = attachRef(this.opts.ref, (v) => (this.context.viewportRef.current = v));789 watch([() => this.activeContentValue, () => this.open], () => {790 afterTick(() => {791 const currNode = this.context.viewportRef.current;792 if (!currNode)793 return;794 const el = currNode.querySelector("[data-state=open]")795 ?.children?.[0] ?? null;796 this.contentNode = el;797 });798 });799 /**800 * Update viewport size to match the active content node.801 * We prefer offset dimensions over `getBoundingClientRect` as the latter respects CSS transform.802 * For example, if content animates in from `scale(0.5)` the dimensions would be anything803 * from `0.5` to `1` of the intended size.804 */805 new SvelteResizeObserver(() => this.contentNode, () => {806 if (this.contentNode) {807 this.size = {808 width: this.contentNode.offsetWidth,809 height: this.contentNode.offsetHeight,810 };811 }812 });813 // reset size when viewport closes to prevent residual size animations814 watch(() => this.mounted, () => {815 if (!this.mounted && this.size) {816 this.size = null;817 }818 });819 }820 props = $derived.by(() => ({821 id: this.opts.id.current,822 "data-state": getDataOpenClosed(this.open),823 "data-orientation": this.context.opts.orientation.current,824 style: {825 pointerEvents: !this.open && this.context.opts.isRootMenu ? "none" : undefined,826 "--bits-navigation-menu-viewport-width": this.viewportWidth,827 "--bits-navigation-menu-viewport-height": this.viewportHeight,828 },829 [navigationMenuAttrs.viewport]: "",830 onpointerenter: this.context.onContentEnter,831 onpointerleave: this.context.onContentLeave,832 ...this.attachment,833 }));834}835//836function focusFirst(candidates, getActiveElement) {837 const previouslyFocusedElement = getActiveElement();838 return candidates.some((candidate) => {839 // if focus is already where we want to go, we don't want to keep going through the candidates840 if (candidate === previouslyFocusedElement)841 return true;842 candidate.focus();843 return getActiveElement() !== previouslyFocusedElement;844 });845}846function removeFromTabOrder(candidates) {847 candidates.forEach((candidate) => {848 candidate.dataset.tabindex = candidate.getAttribute("tabindex") || "";849 candidate.setAttribute("tabindex", "-1");850 });851 return () => {852 candidates.forEach((candidate) => {853 const prevTabIndex = candidate.dataset.tabindex;854 candidate.setAttribute("tabindex", prevTabIndex);855 });856 };857}858function whenMouse(handler) {859 return (e) => (e.pointerType === "mouse" ? handler(e) : undefined);860}861/**862 *863 * We apply the `aria-hidden` attribute to elements that should not be visible to screen readers864 * under specific circumstances, mostly when in a "modal" context or when they are strictly for865 * utility purposes, like the focus guards.866 *867 * When these elements receive focus before we can remove the aria-hidden attribute, we need to868 * handle the focus in a way that does not cause an error to be logged.869 *870 * This function handles the focus of the guard element first by momentary removing the871 * `aria-hidden` attribute, focusing the guard (which will cause something else to focus), and then872 * restoring the attribute.873 */874function handleProxyFocus(guard, focusOptions) {875 if (!guard)876 return;877 const ariaHidden = guard.getAttribute("aria-hidden");878 guard.removeAttribute("aria-hidden");879 guard.focus(focusOptions);880 afterSleep(0, () => {881 if (ariaHidden === null) {882 guard.setAttribute("aria-hidden", "");883 }884 else {885 guard.setAttribute("aria-hidden", ariaHidden);886 }887 });888}889 