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 { Context, Previous, watch } from "runed";2import { afterSleep, afterTick, onDestroyEffect, attachRef, DOMContext, boxWith, } from "svelte-toolbelt";3import { on } from "svelte/events";4import { backward, forward, next, prev } from "../../internal/arrays.js";5import { boolToStr, boolToStrTrueOrUndef, boolToEmptyStrOrUndef, getDataOpenClosed, boolToTrueOrUndef, getDataTransitionAttrs, } from "../../internal/attrs.js";6import { kbd } from "../../internal/kbd.js";7import { noop } from "../../internal/noop.js";8import { isIOS } from "../../internal/is.js";9import { createBitsAttrs } from "../../internal/attrs.js";10import { getFloatingContentCSSVars } from "../../internal/floating-svelte/floating-utils.svelte.js";11import { DataTypeahead } from "../../internal/data-typeahead.svelte.js";12import { DOMTypeahead } from "../../internal/dom-typeahead.svelte.js";13import { PresenceManager } from "../../internal/presence-manager.svelte.js";14import { DEV } from "esm-env";15// prettier-ignore16export const INTERACTION_KEYS = [kbd.ARROW_LEFT, kbd.ESCAPE, kbd.ARROW_RIGHT, kbd.SHIFT, kbd.CAPS_LOCK, kbd.CONTROL, kbd.ALT, kbd.META, kbd.ENTER, kbd.F1, kbd.F2, kbd.F3, kbd.F4, kbd.F5, kbd.F6, kbd.F7, kbd.F8, kbd.F9, kbd.F10, kbd.F11, kbd.F12];17export const FIRST_KEYS = [kbd.ARROW_DOWN, kbd.PAGE_UP, kbd.HOME];18export const LAST_KEYS = [kbd.ARROW_UP, kbd.PAGE_DOWN, kbd.END];19export const FIRST_LAST_KEYS = [...FIRST_KEYS, ...LAST_KEYS];20export const SELECTION_KEYS = [kbd.ENTER, kbd.SPACE];21export const CONTENT_MARGIN = 10;22const selectAttrs = createBitsAttrs({23 component: "select",24 parts: [25 "trigger",26 "content",27 "item",28 "viewport",29 "scroll-up-button",30 "scroll-down-button",31 "group",32 "group-label",33 "separator",34 "arrow",35 "input",36 "content-wrapper",37 "item-text",38 "value",39 ],40});41const SelectRootContext = new Context("Select.Root | Combobox.Root");42const SelectGroupContext = new Context("Select.Group | Combobox.Group");43const SelectContentContext = new Context("Select.Content | Combobox.Content");44class SelectBaseRootState {45 opts;46 touchedInput = $state(false);47 inputNode = $state(null);48 contentNode = $state(null);49 contentPresence;50 viewportNode = $state(null);51 triggerNode = $state(null);52 valueNode = $state(null);53 valueId = $state("");54 highlightedNode = $state(null);55 highlightedValue = $derived.by(() => {56 if (!this.highlightedNode)57 return null;58 return this.highlightedNode.getAttribute("data-value");59 });60 highlightedId = $derived.by(() => {61 if (!this.highlightedNode)62 return undefined;63 return this.highlightedNode.id;64 });65 highlightedLabel = $derived.by(() => {66 if (!this.highlightedNode)67 return null;68 return this.highlightedNode.getAttribute("data-label");69 });70 contentIsPositioned = $state(false);71 isUsingKeyboard = false;72 isCombobox = false;73 domContext = new DOMContext(() => null);74 constructor(opts) {75 this.opts = opts;76 this.isCombobox = opts.isCombobox;77 this.contentPresence = new PresenceManager({78 ref: boxWith(() => this.contentNode),79 open: this.opts.open,80 onComplete: () => {81 this.opts.onOpenChangeComplete.current(this.opts.open.current);82 },83 });84 $effect.pre(() => {85 if (!this.opts.open.current) {86 this.setHighlightedNode(null);87 }88 });89 }90 setHighlightedNode(node, initial = false) {91 this.highlightedNode = node;92 if (node && (this.isUsingKeyboard || initial)) {93 this.scrollHighlightedNodeIntoView(node);94 }95 }96 scrollHighlightedNodeIntoView(node) {97 if (!this.viewportNode || !this.contentIsPositioned)98 return;99 node.scrollIntoView({ block: this.opts.scrollAlignment.current });100 }101 getCandidateNodes() {102 const node = this.contentNode;103 if (!node)104 return [];105 return Array.from(node.querySelectorAll(`[${this.getBitsAttr("item")}]:not([data-disabled])`));106 }107 setHighlightedToFirstCandidate(initial = false) {108 this.setHighlightedNode(null);109 let nodes = this.getCandidateNodes();110 if (!nodes.length)111 return;112 // don't consider nodes that aren't visible within the viewport113 if (this.viewportNode) {114 const viewportRect = this.viewportNode.getBoundingClientRect();115 nodes = nodes.filter((node) => {116 if (!this.viewportNode)117 return false;118 const nodeRect = node.getBoundingClientRect();119 const isNodeFullyVisible = nodeRect.right <= viewportRect.right &&120 nodeRect.left >= viewportRect.left &&121 nodeRect.bottom <= viewportRect.bottom &&122 nodeRect.top >= viewportRect.top;123 return isNodeFullyVisible;124 });125 }126 this.setHighlightedNode(nodes[0], initial);127 }128 getNodeByValue(value) {129 const candidateNodes = this.getCandidateNodes();130 return candidateNodes.find((node) => node.dataset.value === value) ?? null;131 }132 /**133 * Resolves the display label for a value: `items` entry when present, otherwise the134 * mounted item's `data-label` or its text content.135 */136 getLabelForValue(value) {137 if (value === "")138 return "";139 const fromItems = this.opts.items.current.find((item) => item.value === value)?.label;140 if (fromItems !== undefined)141 return fromItems;142 const node = this.getNodeByValue(value);143 if (node) {144 const dataLabel = node.getAttribute("data-label");145 if (dataLabel !== null && dataLabel !== "")146 return dataLabel;147 return node.textContent?.trim() ?? value;148 }149 return value;150 }151 setOpen(open) {152 this.opts.open.current = open;153 }154 toggleOpen() {155 this.opts.open.current = !this.opts.open.current;156 }157 handleOpen() {158 this.setOpen(true);159 }160 handleClose() {161 this.setHighlightedNode(null);162 this.setOpen(false);163 }164 toggleMenu() {165 this.toggleOpen();166 }167 getBitsAttr = (part) => {168 return selectAttrs.getAttr(part, this.isCombobox ? "combobox" : undefined);169 };170}171export class SelectSingleRootState extends SelectBaseRootState {172 opts;173 isMulti = false;174 hasValue = $derived.by(() => this.opts.value.current !== "");175 currentLabel = $derived.by(() => {176 if (!this.opts.items.current.length)177 return "";178 return (this.opts.items.current.find((item) => item.value === this.opts.value.current)?.label ??179 "");180 });181 candidateLabels = $derived.by(() => {182 if (!this.opts.items.current.length)183 return [];184 const filteredItems = this.opts.items.current.filter((item) => !item.disabled);185 return filteredItems.map((item) => item.label);186 });187 dataTypeaheadEnabled = $derived.by(() => {188 if (this.isMulti)189 return false;190 if (this.opts.items.current.length === 0)191 return false;192 return true;193 });194 constructor(opts) {195 super(opts);196 this.opts = opts;197 $effect(() => {198 if (!this.opts.open.current && this.highlightedNode) {199 this.setHighlightedNode(null);200 }201 });202 watch(() => this.opts.open.current, () => {203 if (!this.opts.open.current)204 return;205 this.setInitialHighlightedNode();206 });207 }208 includesItem(itemValue) {209 return this.opts.value.current === itemValue;210 }211 toggleItem(itemValue, itemLabel = itemValue) {212 const newValue = this.includesItem(itemValue) ? "" : itemValue;213 this.opts.value.current = newValue;214 if (newValue !== "") {215 this.opts.inputValue.current = itemLabel;216 }217 }218 setInitialHighlightedNode() {219 afterTick(() => {220 if (this.highlightedNode &&221 this.domContext.getDocument().contains(this.highlightedNode))222 return;223 if (this.opts.value.current !== "") {224 const node = this.getNodeByValue(this.opts.value.current);225 if (node) {226 this.setHighlightedNode(node, true);227 return;228 }229 }230 // if no value is set, we want to highlight the first item231 this.setHighlightedToFirstCandidate(true);232 });233 }234}235class SelectMultipleRootState extends SelectBaseRootState {236 opts;237 isMulti = true;238 hasValue = $derived.by(() => this.opts.value.current.length > 0);239 constructor(opts) {240 super(opts);241 this.opts = opts;242 $effect(() => {243 if (!this.opts.open.current && this.highlightedNode) {244 this.setHighlightedNode(null);245 }246 });247 watch(() => this.opts.open.current, () => {248 if (!this.opts.open.current)249 return;250 this.setInitialHighlightedNode();251 });252 }253 includesItem(itemValue) {254 return this.opts.value.current.includes(itemValue);255 }256 toggleItem(itemValue, itemLabel = itemValue) {257 if (this.includesItem(itemValue)) {258 this.opts.value.current = this.opts.value.current.filter((v) => v !== itemValue);259 }260 else {261 this.opts.value.current = [...this.opts.value.current, itemValue];262 }263 this.opts.inputValue.current = itemLabel;264 }265 setInitialHighlightedNode() {266 afterTick(() => {267 if (!this.domContext)268 return;269 if (this.highlightedNode &&270 this.domContext.getDocument().contains(this.highlightedNode))271 return;272 if (this.opts.value.current.length && this.opts.value.current[0] !== "") {273 const node = this.getNodeByValue(this.opts.value.current[0]);274 if (node) {275 this.setHighlightedNode(node, true);276 return;277 }278 }279 // if no value is set, we want to highlight the first item280 this.setHighlightedToFirstCandidate(true);281 });282 }283}284export class SelectRootState {285 static create(props) {286 const { type, ...rest } = props;287 const rootState = type === "single"288 ? new SelectSingleRootState(rest)289 : new SelectMultipleRootState(rest);290 return SelectRootContext.set(rootState);291 }292}293export class SelectValueState {294 static create(opts) {295 return new SelectValueState(opts, SelectRootContext.get());296 }297 root;298 opts;299 attachment;300 constructor(opts, root) {301 this.root = root;302 this.opts = opts;303 this.attachment = attachRef(opts.ref, (v) => (this.root.valueNode = v));304 this.setValue = this.setValue.bind(this);305 }306 setValue(value) {307 if (this.root.isMulti && !Array.isArray(value)) {308 if (DEV)309 throw new Error(`Expected an array of strings passed to \`setValue\` got ${typeof value}.`);310 return;311 }312 if (!this.root.isMulti && typeof value !== "string") {313 if (DEV)314 throw new Error(`Expected a string passed to \`setValue\` got ${typeof value}.`);315 return;316 }317 this.root.opts.value.current = value;318 }319 // this way consumers get type narrowing for the value on `type`320 snippetProps = $derived.by(() => {321 if (this.root.isMulti) {322 return {323 selection: {324 type: "multiple",325 selected: this.root.opts.value.current.length > 0326 ? this.root.opts.value.current.map((value) => ({327 value,328 label: this.root.getLabelForValue(value),329 }))330 : [],331 setValue: this.setValue,332 },333 placeholder: this.opts.placeholder.current ?? null,334 disabled: this.root.opts.disabled.current,335 };336 }337 const value = this.root.opts.value.current;338 return {339 selection: {340 type: "single",341 selected: value !== ""342 ? { value, label: value === "" ? "" : this.root.getLabelForValue(value) }343 : undefined,344 setValue: this.setValue,345 },346 placeholder: this.opts.placeholder.current ?? null,347 disabled: this.root.opts.disabled.current,348 };349 });350 props = $derived.by(() => ({351 id: this.opts.id.current,352 "data-placeholder": this.root.hasValue ? undefined : "",353 "data-select-value": "",354 ...this.attachment,355 }));356}357export class SelectInputState {358 static create(opts) {359 return new SelectInputState(opts, SelectRootContext.get());360 }361 opts;362 root;363 attachment;364 constructor(opts, root) {365 this.opts = opts;366 this.root = root;367 this.attachment = attachRef(opts.ref, (v) => (this.root.inputNode = v));368 this.root.domContext = new DOMContext(opts.ref);369 this.onkeydown = this.onkeydown.bind(this);370 this.oninput = this.oninput.bind(this);371 watch([() => this.root.opts.value.current, () => this.opts.clearOnDeselect.current], ([value, clearOnDeselect], [prevValue]) => {372 if (!clearOnDeselect)373 return;374 if (Array.isArray(value) && Array.isArray(prevValue)) {375 if (value.length === 0 && prevValue.length !== 0) {376 this.root.opts.inputValue.current = "";377 }378 }379 else if (value === "" && prevValue !== "") {380 this.root.opts.inputValue.current = "";381 }382 });383 }384 onkeydown(e) {385 this.root.isUsingKeyboard = true;386 if (e.key === kbd.ESCAPE)387 return;388 // prevent arrow up/down from moving the position of the cursor in the input389 if (e.key === kbd.ARROW_UP || e.key === kbd.ARROW_DOWN)390 e.preventDefault();391 if (!this.root.opts.open.current) {392 if (INTERACTION_KEYS.includes(e.key))393 return;394 if (e.key === kbd.TAB)395 return;396 if (e.key === kbd.BACKSPACE && this.root.opts.inputValue.current === "")397 return;398 this.root.handleOpen();399 // we need to wait for a tick after the menu opens to ensure the highlighted nodes are400 // set correctly.401 if (this.root.hasValue)402 return;403 const candidateNodes = this.root.getCandidateNodes();404 if (!candidateNodes.length)405 return;406 if (e.key === kbd.ARROW_DOWN) {407 const firstCandidate = candidateNodes[0];408 this.root.setHighlightedNode(firstCandidate);409 }410 else if (e.key === kbd.ARROW_UP) {411 const lastCandidate = candidateNodes[candidateNodes.length - 1];412 this.root.setHighlightedNode(lastCandidate);413 }414 return;415 }416 if (e.key === kbd.TAB) {417 this.root.handleClose();418 return;419 }420 if (e.key === kbd.ENTER && !e.isComposing) {421 e.preventDefault();422 const isCurrentSelectedValue = this.root.highlightedValue === this.root.opts.value.current;423 if (!this.root.opts.allowDeselect.current &&424 isCurrentSelectedValue &&425 !this.root.isMulti) {426 this.root.handleClose();427 return;428 }429 if (this.root.highlightedValue &&430 this.root.highlightedNode &&431 this.root.highlightedNode.isConnected) {432 this.root.toggleItem(this.root.highlightedValue, this.root.highlightedLabel ?? undefined);433 }434 if (!this.root.isMulti && !isCurrentSelectedValue) {435 this.root.handleClose();436 }437 }438 if (e.key === kbd.ARROW_UP && e.altKey) {439 this.root.handleClose();440 }441 if (FIRST_LAST_KEYS.includes(e.key)) {442 e.preventDefault();443 const candidateNodes = this.root.getCandidateNodes();444 const currHighlightedNode = this.root.highlightedNode;445 const currIndex = currHighlightedNode446 ? candidateNodes.indexOf(currHighlightedNode)447 : -1;448 const loop = this.root.opts.loop.current;449 let nextItem;450 if (e.key === kbd.ARROW_DOWN) {451 nextItem = next(candidateNodes, currIndex, loop);452 }453 else if (e.key === kbd.ARROW_UP) {454 nextItem = prev(candidateNodes, currIndex, loop);455 }456 else if (e.key === kbd.PAGE_DOWN) {457 nextItem = forward(candidateNodes, currIndex, 10, loop);458 }459 else if (e.key === kbd.PAGE_UP) {460 nextItem = backward(candidateNodes, currIndex, 10, loop);461 }462 else if (e.key === kbd.HOME) {463 nextItem = candidateNodes[0];464 }465 else if (e.key === kbd.END) {466 nextItem = candidateNodes[candidateNodes.length - 1];467 }468 if (!nextItem)469 return;470 this.root.setHighlightedNode(nextItem);471 return;472 }473 if (INTERACTION_KEYS.includes(e.key))474 return;475 if (!this.root.highlightedNode) {476 this.root.setHighlightedToFirstCandidate();477 }478 }479 oninput(e) {480 this.root.opts.inputValue.current = e.currentTarget.value;481 this.root.setHighlightedToFirstCandidate();482 }483 props = $derived.by(() => ({484 id: this.opts.id.current,485 role: "combobox",486 disabled: this.root.opts.disabled.current ? true : undefined,487 "aria-activedescendant": this.root.highlightedId,488 "aria-autocomplete": "list",489 "aria-expanded": boolToStr(this.root.opts.open.current),490 "data-state": getDataOpenClosed(this.root.opts.open.current),491 "data-disabled": boolToEmptyStrOrUndef(this.root.opts.disabled.current),492 onkeydown: this.onkeydown,493 oninput: this.oninput,494 [this.root.getBitsAttr("input")]: "",495 ...this.attachment,496 }));497}498export class SelectComboTriggerState {499 static create(opts) {500 return new SelectComboTriggerState(opts, SelectRootContext.get());501 }502 opts;503 root;504 attachment;505 constructor(opts, root) {506 this.opts = opts;507 this.root = root;508 this.attachment = attachRef(opts.ref);509 this.onkeydown = this.onkeydown.bind(this);510 this.onpointerdown = this.onpointerdown.bind(this);511 }512 onkeydown(e) {513 if (!this.root.domContext)514 return;515 if (e.key === kbd.ENTER || e.key === kbd.SPACE) {516 e.preventDefault();517 if (this.root.domContext.getActiveElement() !== this.root.inputNode) {518 this.root.inputNode?.focus();519 }520 this.root.toggleMenu();521 }522 }523 /**524 * `pointerdown` fires before the `focus` event, so we can prevent the default525 * behavior of focusing the button and keep focus on the input.526 */527 onpointerdown(e) {528 if (this.root.opts.disabled.current || !this.root.domContext)529 return;530 e.preventDefault();531 if (this.root.domContext.getActiveElement() !== this.root.inputNode) {532 this.root.inputNode?.focus();533 }534 this.root.toggleMenu();535 }536 props = $derived.by(() => ({537 id: this.opts.id.current,538 disabled: this.root.opts.disabled.current ? true : undefined,539 "aria-haspopup": "listbox",540 "data-state": getDataOpenClosed(this.root.opts.open.current),541 "data-disabled": boolToEmptyStrOrUndef(this.root.opts.disabled.current),542 [this.root.getBitsAttr("trigger")]: "",543 onpointerdown: this.onpointerdown,544 onkeydown: this.onkeydown,545 ...this.attachment,546 }));547}548export class SelectTriggerState {549 static create(opts) {550 return new SelectTriggerState(opts, SelectRootContext.get());551 }552 opts;553 root;554 attachment;555 #domTypeahead;556 #dataTypeahead;557 constructor(opts, root) {558 this.opts = opts;559 this.root = root;560 this.attachment = attachRef(opts.ref, (v) => (this.root.triggerNode = v));561 this.root.domContext = new DOMContext(opts.ref);562 this.#domTypeahead = new DOMTypeahead({563 getCurrentItem: () => this.root.highlightedNode,564 onMatch: (node) => {565 this.root.setHighlightedNode(node);566 },567 getActiveElement: () => this.root.domContext.getActiveElement(),568 getWindow: () => this.root.domContext.getWindow(),569 });570 this.#dataTypeahead = new DataTypeahead({571 getCurrentItem: () => {572 if (this.root.isMulti)573 return "";574 return this.root.currentLabel;575 },576 onMatch: (label) => {577 if (this.root.isMulti)578 return;579 if (!this.root.opts.items.current)580 return;581 const matchedItem = this.root.opts.items.current.find((item) => item.label === label);582 if (!matchedItem)583 return;584 this.root.opts.value.current = matchedItem.value;585 },586 enabled: () => !this.root.isMulti && this.root.dataTypeaheadEnabled,587 candidateValues: () => (this.root.isMulti ? [] : this.root.candidateLabels),588 getWindow: () => this.root.domContext.getWindow(),589 });590 this.onkeydown = this.onkeydown.bind(this);591 this.onpointerdown = this.onpointerdown.bind(this);592 this.onpointerup = this.onpointerup.bind(this);593 this.onclick = this.onclick.bind(this);594 }595 #handleOpen() {596 this.root.opts.open.current = true;597 this.#dataTypeahead.resetTypeahead();598 this.#domTypeahead.resetTypeahead();599 }600 #handlePointerOpen(_) {601 this.#handleOpen();602 }603 /**604 * Logic used to handle keyboard selection/deselection.605 *606 * If it returns true, it means the item was selected and whatever is calling607 * this function should return early608 *609 */610 #handleKeyboardSelection() {611 const isCurrentSelectedValue = this.root.highlightedValue === this.root.opts.value.current;612 if (!this.root.opts.allowDeselect.current && isCurrentSelectedValue && !this.root.isMulti) {613 this.root.handleClose();614 return true;615 }616 // "" is a valid value for a select item so we need to check for that617 if (this.root.highlightedValue !== null) {618 this.root.toggleItem(this.root.highlightedValue, this.root.highlightedLabel ?? undefined);619 }620 if (!this.root.isMulti && !isCurrentSelectedValue) {621 this.root.handleClose();622 return true;623 }624 return false;625 }626 onkeydown(e) {627 this.root.isUsingKeyboard = true;628 if (e.key === kbd.ARROW_UP || e.key === kbd.ARROW_DOWN)629 e.preventDefault();630 if (!this.root.opts.open.current) {631 if (e.key === kbd.ENTER ||632 e.key === kbd.SPACE ||633 e.key === kbd.ARROW_DOWN ||634 e.key === kbd.ARROW_UP) {635 e.preventDefault();636 this.root.handleOpen();637 }638 else if (!this.root.isMulti && this.root.dataTypeaheadEnabled) {639 this.#dataTypeahead.handleTypeaheadSearch(e.key);640 return;641 }642 // we need to wait for a tick after the menu opens to ensure643 // the highlighted nodes are set correctly644 if (this.root.hasValue)645 return;646 const candidateNodes = this.root.getCandidateNodes();647 if (!candidateNodes.length)648 return;649 if (e.key === kbd.ARROW_DOWN) {650 const firstCandidate = candidateNodes[0];651 this.root.setHighlightedNode(firstCandidate);652 }653 else if (e.key === kbd.ARROW_UP) {654 const lastCandidate = candidateNodes[candidateNodes.length - 1];655 this.root.setHighlightedNode(lastCandidate);656 }657 return;658 }659 if (e.key === kbd.TAB) {660 this.root.handleClose();661 return;662 }663 if ((e.key === kbd.ENTER ||664 // if we're currently "typing ahead", we don't want to select the item665 // just yet as the item the user is trying to get to may have a space in it,666 // so we defer handling the close for this case until further down667 (e.key === kbd.SPACE && this.#domTypeahead.search === "")) &&668 !e.isComposing) {669 e.preventDefault();670 const shouldReturn = this.#handleKeyboardSelection();671 if (shouldReturn)672 return;673 }674 if (e.key === kbd.ARROW_UP && e.altKey) {675 this.root.handleClose();676 }677 if (FIRST_LAST_KEYS.includes(e.key)) {678 e.preventDefault();679 const candidateNodes = this.root.getCandidateNodes();680 const currHighlightedNode = this.root.highlightedNode;681 const currIndex = currHighlightedNode682 ? candidateNodes.indexOf(currHighlightedNode)683 : -1;684 const loop = this.root.opts.loop.current;685 let nextItem;686 if (e.key === kbd.ARROW_DOWN) {687 nextItem = next(candidateNodes, currIndex, loop);688 }689 else if (e.key === kbd.ARROW_UP) {690 nextItem = prev(candidateNodes, currIndex, loop);691 }692 else if (e.key === kbd.PAGE_DOWN) {693 nextItem = forward(candidateNodes, currIndex, 10, loop);694 }695 else if (e.key === kbd.PAGE_UP) {696 nextItem = backward(candidateNodes, currIndex, 10, loop);697 }698 else if (e.key === kbd.HOME) {699 nextItem = candidateNodes[0];700 }701 else if (e.key === kbd.END) {702 nextItem = candidateNodes[candidateNodes.length - 1];703 }704 if (!nextItem)705 return;706 this.root.setHighlightedNode(nextItem);707 return;708 }709 const isModifierKey = e.ctrlKey || e.altKey || e.metaKey;710 const isCharacterKey = e.key.length === 1;711 const isSpaceKey = e.key === kbd.SPACE;712 const candidateNodes = this.root.getCandidateNodes();713 if (e.key === kbd.TAB)714 return;715 if (!isModifierKey && (isCharacterKey || isSpaceKey)) {716 const matchedNode = this.#domTypeahead.handleTypeaheadSearch(e.key, candidateNodes);717 if (!matchedNode && isSpaceKey) {718 e.preventDefault();719 this.#handleKeyboardSelection();720 }721 return;722 }723 if (!this.root.highlightedNode) {724 this.root.setHighlightedToFirstCandidate();725 }726 }727 onclick(e) {728 // While browsers generally have no issue focusing the trigger when clicking729 // on a label, Safari seems to struggle with the fact that there's no `onClick`.730 // We force `focus` in this case. Note: this doesn't create any other side-effect731 // because we are preventing default in `onpointerdown` so effectively732 // this only runs for a label 'click'733 const currTarget = e.currentTarget;734 currTarget.focus();735 }736 onpointerdown(e) {737 if (this.root.opts.disabled.current)738 return;739 // prevent opening on touch down which can be triggered when scrolling on touch devices740 if (e.pointerType === "touch")741 return e.preventDefault();742 // prevent implicit pointer capture743 const target = e.target;744 if (target?.hasPointerCapture(e.pointerId)) {745 target?.releasePointerCapture(e.pointerId);746 }747 // only call the handle if it's a left click, since pointerdown is triggered748 // by right clicks as well, but not when ctrl is pressed749 if (e.button === 0 && e.ctrlKey === false) {750 if (this.root.opts.open.current === false) {751 this.#handlePointerOpen(e);752 }753 else {754 this.root.handleClose();755 }756 }757 }758 onpointerup(e) {759 if (this.root.opts.disabled.current)760 return;761 e.preventDefault();762 if (e.pointerType === "touch") {763 if (this.root.opts.open.current === false) {764 this.#handlePointerOpen(e);765 }766 else {767 this.root.handleClose();768 }769 }770 }771 props = $derived.by(() => ({772 id: this.opts.id.current,773 disabled: this.root.opts.disabled.current ? true : undefined,774 "aria-haspopup": "listbox",775 "aria-expanded": boolToStr(this.root.opts.open.current),776 "aria-activedescendant": this.root.highlightedId,777 "data-state": getDataOpenClosed(this.root.opts.open.current),778 "data-disabled": boolToEmptyStrOrUndef(this.root.opts.disabled.current),779 "data-placeholder": this.root.hasValue ? undefined : "",780 [this.root.getBitsAttr("trigger")]: "",781 onpointerdown: this.onpointerdown,782 onkeydown: this.onkeydown,783 onclick: this.onclick,784 onpointerup: this.onpointerup,785 ...this.attachment,786 }));787}788export class SelectContentState {789 static create(opts) {790 return SelectContentContext.set(new SelectContentState(opts, SelectRootContext.get()));791 }792 opts;793 root;794 attachment;795 isPositioned = $state(false);796 domContext;797 constructor(opts, root) {798 this.opts = opts;799 this.root = root;800 this.attachment = attachRef(opts.ref, (v) => (this.root.contentNode = v));801 this.domContext = new DOMContext(this.opts.ref);802 if (this.root.domContext === null) {803 this.root.domContext = this.domContext;804 }805 onDestroyEffect(() => {806 this.root.contentNode = null;807 this.root.contentIsPositioned = false;808 this.isPositioned = false;809 });810 watch(() => this.root.opts.open.current, () => {811 if (this.root.opts.open.current)812 return;813 this.root.contentIsPositioned = false;814 this.isPositioned = false;815 });816 watch([() => this.isPositioned, () => this.root.highlightedNode], () => {817 if (!this.isPositioned || !this.root.highlightedNode)818 return;819 this.root.scrollHighlightedNodeIntoView(this.root.highlightedNode);820 });821 this.onpointermove = this.onpointermove.bind(this);822 }823 onpointermove(_) {824 this.root.isUsingKeyboard = false;825 }826 #styles = $derived.by(() => {827 return getFloatingContentCSSVars(this.root.isCombobox ? "combobox" : "select");828 });829 onInteractOutside = (e) => {830 if (e.target === this.root.triggerNode || e.target === this.root.inputNode) {831 e.preventDefault();832 return;833 }834 this.opts.onInteractOutside.current(e);835 if (e.defaultPrevented)836 return;837 this.root.handleClose();838 };839 onEscapeKeydown = (e) => {840 this.opts.onEscapeKeydown.current(e);841 if (e.defaultPrevented)842 return;843 this.root.handleClose();844 };845 onOpenAutoFocus = (e) => {846 e.preventDefault();847 };848 onCloseAutoFocus = (e) => {849 e.preventDefault();850 };851 get shouldRender() {852 return this.root.contentPresence.shouldRender;853 }854 snippetProps = $derived.by(() => ({ open: this.root.opts.open.current }));855 props = $derived.by(() => ({856 id: this.opts.id.current,857 role: "listbox",858 "aria-multiselectable": this.root.isMulti ? "true" : undefined,859 "data-state": getDataOpenClosed(this.root.opts.open.current),860 ...getDataTransitionAttrs(this.root.contentPresence.transitionStatus),861 [this.root.getBitsAttr("content")]: "",862 style: {863 display: "flex",864 flexDirection: "column",865 outline: "none",866 boxSizing: "border-box",867 pointerEvents: "auto",868 ...this.#styles,869 },870 onpointermove: this.onpointermove,871 ...this.attachment,872 }));873 popperProps = {874 onInteractOutside: this.onInteractOutside,875 onEscapeKeydown: this.onEscapeKeydown,876 onOpenAutoFocus: this.onOpenAutoFocus,877 onCloseAutoFocus: this.onCloseAutoFocus,878 trapFocus: false,879 loop: false,880 onPlaced: () => {881 // onPlaced is also called when the menu is closed, so we need to check if the menu882 // is actually open to avoid setting positioning to true when the menu is closed883 if (this.root.opts.open.current) {884 this.root.contentIsPositioned = true;885 this.isPositioned = true;886 }887 },888 };889}890export class SelectItemState {891 static create(opts) {892 return new SelectItemState(opts, SelectRootContext.get());893 }894 opts;895 root;896 attachment;897 isSelected = $derived.by(() => this.root.includesItem(this.opts.value.current));898 isHighlighted = $derived.by(() => this.root.highlightedValue === this.opts.value.current);899 prevHighlighted = new Previous(() => this.isHighlighted);900 mounted = $state(false);901 constructor(opts, root) {902 this.opts = opts;903 this.root = root;904 this.attachment = attachRef(opts.ref);905 watch([() => this.isHighlighted, () => this.prevHighlighted.current], () => {906 if (this.isHighlighted) {907 this.opts.onHighlight.current();908 }909 else if (this.prevHighlighted.current) {910 this.opts.onUnhighlight.current();911 }912 });913 watch(() => this.mounted, () => {914 if (!this.mounted)915 return;916 this.root.setInitialHighlightedNode();917 });918 this.onpointerdown = this.onpointerdown.bind(this);919 this.onpointerup = this.onpointerup.bind(this);920 this.onpointermove = this.onpointermove.bind(this);921 }922 handleSelect() {923 if (this.opts.disabled.current)924 return;925 const isCurrentSelectedValue = this.opts.value.current === this.root.opts.value.current;926 // if allowDeselect is false and the item is already selected and we're not in a927 // multi select, do nothing and close the menu928 if (!this.root.opts.allowDeselect.current && isCurrentSelectedValue && !this.root.isMulti) {929 this.root.handleClose();930 return;931 }932 // otherwise, toggle the item and if we're not in a multi select, close the menu933 this.root.toggleItem(this.opts.value.current, this.opts.label.current);934 if (!this.root.isMulti && !isCurrentSelectedValue) {935 this.root.handleClose();936 }937 }938 snippetProps = $derived.by(() => ({939 selected: this.isSelected,940 highlighted: this.isHighlighted,941 }));942 onpointerdown(e) {943 // prevent focus from leaving the input/select trigger944 e.preventDefault();945 }946 /**947 * Using `pointerup` instead of `click` allows power users to pointerdown948 * the trigger, then release pointerup on an item to select it vs having to do949 * multiple clicks.950 */951 onpointerup(e) {952 if (e.defaultPrevented || !this.opts.ref.current)953 return;954 /**955 * For one reason or another, when it's a touch pointer and _not_ on IOS,956 * we need to listen for the immediate click event to handle the selection,957 * otherwise a click event will fire on the element _behind_ the item.958 */959 if (e.pointerType === "touch" && !isIOS) {960 on(this.opts.ref.current, "click", () => {961 this.handleSelect();962 // set highlighted node since we don't do it on `pointermove` events963 // for touch devices964 this.root.setHighlightedNode(this.opts.ref.current);965 }, { once: true });966 return;967 }968 e.preventDefault();969 this.handleSelect();970 if (e.pointerType === "touch") {971 // set highlighted node since we don't do it on `pointermove` events972 // for touch devices973 this.root.setHighlightedNode(this.opts.ref.current);974 }975 }976 onpointermove(e) {977 /**978 * We don't want to highlight items on touch devices when scrolling,979 * as this is confusing behavior, so we return here and instead handle980 * the highlighting on the `pointerup` (or following `click`) event for981 * touch devices only.982 */983 if (e.pointerType === "touch")984 return;985 if (this.root.highlightedNode !== this.opts.ref.current) {986 this.root.setHighlightedNode(this.opts.ref.current);987 }988 }989 props = $derived.by(() => ({990 id: this.opts.id.current,991 role: "option",992 "aria-selected": this.root.includesItem(this.opts.value.current)993 ? "true"994 : undefined,995 "data-value": this.opts.value.current,996 "data-disabled": boolToEmptyStrOrUndef(this.opts.disabled.current),997 "data-highlighted": this.root.highlightedValue === this.opts.value.current &&998 !this.opts.disabled.current999 ? ""1000 : undefined,1001 "data-selected": this.root.includesItem(this.opts.value.current) ? "" : undefined,1002 "data-label": this.opts.label.current,1003 [this.root.getBitsAttr("item")]: "",1004 onpointermove: this.onpointermove,1005 onpointerdown: this.onpointerdown,1006 onpointerup: this.onpointerup,1007 ...this.attachment,1008 }));1009}1010export class SelectGroupState {1011 static create(opts) {1012 return SelectGroupContext.set(new SelectGroupState(opts, SelectRootContext.get()));1013 }1014 opts;1015 root;1016 labelNode = $state(null);1017 attachment;1018 constructor(opts, root) {1019 this.opts = opts;1020 this.root = root;1021 this.attachment = attachRef(opts.ref);1022 }1023 props = $derived.by(() => ({1024 id: this.opts.id.current,1025 role: "group",1026 [this.root.getBitsAttr("group")]: "",1027 "aria-labelledby": this.labelNode?.id ?? undefined,1028 ...this.attachment,1029 }));1030}1031export class SelectGroupHeadingState {1032 static create(opts) {1033 return new SelectGroupHeadingState(opts, SelectGroupContext.get());1034 }1035 opts;1036 group;1037 attachment;1038 constructor(opts, group) {1039 this.opts = opts;1040 this.group = group;1041 this.attachment = attachRef(opts.ref, (v) => (this.group.labelNode = v));1042 }1043 props = $derived.by(() => ({1044 id: this.opts.id.current,1045 [this.group.root.getBitsAttr("group-label")]: "",1046 ...this.attachment,1047 }));1048}1049export class SelectHiddenInputState {1050 static create(opts) {1051 return new SelectHiddenInputState(opts, SelectRootContext.get());1052 }1053 opts;1054 root;1055 shouldRender = $derived.by(() => this.root.opts.name.current !== "");1056 constructor(opts, root) {1057 this.opts = opts;1058 this.root = root;1059 this.onfocus = this.onfocus.bind(this);1060 }1061 onfocus(e) {1062 e.preventDefault();1063 if (!this.root.isCombobox) {1064 this.root.triggerNode?.focus();1065 }1066 else {1067 this.root.inputNode?.focus();1068 }1069 }1070 props = $derived.by(() => ({1071 disabled: boolToTrueOrUndef(this.root.opts.disabled.current),1072 required: boolToTrueOrUndef(this.root.opts.required.current),1073 name: this.root.opts.name.current,1074 value: this.opts.value.current,1075 onfocus: this.onfocus,1076 }));1077}1078export class SelectViewportState {1079 static create(opts) {1080 return new SelectViewportState(opts, SelectContentContext.get());1081 }1082 opts;1083 content;1084 root;1085 attachment;1086 prevScrollTop = $state(0);1087 constructor(opts, content) {1088 this.opts = opts;1089 this.content = content;1090 this.root = content.root;1091 this.attachment = attachRef(opts.ref, (v) => {1092 this.root.viewportNode = v;1093 });1094 }1095 props = $derived.by(() => ({1096 id: this.opts.id.current,1097 role: "presentation",1098 [this.root.getBitsAttr("viewport")]: "",1099 style: {1100 // we use position: 'relative' here on the `viewport` so that when we call1101 // `selectedItem.offsetTop` in calculations, the offset is relative to the viewport1102 // (independent of the scrollUpButton).1103 position: "relative",1104 flex: 1,1105 overflow: "auto",1106 },1107 ...this.attachment,1108 }));1109}1110export class SelectScrollButtonImplState {1111 opts;1112 content;1113 root;1114 attachment;1115 autoScrollTimer = null;1116 userScrollTimer = -1;1117 isUserScrolling = false;1118 onAutoScroll = noop;1119 mounted = $state(false);1120 constructor(opts, content) {1121 this.opts = opts;1122 this.content = content;1123 this.root = content.root;1124 this.attachment = attachRef(opts.ref);1125 watch([() => this.mounted], () => {1126 if (!this.mounted) {1127 this.isUserScrolling = false;1128 return;1129 }1130 if (this.isUserScrolling)1131 return;1132 });1133 $effect(() => {1134 if (this.mounted)1135 return;1136 this.clearAutoScrollInterval();1137 });1138 this.onpointerdown = this.onpointerdown.bind(this);1139 this.onpointermove = this.onpointermove.bind(this);1140 this.onpointerleave = this.onpointerleave.bind(this);1141 }1142 handleUserScroll() {1143 this.content.domContext.clearTimeout(this.userScrollTimer);1144 this.isUserScrolling = true;1145 this.userScrollTimer = this.content.domContext.setTimeout(() => {1146 this.isUserScrolling = false;1147 }, 200);1148 }1149 clearAutoScrollInterval() {1150 if (this.autoScrollTimer === null)1151 return;1152 this.content.domContext.clearTimeout(this.autoScrollTimer);1153 this.autoScrollTimer = null;1154 }1155 onpointerdown(_) {1156 if (this.autoScrollTimer !== null)1157 return;1158 const autoScroll = (tick) => {1159 this.onAutoScroll();1160 this.autoScrollTimer = this.content.domContext.setTimeout(() => autoScroll(tick + 1), this.opts.delay.current(tick));1161 };1162 this.autoScrollTimer = this.content.domContext.setTimeout(() => autoScroll(1), this.opts.delay.current(0));1163 }1164 onpointermove(e) {1165 this.onpointerdown(e);1166 }1167 onpointerleave(_) {1168 this.clearAutoScrollInterval();1169 }1170 props = $derived.by(() => ({1171 id: this.opts.id.current,1172 "aria-hidden": boolToStrTrueOrUndef(true),1173 style: {1174 flexShrink: 0,1175 },1176 onpointerdown: this.onpointerdown,1177 onpointermove: this.onpointermove,1178 onpointerleave: this.onpointerleave,1179 ...this.attachment,1180 }));1181}1182export class SelectScrollDownButtonState {1183 static create(opts) {1184 return new SelectScrollDownButtonState(new SelectScrollButtonImplState(opts, SelectContentContext.get()));1185 }1186 scrollButtonState;1187 content;1188 root;1189 canScrollDown = $state(false);1190 scrollIntoViewTimer = null;1191 constructor(scrollButtonState) {1192 this.scrollButtonState = scrollButtonState;1193 this.content = scrollButtonState.content;1194 this.root = scrollButtonState.root;1195 this.scrollButtonState.onAutoScroll = this.handleAutoScroll;1196 watch([() => this.root.viewportNode, () => this.content.isPositioned], () => {1197 if (!this.root.viewportNode || !this.content.isPositioned)1198 return;1199 this.handleScroll(true);1200 return on(this.root.viewportNode, "scroll", () => this.handleScroll());