Generated Computers
Locals: an Apple Silicon engine that runs local LLMs up to 3x faster. A product of Generated Computers.
翻譯準備中
尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證A modern, framework-agnostic multi-select dropdown component built as a web component. Features RTL language support, async search, flexible data handling, form integration, multiple display modes, and extensive SCSS customization.
翻譯準備中
取自儲存庫 README
正在載入完整 README。
檢視完整 README ↗A lightweight, themeable multi-select web component with typeahead search, RTL support, rich content, and full keyboard navigation.
--ms-* variables, dark mode, cascade layers, Theme Designer integration.@keenmate/web-multiselect is a custom element (<web-multiselect>) that turns a list of options into a searchable, themeable multi-select dropdown. Framework-agnostic — works in React, Vue, Svelte, Blazor, plain HTML.
Reads the --base-* tokens from @keenmate/base-css-variables 2.0 when the page loads them (the shared layer behind pure-admin and the Theme Designer), falls back to sensible OS-aware defaults otherwise, and ships with first-class dark-mode and per-instance theming.
Headline features:
<option> / <optgroup> markup — no JavaScript required for simple cases.A major release — breaking, no aliases. The callbacks, events and inputs follow the shared
KeenMate interaction model and naming rulebook (@keenmate/web-components-core 1.0.0-rc17), so
code written for 2.x needs updating. Upgrade guide: the
3.0.0-rc01 release notes; full old → new tables: the
CHANGELOG 3.0.0-rc01 section; step-by-step:
ai/migration-guide.txt.
Breaking changes:
ctx — per-option callbacks read ctx.option (row callbacks also
ctx.optionIndex), e.g. getDisplayValueCallback: ({ option }) => option.name; every ctx
carries controller / element. Before-hooks cancel with false
(beforeSelectCallback, beforeAddCallback (was addNewCallback), beforeKeydownCallback
(was keydownCallback)). on* properties receive the event detail. Custom action
buttons use id + onClick(detail); a new action event reports every button /
data-action click; the toggle state is isToggled / getIsToggledCallback.isMultiple, shouldCloseOnSelect, name, isDisabledMember /
is-disabled-member, maxVisibleBadgeCount, virtualScrollItemHeight, … (attributes derived
from the property names).labels — search-placeholder, select-placeholder, empty-message,
loading-message, add-new-text, … are gone; set labels (property or JSON attribute,
labels='{"selectPlaceholder":"Pick a country"}'). Empty states are noData / noResults.value / defaultValue — was initial-values / initialValues; parsed
per value-format; el.value is the live submitted value. value-format="csv" → text
(joined by the new value-separator).form.reset() restores the initial selection — it used to clear it; it does so without firing
deselect / change.selectedOptions / selectedValue / selectedOption /
searchTerm on the element and el.controller (was getSelected() / getValue() /
selectedItem / ); (was ; action id
and label / ).Also new: the motion level (motion="none | snappy | smooth", data-motion on a section,
window.components.motion), list scrolling that never moves the page, responsive rules (below),
disabled / readonly, open / close /
value-format-error events, getOptionByValue(), the shared loader (renderLoadingCallback),
standalone colours that follow the base-css-variables contract (accent #3b82f6; disabled rows
are tinted).
Earlier releases: 2.6.0-rc03, 2.6.0-rc02, CHANGELOG.
npm install @keenmate/web-multiselect
Declarative — no JavaScript required:
<script type="module">
import '@keenmate/web-multiselect';
</script>
<web-multiselect labels='{"selectPlaceholder": "Pick a country"}'>
<option value="cz">Czech Republic</option>
<option value="sk">Slovakia</option>
<option value="at">Austria</option>
</web-multiselect>
Programmatic — dynamic data + events:
<web-multiselect id="picker" labels='{"searchPlaceholder": "Search…"}'></web-multiselect>
<script type="module">
import '@keenmate/web-multiselect';
const picker = document.getElementById('picker');
picker.options = [
{ value: 'js', label: 'JavaScript', icon: '🟨' },
{ value: 'ts', label: 'TypeScript', icon: '🔷' },
{ value: 'py', label: 'Python', icon: '🐍' }
];
picker.addEventListener('change', (e) => {
console.log('Selected:', e.detail.selectedValues);
});
</script>
See docs/usage.md for the full API and docs/examples.md for advanced patterns (async data, virtual scrolling, custom rendering, form integration).
Tune what the picker does per device or window width without app-side viewport code — a map
of partial configs, or a callback that computes one. A map has two axes, each keyed
mobile / tablet / desktop:
device — the device class (classifyDevice): a phone / tablet by capability
(touch-primary, then the short side); a mouse device is desktop at any width.breakpoint — the window width bucket (768 / 1024px lines, --base-breakpoint-*):
a narrowed desktop window is mobile below 768px.Within one map the breakpoint entry applies first and the device entry over it (device wins
on the same key). Own widths or combinations (ctx.env.viewportWidth, device class AND
width, …) go in a responsiveCallback. Keys are configKeys; set them page-wide, per tag
or per instance
(the generic core mechanism, core SPEC §6.7):
// page-wide (every KeenMate component) and per tag — may be set before the script loads
window.components.responsive = { device: { mobile: { presentation: 'fullscreen' } } };
window.components['web-multiselect'].responsiveCallback = (ctx) =>
ctx.deviceClass === 'mobile' ? { actionsLayout: 'wrap' } : undefined;
// per instance (property, or the JSON attribute responsive='{"device":{"tablet":{…}}}')
el.responsive = {
device: { tablet: { presentation: 'fullscreen' } }, // a touch tablet
breakpoint: { mobile: { badgesDisplayMode: 'count' } }, // any window under 768px
};
el.responsiveCallback = (ctx) => (ctx.env.orientation === 'landscape' ? { badgesThreshold: 5 } : undefined);
Precedence (later wins, key by key): global map → global callback → per-tag map → per-tag
callback → the instance's own attributes / properties → instance map → instance callback.
An attribute written on an element is never overridden by a page rule; null from the
instance callback ignores every page rule. Maps re-run when the device class / breakpoint
changes, callbacks also on every viewport resize; overrides never reflect to attributes. The
pre-rc15 device-keyed shape ({ mobile: … }) warns and is ignored. Typed as MultiSelectResponsiveMap /
MultiSelectResponsiveCallback. Demo: examples-responsive.html, examples-action-buttons.html.
The package ships editor metadata so you get autocomplete and hover docs for the
element's attributes, events, and all --ms-* CSS custom properties. All of it is
generated from the component's source on every build, so it never drifts.
JetBrains (WebStorm / IntelliJ) — works automatically. The IDE discovers
web-types.json via the web-types field in package.json; no setup needed.
VS Code — the data files ship but VS Code doesn't auto-discover them from a
dependency, so point your workspace at them once in .vscode/settings.json:
{
"html.customData": [
"./node_modules/@keenmate/web-multiselect/vscode.html-custom-data.json"
],
"css.customData": [
"./node_modules/@keenmate/web-multiselect/vscode.css-custom-data.json"
]
}
html.customData powers tag/attribute completion on <web-multiselect>;
css.customData powers completion for the --ms-* theming variables. Reload
the window after adding them.
These inputs are inserted as raw HTML — intentionally not XSS-safe, so you control the markup. Sanitize any untrusted text before passing it in:
| Input | Output used in |
|---|---|
labels.noData, labels.noResults, labels.searching | Dropdown empty / searching text (innerHTML) |
labels.addNew, labels.addNewPending | "Add new" prompt (innerHTML; the {value} typed text is escaped) |
labels.selectedCount, labels.moreCount | Counter badge text (innerHTML) |
renderOptionContentCallback, renderGroupLabelContentCallback, renderBadgeContentCallback, renderBadgeCallback, renderSelectedItemContentCallback (string return) | Option rows, group headers, badges, selected panels (innerHTML) |
All other labels (ARIA labels, tooltips, placeholders, popover title, action-button texts) are
plain text, as are showMessage() strings and renderSelectedContentCallback (the closed input's value); option data (values, display values) is escaped.
Modern evergreen browsers — the standalone colour fallbacks use CSS relative-colour syntax (oklch(from …)), the same floor as @keenmate/base-css-variables 2.x:
No polyfills are shipped. SSR-safe: the module imports without crashing in Node, but renders only after hydration in the browser.
# Install dependencies
npm install
# Start dev server (HMR)
npm run dev
# Build for production
npm run build
# Create package tarball
npm run package
# Run tests
npm run test:unit # Vitest (happy-dom) — fast logic checks
npm run test:e2e # Playwright (browser) — interaction/visual
npm test # both
Follows the BlissFramework four-layer web-component layout:
| Layer | File | Role |
|---|---|---|
| Element | src/web-component.ts | MultiSelectElement — custom-element I/O wrapper, driven by the INPUTS table (attributes, properties, callbacks) and the events table |
| Logic | src/multiselect.ts | MultiSelectEngine<T> — framework-agnostic core (search, selection, keyboard, rendering) |
| Service | src/virtual-scroll.ts, src/tree/ | VirtualScroll; the ltree option model and checkbox cascade for tree mode |
| Side | src/types.ts, src/option-formats.ts, src/converters.ts, src/logger.ts, src/index.ts | types, data-options parsers, component-local converters, logging shim, package entry |
Positioning (dropdown, popovers, tooltips), attribute parsing and reflection, and the logging engine come from @keenmate/web-components-core.
Two deviations from the canonical shape, both intentional:
MultiSelectElement extends BlissElement, not HTMLElement directly. Core's BlissElement resolves to HTMLElement in the browser and to a stub class under SSR (typeof HTMLElement === 'undefined'), so importing the module in Node doesn't throw. A literal grep "extends HTMLElement" structure check will not match here by design.src/vite-env.d.ts is a standard Vite ambient-types file, not part of the four-layer model.MIT — see LICENSE.
Follows the BlissFramework component guidelines for structure, theming, color-scheme, and accessibility. Per-check verifications run via /validate-web-component.
Created by Keenmate as part of the Pure Admin design system.
searchTextel.controller.clearSelection()clearAll()clear-selectionlabels.clearSelectionvalue / label / group / icon / subtitle /
disabled, so [{ value, label }] works without configuration; a get*Callback always beats its member.presentation — was mobile-presentation; the mobile width breakpoint is 768px (was 640).el.release() (was el.ready()), host attribute ready +
:state(ready) (was is-ready), el.whenReady(), property shouldDefer.show-debug-info and the debug panel (use el.enableLogging()),
--ms-transition-fast (one duration: --ms-transition-normal), the component's own
prefers-reduced-motion block (core applies reduced motion on every page; the 1.5s loader
slowdown needs base-css-variables).