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 { afterSleep, afterTick, srOnlyStyles, attachRef, } from "svelte-toolbelt";2import { Context, watch } from "runed";3import { findNextSibling, findPreviousSibling } from "./utils.js";4import { kbd } from "../../internal/kbd.js";5import { createBitsAttrs, boolToStr, boolToEmptyStrOrUndef } from "../../internal/attrs.js";6import { getFirstNonCommentChild } from "../../internal/dom.js";7import { computeCommandScore } from "./index.js";8import { cssEscape } from "../../internal/css-escape.js";9const COMMAND_VALUE_ATTR = "data-value";10const commandAttrs = createBitsAttrs({11 component: "command",12 parts: [13 "root",14 "list",15 "input",16 "separator",17 "loading",18 "empty",19 "group",20 "group-items",21 "group-heading",22 "item",23 "viewport",24 "input-label",25 ],26});27// selectors28const COMMAND_GROUP_SELECTOR = commandAttrs.selector("group");29const COMMAND_GROUP_ITEMS_SELECTOR = commandAttrs.selector("group-items");30const COMMAND_GROUP_HEADING_SELECTOR = commandAttrs.selector("group-heading");31const COMMAND_ITEM_SELECTOR = commandAttrs.selector("item");32const COMMAND_VALID_ITEM_SELECTOR = `${commandAttrs.selector("item")}:not([aria-disabled="true"])`;33const CommandRootContext = new Context("Command.Root");34const CommandListContext = new Context("Command.List");35const CommandGroupContainerContext = new Context("Command.Group");36const defaultState = {37 /** Value of the search query */38 search: "",39 /** Currently selected item value */40 value: "",41 filtered: {42 /** The count of all visible items. */43 count: 0,44 /** Map from visible item id to its search store. */45 items: new Map(),46 /** Set of groups with at least one visible item. */47 groups: new Set(),48 },49};50export class CommandRootState {51 static create(opts) {52 return CommandRootContext.set(new CommandRootState(opts));53 }54 opts;55 attachment;56 #updateScheduled = false;57 #isInitialMount = true;58 sortAfterTick = false;59 sortAndFilterAfterTick = false;60 allItems = new Set();61 allGroups = new Map();62 allIds = new Map();63 // attempt to prevent the harsh delay when user is typing fast64 key = $state(0);65 viewportNode = $state(null);66 inputNode = $state(null);67 labelNode = $state(null);68 // published state that the components and other things can react to69 commandState = $state.raw(defaultState);70 // internal state that we mutate in batches and publish to the `state` at once71 _commandState = $state(defaultState);72 #snapshot() {73 return $state.snapshot(this._commandState);74 }75 #scheduleUpdate() {76 if (this.#updateScheduled)77 return;78 this.#updateScheduled = true;79 afterTick(() => {80 this.#updateScheduled = false;81 const currentState = this.#snapshot();82 const hasStateChanged = !Object.is(this.commandState, currentState);83 if (hasStateChanged) {84 this.commandState = currentState;85 this.opts.onStateChange?.current?.(currentState);86 }87 });88 }89 setState(key, value, preventScroll) {90 if (Object.is(this._commandState[key], value))91 return;92 this._commandState[key] = value;93 if (key === "search") {94 // Filter synchronously before emitting back to children95 this.#filterItems();96 this.#sort();97 }98 else if (key === "value") {99 if (!preventScroll)100 this.#scrollSelectedIntoView();101 }102 this.#scheduleUpdate();103 }104 constructor(opts) {105 this.opts = opts;106 this.attachment = attachRef(this.opts.ref);107 const defaults = { ...this._commandState, value: this.opts.value.current ?? "" };108 this._commandState = defaults;109 this.commandState = defaults;110 this.onkeydown = this.onkeydown.bind(this);111 }112 /**113 * Calculates score for an item based on search text and keywords.114 * Higher score = better match.115 *116 * @param value - Item's display text117 * @param keywords - Optional keywords to boost scoring118 * @returns Score from 0-1, where 0 = no match119 */120 #score(value, keywords) {121 const filter = this.opts.filter.current ?? computeCommandScore;122 const score = value ? filter(value, this._commandState.search, keywords) : 0;123 return score;124 }125 /**126 * Sorts items and groups based on search scores.127 * Groups are sorted by their highest scoring item.128 * When no search active, selects first item.129 */130 #sort() {131 if (!this._commandState.search || this.opts.shouldFilter.current === false) {132 // if no search and no initial value set or when clearing search,133 // we select the first item.134 if (!this._commandState.value || !this.#isInitialMount) {135 this.#selectFirstItem();136 }137 else if (this.#isInitialMount && this._commandState.value) {138 // scroll the initial value into view if it exists139 this.#scrollInitialValue();140 }141 return;142 }143 const scores = this._commandState.filtered.items;144 // sort the groups145 const groups = [];146 for (const value of this._commandState.filtered.groups) {147 const items = this.allGroups.get(value);148 let max = 0;149 if (!items) {150 groups.push([value, max]);151 continue;152 }153 // get the max score of the group's items154 for (const item of items) {155 const score = scores.get(item);156 max = Math.max(score ?? 0, max);157 }158 groups.push([value, max]);159 }160 // Sort items within groups to bottom161 // Sort items outside of groups162 // Sort groups to bottom (pushes all non-grouped items to the top)163 const listInsertionElement = this.viewportNode;164 const sorted = this.getValidItems().sort((a, b) => {165 const valueA = a.getAttribute("data-value");166 const valueB = b.getAttribute("data-value");167 const scoresA = scores.get(valueA) ?? 0;168 const scoresB = scores.get(valueB) ?? 0;169 return scoresB - scoresA;170 });171 for (const item of sorted) {172 const group = item.closest(COMMAND_GROUP_ITEMS_SELECTOR);173 if (group) {174 const itemToAppend = item.parentElement === group175 ? item176 : item.closest(`${COMMAND_GROUP_ITEMS_SELECTOR} > *`);177 if (itemToAppend) {178 group.appendChild(itemToAppend);179 }180 }181 else {182 const itemToAppend = item.parentElement === listInsertionElement183 ? item184 : item.closest(`${COMMAND_GROUP_ITEMS_SELECTOR} > *`);185 if (itemToAppend) {186 listInsertionElement?.appendChild(itemToAppend);187 }188 }189 }190 const sortedGroups = groups.sort((a, b) => b[1] - a[1]);191 for (const group of sortedGroups) {192 const element = listInsertionElement?.querySelector(`${COMMAND_GROUP_SELECTOR}[${COMMAND_VALUE_ATTR}="${cssEscape(group[0])}"]`);193 element?.parentElement?.appendChild(element);194 }195 this.#selectFirstItem();196 }197 /**198 * Sets current value and triggers re-render if cleared.199 *200 * @param value - New value to set201 */202 setValue(value, opts) {203 if (value !== this.opts.value.current && value === "") {204 afterTick(() => {205 this.key++;206 });207 }208 this.setState("value", value, opts);209 this.opts.value.current = value;210 }211 /**212 * Selects first non-disabled item on next tick.213 */214 #selectFirstItem() {215 afterTick(() => {216 const item = this.getValidItems().find((item) => item.getAttribute("aria-disabled") !== "true");217 const value = item?.getAttribute(COMMAND_VALUE_ATTR);218 const shouldPreventScroll = this.#isInitialMount && this.opts.disableInitialScroll.current;219 this.setValue(value ?? "", shouldPreventScroll);220 this.#isInitialMount = false;221 });222 }223 /**224 * Scrolls the initial value into view if it exists and is not the first item.225 * Called during initial mount when a value is provided.226 */227 #scrollInitialValue() {228 afterTick(() => {229 const shouldPreventScroll = this.opts.disableInitialScroll.current;230 if (!shouldPreventScroll) {231 this.#scrollSelectedIntoView();232 }233 this.#isInitialMount = false;234 });235 }236 /**237 * Updates filtered items/groups based on search.238 * Recalculates scores and filtered count.239 */240 #filterItems() {241 if (!this._commandState.search || this.opts.shouldFilter.current === false) {242 this._commandState.filtered.count = this.allItems.size;243 return;244 }245 // reset the groups246 this._commandState.filtered.groups = new Set();247 let itemCount = 0;248 // Check which items should be included249 for (const id of this.allItems) {250 const value = this.allIds.get(id)?.value ?? "";251 const keywords = this.allIds.get(id)?.keywords ?? [];252 const rank = this.#score(value, keywords);253 this._commandState.filtered.items.set(id, rank);254 if (rank > 0)255 itemCount++;256 }257 // Check which groups have at least 1 item shown258 for (const [groupId, group] of this.allGroups) {259 for (const itemId of group) {260 const currItem = this._commandState.filtered.items.get(itemId);261 if (currItem && currItem > 0) {262 this._commandState.filtered.groups.add(groupId);263 break;264 }265 }266 }267 this._commandState.filtered.count = itemCount;268 }269 /**270 * Gets all non-disabled, visible command items.271 *272 * @returns Array of valid item elements273 * @remarks Exposed for direct item access and bound checking274 */275 getValidItems() {276 const node = this.opts.ref.current;277 if (!node)278 return [];279 const validItems = Array.from(node.querySelectorAll(COMMAND_VALID_ITEM_SELECTOR)).filter((el) => !!el);280 return validItems;281 }282 /**283 * Gets all visible command items.284 *285 * @returns Array of valid item elements286 * @remarks Exposed for direct item access and bound checking287 */288 getVisibleItems() {289 const node = this.opts.ref.current;290 if (!node)291 return [];292 const visibleItems = Array.from(node.querySelectorAll(COMMAND_ITEM_SELECTOR)).filter((el) => !!el);293 return visibleItems;294 }295 /** Returns all visible items in a matrix structure296 *297 * @remarks Returns empty if the command isn't configured as a grid298 *299 * @returns300 */301 get itemsGrid() {302 if (!this.isGrid)303 return [];304 const columns = this.opts.columns.current ?? 1;305 const items = this.getVisibleItems();306 const grid = [[]];307 let currentGroup = items[0]?.getAttribute("data-group");308 let column = 0;309 let row = 0;310 for (let i = 0; i < items.length; i++) {311 const item = items[i];312 const itemGroup = item?.getAttribute("data-group");313 if (currentGroup !== itemGroup) {314 currentGroup = itemGroup;315 column = 1;316 row++;317 grid.push([{ index: i, firstRowOfGroup: true, ref: item }]);318 }319 else {320 column++;321 if (column > columns) {322 row++;323 column = 1;324 grid.push([]);325 }326 grid[row]?.push({327 index: i,328 firstRowOfGroup: grid[row]?.[0]?.firstRowOfGroup ?? i === 0,329 ref: item,330 });331 }332 }333 return grid;334 }335 /**336 * Gets currently selected command item.337 *338 * @returns Selected element or undefined339 */340 #getSelectedItem() {341 const node = this.opts.ref.current;342 if (!node)343 return;344 const selectedNode = node.querySelector(`${COMMAND_VALID_ITEM_SELECTOR}[data-selected]`);345 if (!selectedNode)346 return;347 return selectedNode;348 }349 /**350 * Scrolls selected item into view.351 * Special handling for first items in groups.352 */353 #scrollSelectedIntoView() {354 afterTick(() => {355 const item = this.#getSelectedItem();356 if (!item)357 return;358 const grandparent = item.parentElement?.parentElement;359 if (!grandparent)360 return;361 if (this.isGrid) {362 const isFirstRowOfGroup = this.#itemIsFirstRowOfGroup(item);363 // ensure item is visible364 item.scrollIntoView({ block: "nearest" });365 if (isFirstRowOfGroup) {366 const closestGroupHeader = item367 ?.closest(COMMAND_GROUP_SELECTOR)368 ?.querySelector(COMMAND_GROUP_HEADING_SELECTOR);369 closestGroupHeader?.scrollIntoView({ block: "nearest" });370 return;371 }372 }373 else {374 const firstChildOfParent = getFirstNonCommentChild(grandparent);375 if (firstChildOfParent &&376 firstChildOfParent.dataset?.value === item.dataset?.value) {377 const closestGroupHeader = item378 ?.closest(COMMAND_GROUP_SELECTOR)379 ?.querySelector(COMMAND_GROUP_HEADING_SELECTOR);380 closestGroupHeader?.scrollIntoView({ block: "nearest" });381 return;382 }383 }384 item.scrollIntoView({ block: "nearest" });385 });386 }387 #itemIsFirstRowOfGroup(item) {388 const grid = this.itemsGrid;389 if (grid.length === 0)390 return false;391 for (let r = 0; r < grid.length; r++) {392 const row = grid[r];393 if (row === undefined)394 continue;395 for (let c = 0; c < row.length; c++) {396 const column = row[c];397 if (column === undefined || column.ref !== item)398 continue;399 return column.firstRowOfGroup;400 }401 }402 return false;403 }404 /**405 * Sets selection to item at specified index in valid items array.406 * If index is out of bounds, does nothing.407 *408 * @param index - Zero-based index of item to select409 * @remarks410 * Uses `getValidItems()` to get selectable items, filtering out disabled/hidden ones.411 * Access valid items directly via `getValidItems()` to check bounds before calling.412 *413 * @example414 * // get valid items length for bounds check415 * const items = getValidItems()416 * if (index < items.length) {417 * updateSelectedToIndex(index)418 * }419 */420 updateSelectedToIndex(index) {421 const item = this.getValidItems()[index];422 if (!item)423 return;424 this.setValue(item.getAttribute(COMMAND_VALUE_ATTR) ?? "");425 }426 /**427 * Updates selected item by moving up/down relative to current selection.428 * Handles wrapping when loop option is enabled.429 *430 * @param change - Direction to move: 1 for next item, -1 for previous item431 * @remarks432 * The loop behavior wraps:433 * - From last item to first when moving next434 * - From first item to last when moving previous435 *436 * Uses `getValidItems()` to get all selectable items, which filters out disabled/hidden items.437 * You can call `getValidItems()` directly to get the current valid items array.438 *439 * @example440 * // select next item441 * updateSelectedByItem(1)442 *443 * // get all valid items444 * const items = getValidItems()445 */446 updateSelectedByItem(change) {447 const selected = this.#getSelectedItem();448 const items = this.getValidItems();449 const index = items.findIndex((item) => item === selected);450 // Get item at this index451 let newSelected = items[index + change];452 if (this.opts.loop.current) {453 newSelected =454 index + change < 0455 ? items[items.length - 1]456 : index + change === items.length457 ? items[0]458 : items[index + change];459 }460 if (newSelected) {461 this.setValue(newSelected.getAttribute(COMMAND_VALUE_ATTR) ?? "");462 }463 }464 /**465 * Moves selection to the first valid item in the next/previous group.466 * If no group is found, falls back to selecting the next/previous item globally.467 *468 * @param change - Direction to move: 1 for next group, -1 for previous group469 * @example470 * // move to first item in next group471 * updateSelectedByGroup(1)472 *473 * // move to first item in previous group474 * updateSelectedByGroup(-1)475 */476 updateSelectedByGroup(change) {477 const selected = this.#getSelectedItem();478 let group = selected?.closest(COMMAND_GROUP_SELECTOR);479 let item;480 while (group && !item) {481 group =482 change > 0483 ? findNextSibling(group, COMMAND_GROUP_SELECTOR)484 : findPreviousSibling(group, COMMAND_GROUP_SELECTOR);485 item = group?.querySelector(COMMAND_VALID_ITEM_SELECTOR);486 }487 if (item) {488 this.setValue(item.getAttribute(COMMAND_VALUE_ATTR) ?? "");489 }490 else {491 this.updateSelectedByItem(change);492 }493 }494 /**495 * Maps item id to display value and search keywords.496 * Returns cleanup function to remove mapping.497 *498 * @param id - Unique item identifier499 * @param value - Display text500 * @param keywords - Optional search boost terms501 * @returns Cleanup function502 */503 registerValue(value, keywords) {504 if (!(value && value === this.allIds.get(value)?.value)) {505 this.allIds.set(value, { value, keywords });506 }507 this._commandState.filtered.items.set(value, this.#score(value, keywords));508 // Schedule sorting to run after this tick when all items are added not each time an item is added509 if (!this.sortAfterTick) {510 this.sortAfterTick = true;511 afterTick(() => {512 this.#sort();513 this.sortAfterTick = false;514 });515 }516 return () => {517 this.allIds.delete(value);518 };519 }520 /**521 * Registers item in command list and its group.522 * Handles filtering, sorting and selection updates.523 *524 * @param id - Item identifier525 * @param groupId - Optional group to add item to526 * @returns Cleanup function that handles selection527 */528 registerItem(id, groupId) {529 this.allItems.add(id);530 // Track this item within the group531 if (groupId) {532 if (!this.allGroups.has(groupId)) {533 this.allGroups.set(groupId, new Set([id]));534 }535 else {536 this.allGroups.get(groupId).add(id);537 }538 }539 // Schedule sorting and filtering to run after this tick when all items are added not each time an item is added540 if (!this.sortAndFilterAfterTick) {541 this.sortAndFilterAfterTick = true;542 afterTick(() => {543 this.#filterItems();544 this.#sort();545 this.sortAndFilterAfterTick = false;546 });547 }548 this.#scheduleUpdate();549 return () => {550 const selectedItem = this.#getSelectedItem();551 this.allItems.delete(id);552 this.commandState.filtered.items.delete(id);553 this.#filterItems();554 // The item removed have been the selected one,555 // so selection should be moved to the first556 if (selectedItem?.getAttribute("id") === id) {557 this.#selectFirstItem();558 }559 this.#scheduleUpdate();560 };561 }562 /**563 * Creates empty group if not exists.564 *565 * @param id - Group identifier566 * @returns Cleanup function567 */568 registerGroup(id) {569 if (!this.allGroups.has(id)) {570 this.allGroups.set(id, new Set());571 }572 return () => {573 this.allIds.delete(id);574 this.allGroups.delete(id);575 };576 }577 get isGrid() {578 return this.opts.columns.current !== null;579 }580 /**581 * Selects last valid item.582 */583 #last() {584 return this.updateSelectedToIndex(this.getValidItems().length - 1);585 }586 /**587 * Handles next item selection:588 * - Meta: Jump to last589 * - Alt: Next group590 * - Default: Next item591 *592 * @param e - Keyboard event593 */594 #next(e) {595 e.preventDefault();596 if (e.metaKey) {597 this.#last();598 }599 else if (e.altKey) {600 this.updateSelectedByGroup(1);601 }602 else {603 this.updateSelectedByItem(1);604 }605 }606 #down(e) {607 if (this.opts.columns.current === null)608 return;609 e.preventDefault();610 if (e.metaKey) {611 this.updateSelectedByGroup(1);612 }613 else {614 this.updateSelectedByItem(this.#nextRowColumnOffset(e));615 }616 }617 #getColumn(item, grid) {618 if (grid.length === 0)619 return null;620 for (let r = 0; r < grid.length; r++) {621 const row = grid[r];622 if (row === undefined)623 continue;624 for (let c = 0; c < row.length; c++) {625 const column = row[c];626 if (column === undefined || column.ref !== item)627 continue;628 return { columnIndex: c, rowIndex: r };629 }630 }631 return null;632 }633 #nextRowColumnOffset(e) {634 const grid = this.itemsGrid;635 const selected = this.#getSelectedItem();636 if (!selected)637 return 0;638 const column = this.#getColumn(selected, grid);639 if (!column)640 return 0;641 let newItem = null;642 const skipRows = e.altKey ? 1 : 0;643 // if this is the second to last row then we need to go to the last row when skipping and not in loop mode644 if (e.altKey && column.rowIndex === grid.length - 2 && !this.opts.loop.current) {645 newItem = this.#findNextNonDisabledItem({646 start: grid.length - 1,647 end: grid.length,648 expectedColumnIndex: column.columnIndex,649 grid,650 });651 }652 else if (column.rowIndex === grid.length - 1) {653 // if this is the last row we apply the loop logic654 if (!this.opts.loop.current)655 return 0;656 newItem = this.#findNextNonDisabledItem({657 start: 0 + skipRows,658 end: column.rowIndex,659 expectedColumnIndex: column.columnIndex,660 grid,661 });662 }663 else {664 newItem = this.#findNextNonDisabledItem({665 start: column.rowIndex + 1 + skipRows,666 end: grid.length,667 expectedColumnIndex: column.columnIndex,668 grid,669 });670 // this happens if there were no non-disabled columns below the current column671 // we can now try starting from the beginning to find the right column672 if (newItem === null && this.opts.loop.current) {673 newItem = this.#findNextNonDisabledItem({674 start: 0,675 end: column.rowIndex,676 expectedColumnIndex: column.columnIndex,677 grid,678 });679 }680 }681 return this.#calculateOffset(selected, newItem);682 }683 /** Attempts to find the next non-disabled column that matches the expected column.684 *685 * @remarks686 * - Skips over disabled columns687 * - When a row is shorter than the expected column it defaults to the last item in the row688 *689 * @param param0690 * @returns691 */692 #findNextNonDisabledItem({ start, end, grid, expectedColumnIndex, }) {693 let newItem = null;694 for (let r = start; r < end; r++) {695 const row = grid[r];696 // try to get the next column697 newItem = row[expectedColumnIndex]?.ref ?? null;698 // skip over disabled items699 if (newItem !== null && itemIsDisabled(newItem)) {700 newItem = null;701 continue;702 }703 // if that column doesn't exist default to the next highest column704 if (newItem === null) {705 // try and find the next highest non-disabled item in the row706 // if there aren't any non-disabled items we just give up and return null707 for (let i = row.length - 1; i >= 0; i--) {708 const item = row[row.length - 1];709 if (item === undefined || itemIsDisabled(item.ref))710 continue;711 newItem = item.ref;712 break;713 }714 }715 break;716 }717 return newItem;718 }719 #calculateOffset(selected, newSelected) {720 if (newSelected === null)721 return 0;722 const items = this.getValidItems();723 const ogIndex = items.findIndex((item) => item === selected);724 const newIndex = items.findIndex((item) => item === newSelected);725 return newIndex - ogIndex;726 }727 #up(e) {728 if (this.opts.columns.current === null)729 return;730 e.preventDefault();731 if (e.metaKey) {732 this.updateSelectedByGroup(-1);733 }734 else {735 this.updateSelectedByItem(this.#previousRowColumnOffset(e));736 }737 }738 #previousRowColumnOffset(e) {739 const grid = this.itemsGrid;740 const selected = this.#getSelectedItem();741 if (selected === undefined)742 return 0;743 const column = this.#getColumn(selected, grid);744 if (column === null)745 return 0;746 let newItem = null;747 const skipRows = e.altKey ? 1 : 0;748 // if this is the second row then we need to go to the top when skipping and not in loop mode749 if (e.altKey && column.rowIndex === 1 && this.opts.loop.current === false) {750 newItem = this.#findNextNonDisabledItemDesc({751 start: 0,752 end: 0,753 expectedColumnIndex: column.columnIndex,754 grid,755 });756 }757 else if (column.rowIndex === 0) {758 // if this is the last row we apply the loop logic759 if (this.opts.loop.current === false)760 return 0;761 newItem = this.#findNextNonDisabledItemDesc({762 start: grid.length - 1 - skipRows,763 end: column.rowIndex + 1,764 expectedColumnIndex: column.columnIndex,765 grid,766 });767 }768 else {769 newItem = this.#findNextNonDisabledItemDesc({770 start: column.rowIndex - 1 - skipRows,771 end: 0,772 expectedColumnIndex: column.columnIndex,773 grid,774 });775 // this happens if there were no non-disabled columns below the current column776 // we can now try starting from the beginning to find the right column777 if (newItem === null && this.opts.loop.current) {778 newItem = this.#findNextNonDisabledItemDesc({779 start: grid.length - 1,780 end: column.rowIndex + 1,781 expectedColumnIndex: column.columnIndex,782 grid,783 });784 }785 }786 return this.#calculateOffset(selected, newItem);787 }788 /**789 * Attempts to find the next non-disabled column that matches the expected column.790 *791 * @remarks792 * - Skips over disabled columns793 * - When a row is shorter than the expected column it defaults to the last item in the row794 */795 #findNextNonDisabledItemDesc({ start, end, grid, expectedColumnIndex, }) {796 let newItem = null;797 for (let r = start; r >= end; r--) {798 const row = grid[r];799 if (row === undefined)800 continue;801 // try to get the next column802 newItem = row[expectedColumnIndex]?.ref ?? null;803 // skip over disabled items804 if (newItem !== null && itemIsDisabled(newItem)) {805 newItem = null;806 continue;807 }808 // if that column doesn't exist default to the next highest column809 if (newItem === null) {810 // try and find the next highest non-disabled item in the row811 // if there aren't any non-disabled items we just give up and return null812 for (let i = row.length - 1; i >= 0; i--) {813 const item = row[row.length - 1];814 if (item === undefined || itemIsDisabled(item.ref))815 continue;816 newItem = item.ref;817 break;818 }819 }820 break;821 }822 return newItem;823 }824 /**825 * Handles previous item selection:826 * - Meta: Jump to first827 * - Alt: Previous group828 * - Default: Previous item829 *830 * @param e - Keyboard event831 */832 #prev(e) {833 e.preventDefault();834 if (e.metaKey) {835 // First item836 this.updateSelectedToIndex(0);837 }838 else if (e.altKey) {839 // Previous group840 this.updateSelectedByGroup(-1);841 }842 else {843 // Previous item844 this.updateSelectedByItem(-1);845 }846 }847 onkeydown(e) {848 const isVim = this.opts.vimBindings.current && e.ctrlKey;849 switch (e.key) {850 case kbd.n:851 case kbd.j: {852 // vim down853 if (isVim) {854 if (this.isGrid) {855 this.#down(e);856 }857 else {858 this.#next(e);859 }860 }861 break;862 }863 case kbd.l: {864 // vim right865 if (isVim) {866 if (this.isGrid) {867 this.#next(e);868 }869 }870 break;871 }872 case kbd.ARROW_DOWN:873 if (this.isGrid) {874 this.#down(e);875 }876 else {877 this.#next(e);878 }879 break;880 case kbd.ARROW_RIGHT:881 if (!this.isGrid)882 break;883 this.#next(e);884 break;885 case kbd.p:886 case kbd.k: {887 // vim up888 if (isVim) {889 if (this.isGrid) {890 this.#up(e);891 }892 else {893 this.#prev(e);894 }895 }896 break;897 }898 case kbd.h: {899 // vim left900 if (isVim && this.isGrid) {901 this.#prev(e);902 }903 break;904 }905 case kbd.ARROW_UP:906 if (this.isGrid) {907 this.#up(e);908 }909 else {910 this.#prev(e);911 }912 break;913 case kbd.ARROW_LEFT:914 if (!this.isGrid)915 break;916 this.#prev(e);917 break;918 case kbd.HOME:919 // first item920 e.preventDefault();921 this.updateSelectedToIndex(0);922 break;923 case kbd.END:924 // last item925 e.preventDefault();926 this.#last();927 break;928 case kbd.ENTER: {929 /**930 * Check if IME composition is finished before triggering the select event.931 * This prevents unwanted triggering while user is still inputting text with IME.932 * e.keyCode === 229 is for the Japanese IME && Safari as `isComposing` does not933 * work with Japanese IME and Safari in combination.934 */935 if (!e.isComposing && e.keyCode !== 229) {936 e.preventDefault();937 const item = this.#getSelectedItem();938 if (item) {939 item?.click();940 }941 }942 }943 }944 }945 props = $derived.by(() => ({946 id: this.opts.id.current,947 role: "application",948 [commandAttrs.root]: "",949 tabindex: -1,950 onkeydown: this.onkeydown,951 ...this.attachment,952 }));953}954function itemIsDisabled(item) {955 return item.getAttribute("aria-disabled") === "true";956}957export class CommandEmptyState {958 static create(opts) {959 return new CommandEmptyState(opts, CommandRootContext.get());960 }961 opts;962 root;963 attachment;964 shouldRender = $derived.by(() => {965 return ((this.root._commandState.filtered.count === 0 && this.#isInitialRender === false) ||966 this.opts.forceMount.current);967 });968 #isInitialRender = true;969 constructor(opts, root) {970 this.opts = opts;971 this.root = root;972 this.attachment = attachRef(this.opts.ref);973 $effect.pre(() => {974 this.#isInitialRender = false;975 });976 }977 props = $derived.by(() => ({978 id: this.opts.id.current,979 role: "presentation",980 [commandAttrs.empty]: "",981 ...this.attachment,982 }));983}984export class CommandGroupContainerState {985 static create(opts) {986 return CommandGroupContainerContext.set(new CommandGroupContainerState(opts, CommandRootContext.get()));987 }988 opts;989 root;990 attachment;991 shouldRender = $derived.by(() => {992 if (this.opts.forceMount.current)993 return true;994 if (this.root.opts.shouldFilter.current === false)995 return true;996 if (!this.root.commandState.search)997 return true;998 return this.root._commandState.filtered.groups.has(this.trueValue);999 });1000 headingNode = $state(null);1001 trueValue = $state("");1002 constructor(opts, root) {1003 this.opts = opts;1004 this.root = root;1005 this.attachment = attachRef(this.opts.ref);1006 this.trueValue = opts.value.current ?? opts.id.current;1007 watch(() => this.trueValue, () => {1008 return this.root.registerGroup(this.trueValue);1009 });1010 $effect(() => {1011 if (this.opts.value.current) {1012 this.trueValue = this.opts.value.current;1013 return this.root.registerValue(this.opts.value.current);1014 }1015 else if (this.headingNode && this.headingNode.textContent) {1016 this.trueValue = this.headingNode.textContent.trim().toLowerCase();1017 return this.root.registerValue(this.trueValue);1018 }1019 else {1020 this.trueValue = `-----${this.opts.id.current}`;1021 return this.root.registerValue(this.trueValue);1022 }1023 });1024 }1025 props = $derived.by(() => ({1026 id: this.opts.id.current,1027 role: "presentation",1028 hidden: this.shouldRender ? undefined : true,1029 "data-value": this.trueValue,1030 [commandAttrs.group]: "",1031 ...this.attachment,1032 }));1033}1034export class CommandGroupHeadingState {1035 static create(opts) {1036 return new CommandGroupHeadingState(opts, CommandGroupContainerContext.get());1037 }1038 opts;1039 group;1040 attachment;1041 constructor(opts, group) {1042 this.opts = opts;1043 this.group = group;1044 this.attachment = attachRef(this.opts.ref, (v) => (this.group.headingNode = v));1045 }1046 props = $derived.by(() => ({1047 id: this.opts.id.current,1048 [commandAttrs["group-heading"]]: "",1049 ...this.attachment,1050 }));1051}1052export class CommandGroupItemsState {1053 static create(opts) {1054 return new CommandGroupItemsState(opts, CommandGroupContainerContext.get());1055 }1056 opts;1057 group;1058 attachment;1059 constructor(opts, group) {1060 this.opts = opts;1061 this.group = group;1062 this.attachment = attachRef(this.opts.ref);1063 }1064 props = $derived.by(() => ({1065 id: this.opts.id.current,1066 role: "group",1067 [commandAttrs["group-items"]]: "",1068 "aria-labelledby": this.group.headingNode?.id ?? undefined,1069 ...this.attachment,1070 }));1071}1072export class CommandInputState {1073 static create(opts) {1074 return new CommandInputState(opts, CommandRootContext.get());1075 }1076 opts;1077 root;1078 attachment;1079 #selectedItemId = $derived.by(() => {1080 const item = this.root.viewportNode?.querySelector(`${COMMAND_ITEM_SELECTOR}[${COMMAND_VALUE_ATTR}="${cssEscape(this.root.opts.value.current)}"]`);1081 if (item === undefined || item === null)1082 return;1083 return item.getAttribute("id") ?? undefined;1084 });1085 constructor(opts, root) {1086 this.opts = opts;1087 this.root = root;1088 this.attachment = attachRef(this.opts.ref, (v) => (this.root.inputNode = v));1089 watch(() => this.opts.ref.current, () => {1090 const node = this.opts.ref.current;1091 if (node && this.opts.autofocus.current) {1092 afterSleep(10, () => node.focus());1093 }1094 });1095 watch(() => this.opts.value.current, () => {1096 if (this.root.commandState.search !== this.opts.value.current) {1097 this.root.setState("search", this.opts.value.current);1098 }1099 });1100 }1101 props = $derived.by(() => ({1102 id: this.opts.id.current,1103 type: "text",1104 [commandAttrs.input]: "",1105 autocomplete: "off",1106 autocorrect: "off",1107 spellcheck: false,1108 "aria-autocomplete": "list",1109 role: "combobox",1110 "aria-expanded": boolToStr(true),1111 "aria-controls": this.root.viewportNode?.id ?? undefined,1112 "aria-labelledby": this.root.labelNode?.id ?? undefined,1113 "aria-activedescendant": this.#selectedItemId,1114 ...this.attachment,1115 }));1116}1117export class CommandItemState {1118 static create(opts) {1119 const group = CommandGroupContainerContext.getOr(null);1120 return new CommandItemState({ ...opts, group }, CommandRootContext.get());1121 }1122 opts;1123 root;1124 attachment;1125 #group = null;1126 #trueForceMount = $derived.by(() => {1127 return this.opts.forceMount.current || this.#group?.opts.forceMount.current === true;1128 });1129 shouldRender = $derived.by(() => {1130 this.opts.ref.current;1131 if (this.#trueForceMount ||1132 this.root.opts.shouldFilter.current === false ||1133 !this.root.commandState.search) {1134 return true;1135 }1136 const currentScore = this.root.commandState.filtered.items.get(this.trueValue);1137 if (currentScore === undefined)1138 return false;1139 return currentScore > 0;1140 });1141 isSelected = $derived.by(() => this.root.opts.value.current === this.trueValue && this.trueValue !== "");1142 trueValue = $state("");1143 constructor(opts, root) {1144 this.opts = opts;1145 this.root = root;1146 this.#group = CommandGroupContainerContext.getOr(null);1147 this.trueValue = opts.value.current;1148 this.attachment = attachRef(this.opts.ref);1149 watch([1150 () => this.trueValue,1151 () => this.#group?.trueValue,1152 () => this.opts.forceMount.current,1153 ], () => {1154 if (this.opts.forceMount.current || !this.trueValue)1155 return;1156 return this.root.registerItem(this.trueValue, this.#group?.trueValue);1157 });1158 watch([() => this.opts.value.current, () => this.opts.ref.current], () => {1159 if (this.opts.value.current) {1160 this.trueValue = this.opts.value.current;1161 }1162 else if (this.opts.ref.current?.textContent) {1163 this.trueValue = this.opts.ref.current.textContent.trim();1164 }1165 if (this.trueValue) {1166 this.root.registerValue(this.trueValue, opts.keywords.current.map((kw) => kw.trim()));1167 this.opts.ref.current?.setAttribute(COMMAND_VALUE_ATTR, this.trueValue);1168 }1169 });1170 // bindings1171 this.onclick = this.onclick.bind(this);1172 this.onpointermove = this.onpointermove.bind(this);1173 }1174 #onSelect() {1175 if (this.opts.disabled.current)1176 return;1177 this.#select();1178 this.opts.onSelect?.current();1179 }1180 #select() {1181 if (this.opts.disabled.current)1182 return;1183 this.root.setValue(this.trueValue, true);1184 }1185 onpointermove(_) {1186 if (this.opts.disabled.current || this.root.opts.disablePointerSelection.current)1187 return;1188 this.#select();1189 }1190 onclick(_) {1191 if (this.opts.disabled.current)1192 return;1193 this.#onSelect();1194 }1195 props = $derived.by(() => ({1196 id: this.opts.id.current,1197 "aria-disabled": boolToStr(this.opts.disabled.current),1198 "aria-selected": boolToStr(this.isSelected),1199 "data-disabled": boolToEmptyStrOrUndef(this.opts.disabled.current),1200 "data-selected": boolToEmptyStrOrUndef(this.isSelected),