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
1import { afterTick, mergeProps, onDestroyEffect, attachRef, DOMContext, getDocument, getWindow, simpleBox, boxWith, } from "svelte-toolbelt";2import { Context, watch } from "runed";3import { FIRST_LAST_KEYS, LAST_KEYS, SELECTION_KEYS, SUB_OPEN_KEYS, getCheckedState, isMouseEvent, } from "./utils.js";4import { focusFirst } from "../../internal/focus.js";5import { CustomEventDispatcher } from "../../internal/events.js";6import { isElement, isElementOrSVGElement, isHTMLElement } from "../../internal/is.js";7import { kbd } from "../../internal/kbd.js";8import { createBitsAttrs, getAriaChecked, boolToStr, getDataOpenClosed, boolToEmptyStrOrUndef, getDataTransitionAttrs, } from "../../internal/attrs.js";9import { IsUsingKeyboard } from "../utilities/is-using-keyboard/is-using-keyboard.svelte.js";10import { getTabbableFrom } from "../../internal/tabbable.js";11import { isTabbable } from "tabbable";12import { DOMTypeahead } from "../../internal/dom-typeahead.svelte.js";13import { RovingFocusGroup } from "../../internal/roving-focus-group.js";14import { PresenceManager } from "../../internal/presence-manager.svelte.js";15import { arraysAreEqual } from "../../internal/arrays.js";16export const CONTEXT_MENU_TRIGGER_ATTR = "data-context-menu-trigger";17export const CONTEXT_MENU_CONTENT_ATTR = "data-context-menu-content";18const MenuRootContext = new Context("Menu.Root");19const MenuMenuContext = new Context("Menu.Root | Menu.Sub");20const MenuContentContext = new Context("Menu.Content");21const MenuGroupContext = new Context("Menu.Group | Menu.RadioGroup");22const MenuRadioGroupContext = new Context("Menu.RadioGroup");23export const MenuCheckboxGroupContext = new Context("Menu.CheckboxGroup");24export const MenuOpenEvent = new CustomEventDispatcher("bitsmenuopen", {25 bubbles: false,26 cancelable: true,27});28export const menuAttrs = createBitsAttrs({29 component: "menu",30 parts: [31 "trigger",32 "content",33 "sub-trigger",34 "item",35 "group",36 "group-heading",37 "checkbox-group",38 "checkbox-item",39 "radio-group",40 "radio-item",41 "separator",42 "sub-content",43 "arrow",44 ],45});46/*47interface MenuIntentDebugSnapshot {48 active: boolean;49 target: IntentTarget | null;50 exitPoint: Point | null;51 pointerPoint: Point | null;52 corridor: Polygon | null;53 intentPolygon: Polygon | null;54}55 56class MenuIntentDebugOverlay {57 readonly #enabled: () => boolean;58 readonly #getDocument: () => Document | null;59 #root: HTMLDivElement | null = null;60 #corridorPolygon: SVGPolygonElement | null = null;61 #intentPolygon: SVGPolygonElement | null = null;62 #exitPoint: SVGCircleElement | null = null;63 #pointerPoint: SVGCircleElement | null = null;64 65 constructor(opts: { enabled: () => boolean; getDocument: () => Document | null }) {66 this.#enabled = opts.enabled;67 this.#getDocument = opts.getDocument;68 }69 70 update(state: MenuIntentDebugSnapshot) {71 if (!this.#enabled()) {72 this.#detach();73 return;74 }75 this.#ensureRoot();76 if (!this.#root) return;77 78 if (!state.active || !state.corridor || !state.intentPolygon || !state.exitPoint) {79 this.#root.style.display = "none";80 return;81 }82 83 const color = state.target === "trigger" ? "16 185 129" : "59 130 246";84 const strokeColor = `rgb(${color})`;85 const fillColor = `rgb(${color} / 0.18)`;86 const corridorFill = `rgb(${color} / 0.1)`;87 88 this.#root.style.display = "block";89 this.#setPolygon(this.#corridorPolygon, state.corridor, corridorFill, strokeColor, 1.5);90 this.#setPolygon(this.#intentPolygon, state.intentPolygon, fillColor, strokeColor, 2);91 this.#setPoint(this.#exitPoint, state.exitPoint, strokeColor, 5);92 this.#setPoint(93 this.#pointerPoint,94 state.pointerPoint,95 "rgb(15 23 42 / 0.9)",96 4,97 "rgb(255 255 255 / 0.95)"98 );99 }100 101 destroy() {102 this.#detach();103 }104 105 #setPolygon(106 node: SVGPolygonElement | null,107 points: Polygon | null,108 fill: string,109 stroke: string,110 strokeWidth: number111 ) {112 if (!node || !points || points.length === 0) return;113 node.setAttribute("points", polygonToSvgPoints(points));114 node.setAttribute("fill", fill);115 node.setAttribute("stroke", stroke);116 node.setAttribute("stroke-width", `${strokeWidth}`);117 node.setAttribute("stroke-dasharray", "6 4");118 }119 120 #setPoint(121 node: SVGCircleElement | null,122 point: Point | null,123 fill: string,124 radius: number,125 stroke = "transparent"126 ) {127 if (!node || !point) return;128 node.setAttribute("cx", `${point.x}`);129 node.setAttribute("cy", `${point.y}`);130 node.setAttribute("r", `${radius}`);131 node.setAttribute("fill", fill);132 node.setAttribute("stroke", stroke);133 node.setAttribute("stroke-width", "1.5");134 }135 136 #ensureRoot() {137 if (this.#root) return;138 const doc = this.#getDocument();139 if (!doc?.body) return;140 141 const root = doc.createElement("div");142 root.setAttribute("aria-hidden", "true");143 root.style.position = "fixed";144 root.style.inset = "0";145 root.style.pointerEvents = "none";146 root.style.zIndex = "2147483647";147 root.style.display = "none";148 149 const svg = doc.createElementNS(SVG_NS, "svg");150 svg.setAttribute("width", "100%");151 svg.setAttribute("height", "100%");152 svg.style.overflow = "visible";153 154 const corridorPolygon = doc.createElementNS(SVG_NS, "polygon");155 const intentPolygon = doc.createElementNS(SVG_NS, "polygon");156 const exitPoint = doc.createElementNS(SVG_NS, "circle");157 const pointerPoint = doc.createElementNS(SVG_NS, "circle");158 159 svg.append(corridorPolygon, intentPolygon, exitPoint, pointerPoint);160 root.append(svg);161 doc.body.append(root);162 163 this.#root = root;164 this.#corridorPolygon = corridorPolygon;165 this.#intentPolygon = intentPolygon;166 this.#exitPoint = exitPoint;167 this.#pointerPoint = pointerPoint;168 }169 170 #detach() {171 this.#root?.remove();172 this.#root = null;173 this.#corridorPolygon = null;174 this.#intentPolygon = null;175 this.#exitPoint = null;176 this.#pointerPoint = null;177 }178}179*/180class MenuSubmenuIntent {181 #opts;182 // readonly #debugOverlay: MenuIntentDebugOverlay;183 #cleanupDocMove = null;184 #fallbackTimer = null;185 #active = false;186 #target = null;187 #apex = null;188 #pointerPoint = null;189 // #corridor: Polygon | null = null;190 // #intentPolygon: Polygon | null = null;191 #launchPoint = null;192 constructor(opts) {193 this.#opts = opts;194 // this.#debugOverlay = new MenuIntentDebugOverlay({195 // enabled: () => this.#opts.debugMode(),196 // getDocument: () => getDocument(this.#opts.triggerNode() ?? this.#opts.contentNode()),197 // });198 watch([opts.triggerNode, opts.contentNode, opts.enabled], ([triggerNode, contentNode, enabled]) => {199 this.#reset();200 if (!triggerNode || !contentNode || !enabled)201 return;202 const onTriggerMove = (e) => {203 if (!isMouseEvent(e))204 return;205 this.#launchPoint = { x: e.clientX, y: e.clientY };206 if (!this.#active)207 this.#preview(e, "content");208 };209 const onTriggerLeave = (e) => {210 if (!isMouseEvent(e))211 return;212 this.#engage(e, "content");213 };214 const onContentMove = (e) => {215 if (!isMouseEvent(e))216 return;217 if (!this.#active)218 this.#preview(e, "trigger");219 };220 const onContentLeave = (e) => {221 if (!isMouseEvent(e))222 return;223 if (isElement(e.relatedTarget)) {224 const selector = this.#opts.subContentSelector();225 const matchedSubContent = e.relatedTarget.closest(selector);226 if (matchedSubContent &&227 matchedSubContent !== contentNode &&228 matchedSubContent.id) {229 const isChild = !!contentNode.querySelector(`[aria-controls="${matchedSubContent.id}"]`);230 if (isChild) {231 return;232 }233 }234 }235 this.#engage(e, "trigger");236 };237 const onTriggerEnter = (e) => {238 if (!isMouseEvent(e))239 return;240 this.#disengage();241 };242 const onContentEnter = (e) => {243 if (!isMouseEvent(e))244 return;245 this.#disengage();246 };247 triggerNode.addEventListener("pointermove", onTriggerMove);248 triggerNode.addEventListener("pointerleave", onTriggerLeave);249 triggerNode.addEventListener("pointerenter", onTriggerEnter);250 contentNode.addEventListener("pointermove", onContentMove);251 contentNode.addEventListener("pointerleave", onContentLeave);252 contentNode.addEventListener("pointerenter", onContentEnter);253 return () => {254 triggerNode.removeEventListener("pointermove", onTriggerMove);255 triggerNode.removeEventListener("pointerleave", onTriggerLeave);256 triggerNode.removeEventListener("pointerenter", onTriggerEnter);257 contentNode.removeEventListener("pointermove", onContentMove);258 contentNode.removeEventListener("pointerleave", onContentLeave);259 contentNode.removeEventListener("pointerenter", onContentEnter);260 this.#reset();261 };262 });263 onDestroyEffect(() => {264 this.#reset();265 // this.#debugOverlay.destroy();266 });267 }268 #parentTargetRect() {269 const parent = this.#opts.parentContentNode();270 if (parent)271 return parent.getBoundingClientRect();272 return this.#opts.triggerNode()?.getBoundingClientRect() ?? null;273 }274 #computePolygons(pointerPt, target) {275 const triggerNode = this.#opts.triggerNode();276 const contentNode = this.#opts.contentNode();277 if (!triggerNode || !contentNode)278 return null;279 const triggerRect = triggerNode.getBoundingClientRect();280 const contentRect = contentNode.getBoundingClientRect();281 const side = getSide(triggerRect, contentRect);282 let apex;283 let targetRect;284 let sourceRect;285 if (target === "content") {286 apex = this.#active ? (this.#apex ?? pointerPt) : pointerPt;287 targetRect = contentRect;288 }289 else {290 apex = this.#launchPoint ?? pointerPt;291 targetRect = this.#parentTargetRect() ?? triggerRect;292 sourceRect = contentRect;293 }294 this.#apex = apex;295 return {296 corridor: getCorridorPolygon(triggerRect, contentRect, side),297 intent: getIntentPolygon(apex, targetRect, side, target, sourceRect),298 targetRect,299 side,300 };301 }302 #isInSafeZone(pt, corridor, intent) {303 return isPointInPolygon(pt, corridor) || isPointInPolygon(pt, intent);304 }305 #preview(e, target) {306 const pt = { x: e.clientX, y: e.clientY };307 const geo = this.#computePolygons(pt, target);308 if (!geo)309 return;310 this.#target = target;311 this.#pointerPoint = pt;312 // this.#corridor = geo.corridor;313 // this.#intentPolygon = geo.intent;314 // this.#syncDebug();315 }316 #engage(e, target) {317 if (!this.#opts.enabled())318 return;319 const triggerNode = this.#opts.triggerNode();320 const contentNode = this.#opts.contentNode();321 if (!triggerNode || !contentNode)322 return;323 const related = e.relatedTarget;324 if (isElement(related)) {325 if (target === "content" && contentNode.contains(related))326 return;327 if (target === "trigger" && triggerNode.contains(related))328 return;329 }330 const pt = { x: e.clientX, y: e.clientY };331 const geo = this.#computePolygons(pt, target);332 if (!geo)333 return;334 if (!isInsideRect(pt, geo.targetRect) &&335 !this.#isInSafeZone(pt, geo.corridor, geo.intent)) {336 this.#clearVisuals();337 return;338 }339 this.#active = true;340 this.#target = target;341 this.#pointerPoint = pt;342 // this.#corridor = geo.corridor;343 // this.#intentPolygon = geo.intent;344 this.#opts.setIsPointerInTransit(true);345 this.#attachDocMove();346 this.#startFallback();347 // this.#syncDebug();348 }349 #disengageTimer = null;350 #disengage() {351 if (!this.#active)352 return;353 const wasReturning = this.#target === "trigger";354 this.#detachDocMove();355 this.#clearFallback();356 this.#active = false;357 this.#clearVisuals();358 if (wasReturning) {359 this.#clearDisengageTimer();360 this.#disengageTimer = setTimeout(() => {361 this.#disengageTimer = null;362 this.#opts.setIsPointerInTransit(false);363 }, 100);364 }365 else {366 this.#opts.setIsPointerInTransit(false);367 }368 }369 #clearDisengageTimer() {370 if (this.#disengageTimer === null)371 return;372 clearTimeout(this.#disengageTimer);373 this.#disengageTimer = null;374 }375 #intentExit() {376 const pointerPoint = this.#pointerPoint;377 this.#detachDocMove();378 this.#clearFallback();379 this.#clearDisengageTimer();380 this.#active = false;381 this.#opts.setIsPointerInTransit(false);382 this.#clearVisuals();383 this.#opts.onIntentExit(pointerPoint);384 }385 #reset() {386 this.#detachDocMove();387 this.#clearFallback();388 this.#clearDisengageTimer();389 if (this.#active)390 this.#opts.setIsPointerInTransit(false);391 this.#active = false;392 this.#target = null;393 this.#apex = null;394 this.#pointerPoint = null;395 // this.#corridor = null;396 // this.#intentPolygon = null;397 this.#launchPoint = null;398 // this.#syncDebug();399 }400 #isPointerInDescendantSubContent(pt) {401 const contentNode = this.#opts.contentNode();402 if (!contentNode)403 return false;404 const doc = contentNode.ownerDocument;405 const el = doc.elementFromPoint(pt.x, pt.y);406 if (!el)407 return false;408 const selector = this.#opts.subContentSelector();409 const subContent = el.closest(selector);410 if (!subContent || subContent === contentNode)411 return false;412 if (subContent.id)413 return !!contentNode.querySelector(`[aria-controls="${subContent.id}"]`);414 return false;415 }416 #onDocMove = (e) => {417 if (!this.#active || !this.#target)418 return;419 if (!isMouseEvent(e))420 return;421 const triggerNode = this.#opts.triggerNode();422 const contentNode = this.#opts.contentNode();423 if (!triggerNode || !contentNode) {424 this.#intentExit();425 return;426 }427 this.#clearFallback();428 const pt = { x: e.clientX, y: e.clientY };429 this.#pointerPoint = pt;430 const triggerRect = triggerNode.getBoundingClientRect();431 const contentRect = contentNode.getBoundingClientRect();432 if (this.#target === "content" && isInsideRect(pt, contentRect)) {433 this.#disengage();434 return;435 }436 if (this.#target === "trigger" && isInsideInsetRect(pt, triggerRect, 4)) {437 this.#disengage();438 return;439 }440 if (this.#isPointerInDescendantSubContent(pt)) {441 this.#startFallback();442 return;443 }444 const geo = this.#computePolygons(pt, this.#target);445 if (!geo) {446 this.#intentExit();447 return;448 }449 // this.#corridor = geo.corridor;450 // this.#intentPolygon = geo.intent;451 // this.#syncDebug();452 if (this.#isInSafeZone(pt, geo.corridor, geo.intent)) {453 this.#startFallback();454 return;455 }456 this.#intentExit();457 };458 #attachDocMove() {459 if (this.#cleanupDocMove)460 return;461 const doc = getDocument(this.#opts.triggerNode() ?? this.#opts.contentNode());462 if (!doc)463 return;464 doc.addEventListener("pointermove", this.#onDocMove, true);465 this.#cleanupDocMove = () => {466 doc.removeEventListener("pointermove", this.#onDocMove, true);467 this.#cleanupDocMove = null;468 };469 }470 #detachDocMove() {471 this.#cleanupDocMove?.();472 }473 #startFallback() {474 this.#clearFallback();475 this.#fallbackTimer = setTimeout(() => {476 this.#fallbackTimer = null;477 if (this.#active)478 this.#intentExit();479 }, 500);480 }481 #clearFallback() {482 if (this.#fallbackTimer === null)483 return;484 clearTimeout(this.#fallbackTimer);485 this.#fallbackTimer = null;486 }487 #clearVisuals() {488 this.#target = null;489 this.#apex = null;490 this.#pointerPoint = null;491 // this.#corridor = null;492 // this.#intentPolygon = null;493 // this.#syncDebug();494 }495}496/*497function polygonToSvgPoints(points: Polygon): string {498 return points.map((point) => `${point.x},${point.y}`).join(" ");499}500*/501function isPointInPolygon(point, polygon) {502 const { x, y } = point;503 let inside = false;504 for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {505 const xi = polygon[i].x;506 const yi = polygon[i].y;507 const xj = polygon[j].x;508 const yj = polygon[j].y;509 // prettier-ignore510 const intersect = ((yi > y) !== (yj > y)) && (x < ((xj - xi) * (y - yi)) / (yj - yi) + xi);511 if (intersect)512 inside = !inside;513 }514 return inside;515}516function isInsideRect(point, rect) {517 return (point.x >= rect.left &&518 point.x <= rect.right &&519 point.y >= rect.top &&520 point.y <= rect.bottom);521}522function isInsideInsetRect(point, rect, inset) {523 return (point.x >= rect.left + inset &&524 point.x <= rect.right - inset &&525 point.y >= rect.top + inset &&526 point.y <= rect.bottom - inset);527}528function getSide(triggerRect, contentRect) {529 const triggerCenterX = triggerRect.left + triggerRect.width / 2;530 const triggerCenterY = triggerRect.top + triggerRect.height / 2;531 const contentCenterX = contentRect.left + contentRect.width / 2;532 const contentCenterY = contentRect.top + contentRect.height / 2;533 const deltaX = contentCenterX - triggerCenterX;534 const deltaY = contentCenterY - triggerCenterY;535 if (Math.abs(deltaX) > Math.abs(deltaY)) {536 return deltaX > 0 ? "right" : "left";537 }538 return deltaY > 0 ? "bottom" : "top";539}540function getCorridorPolygon(triggerRect, contentRect, side) {541 const buffer = 2;542 switch (side) {543 case "top":544 return [545 { x: Math.min(triggerRect.left, contentRect.left) - buffer, y: triggerRect.top },546 { x: Math.min(triggerRect.left, contentRect.left) - buffer, y: contentRect.bottom },547 {548 x: Math.max(triggerRect.right, contentRect.right) + buffer,549 y: contentRect.bottom,550 },551 { x: Math.max(triggerRect.right, contentRect.right) + buffer, y: triggerRect.top },552 ];553 case "bottom":554 return [555 { x: Math.min(triggerRect.left, contentRect.left) - buffer, y: triggerRect.bottom },556 { x: Math.min(triggerRect.left, contentRect.left) - buffer, y: contentRect.top },557 { x: Math.max(triggerRect.right, contentRect.right) + buffer, y: contentRect.top },558 {559 x: Math.max(triggerRect.right, contentRect.right) + buffer,560 y: triggerRect.bottom,561 },562 ];563 case "left":564 return [565 { x: triggerRect.left, y: Math.min(triggerRect.top, contentRect.top) - buffer },566 { x: contentRect.right, y: Math.min(triggerRect.top, contentRect.top) - buffer },567 {568 x: contentRect.right,569 y: Math.max(triggerRect.bottom, contentRect.bottom) + buffer,570 },571 {572 x: triggerRect.left,573 y: Math.max(triggerRect.bottom, contentRect.bottom) + buffer,574 },575 ];576 case "right":577 return [578 { x: triggerRect.right, y: Math.min(triggerRect.top, contentRect.top) - buffer },579 { x: contentRect.left, y: Math.min(triggerRect.top, contentRect.top) - buffer },580 {581 x: contentRect.left,582 y: Math.max(triggerRect.bottom, contentRect.bottom) + buffer,583 },584 {585 x: triggerRect.right,586 y: Math.max(triggerRect.bottom, contentRect.bottom) + buffer,587 },588 ];589 }590}591function getIntentPolygon(exitPoint, targetRect, side, target, sourceRect) {592 const edgeBuffer = 8;593 const effectiveSide = target === "trigger" ? flipSide(side) : side;594 const top = sourceRect595 ? Math.min(targetRect.top, sourceRect.top) - edgeBuffer596 : targetRect.top - edgeBuffer;597 const bottom = sourceRect598 ? Math.max(targetRect.bottom, sourceRect.bottom) + edgeBuffer599 : targetRect.bottom + edgeBuffer;600 const left = sourceRect601 ? Math.min(targetRect.left, sourceRect.left) - edgeBuffer602 : targetRect.left - edgeBuffer;603 const right = sourceRect604 ? Math.max(targetRect.right, sourceRect.right) + edgeBuffer605 : targetRect.right + edgeBuffer;606 switch (effectiveSide) {607 case "right":608 return [exitPoint, { x: targetRect.left, y: top }, { x: targetRect.left, y: bottom }];609 case "left":610 return [exitPoint, { x: targetRect.right, y: top }, { x: targetRect.right, y: bottom }];611 case "bottom":612 return [exitPoint, { x: left, y: targetRect.top }, { x: right, y: targetRect.top }];613 case "top":614 return [615 exitPoint,616 { x: left, y: targetRect.bottom },617 { x: right, y: targetRect.bottom },618 ];619 }620}621function flipSide(side) {622 switch (side) {623 case "top":624 return "bottom";625 case "bottom":626 return "top";627 case "left":628 return "right";629 case "right":630 return "left";631 }632}633export class MenuRootState {634 static create(opts) {635 const root = new MenuRootState(opts);636 return MenuRootContext.set(root);637 }638 opts;639 isUsingKeyboard = new IsUsingKeyboard();640 ignoreCloseAutoFocus = $state(false);641 isPointerInTransit = $state(false);642 constructor(opts) {643 this.opts = opts;644 }645 getBitsAttr = (part) => {646 return menuAttrs.getAttr(part, this.opts.variant.current);647 };648}649export class MenuMenuState {650 static create(opts, root) {651 return MenuMenuContext.set(new MenuMenuState(opts, root, null));652 }653 opts;654 root;655 parentMenu;656 contentId = boxWith(() => "");657 contentNode = $state(null);658 contentPresence;659 triggerNode = $state(null);660 constructor(opts, root, parentMenu) {661 this.opts = opts;662 this.root = root;663 this.parentMenu = parentMenu;664 this.contentPresence = new PresenceManager({665 ref: boxWith(() => this.contentNode),666 open: this.opts.open,667 onComplete: () => {668 this.opts.onOpenChangeComplete.current(this.opts.open.current);669 },670 shouldSkipExitAnimation: () => {671 if (this.root.opts.variant.current !== "menubar" || this.parentMenu !== null) {672 return false;673 }674 return this.root.opts.shouldSkipExitAnimation?.() ?? false;675 },676 });677 if (parentMenu) {678 watch(() => parentMenu.opts.open.current, () => {679 if (parentMenu.opts.open.current)680 return;681 this.opts.open.current = false;682 });683 }684 }685 toggleOpen() {686 this.opts.open.current = !this.opts.open.current;687 }688 onOpen() {689 this.opts.open.current = true;690 }691 onClose() {692 this.opts.open.current = false;693 }694}695export class MenuContentState {696 static create(opts) {697 return MenuContentContext.set(new MenuContentState(opts, MenuMenuContext.get()));698 }699 opts;700 parentMenu;701 rovingFocusGroup;702 domContext;703 attachment;704 search = $state("");705 #timer = 0;706 #handleTypeaheadSearch;707 mounted = $state(false);708 #isSub;709 constructor(opts, parentMenu) {710 this.opts = opts;711 this.parentMenu = parentMenu;712 this.domContext = new DOMContext(opts.ref);713 this.attachment = attachRef(this.opts.ref, (v) => {714 if (this.parentMenu.contentNode !== v) {715 this.parentMenu.contentNode = v;716 }717 });718 parentMenu.contentId = opts.id;719 this.#isSub = opts.isSub ?? false;720 this.onkeydown = this.onkeydown.bind(this);721 this.onblur = this.onblur.bind(this);722 this.onfocus = this.onfocus.bind(this);723 this.handleInteractOutside = this.handleInteractOutside.bind(this);724 new MenuSubmenuIntent({725 contentNode: () => this.parentMenu.contentNode,726 triggerNode: () => this.parentMenu.triggerNode,727 parentContentNode: () => this.parentMenu.parentMenu?.contentNode ?? null,728 subContentSelector: () => `[${this.parentMenu.root.getBitsAttr("sub-content")}]`,729 // debugMode: () => this.parentMenu.root.opts.debugMode.current,730 enabled: () => this.parentMenu.opts.open.current &&731 Boolean(this.parentMenu.triggerNode?.hasAttribute(this.parentMenu.root.getBitsAttr("sub-trigger"))),732 onIntentExit: (pointerPoint) => {733 this.parentMenu.opts.open.current = false;734 this.#dispatchPointerMoveToHoveredSubTrigger(pointerPoint);735 },736 setIsPointerInTransit: (value) => {737 this.parentMenu.root.isPointerInTransit = value;738 },739 });740 this.#handleTypeaheadSearch = new DOMTypeahead({741 getActiveElement: () => this.domContext.getActiveElement(),742 getWindow: () => this.domContext.getWindow(),743 }).handleTypeaheadSearch;744 this.rovingFocusGroup = new RovingFocusGroup({745 rootNode: boxWith(() => this.parentMenu.contentNode),746 candidateAttr: this.parentMenu.root.getBitsAttr("item"),747 loop: this.opts.loop,748 orientation: boxWith(() => "vertical"),749 });750 watch(() => this.parentMenu.contentNode, (contentNode) => {751 if (!contentNode)752 return;753 const handler = () => {754 afterTick(() => {755 if (!this.parentMenu.root.isUsingKeyboard.current)756 return;757 this.rovingFocusGroup.focusFirstCandidate();758 });759 };760 return MenuOpenEvent.listen(contentNode, handler);761 });762 $effect(() => {763 if (!this.parentMenu.opts.open.current) {764 this.domContext.getWindow().clearTimeout(this.#timer);765 }766 });767 }768 #getCandidateNodes() {769 const node = this.parentMenu.contentNode;770 if (!node)771 return [];772 const candidates = Array.from(node.querySelectorAll(`[${this.parentMenu.root.getBitsAttr("item")}]:not([data-disabled])`));773 return candidates;774 }775 #isPointerMovingToSubmenu() {776 return this.parentMenu.root.isPointerInTransit;777 }778 #dispatchPointerMoveToHoveredSubTrigger(pointerPoint) {779 if (!pointerPoint)780 return;781 const parentContentNode = this.parentMenu.parentMenu?.contentNode;782 if (!parentContentNode)783 return;784 const hoveredNode = this.domContext785 .getDocument()786 .elementFromPoint(pointerPoint.x, pointerPoint.y);787 if (!isElement(hoveredNode))788 return;789 const hoveredSubTrigger = hoveredNode.closest(`[${this.parentMenu.root.getBitsAttr("sub-trigger")}]`);790 if (!hoveredSubTrigger || !parentContentNode.contains(hoveredSubTrigger))791 return;792 if (hoveredSubTrigger === this.parentMenu.triggerNode)793 return;794 hoveredSubTrigger.dispatchEvent(new PointerEvent("pointermove", {795 bubbles: true,796 cancelable: true,797 pointerType: "mouse",798 clientX: pointerPoint.x,799 clientY: pointerPoint.y,800 }));801 }802 onCloseAutoFocus = (e) => {803 this.opts.onCloseAutoFocus.current?.(e);804 if (e.defaultPrevented || this.#isSub)805 return;806 if (this.parentMenu.root.ignoreCloseAutoFocus) {807 e.preventDefault();808 return;809 }810 if (this.parentMenu.triggerNode && isTabbable(this.parentMenu.triggerNode)) {811 e.preventDefault();812 this.parentMenu.triggerNode.focus();813 }814 };815 handleTabKeyDown(e) {816 /**817 * We locate the root `menu`'s trigger by going up the tree until818 * we find a menu that has no parent. This will allow us to focus the next819 * tabbable element before/after the root trigger.820 */821 let rootMenu = this.parentMenu;822 while (rootMenu.parentMenu !== null) {823 rootMenu = rootMenu.parentMenu;824 }825 if (!rootMenu.triggerNode)826 return;827 e.preventDefault();828 const nodeToFocus = getTabbableFrom(rootMenu.triggerNode, e.shiftKey ? "prev" : "next");829 if (nodeToFocus) {830 /**831 * We set a flag to ignore the `onCloseAutoFocus` event handler832 * as well as the fallbacks inside the focus scope to prevent833 * race conditions causing focus to fall back to the body even834 * though we're trying to focus the next tabbable element.835 */836 this.parentMenu.root.ignoreCloseAutoFocus = true;837 rootMenu.onClose();838 afterTick(() => {839 nodeToFocus.focus();840 afterTick(() => {841 this.parentMenu.root.ignoreCloseAutoFocus = false;842 });843 });844 }845 else {846 this.domContext.getDocument().body.focus();847 }848 }849 onkeydown(e) {850 if (e.defaultPrevented)851 return;852 if (e.key === kbd.TAB) {853 this.handleTabKeyDown(e);854 return;855 }856 const target = e.target;857 const currentTarget = e.currentTarget;858 if (!isHTMLElement(target) || !isHTMLElement(currentTarget))859 return;860 const isKeydownInside = target.closest(`[${this.parentMenu.root.getBitsAttr("content")}]`)?.id ===861 this.parentMenu.contentId.current;862 const isModifierKey = e.ctrlKey || e.altKey || e.metaKey;863 const isCharacterKey = e.key.length === 1;864 const kbdFocusedEl = this.rovingFocusGroup.handleKeydown(target, e);865 if (kbdFocusedEl)866 return;867 // prevent space from being considered with typeahead868 if (e.code === "Space")869 return;870 const candidateNodes = this.#getCandidateNodes();871 if (isKeydownInside) {872 if (!isModifierKey && isCharacterKey) {873 this.#handleTypeaheadSearch(e.key, candidateNodes);874 }875 }876 // focus first/last based on key pressed877 if (e.target?.id !== this.parentMenu.contentId.current)878 return;879 if (!FIRST_LAST_KEYS.includes(e.key))880 return;881 e.preventDefault();882 if (LAST_KEYS.includes(e.key)) {883 candidateNodes.reverse();884 }885 focusFirst(candidateNodes, { select: false }, () => this.domContext.getActiveElement());886 }887 onblur(e) {888 if (!isElement(e.currentTarget))889 return;890 if (!isElement(e.target))891 return;892 // clear search buffer when leaving the menu893 if (!e.currentTarget.contains?.(e.target)) {894 this.domContext.getWindow().clearTimeout(this.#timer);895 this.search = "";896 }897 }898 onfocus(_) {899 if (!this.parentMenu.root.isUsingKeyboard.current)900 return;901 afterTick(() => this.rovingFocusGroup.focusFirstCandidate());902 }903 onItemEnter() {904 return this.#isPointerMovingToSubmenu();905 }906 onItemLeave(e) {907 if (e.currentTarget.hasAttribute(this.parentMenu.root.getBitsAttr("sub-trigger")))908 return;909 if (this.#isPointerMovingToSubmenu() || this.parentMenu.root.isUsingKeyboard.current)910 return;911 const contentNode = this.parentMenu.contentNode;912 contentNode?.focus({ preventScroll: true });913 this.rovingFocusGroup.setCurrentTabStopId("");914 }915 onTriggerLeave() {916 if (this.#isPointerMovingToSubmenu())917 return true;918 return false;919 }920 handleInteractOutside(e) {921 if (!isElementOrSVGElement(e.target))922 return;923 const triggerId = this.parentMenu.triggerNode?.id;924 if (e.target.id === triggerId) {925 e.preventDefault();926 return;927 }928 if (e.target.closest(`#${triggerId}`)) {929 e.preventDefault();930 return;931 }932 /**933 * when the menu closes due to an outside pointer interaction (for example,934 * clicking another dropdown trigger), avoid focusing this menu's trigger935 * to prevent stealing focus from the new interaction target.936 */937 this.parentMenu.root.ignoreCloseAutoFocus = true;938 afterTick(() => {939 this.parentMenu.root.ignoreCloseAutoFocus = false;940 });941 }942 get shouldRender() {943 return this.parentMenu.contentPresence.shouldRender;944 }945 snippetProps = $derived.by(() => ({ open: this.parentMenu.opts.open.current }));946 props = $derived.by(() => ({947 id: this.opts.id.current,948 role: "menu",949 "aria-orientation": "vertical",950 [this.parentMenu.root.getBitsAttr("content")]: "",951 "data-state": getDataOpenClosed(this.parentMenu.opts.open.current),952 ...getDataTransitionAttrs(this.parentMenu.contentPresence.transitionStatus),953 onkeydown: this.onkeydown,954 onblur: this.onblur,955 onfocus: this.onfocus,956 dir: this.parentMenu.root.opts.dir.current,957 style: {958 pointerEvents: "auto",959 contain: "layout style",960 },961 ...this.attachment,962 }));963 popperProps = {964 onCloseAutoFocus: (e) => this.onCloseAutoFocus(e),965 };966}967class MenuItemSharedState {968 opts;969 content;970 attachment;971 #isFocused = $state(false);972 constructor(opts, content) {973 this.opts = opts;974 this.content = content;975 this.attachment = attachRef(this.opts.ref);976 this.onpointermove = this.onpointermove.bind(this);977 this.onpointerleave = this.onpointerleave.bind(this);978 this.onfocus = this.onfocus.bind(this);979 this.onblur = this.onblur.bind(this);980 }981 onpointermove(e) {982 if (e.defaultPrevented)983 return;984 if (!isMouseEvent(e))985 return;986 if (this.opts.disabled.current) {987 this.content.onItemLeave(e);988 }989 else {990 const defaultPrevented = this.content.onItemEnter();991 if (defaultPrevented)992 return;993 const item = e.currentTarget;994 if (!isHTMLElement(item))995 return;996 item.focus({ preventScroll: true });997 }998 }999 onpointerleave(e) {1000 if (e.defaultPrevented)1001 return;1002 if (!isMouseEvent(e))1003 return;1004 this.content.onItemLeave(e);1005 }1006 onfocus(e) {1007 afterTick(() => {1008 if (e.defaultPrevented || this.opts.disabled.current)1009 return;1010 this.#isFocused = true;1011 });1012 }1013 onblur(e) {1014 afterTick(() => {1015 if (e.defaultPrevented)1016 return;1017 this.#isFocused = false;1018 });1019 }1020 props = $derived.by(() => ({1021 id: this.opts.id.current,1022 tabindex: -1,1023 role: "menuitem",1024 "aria-disabled": boolToStr(this.opts.disabled.current),1025 "data-disabled": boolToEmptyStrOrUndef(this.opts.disabled.current),1026 "data-highlighted": this.#isFocused ? "" : undefined,1027 [this.content.parentMenu.root.getBitsAttr("item")]: "",1028 //1029 onpointermove: this.onpointermove,1030 onpointerleave: this.onpointerleave,1031 onfocus: this.onfocus,1032 onblur: this.onblur,1033 ...this.attachment,1034 }));1035}1036export class MenuItemState {1037 static create(opts) {1038 const item = new MenuItemSharedState(opts, MenuContentContext.get());1039 return new MenuItemState(opts, item);1040 }1041 opts;1042 item;1043 root;1044 #isPointerDown = false;1045 constructor(opts, item) {1046 this.opts = opts;1047 this.item = item;1048 this.root = item.content.parentMenu.root;1049 this.onkeydown = this.onkeydown.bind(this);1050 this.onclick = this.onclick.bind(this);1051 this.onpointerdown = this.onpointerdown.bind(this);1052 this.onpointerup = this.onpointerup.bind(this);1053 }1054 #handleSelect() {1055 if (this.item.opts.disabled.current)1056 return;1057 const selectEvent = new CustomEvent("menuitemselect", { bubbles: true, cancelable: true });1058 this.opts.onSelect.current(selectEvent);1059 if (selectEvent.defaultPrevented) {1060 this.item.content.parentMenu.root.isUsingKeyboard.current = false;1061 return;1062 }1063 if (this.opts.closeOnSelect.current) {1064 this.item.content.parentMenu.root.opts.onClose();1065 }1066 }1067 onkeydown(e) {1068 const isTypingAhead = this.item.content.search !== "";1069 if (this.item.opts.disabled.current || (isTypingAhead && e.key === kbd.SPACE))1070 return;1071 if (SELECTION_KEYS.includes(e.key)) {1072 if (!isHTMLElement(e.currentTarget))1073 return;1074 e.currentTarget.click();1075 /**1076 * We prevent default browser behavior for selection keys as they should trigger1077 * a selection only:1078 * - prevents space from scrolling the page.1079 * - if keydown causes focus to move, prevents keydown from firing on the new target.1080 */1081 e.preventDefault();1082 }1083 }1084 onclick(_) {1085 if (this.item.opts.disabled.current)1086 return;1087 this.#handleSelect();1088 }1089 onpointerup(e) {1090 if (e.defaultPrevented)1091 return;1092 if (!this.#isPointerDown) {1093 if (!isHTMLElement(e.currentTarget))1094 return;1095 e.currentTarget?.click();1096 }1097 }1098 onpointerdown(_) {1099 this.#isPointerDown = true;1100 }1101 props = $derived.by(() => mergeProps(this.item.props, {1102 onclick: this.onclick,1103 onpointerdown: this.onpointerdown,1104 onpointerup: this.onpointerup,1105 onkeydown: this.onkeydown,1106 }));1107}1108export class MenuSubTriggerState {1109 static create(opts) {1110 const content = MenuContentContext.get();1111 const item = new MenuItemSharedState(opts, content);1112 const submenu = MenuMenuContext.get();1113 return new MenuSubTriggerState(opts, item, content, submenu);1114 }1115 opts;1116 item;1117 content;1118 submenu;1119 attachment;1120 #openTimer = null;1121 constructor(opts, item, content, submenu) {1122 this.opts = opts;1123 this.item = item;1124 this.content = content;1125 this.submenu = submenu;1126 this.attachment = attachRef(this.opts.ref, (v) => (this.submenu.triggerNode = v));1127 this.onpointerleave = this.onpointerleave.bind(this);1128 this.onpointermove = this.onpointermove.bind(this);1129 this.onkeydown = this.onkeydown.bind(this);1130 this.onclick = this.onclick.bind(this);1131 onDestroyEffect(() => {1132 this.#clearOpenTimer();1133 });1134 }1135 #clearOpenTimer() {1136 if (this.#openTimer === null)1137 return;1138 this.content.domContext.getWindow().clearTimeout(this.#openTimer);1139 this.#openTimer = null;1140 }1141 onpointermove(e) {1142 if (!isMouseEvent(e))1143 return;1144 if (this.submenu.root.isPointerInTransit) {1145 if (this.#openTimer !== null)1146 this.#clearOpenTimer();1147 return;1148 }1149 if (!this.item.opts.disabled.current &&1150 !this.submenu.opts.open.current &&1151 !this.#openTimer) {1152 const openDelay = this.opts.openDelay.current;1153 if (openDelay <= 0) {1154 this.submenu.onOpen();1155 return;1156 }1157 this.#openTimer = this.content.domContext.setTimeout(() => {1158 if (this.submenu.root.isPointerInTransit) {1159 this.#clearOpenTimer();1160 return;1161 }1162 this.submenu.onOpen();1163 this.#clearOpenTimer();1164 }, openDelay);1165 }1166 }1167 onpointerleave(e) {1168 if (!isMouseEvent(e))1169 return;1170 this.#clearOpenTimer();1171 }1172 onkeydown(e) {1173 const isTypingAhead = this.content.search !== "";1174 if (this.item.opts.disabled.current || (isTypingAhead && e.key === kbd.SPACE))1175 return;1176 if (SUB_OPEN_KEYS[this.submenu.root.opts.dir.current].includes(e.key)) {1177 e.currentTarget.click();1178 e.preventDefault();1179 }1180 }1181 onclick(e) {1182 if (this.item.opts.disabled.current)1183 return;1184 /**1185 * We manually focus because iOS Safari doesn't always focus on click (e.g. buttons)1186 * and we rely heavily on `onFocusOutside` for submenus to close when switching1187 * between separate submenus.1188 */1189 if (!isHTMLElement(e.currentTarget))1190 return;1191 e.currentTarget.focus();1192 const selectEvent = new CustomEvent("menusubtriggerselect", {1193 bubbles: true,1194 cancelable: true,1195 });1196 this.opts.onSelect.current(selectEvent);1197 if (!this.submenu.opts.open.current) {1198 this.submenu.onOpen();1199 afterTick(() => {1200 const contentNode = this.submenu.contentNode;