@tartaguti/ui-components 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/LICENSE +21 -0
- package/README.md +192 -2
- package/THIRD-PARTY-NOTICES.md +29 -0
- package/custom-elements.json +16731 -0
- package/dist/accordion/tt-accordion-item.d.ts +32 -0
- package/dist/accordion/tt-accordion-item.d.ts.map +1 -0
- package/dist/accordion/tt-accordion-item.js +247 -0
- package/dist/accordion/tt-accordion-item.js.map +1 -0
- package/dist/accordion/tt-accordion.d.ts +28 -0
- package/dist/accordion/tt-accordion.d.ts.map +1 -0
- package/dist/accordion/tt-accordion.js +148 -0
- package/dist/accordion/tt-accordion.js.map +1 -0
- package/dist/bento/tt-bento-item.d.ts +35 -0
- package/dist/bento/tt-bento-item.d.ts.map +1 -0
- package/dist/bento/tt-bento-item.js +201 -0
- package/dist/bento/tt-bento-item.js.map +1 -0
- package/dist/bento/tt-bento.d.ts +25 -0
- package/dist/bento/tt-bento.d.ts.map +1 -0
- package/dist/bento/tt-bento.js +314 -0
- package/dist/bento/tt-bento.js.map +1 -0
- package/dist/floating.d.ts +61 -0
- package/dist/floating.d.ts.map +1 -0
- package/dist/floating.js +120 -0
- package/dist/floating.js.map +1 -0
- package/dist/index.d.ts +92 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +96 -0
- package/dist/index.js.map +1 -0
- package/dist/interaction/tt-draggable.d.ts +29 -0
- package/dist/interaction/tt-draggable.d.ts.map +1 -0
- package/dist/interaction/tt-draggable.js +66 -0
- package/dist/interaction/tt-draggable.js.map +1 -0
- package/dist/interaction/tt-droppable.d.ts +37 -0
- package/dist/interaction/tt-droppable.d.ts.map +1 -0
- package/dist/interaction/tt-droppable.js +108 -0
- package/dist/interaction/tt-droppable.js.map +1 -0
- package/dist/interaction/tt-resizable.d.ts +45 -0
- package/dist/interaction/tt-resizable.d.ts.map +1 -0
- package/dist/interaction/tt-resizable.js +210 -0
- package/dist/interaction/tt-resizable.js.map +1 -0
- package/dist/interaction/tt-reveal.d.ts +54 -0
- package/dist/interaction/tt-reveal.d.ts.map +1 -0
- package/dist/interaction/tt-reveal.js +302 -0
- package/dist/interaction/tt-reveal.js.map +1 -0
- package/dist/motion.d.ts +8 -0
- package/dist/motion.d.ts.map +1 -0
- package/dist/motion.js +49 -0
- package/dist/motion.js.map +1 -0
- package/dist/overlay.d.ts +7 -0
- package/dist/overlay.d.ts.map +1 -0
- package/dist/overlay.js +121 -0
- package/dist/overlay.js.map +1 -0
- package/dist/safe-url.d.ts +14 -0
- package/dist/safe-url.d.ts.map +1 -0
- package/dist/safe-url.js +46 -0
- package/dist/safe-url.js.map +1 -0
- package/dist/scroll-lock.d.ts +22 -0
- package/dist/scroll-lock.d.ts.map +1 -0
- package/dist/scroll-lock.js +56 -0
- package/dist/scroll-lock.js.map +1 -0
- package/dist/sidebar/tt-menu-group.d.ts +69 -0
- package/dist/sidebar/tt-menu-group.d.ts.map +1 -0
- package/dist/sidebar/tt-menu-group.js +358 -0
- package/dist/sidebar/tt-menu-group.js.map +1 -0
- package/dist/sidebar/tt-menu-item.d.ts +70 -0
- package/dist/sidebar/tt-menu-item.d.ts.map +1 -0
- package/dist/sidebar/tt-menu-item.js +502 -0
- package/dist/sidebar/tt-menu-item.js.map +1 -0
- package/dist/sidebar/tt-sidebar-footer.d.ts +39 -0
- package/dist/sidebar/tt-sidebar-footer.d.ts.map +1 -0
- package/dist/sidebar/tt-sidebar-footer.js +205 -0
- package/dist/sidebar/tt-sidebar-footer.js.map +1 -0
- package/dist/sidebar/tt-sidebar-header.d.ts +31 -0
- package/dist/sidebar/tt-sidebar-header.d.ts.map +1 -0
- package/dist/sidebar/tt-sidebar-header.js +150 -0
- package/dist/sidebar/tt-sidebar-header.js.map +1 -0
- package/dist/sidebar/tt-sidebar.d.ts +72 -0
- package/dist/sidebar/tt-sidebar.d.ts.map +1 -0
- package/dist/sidebar/tt-sidebar.js +539 -0
- package/dist/sidebar/tt-sidebar.js.map +1 -0
- package/dist/sr-only.d.ts +15 -0
- package/dist/sr-only.d.ts.map +1 -0
- package/dist/sr-only.js +27 -0
- package/dist/sr-only.js.map +1 -0
- package/dist/tokens.css +1124 -0
- package/dist/tt-alert.d.ts +26 -0
- package/dist/tt-alert.d.ts.map +1 -0
- package/dist/tt-alert.js +165 -0
- package/dist/tt-alert.js.map +1 -0
- package/dist/tt-aspect-ratio.d.ts +40 -0
- package/dist/tt-aspect-ratio.d.ts.map +1 -0
- package/dist/tt-aspect-ratio.js +98 -0
- package/dist/tt-aspect-ratio.js.map +1 -0
- package/dist/tt-auth.d.ts +75 -0
- package/dist/tt-auth.d.ts.map +1 -0
- package/dist/tt-auth.js +520 -0
- package/dist/tt-auth.js.map +1 -0
- package/dist/tt-avatar.d.ts +26 -0
- package/dist/tt-avatar.d.ts.map +1 -0
- package/dist/tt-avatar.js +169 -0
- package/dist/tt-avatar.js.map +1 -0
- package/dist/tt-badge.d.ts +36 -0
- package/dist/tt-badge.d.ts.map +1 -0
- package/dist/tt-badge.js +135 -0
- package/dist/tt-badge.js.map +1 -0
- package/dist/tt-breadcrumbs.d.ts +22 -0
- package/dist/tt-breadcrumbs.d.ts.map +1 -0
- package/dist/tt-breadcrumbs.js +144 -0
- package/dist/tt-breadcrumbs.js.map +1 -0
- package/dist/tt-button-group.d.ts +32 -0
- package/dist/tt-button-group.d.ts.map +1 -0
- package/dist/tt-button-group.js +132 -0
- package/dist/tt-button-group.js.map +1 -0
- package/dist/tt-button.d.ts +31 -0
- package/dist/tt-button.d.ts.map +1 -0
- package/dist/tt-button.js +483 -0
- package/dist/tt-button.js.map +1 -0
- package/dist/tt-card.d.ts +26 -0
- package/dist/tt-card.d.ts.map +1 -0
- package/dist/tt-card.js +429 -0
- package/dist/tt-card.js.map +1 -0
- package/dist/tt-chart.d.ts +36 -0
- package/dist/tt-chart.d.ts.map +1 -0
- package/dist/tt-chart.js +332 -0
- package/dist/tt-chart.js.map +1 -0
- package/dist/tt-checkbox-group.d.ts +38 -0
- package/dist/tt-checkbox-group.d.ts.map +1 -0
- package/dist/tt-checkbox-group.js +243 -0
- package/dist/tt-checkbox-group.js.map +1 -0
- package/dist/tt-checkbox.d.ts +35 -0
- package/dist/tt-checkbox.d.ts.map +1 -0
- package/dist/tt-checkbox.js +354 -0
- package/dist/tt-checkbox.js.map +1 -0
- package/dist/tt-code-block.d.ts +37 -0
- package/dist/tt-code-block.d.ts.map +1 -0
- package/dist/tt-code-block.js +409 -0
- package/dist/tt-code-block.js.map +1 -0
- package/dist/tt-collapsible.d.ts +71 -0
- package/dist/tt-collapsible.d.ts.map +1 -0
- package/dist/tt-collapsible.js +206 -0
- package/dist/tt-collapsible.js.map +1 -0
- package/dist/tt-combobox.d.ts +107 -0
- package/dist/tt-combobox.d.ts.map +1 -0
- package/dist/tt-combobox.js +816 -0
- package/dist/tt-combobox.js.map +1 -0
- package/dist/tt-company-card.d.ts +30 -0
- package/dist/tt-company-card.d.ts.map +1 -0
- package/dist/tt-company-card.js +382 -0
- package/dist/tt-company-card.js.map +1 -0
- package/dist/tt-comparison-table.d.ts +59 -0
- package/dist/tt-comparison-table.d.ts.map +1 -0
- package/dist/tt-comparison-table.js +441 -0
- package/dist/tt-comparison-table.js.map +1 -0
- package/dist/tt-component-doc.d.ts +63 -0
- package/dist/tt-component-doc.d.ts.map +1 -0
- package/dist/tt-component-doc.js +585 -0
- package/dist/tt-component-doc.js.map +1 -0
- package/dist/tt-confirm.d.ts +45 -0
- package/dist/tt-confirm.d.ts.map +1 -0
- package/dist/tt-confirm.js +151 -0
- package/dist/tt-confirm.js.map +1 -0
- package/dist/tt-contact-list.d.ts +18 -0
- package/dist/tt-contact-list.d.ts.map +1 -0
- package/dist/tt-contact-list.js +101 -0
- package/dist/tt-contact-list.js.map +1 -0
- package/dist/tt-copy-button.d.ts +25 -0
- package/dist/tt-copy-button.d.ts.map +1 -0
- package/dist/tt-copy-button.js +194 -0
- package/dist/tt-copy-button.js.map +1 -0
- package/dist/tt-data-table.d.ts +69 -0
- package/dist/tt-data-table.d.ts.map +1 -0
- package/dist/tt-data-table.js +542 -0
- package/dist/tt-data-table.js.map +1 -0
- package/dist/tt-divider.d.ts +19 -0
- package/dist/tt-divider.d.ts.map +1 -0
- package/dist/tt-divider.js +104 -0
- package/dist/tt-divider.js.map +1 -0
- package/dist/tt-drawer.d.ts +38 -0
- package/dist/tt-drawer.d.ts.map +1 -0
- package/dist/tt-drawer.js +433 -0
- package/dist/tt-drawer.js.map +1 -0
- package/dist/tt-dropdown-menu.d.ts +57 -0
- package/dist/tt-dropdown-menu.d.ts.map +1 -0
- package/dist/tt-dropdown-menu.js +631 -0
- package/dist/tt-dropdown-menu.js.map +1 -0
- package/dist/tt-empty-state.d.ts +13 -0
- package/dist/tt-empty-state.d.ts.map +1 -0
- package/dist/tt-empty-state.js +94 -0
- package/dist/tt-empty-state.js.map +1 -0
- package/dist/tt-example.d.ts +33 -0
- package/dist/tt-example.d.ts.map +1 -0
- package/dist/tt-example.js +250 -0
- package/dist/tt-example.js.map +1 -0
- package/dist/tt-file-upload.d.ts +85 -0
- package/dist/tt-file-upload.d.ts.map +1 -0
- package/dist/tt-file-upload.js +662 -0
- package/dist/tt-file-upload.js.map +1 -0
- package/dist/tt-filter.d.ts +62 -0
- package/dist/tt-filter.d.ts.map +1 -0
- package/dist/tt-filter.js +393 -0
- package/dist/tt-filter.js.map +1 -0
- package/dist/tt-form.d.ts +33 -0
- package/dist/tt-form.d.ts.map +1 -0
- package/dist/tt-form.js +202 -0
- package/dist/tt-form.js.map +1 -0
- package/dist/tt-grid.d.ts +22 -0
- package/dist/tt-grid.d.ts.map +1 -0
- package/dist/tt-grid.js +106 -0
- package/dist/tt-grid.js.map +1 -0
- package/dist/tt-icon.d.ts +20 -0
- package/dist/tt-icon.d.ts.map +1 -0
- package/dist/tt-icon.js +510 -0
- package/dist/tt-icon.js.map +1 -0
- package/dist/tt-image.d.ts +59 -0
- package/dist/tt-image.d.ts.map +1 -0
- package/dist/tt-image.js +314 -0
- package/dist/tt-image.js.map +1 -0
- package/dist/tt-inbox.d.ts +45 -0
- package/dist/tt-inbox.d.ts.map +1 -0
- package/dist/tt-inbox.js +275 -0
- package/dist/tt-inbox.js.map +1 -0
- package/dist/tt-input.d.ts +43 -0
- package/dist/tt-input.d.ts.map +1 -0
- package/dist/tt-input.js +476 -0
- package/dist/tt-input.js.map +1 -0
- package/dist/tt-kbd.d.ts +39 -0
- package/dist/tt-kbd.d.ts.map +1 -0
- package/dist/tt-kbd.js +145 -0
- package/dist/tt-kbd.js.map +1 -0
- package/dist/tt-key-values.d.ts +17 -0
- package/dist/tt-key-values.d.ts.map +1 -0
- package/dist/tt-key-values.js +126 -0
- package/dist/tt-key-values.js.map +1 -0
- package/dist/tt-knowledge-doc.d.ts +24 -0
- package/dist/tt-knowledge-doc.d.ts.map +1 -0
- package/dist/tt-knowledge-doc.js +120 -0
- package/dist/tt-knowledge-doc.js.map +1 -0
- package/dist/tt-layout.d.ts +36 -0
- package/dist/tt-layout.d.ts.map +1 -0
- package/dist/tt-layout.js +318 -0
- package/dist/tt-layout.js.map +1 -0
- package/dist/tt-modal.d.ts +32 -0
- package/dist/tt-modal.d.ts.map +1 -0
- package/dist/tt-modal.js +553 -0
- package/dist/tt-modal.js.map +1 -0
- package/dist/tt-navbar.d.ts +33 -0
- package/dist/tt-navbar.d.ts.map +1 -0
- package/dist/tt-navbar.js +390 -0
- package/dist/tt-navbar.js.map +1 -0
- package/dist/tt-page-header.d.ts +28 -0
- package/dist/tt-page-header.d.ts.map +1 -0
- package/dist/tt-page-header.js +166 -0
- package/dist/tt-page-header.js.map +1 -0
- package/dist/tt-page.d.ts +14 -0
- package/dist/tt-page.d.ts.map +1 -0
- package/dist/tt-page.js +163 -0
- package/dist/tt-page.js.map +1 -0
- package/dist/tt-pagination.d.ts +27 -0
- package/dist/tt-pagination.d.ts.map +1 -0
- package/dist/tt-pagination.js +194 -0
- package/dist/tt-pagination.js.map +1 -0
- package/dist/tt-preview.d.ts +11 -0
- package/dist/tt-preview.d.ts.map +1 -0
- package/dist/tt-preview.js +68 -0
- package/dist/tt-preview.js.map +1 -0
- package/dist/tt-product.d.ts +40 -0
- package/dist/tt-product.d.ts.map +1 -0
- package/dist/tt-product.js +590 -0
- package/dist/tt-product.js.map +1 -0
- package/dist/tt-profile-card.d.ts +23 -0
- package/dist/tt-profile-card.d.ts.map +1 -0
- package/dist/tt-profile-card.js +199 -0
- package/dist/tt-profile-card.js.map +1 -0
- package/dist/tt-progress.d.ts +26 -0
- package/dist/tt-progress.d.ts.map +1 -0
- package/dist/tt-progress.js +156 -0
- package/dist/tt-progress.js.map +1 -0
- package/dist/tt-quantity.d.ts +36 -0
- package/dist/tt-quantity.d.ts.map +1 -0
- package/dist/tt-quantity.js +396 -0
- package/dist/tt-quantity.js.map +1 -0
- package/dist/tt-radio-group.d.ts +50 -0
- package/dist/tt-radio-group.d.ts.map +1 -0
- package/dist/tt-radio-group.js +372 -0
- package/dist/tt-radio-group.js.map +1 -0
- package/dist/tt-rating.d.ts +54 -0
- package/dist/tt-rating.d.ts.map +1 -0
- package/dist/tt-rating.js +312 -0
- package/dist/tt-rating.js.map +1 -0
- package/dist/tt-search.d.ts +47 -0
- package/dist/tt-search.d.ts.map +1 -0
- package/dist/tt-search.js +360 -0
- package/dist/tt-search.js.map +1 -0
- package/dist/tt-section.d.ts +13 -0
- package/dist/tt-section.d.ts.map +1 -0
- package/dist/tt-section.js +98 -0
- package/dist/tt-section.js.map +1 -0
- package/dist/tt-select.d.ts +62 -0
- package/dist/tt-select.d.ts.map +1 -0
- package/dist/tt-select.js +662 -0
- package/dist/tt-select.js.map +1 -0
- package/dist/tt-skeleton.d.ts +20 -0
- package/dist/tt-skeleton.d.ts.map +1 -0
- package/dist/tt-skeleton.js +143 -0
- package/dist/tt-skeleton.js.map +1 -0
- package/dist/tt-slider.d.ts +74 -0
- package/dist/tt-slider.d.ts.map +1 -0
- package/dist/tt-slider.js +426 -0
- package/dist/tt-slider.js.map +1 -0
- package/dist/tt-social-links.d.ts +34 -0
- package/dist/tt-social-links.d.ts.map +1 -0
- package/dist/tt-social-links.js +242 -0
- package/dist/tt-social-links.js.map +1 -0
- package/dist/tt-sparkline.d.ts +19 -0
- package/dist/tt-sparkline.d.ts.map +1 -0
- package/dist/tt-sparkline.js +191 -0
- package/dist/tt-sparkline.js.map +1 -0
- package/dist/tt-spinner.d.ts +19 -0
- package/dist/tt-spinner.d.ts.map +1 -0
- package/dist/tt-spinner.js +93 -0
- package/dist/tt-spinner.js.map +1 -0
- package/dist/tt-stack.d.ts +42 -0
- package/dist/tt-stack.d.ts.map +1 -0
- package/dist/tt-stack.js +158 -0
- package/dist/tt-stack.js.map +1 -0
- package/dist/tt-stat.d.ts +17 -0
- package/dist/tt-stat.d.ts.map +1 -0
- package/dist/tt-stat.js +134 -0
- package/dist/tt-stat.js.map +1 -0
- package/dist/tt-switch.d.ts +39 -0
- package/dist/tt-switch.d.ts.map +1 -0
- package/dist/tt-switch.js +254 -0
- package/dist/tt-switch.js.map +1 -0
- package/dist/tt-switcher.d.ts +23 -0
- package/dist/tt-switcher.d.ts.map +1 -0
- package/dist/tt-switcher.js +203 -0
- package/dist/tt-switcher.js.map +1 -0
- package/dist/tt-table-cell.d.ts +26 -0
- package/dist/tt-table-cell.d.ts.map +1 -0
- package/dist/tt-table-cell.js +121 -0
- package/dist/tt-table-cell.js.map +1 -0
- package/dist/tt-table-row.d.ts +31 -0
- package/dist/tt-table-row.d.ts.map +1 -0
- package/dist/tt-table-row.js +72 -0
- package/dist/tt-table-row.js.map +1 -0
- package/dist/tt-table.d.ts +44 -0
- package/dist/tt-table.d.ts.map +1 -0
- package/dist/tt-table.js +270 -0
- package/dist/tt-table.js.map +1 -0
- package/dist/tt-tabs.d.ts +31 -0
- package/dist/tt-tabs.d.ts.map +1 -0
- package/dist/tt-tabs.js +223 -0
- package/dist/tt-tabs.js.map +1 -0
- package/dist/tt-tag-list.d.ts +19 -0
- package/dist/tt-tag-list.d.ts.map +1 -0
- package/dist/tt-tag-list.js +136 -0
- package/dist/tt-tag-list.js.map +1 -0
- package/dist/tt-text.d.ts +52 -0
- package/dist/tt-text.d.ts.map +1 -0
- package/dist/tt-text.js +337 -0
- package/dist/tt-text.js.map +1 -0
- package/dist/tt-textarea.d.ts +43 -0
- package/dist/tt-textarea.d.ts.map +1 -0
- package/dist/tt-textarea.js +356 -0
- package/dist/tt-textarea.js.map +1 -0
- package/dist/tt-thread.d.ts +62 -0
- package/dist/tt-thread.d.ts.map +1 -0
- package/dist/tt-thread.js +374 -0
- package/dist/tt-thread.js.map +1 -0
- package/dist/tt-toast.d.ts +49 -0
- package/dist/tt-toast.d.ts.map +1 -0
- package/dist/tt-toast.js +440 -0
- package/dist/tt-toast.js.map +1 -0
- package/dist/tt-toggle-group.d.ts +73 -0
- package/dist/tt-toggle-group.d.ts.map +1 -0
- package/dist/tt-toggle-group.js +273 -0
- package/dist/tt-toggle-group.js.map +1 -0
- package/dist/tt-toggle.d.ts +54 -0
- package/dist/tt-toggle.d.ts.map +1 -0
- package/dist/tt-toggle.js +232 -0
- package/dist/tt-toggle.js.map +1 -0
- package/dist/tt-toolbar.d.ts +26 -0
- package/dist/tt-toolbar.d.ts.map +1 -0
- package/dist/tt-toolbar.js +164 -0
- package/dist/tt-toolbar.js.map +1 -0
- package/dist/tt-tooltip.d.ts +42 -0
- package/dist/tt-tooltip.d.ts.map +1 -0
- package/dist/tt-tooltip.js +299 -0
- package/dist/tt-tooltip.js.map +1 -0
- package/package.json +84 -4
- package/vscode.html-custom-data.json +2907 -0
|
@@ -0,0 +1,816 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
import { LitElement, html, css } from "lit";
|
|
8
|
+
import { customElement, property, state, query } from "lit/decorators.js";
|
|
9
|
+
import { ifDefined } from "lit/directives/if-defined.js";
|
|
10
|
+
import { srOnly } from "./sr-only.js";
|
|
11
|
+
import { trackAnchor, placeBelow } from "./floating.js";
|
|
12
|
+
import "./tt-icon.js";
|
|
13
|
+
let uid = 0;
|
|
14
|
+
/**
|
|
15
|
+
* A select you can type into.
|
|
16
|
+
*
|
|
17
|
+
* WHERE IT SITS. tt-select is right up to roughly twenty options: you open
|
|
18
|
+
* it, you look, you pick. Past that, looking stops working and you need to
|
|
19
|
+
* narrow the list by typing. That is the only difference in intent, but it
|
|
20
|
+
* changes the control's shape: the trigger becomes a text input rather
|
|
21
|
+
* than a button, which brings a filtering state, a no-results state, and
|
|
22
|
+
* the problem below. Keeping tt-select simple was worth a second component.
|
|
23
|
+
*
|
|
24
|
+
* THE AWKWARD BIT, which every combobox has to answer. One input has two
|
|
25
|
+
* jobs: it displays the current selection, and it holds what you are
|
|
26
|
+
* typing. Those conflict the moment you type, because the selection is
|
|
27
|
+
* still set but the text no longer shows it.
|
|
28
|
+
*
|
|
29
|
+
* This resolves it by treating the two as different modes rather than one
|
|
30
|
+
* value. Closed, the input shows the selected option's label. Open, it
|
|
31
|
+
* shows the query, which starts empty so the whole list is visible. Close
|
|
32
|
+
* without choosing and the query is discarded, so the field can never be
|
|
33
|
+
* left displaying text that is not the actual value. The selection only
|
|
34
|
+
* ever changes by choosing an option.
|
|
35
|
+
*/
|
|
36
|
+
let TTCombobox = class TTCombobox extends LitElement {
|
|
37
|
+
constructor() {
|
|
38
|
+
super(...arguments);
|
|
39
|
+
this.internals = this.attachInternals();
|
|
40
|
+
/* See tt-quantity: a form-associated element submits under its name
|
|
41
|
+
CONTENT ATTRIBUTE, so this has to reflect. */
|
|
42
|
+
this.name = "";
|
|
43
|
+
this.value = "";
|
|
44
|
+
this.options = [];
|
|
45
|
+
this.label = "";
|
|
46
|
+
this.placeholder = "Search or select";
|
|
47
|
+
this.emptyText = "No matches";
|
|
48
|
+
this.error = "";
|
|
49
|
+
this.disabled = false;
|
|
50
|
+
this.required = false;
|
|
51
|
+
// small | medium | large, as on tt-input (Oct 2026), so a row of
|
|
52
|
+
// fields can share one height. Reflected: the sizes are :host rules.
|
|
53
|
+
this.size = "medium";
|
|
54
|
+
// Named for a screen reader, invisible on screen. Same contract as
|
|
55
|
+
// tt-input and tt-checkbox.
|
|
56
|
+
this.labelHidden = false;
|
|
57
|
+
// Offer a clear button once something is chosen.
|
|
58
|
+
this.clearable = false;
|
|
59
|
+
this.open = false;
|
|
60
|
+
// What has been typed this session. Never the selection.
|
|
61
|
+
this.query = "";
|
|
62
|
+
// Where the arrow keys are. Not the selection: you can move the cursor
|
|
63
|
+
// over an option without choosing it.
|
|
64
|
+
this.activeIndex = -1;
|
|
65
|
+
this.instanceId = ++uid;
|
|
66
|
+
this.listId = `tt-combobox-list-${this.instanceId}`;
|
|
67
|
+
this.handleDocumentClick = (event) => {
|
|
68
|
+
if (!event.composedPath().includes(this))
|
|
69
|
+
this.closeList(false);
|
|
70
|
+
};
|
|
71
|
+
this.reposition = () => {
|
|
72
|
+
if (this.open)
|
|
73
|
+
this.position();
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
static { this.formAssociated = true; }
|
|
77
|
+
get form() {
|
|
78
|
+
return this.internals.form;
|
|
79
|
+
}
|
|
80
|
+
get validity() {
|
|
81
|
+
return this.internals.validity;
|
|
82
|
+
}
|
|
83
|
+
checkValidity() {
|
|
84
|
+
return this.internals.checkValidity();
|
|
85
|
+
}
|
|
86
|
+
reportValidity() {
|
|
87
|
+
return this.internals.reportValidity();
|
|
88
|
+
}
|
|
89
|
+
connectedCallback() {
|
|
90
|
+
super.connectedCallback();
|
|
91
|
+
this.syncFormState();
|
|
92
|
+
}
|
|
93
|
+
disconnectedCallback() {
|
|
94
|
+
this.removeGlobalListeners();
|
|
95
|
+
super.disconnectedCallback();
|
|
96
|
+
}
|
|
97
|
+
formResetCallback() {
|
|
98
|
+
this.value = "";
|
|
99
|
+
this.query = "";
|
|
100
|
+
}
|
|
101
|
+
formStateRestoreCallback(state) {
|
|
102
|
+
this.value = state ?? "";
|
|
103
|
+
}
|
|
104
|
+
syncFormState() {
|
|
105
|
+
this.internals.setFormValue(this.value);
|
|
106
|
+
if (this.required && !this.value) {
|
|
107
|
+
this.internals.setValidity({ valueMissing: true }, "Please choose an option.", this.input ?? undefined);
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
this.internals.setValidity({});
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/* ---- derived ---- */
|
|
114
|
+
get selected() {
|
|
115
|
+
return this.options.find((o) => o.value === this.value);
|
|
116
|
+
}
|
|
117
|
+
/*
|
|
118
|
+
Named "filtered", not "matches". Element already has a matches()
|
|
119
|
+
method, and shadowing a built-in DOM member breaks the element type
|
|
120
|
+
outright - the same trap as naming a property "title".
|
|
121
|
+
|
|
122
|
+
Substring, case-insensitive, on the label. Deliberately not fuzzy
|
|
123
|
+
matching: fuzzy ranks results in an order the user cannot predict,
|
|
124
|
+
which is fine for a command palette and wrong for a field where they
|
|
125
|
+
already know the name of the thing they want.
|
|
126
|
+
*/
|
|
127
|
+
/*
|
|
128
|
+
Matches are RANKED by where they hit, not just whether they hit.
|
|
129
|
+
|
|
130
|
+
A plain `label.includes(q)` is fine for a list of twelve and useless
|
|
131
|
+
for a list of hundreds: in a car catalogue, typing "a" matched 181 of
|
|
132
|
+
248 options, because almost every label contains an "a" somewhere, and
|
|
133
|
+
they came back in whatever order the consumer happened to supply. The
|
|
134
|
+
first keystroke should narrow the list, not shuffle it.
|
|
135
|
+
|
|
136
|
+
Four tiers, and the order between them is the whole point. An exact
|
|
137
|
+
label beats a label that starts with the query, which beats a label
|
|
138
|
+
where some WORD starts with it ("x5" finding "BMW X5"), which beats a
|
|
139
|
+
match buried mid-word. Ties keep the order the consumer gave, which is
|
|
140
|
+
usually already meaningful - most popular first, say - so the sort has
|
|
141
|
+
to be stable, and Array.prototype.sort is only stable with respect to
|
|
142
|
+
the input order. Mapping to indexed pairs first is what guarantees it.
|
|
143
|
+
*/
|
|
144
|
+
score(label, q) {
|
|
145
|
+
const l = label.toLowerCase();
|
|
146
|
+
if (l === q)
|
|
147
|
+
return 4;
|
|
148
|
+
if (l.startsWith(q))
|
|
149
|
+
return 3;
|
|
150
|
+
// \b would not fire after "-" or "/", which is exactly where model
|
|
151
|
+
// names break: "E-Class", "CR-V".
|
|
152
|
+
if (l.split(/[\s\-/(),.]+/).some((word) => word.startsWith(q)))
|
|
153
|
+
return 2;
|
|
154
|
+
if (l.includes(q))
|
|
155
|
+
return 1;
|
|
156
|
+
return 0;
|
|
157
|
+
}
|
|
158
|
+
get filtered() {
|
|
159
|
+
const q = this.query.trim().toLowerCase();
|
|
160
|
+
if (q === "")
|
|
161
|
+
return this.options;
|
|
162
|
+
return this.options
|
|
163
|
+
.map((option, index) => ({ option, index, score: this.score(option.label, q) }))
|
|
164
|
+
.filter((entry) => entry.score > 0)
|
|
165
|
+
.sort((a, b) => b.score - a.score || a.index - b.index)
|
|
166
|
+
.map((entry) => entry.option);
|
|
167
|
+
}
|
|
168
|
+
/* ---- open / close ---- */
|
|
169
|
+
openList() {
|
|
170
|
+
if (this.disabled || this.open)
|
|
171
|
+
return;
|
|
172
|
+
this.open = true;
|
|
173
|
+
// Empty, so opening shows everything. Pre-filling with the current
|
|
174
|
+
// label would filter the list down to the thing already chosen, which
|
|
175
|
+
// is the least useful list possible.
|
|
176
|
+
this.query = "";
|
|
177
|
+
this.activeIndex = this.filtered.findIndex((o) => o.value === this.value);
|
|
178
|
+
document.addEventListener("click", this.handleDocumentClick);
|
|
179
|
+
this.untrack = trackAnchor(this, this.reposition);
|
|
180
|
+
}
|
|
181
|
+
closeList(refocus = true) {
|
|
182
|
+
if (!this.open)
|
|
183
|
+
return;
|
|
184
|
+
this.open = false;
|
|
185
|
+
this.activeIndex = -1;
|
|
186
|
+
// Discard the query. The input goes back to showing the selection, so
|
|
187
|
+
// it can never be left displaying something that is not the value.
|
|
188
|
+
this.query = "";
|
|
189
|
+
this.removeGlobalListeners();
|
|
190
|
+
if (refocus)
|
|
191
|
+
this.input?.focus();
|
|
192
|
+
}
|
|
193
|
+
removeGlobalListeners() {
|
|
194
|
+
document.removeEventListener("click", this.handleDocumentClick);
|
|
195
|
+
this.untrack?.();
|
|
196
|
+
this.untrack = undefined;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Clicking the field opens the list. FOCUS DOES NOT.
|
|
200
|
+
*
|
|
201
|
+
* This looks like a downgrade and is the opposite. Opening on focus
|
|
202
|
+
* creates a loop that is invisible until somebody uses a real mouse:
|
|
203
|
+
* choosing an option calls closeList, closeList returns focus to the
|
|
204
|
+
* input so a keyboard user does not lose their place, that focus fires
|
|
205
|
+
* the focus handler, and the list reopens the instant it closes. The
|
|
206
|
+
* chosen value is set correctly, so it looks purely like a broken menu.
|
|
207
|
+
*
|
|
208
|
+
* It survived a headless test because a synthetic .click() never moves
|
|
209
|
+
* focus off the input, so the focus() inside closeList is a no-op and
|
|
210
|
+
* dispatches nothing. Only a real pointer, which blurs the input on
|
|
211
|
+
* mousedown, makes it fire.
|
|
212
|
+
*
|
|
213
|
+
* Every way a person actually asks for the list is still covered: click
|
|
214
|
+
* the field here, type (handleInput opens), or press ArrowDown/ArrowUp.
|
|
215
|
+
* Only tabbing THROUGH the field no longer explodes a listbox open,
|
|
216
|
+
* which matches the ARIA combobox pattern anyway.
|
|
217
|
+
*/
|
|
218
|
+
handleFieldClick() {
|
|
219
|
+
if (this.disabled)
|
|
220
|
+
return;
|
|
221
|
+
this.input?.focus();
|
|
222
|
+
// The list is a sibling of the field, not a child, so an option click
|
|
223
|
+
// never bubbles back through here and cannot re-open what it closed.
|
|
224
|
+
this.openList();
|
|
225
|
+
}
|
|
226
|
+
/* ---- selection ---- */
|
|
227
|
+
choose(option) {
|
|
228
|
+
if (option.disabled)
|
|
229
|
+
return;
|
|
230
|
+
this.value = option.value;
|
|
231
|
+
this.closeList();
|
|
232
|
+
this.dispatchEvent(new CustomEvent("tt-change", {
|
|
233
|
+
detail: { value: this.value },
|
|
234
|
+
bubbles: true,
|
|
235
|
+
composed: true,
|
|
236
|
+
}));
|
|
237
|
+
}
|
|
238
|
+
clear(event) {
|
|
239
|
+
event.stopPropagation();
|
|
240
|
+
this.value = "";
|
|
241
|
+
this.query = "";
|
|
242
|
+
this.dispatchEvent(new CustomEvent("tt-change", {
|
|
243
|
+
detail: { value: "" },
|
|
244
|
+
bubbles: true,
|
|
245
|
+
composed: true,
|
|
246
|
+
}));
|
|
247
|
+
this.input?.focus();
|
|
248
|
+
}
|
|
249
|
+
/* ---- input ---- */
|
|
250
|
+
handleInput(event) {
|
|
251
|
+
if (!this.open)
|
|
252
|
+
this.openList();
|
|
253
|
+
this.query = event.target.value;
|
|
254
|
+
// Typing invalidates wherever the cursor was, so put it on the best
|
|
255
|
+
// remaining candidate rather than leaving it pointing at nothing.
|
|
256
|
+
this.activeIndex = this.filtered.length > 0 ? 0 : -1;
|
|
257
|
+
}
|
|
258
|
+
move(delta) {
|
|
259
|
+
const list = this.filtered;
|
|
260
|
+
if (list.length === 0)
|
|
261
|
+
return;
|
|
262
|
+
let next = this.activeIndex;
|
|
263
|
+
for (let i = 0; i < list.length; i++) {
|
|
264
|
+
next = (next + delta + list.length) % list.length;
|
|
265
|
+
if (!list[next].disabled)
|
|
266
|
+
break;
|
|
267
|
+
}
|
|
268
|
+
this.activeIndex = next;
|
|
269
|
+
this.scrollActiveIntoView();
|
|
270
|
+
}
|
|
271
|
+
scrollActiveIntoView() {
|
|
272
|
+
this.updateComplete.then(() => {
|
|
273
|
+
const items = this.list?.querySelectorAll("li");
|
|
274
|
+
items?.[this.activeIndex]?.scrollIntoView({ block: "nearest" });
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
handleKeydown(event) {
|
|
278
|
+
if (this.disabled)
|
|
279
|
+
return;
|
|
280
|
+
switch (event.key) {
|
|
281
|
+
case "ArrowDown":
|
|
282
|
+
event.preventDefault();
|
|
283
|
+
if (this.open)
|
|
284
|
+
this.move(1);
|
|
285
|
+
else
|
|
286
|
+
this.openList();
|
|
287
|
+
break;
|
|
288
|
+
case "ArrowUp":
|
|
289
|
+
event.preventDefault();
|
|
290
|
+
if (!this.open)
|
|
291
|
+
this.openList();
|
|
292
|
+
this.move(-1);
|
|
293
|
+
break;
|
|
294
|
+
case "Home":
|
|
295
|
+
if (this.open) {
|
|
296
|
+
event.preventDefault();
|
|
297
|
+
this.activeIndex = -1;
|
|
298
|
+
this.move(1);
|
|
299
|
+
}
|
|
300
|
+
break;
|
|
301
|
+
case "End":
|
|
302
|
+
if (this.open) {
|
|
303
|
+
event.preventDefault();
|
|
304
|
+
this.activeIndex = 0;
|
|
305
|
+
this.move(-1);
|
|
306
|
+
}
|
|
307
|
+
break;
|
|
308
|
+
case "Enter": {
|
|
309
|
+
// Only swallow Enter while a choice is actually pending, so the
|
|
310
|
+
// key still submits the surrounding form the rest of the time.
|
|
311
|
+
const option = this.filtered[this.activeIndex];
|
|
312
|
+
if (this.open && option) {
|
|
313
|
+
event.preventDefault();
|
|
314
|
+
this.choose(option);
|
|
315
|
+
}
|
|
316
|
+
break;
|
|
317
|
+
}
|
|
318
|
+
case "Escape":
|
|
319
|
+
if (this.open) {
|
|
320
|
+
event.preventDefault();
|
|
321
|
+
this.closeList();
|
|
322
|
+
}
|
|
323
|
+
break;
|
|
324
|
+
case "Tab":
|
|
325
|
+
this.closeList(false);
|
|
326
|
+
break;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
/* ---- positioning ---- */
|
|
330
|
+
updated(changed) {
|
|
331
|
+
if (changed.has("value") || changed.has("required")) {
|
|
332
|
+
this.syncFormState();
|
|
333
|
+
}
|
|
334
|
+
if (changed.has("open") || (this.open && changed.has("query"))) {
|
|
335
|
+
this.syncPopover();
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
syncPopover() {
|
|
339
|
+
const list = this.list;
|
|
340
|
+
if (!list)
|
|
341
|
+
return;
|
|
342
|
+
if (this.open) {
|
|
343
|
+
if (!list.matches(":popover-open"))
|
|
344
|
+
list.showPopover();
|
|
345
|
+
this.position();
|
|
346
|
+
}
|
|
347
|
+
else if (list.matches(":popover-open")) {
|
|
348
|
+
list.hidePopover();
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
position() {
|
|
352
|
+
if (!this.field || !this.list)
|
|
353
|
+
return;
|
|
354
|
+
// A field's dropdown should be exactly as wide as the field, which is
|
|
355
|
+
// the one thing placeBelow does not decide for you. Set BEFORE placing:
|
|
356
|
+
// the width decides how options wrap, so the height placeBelow
|
|
357
|
+
// measures to choose above or below was the previous width's.
|
|
358
|
+
this.list.style.width = `${this.field.getBoundingClientRect().width}px`;
|
|
359
|
+
placeBelow(this.field, this.list, { align: "start" });
|
|
360
|
+
}
|
|
361
|
+
focus(options) {
|
|
362
|
+
this.input?.focus(options);
|
|
363
|
+
}
|
|
364
|
+
static { this.styles = [srOnly, css `
|
|
365
|
+
/* The browser's own [hidden] rule loses to any :host display, so a
|
|
366
|
+
hidden element would still show. Important, so it always wins. */
|
|
367
|
+
:host([hidden]) {
|
|
368
|
+
display: none !important;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
:host {
|
|
372
|
+
/* A grid or flex item defaults to min-width: auto, which floors it at
|
|
373
|
+
its MIN-CONTENT width. See tt-input for what that broke. */
|
|
374
|
+
min-width: 0;
|
|
375
|
+
|
|
376
|
+
display: block;
|
|
377
|
+
width: 100%;
|
|
378
|
+
font-family: var(--tt-font-sans);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
.wrap {
|
|
382
|
+
display: flex;
|
|
383
|
+
flex-direction: column;
|
|
384
|
+
gap: var(--tt-space-2);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
.label {
|
|
388
|
+
font-size: var(--tt-text-sm);
|
|
389
|
+
font-weight: var(--tt-weight-medium);
|
|
390
|
+
color: var(--tt-foreground);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
.req {
|
|
394
|
+
margin-left: 2px;
|
|
395
|
+
color: var(--tt-destructive);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/* Matches tt-input's control box, because this IS a field. The border
|
|
399
|
+
lives here so the icons sit inside it. */
|
|
400
|
+
.field {
|
|
401
|
+
display: flex;
|
|
402
|
+
align-items: center;
|
|
403
|
+
gap: var(--tt-space-2);
|
|
404
|
+
|
|
405
|
+
min-height: var(--tt-control-md);
|
|
406
|
+
padding: 0 13px;
|
|
407
|
+
box-sizing: border-box;
|
|
408
|
+
|
|
409
|
+
border: 1px solid var(--tt-input);
|
|
410
|
+
border-radius: var(--tt-group-radius, var(--tt-radius));
|
|
411
|
+
|
|
412
|
+
background: var(--tt-field);
|
|
413
|
+
color: var(--tt-foreground);
|
|
414
|
+
|
|
415
|
+
cursor: text;
|
|
416
|
+
|
|
417
|
+
transition: border-color var(--tt-duration-normal) var(--tt-ease);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/* The same three steps as tt-input. */
|
|
421
|
+
:host([size="small"]) .field {
|
|
422
|
+
min-height: var(--tt-control-sm);
|
|
423
|
+
padding: 0 11px;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
:host([size="large"]) .field {
|
|
427
|
+
min-height: var(--tt-control-lg);
|
|
428
|
+
padding: 0 16px;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
:host([size="large"]) input {
|
|
432
|
+
font-size: var(--tt-text-md);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/* iOS zooms the viewport when a focused field is under 16px. tt-input
|
|
436
|
+
and tt-textarea guarded against it; this field did not. */
|
|
437
|
+
@media (max-width: 480px) {
|
|
438
|
+
input,
|
|
439
|
+
:host([size="large"]) input {
|
|
440
|
+
font-size: 16px;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
.field:hover:not(.disabled):not(.error) {
|
|
445
|
+
border-color: var(--tt-border-strong);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
.field:focus-within {
|
|
449
|
+
border-color: var(--tt-ring);
|
|
450
|
+
outline: var(--tt-ring-width) solid var(--tt-ring);
|
|
451
|
+
outline-offset: calc(var(--tt-ring-width) * -1);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
.field.error {
|
|
455
|
+
border-color: var(--tt-destructive);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
.field.error:focus-within {
|
|
459
|
+
outline-color: var(--tt-destructive);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/*
|
|
463
|
+
A disabled FIELD does not fade. It takes the muted surface and muted
|
|
464
|
+
text, the same as tt-input and tt-textarea, so a disabled row of
|
|
465
|
+
fields reads as one state. It used to fade the whole host as well,
|
|
466
|
+
which made a disabled select look different from a disabled input
|
|
467
|
+
beside it.
|
|
468
|
+
*/
|
|
469
|
+
.field.disabled {
|
|
470
|
+
background: var(--tt-muted);
|
|
471
|
+
color: var(--tt-muted-foreground);
|
|
472
|
+
cursor: not-allowed;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
input {
|
|
476
|
+
flex: 1;
|
|
477
|
+
min-width: 0;
|
|
478
|
+
|
|
479
|
+
padding: 0;
|
|
480
|
+
border: none;
|
|
481
|
+
background: transparent;
|
|
482
|
+
color: inherit;
|
|
483
|
+
|
|
484
|
+
font: inherit;
|
|
485
|
+
font-size: var(--tt-text-sm);
|
|
486
|
+
/* font: inherit also brings the PAGE's line-height in. Inside a
|
|
487
|
+
paragraph at line-height 2 the input was 28px tall, and a compact
|
|
488
|
+
small combobox grew to 30px beside 28px controls. normal is what
|
|
489
|
+
tt-input's input keeps. */
|
|
490
|
+
line-height: normal;
|
|
491
|
+
|
|
492
|
+
cursor: inherit;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
input:focus {
|
|
496
|
+
outline: none;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
input::placeholder {
|
|
500
|
+
color: var(--tt-muted-foreground);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
.chevron {
|
|
504
|
+
color: var(--tt-muted-foreground);
|
|
505
|
+
transition: transform var(--tt-duration-fast) var(--tt-ease);
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
/* Keyed off a class, not :host([open]). The open flag is @state, so it
|
|
509
|
+
never reaches the DOM and :host([open]) can never match. */
|
|
510
|
+
.field.open .chevron {
|
|
511
|
+
transform: rotate(180deg);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
.clear {
|
|
515
|
+
display: inline-flex;
|
|
516
|
+
align-items: center;
|
|
517
|
+
justify-content: center;
|
|
518
|
+
|
|
519
|
+
/* Same size as tt-search's clear button: two fields, one clear
|
|
520
|
+
control. It was 20px here and 24px there. */
|
|
521
|
+
width: calc(var(--tt-control-sm) - 8px);
|
|
522
|
+
height: calc(var(--tt-control-sm) - 8px);
|
|
523
|
+
|
|
524
|
+
padding: 0;
|
|
525
|
+
border: none;
|
|
526
|
+
border-radius: var(--tt-radius-full);
|
|
527
|
+
|
|
528
|
+
background: transparent;
|
|
529
|
+
color: var(--tt-muted-foreground);
|
|
530
|
+
|
|
531
|
+
cursor: pointer;
|
|
532
|
+
|
|
533
|
+
-webkit-user-select: none;
|
|
534
|
+
user-select: none;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
.clear:hover {
|
|
538
|
+
background: var(--tt-accent);
|
|
539
|
+
color: var(--tt-foreground);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
.clear:focus-visible {
|
|
543
|
+
outline: var(--tt-ring-width) solid var(--tt-ring);
|
|
544
|
+
outline-offset: var(--tt-ring-offset);
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
/* Top layer, so no ancestor's overflow can clip it. Position and
|
|
548
|
+
width both come from script. */
|
|
549
|
+
ul {
|
|
550
|
+
position: fixed;
|
|
551
|
+
margin: 0;
|
|
552
|
+
padding: var(--tt-space-1);
|
|
553
|
+
|
|
554
|
+
max-height: 260px;
|
|
555
|
+
overflow-y: auto;
|
|
556
|
+
box-sizing: border-box;
|
|
557
|
+
|
|
558
|
+
list-style: none;
|
|
559
|
+
|
|
560
|
+
background: var(--tt-popover);
|
|
561
|
+
color: var(--tt-popover-foreground);
|
|
562
|
+
|
|
563
|
+
border: 1px solid var(--tt-border);
|
|
564
|
+
border-radius: var(--tt-radius);
|
|
565
|
+
box-shadow: var(--tt-elevation-2);
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
/* In and out of the top layer, as on tt-dropdown-menu: see the note
|
|
569
|
+
there for why display and overlay ride along, and why translate. */
|
|
570
|
+
ul {
|
|
571
|
+
transition:
|
|
572
|
+
opacity var(--tt-duration-fast) var(--tt-ease-in),
|
|
573
|
+
translate var(--tt-duration-fast) var(--tt-ease-in),
|
|
574
|
+
overlay var(--tt-duration-fast) allow-discrete,
|
|
575
|
+
display var(--tt-duration-fast) allow-discrete;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
ul:popover-open {
|
|
579
|
+
transition-duration: var(--tt-duration-slow);
|
|
580
|
+
transition-timing-function: var(--tt-ease-out);
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
@starting-style {
|
|
584
|
+
ul:popover-open {
|
|
585
|
+
opacity: 0;
|
|
586
|
+
translate: 0 -4px;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
/* Closed, and closing: clicks during the fade reach the page. */
|
|
591
|
+
ul:not(:popover-open) {
|
|
592
|
+
display: none;
|
|
593
|
+
opacity: 0;
|
|
594
|
+
translate: 0 -4px;
|
|
595
|
+
pointer-events: none;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
li {
|
|
599
|
+
padding: var(--tt-space-2) var(--tt-space-3);
|
|
600
|
+
border-radius: var(--tt-radius-sm);
|
|
601
|
+
|
|
602
|
+
font-size: var(--tt-text-sm);
|
|
603
|
+
cursor: pointer;
|
|
604
|
+
|
|
605
|
+
-webkit-user-select: none;
|
|
606
|
+
user-select: none;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
li[aria-selected="true"] {
|
|
610
|
+
font-weight: var(--tt-weight-semibold);
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
li.active {
|
|
614
|
+
background: var(--tt-accent);
|
|
615
|
+
color: var(--tt-accent-foreground);
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
li[aria-disabled="true"] {
|
|
619
|
+
opacity: var(--tt-opacity-disabled);
|
|
620
|
+
cursor: not-allowed;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
.empty {
|
|
624
|
+
padding: var(--tt-space-3);
|
|
625
|
+
font-size: var(--tt-text-sm);
|
|
626
|
+
color: var(--tt-muted-foreground);
|
|
627
|
+
text-align: center;
|
|
628
|
+
cursor: default;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
.error-text {
|
|
632
|
+
color: var(--tt-destructive);
|
|
633
|
+
font-size: var(--tt-text-xs);
|
|
634
|
+
font-weight: var(--tt-weight-medium);
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
`]; }
|
|
638
|
+
render() {
|
|
639
|
+
const filtered = this.filtered;
|
|
640
|
+
const errorId = this.error ? `tt-combobox-error-${this.instanceId}` : undefined;
|
|
641
|
+
const labelId = `tt-combobox-label-${this.instanceId}`;
|
|
642
|
+
// Open means "showing the query", closed means "showing the value".
|
|
643
|
+
const shown = this.open ? this.query : (this.selected?.label ?? "");
|
|
644
|
+
return html `
|
|
645
|
+
<div class="wrap">
|
|
646
|
+
${this.label
|
|
647
|
+
? html `
|
|
648
|
+
<span
|
|
649
|
+
class="label ${this.labelHidden ? "sr-only" : ""}"
|
|
650
|
+
part="label"
|
|
651
|
+
id=${labelId}
|
|
652
|
+
>
|
|
653
|
+
${this.label}
|
|
654
|
+
${this.required
|
|
655
|
+
? html `<span class="req" aria-hidden="true">*</span>`
|
|
656
|
+
: ""}
|
|
657
|
+
</span>
|
|
658
|
+
`
|
|
659
|
+
: ""}
|
|
660
|
+
|
|
661
|
+
<div
|
|
662
|
+
class="field ${this.open ? "open" : ""} ${this.error
|
|
663
|
+
? "error"
|
|
664
|
+
: ""} ${this.disabled ? "disabled" : ""}"
|
|
665
|
+
part="field"
|
|
666
|
+
@click=${this.handleFieldClick}
|
|
667
|
+
>
|
|
668
|
+
<input
|
|
669
|
+
part="input"
|
|
670
|
+
type="text"
|
|
671
|
+
role="combobox"
|
|
672
|
+
aria-labelledby=${ifDefined(this.label ? labelId : undefined)}
|
|
673
|
+
autocomplete="off"
|
|
674
|
+
spellcheck="false"
|
|
675
|
+
.value=${shown}
|
|
676
|
+
placeholder=${this.selected && !this.open
|
|
677
|
+
? this.selected.label
|
|
678
|
+
: this.placeholder}
|
|
679
|
+
aria-expanded=${this.open ? "true" : "false"}
|
|
680
|
+
aria-controls=${this.listId}
|
|
681
|
+
aria-autocomplete="list"
|
|
682
|
+
aria-activedescendant=${this.open && this.activeIndex >= 0
|
|
683
|
+
? `${this.listId}-${this.activeIndex}`
|
|
684
|
+
: ""}
|
|
685
|
+
aria-required=${this.required ? "true" : "false"}
|
|
686
|
+
aria-invalid=${this.error ? "true" : "false"}
|
|
687
|
+
aria-describedby=${errorId ?? ""}
|
|
688
|
+
?disabled=${this.disabled}
|
|
689
|
+
@input=${this.handleInput}
|
|
690
|
+
@keydown=${this.handleKeydown}
|
|
691
|
+
/>
|
|
692
|
+
|
|
693
|
+
${this.clearable && this.value && !this.disabled
|
|
694
|
+
? html `
|
|
695
|
+
<button
|
|
696
|
+
class="clear"
|
|
697
|
+
type="button"
|
|
698
|
+
aria-label="Clear selection"
|
|
699
|
+
@click=${this.clear}
|
|
700
|
+
>
|
|
701
|
+
<tt-icon name="x" size="14px"></tt-icon>
|
|
702
|
+
</button>
|
|
703
|
+
`
|
|
704
|
+
: ""}
|
|
705
|
+
|
|
706
|
+
<tt-icon
|
|
707
|
+
class="chevron"
|
|
708
|
+
name="chevron-down"
|
|
709
|
+
size="16px"
|
|
710
|
+
></tt-icon>
|
|
711
|
+
</div>
|
|
712
|
+
|
|
713
|
+
<ul part="list" id=${this.listId} popover="manual" role="listbox">
|
|
714
|
+
${filtered.length === 0
|
|
715
|
+
? html `<li class="empty" role="presentation">${this.emptyText}</li>`
|
|
716
|
+
: filtered.map((option, index) => html `
|
|
717
|
+
<li
|
|
718
|
+
id=${`${this.listId}-${index}`}
|
|
719
|
+
role="option"
|
|
720
|
+
class=${index === this.activeIndex ? "active" : ""}
|
|
721
|
+
aria-selected=${option.value === this.value
|
|
722
|
+
? "true"
|
|
723
|
+
: "false"}
|
|
724
|
+
aria-disabled=${option.disabled ? "true" : "false"}
|
|
725
|
+
@click=${() => this.choose(option)}
|
|
726
|
+
@mousemove=${() => (this.activeIndex = index)}
|
|
727
|
+
>
|
|
728
|
+
${option.label}
|
|
729
|
+
</li>
|
|
730
|
+
`)}
|
|
731
|
+
</ul>
|
|
732
|
+
|
|
733
|
+
<!--
|
|
734
|
+
The list changes as you type, and nothing said so. Focus stays in
|
|
735
|
+
the input, so a screen-reader user typed into silence, and an
|
|
736
|
+
empty result ("No matches") was never spoken at all. Polite, and
|
|
737
|
+
only while there is a query, so opening the list stays quiet.
|
|
738
|
+
-->
|
|
739
|
+
<span class="sr-only" role="status">
|
|
740
|
+
${this.open && this.query.trim()
|
|
741
|
+
? filtered.length === 0
|
|
742
|
+
? this.emptyText
|
|
743
|
+
: `${filtered.length} ${filtered.length === 1 ? "result" : "results"}`
|
|
744
|
+
: ""}
|
|
745
|
+
</span>
|
|
746
|
+
|
|
747
|
+
${this.error
|
|
748
|
+
? html `
|
|
749
|
+
<span id=${ifDefined(errorId)} class="error-text" role="alert">
|
|
750
|
+
${this.error}
|
|
751
|
+
</span>
|
|
752
|
+
`
|
|
753
|
+
: ""}
|
|
754
|
+
</div>
|
|
755
|
+
`;
|
|
756
|
+
}
|
|
757
|
+
};
|
|
758
|
+
__decorate([
|
|
759
|
+
property({ type: String, reflect: true })
|
|
760
|
+
], TTCombobox.prototype, "name", void 0);
|
|
761
|
+
__decorate([
|
|
762
|
+
property({ type: String })
|
|
763
|
+
], TTCombobox.prototype, "value", void 0);
|
|
764
|
+
__decorate([
|
|
765
|
+
property({ type: Array })
|
|
766
|
+
], TTCombobox.prototype, "options", void 0);
|
|
767
|
+
__decorate([
|
|
768
|
+
property({ type: String })
|
|
769
|
+
], TTCombobox.prototype, "label", void 0);
|
|
770
|
+
__decorate([
|
|
771
|
+
property({ type: String })
|
|
772
|
+
], TTCombobox.prototype, "placeholder", void 0);
|
|
773
|
+
__decorate([
|
|
774
|
+
property({ type: String, attribute: "empty-text" })
|
|
775
|
+
], TTCombobox.prototype, "emptyText", void 0);
|
|
776
|
+
__decorate([
|
|
777
|
+
property({ type: String })
|
|
778
|
+
], TTCombobox.prototype, "error", void 0);
|
|
779
|
+
__decorate([
|
|
780
|
+
property({ type: Boolean, reflect: true })
|
|
781
|
+
], TTCombobox.prototype, "disabled", void 0);
|
|
782
|
+
__decorate([
|
|
783
|
+
property({ type: Boolean })
|
|
784
|
+
], TTCombobox.prototype, "required", void 0);
|
|
785
|
+
__decorate([
|
|
786
|
+
property({ type: String, reflect: true })
|
|
787
|
+
], TTCombobox.prototype, "size", void 0);
|
|
788
|
+
__decorate([
|
|
789
|
+
property({ type: Boolean, attribute: "label-hidden", reflect: true })
|
|
790
|
+
], TTCombobox.prototype, "labelHidden", void 0);
|
|
791
|
+
__decorate([
|
|
792
|
+
property({ type: Boolean })
|
|
793
|
+
], TTCombobox.prototype, "clearable", void 0);
|
|
794
|
+
__decorate([
|
|
795
|
+
state()
|
|
796
|
+
], TTCombobox.prototype, "open", void 0);
|
|
797
|
+
__decorate([
|
|
798
|
+
state()
|
|
799
|
+
], TTCombobox.prototype, "query", void 0);
|
|
800
|
+
__decorate([
|
|
801
|
+
state()
|
|
802
|
+
], TTCombobox.prototype, "activeIndex", void 0);
|
|
803
|
+
__decorate([
|
|
804
|
+
query("input")
|
|
805
|
+
], TTCombobox.prototype, "input", void 0);
|
|
806
|
+
__decorate([
|
|
807
|
+
query(".field")
|
|
808
|
+
], TTCombobox.prototype, "field", void 0);
|
|
809
|
+
__decorate([
|
|
810
|
+
query("ul")
|
|
811
|
+
], TTCombobox.prototype, "list", void 0);
|
|
812
|
+
TTCombobox = __decorate([
|
|
813
|
+
customElement("tt-combobox")
|
|
814
|
+
], TTCombobox);
|
|
815
|
+
export { TTCombobox };
|
|
816
|
+
//# sourceMappingURL=tt-combobox.js.map
|