From 7417bfe7af8677e267b60d2994a8b0c9492110f9 Mon Sep 17 00:00:00 2001 From: timea-solid <4144203+timea-solid@users.noreply.github.com> Date: Tue, 30 Jun 2026 13:44:57 +0200 Subject: [PATCH 1/4] remove duplicate load of button component --- .../components/actions/button/Button.test.ts | 164 ------------------ src/v2/components/actions/button/index.ts | 12 -- 2 files changed, 176 deletions(-) delete mode 100644 src/v2/components/actions/button/Button.test.ts diff --git a/src/v2/components/actions/button/Button.test.ts b/src/v2/components/actions/button/Button.test.ts deleted file mode 100644 index eb0ac217e..000000000 --- a/src/v2/components/actions/button/Button.test.ts +++ /dev/null @@ -1,164 +0,0 @@ -import { beforeEach, describe, expect, it, vi } from 'vitest' -import { Button } from './Button' -import './index' - -describe('SolidUIButton', () => { - beforeEach(() => { - document.body.innerHTML = '' - }) - - it('is defined as a custom element', () => { - expect(customElements.get('solid-ui-button')).toBe(Button) - }) - - it('renders a secondary button by default', async () => { - const button = new Button() - button.label = 'Upload' - - document.body.appendChild(button) - await button.updateComplete - - const nativeButton = button.shadowRoot?.querySelector( - 'button' - ) as HTMLButtonElement - - expect(button.variant).toBe('secondary') - expect(nativeButton.type).toBe('button') - expect(nativeButton.textContent?.trim()).toBe('Upload') - }) - - it('supports a selected state without forcing toggle semantics', async () => { - const button = new Button() - button.selected = true - - document.body.appendChild(button) - await button.updateComplete - - const nativeButton = button.shadowRoot?.querySelector( - 'button' - ) as HTMLButtonElement - - expect(button.hasAttribute('selected')).toBe(true) - expect(nativeButton.hasAttribute('aria-pressed')).toBe(false) - expect(nativeButton.hasAttribute('aria-selected')).toBe(false) - }) - - it('calls the callback property and still emits the native click event', async () => { - const button = new Button() - const handleClick = vi.fn() - const clickListener = vi.fn() - button.handleClick = handleClick - button.addEventListener('click', clickListener) - - document.body.appendChild(button) - await button.updateComplete - - const nativeButton = button.shadowRoot?.querySelector( - 'button' - ) as HTMLButtonElement - nativeButton.click() - - expect(handleClick).toHaveBeenCalledTimes(1) - expect(clickListener).toHaveBeenCalledTimes(1) - }) - - it('renders an image icon when the icon property is provided', async () => { - const button = new Button() - button.icon = 'data:image/svg+xml,%3Csvg%3E%3C/svg%3E' - - document.body.appendChild(button) - await button.updateComplete - - const icon = button.shadowRoot?.querySelector( - '.button__icon-image' - ) as HTMLImageElement - expect(icon.getAttribute('src')).toBe(button.icon) - }) - - it('supports an icon-only variant without rendering the label text', async () => { - const button = new Button() - button.variant = 'icon' - button.icon = 'data:image/svg+xml,%3Csvg%3E%3C/svg%3E' - button.label = 'Settings' - - document.body.appendChild(button) - await button.updateComplete - - const label = button.shadowRoot?.querySelector( - '.button__label' - ) as HTMLSpanElement - const icon = button.shadowRoot?.querySelector( - '.button__icon-image' - ) as HTMLImageElement - - expect(button.variant).toBe('icon') - expect(label).not.toBeNull() - expect(icon.getAttribute('src')).toBe(button.icon) - }) - - it('prefers slotted icon content over the icon property fallback', async () => { - const button = document.createElement('solid-ui-button') as Button - button.icon = 'data:image/svg+xml,%3Csvg%3E%3C/svg%3E' - - const slottedIcon = document.createElement('span') - slottedIcon.slot = 'icon' - slottedIcon.textContent = 'icon' - button.appendChild(slottedIcon) - - document.body.appendChild(button) - await button.updateComplete - await Promise.resolve() - await button.updateComplete - - expect(button.shadowRoot?.querySelector('slot[name="icon"]')).not.toBeNull() - expect(button.shadowRoot?.querySelector('.button__icon-image')).toBeNull() - }) - - it('renders slotted icon content without requiring an icon fallback property', async () => { - const button = document.createElement('solid-ui-button') as Button - - const slottedIcon = document.createElement('span') - slottedIcon.slot = 'icon' - slottedIcon.textContent = 'icon' - button.appendChild(slottedIcon) - - document.body.appendChild(button) - await button.updateComplete - await Promise.resolve() - await button.updateComplete - - expect(button.shadowRoot?.querySelector('slot[name="icon"]')).not.toBeNull() - expect(button.shadowRoot?.querySelector('.button__icon')).not.toBeNull() - expect(button.shadowRoot?.querySelector('.button__icon-image')).toBeNull() - }) - - it('applies layout styling hooks exposed through CSS custom properties', async () => { - const stylesheetText = Array.isArray(Button.styles) - ? Button.styles.map((styleSheet) => styleSheet.toString()).join('\n') - : Button.styles.toString() - - expect(stylesheetText).toContain( - '--button-padding-sm: 0 var(--button-padding-x-sm);' - ) - expect(stylesheetText).toContain('--button-border-width: 1px;') - expect(stylesheetText).toContain('padding: var(--button-padding-md);') - expect(stylesheetText).toContain( - 'border: var(--button-border-width) solid var(--button-border);' - ) - expect(stylesheetText).toContain( - 'border-radius: var(--button-border-radius);' - ) - expect(stylesheetText).toContain('font-weight: var(--button-font-weight);') - expect(stylesheetText).toContain('line-height: var(--button-line-height);') - expect(stylesheetText).toContain( - 'justify-content: var(--button-justify-content);' - ) - expect(stylesheetText).toContain( - 'box-shadow: var(--button-hover-box-shadow, var(--button-box-shadow));' - ) - expect(stylesheetText).toContain('outline: var(--button-focus-outline);') - expect(stylesheetText).toContain( - 'transform: var(--button-active-transform);' - ) - }) -}) diff --git a/src/v2/components/actions/button/index.ts b/src/v2/components/actions/button/index.ts index 3904c59e2..dff3e822a 100644 --- a/src/v2/components/actions/button/index.ts +++ b/src/v2/components/actions/button/index.ts @@ -1,15 +1,3 @@ import { Button } from './Button' export { Button } - -const BUTTON_TAG_NAME = 'solid-ui-button' - -declare global { - interface HTMLElementTagNameMap { - 'solid-ui-button': Button - } -} - -if (!customElements.get(BUTTON_TAG_NAME)) { - customElements.define(BUTTON_TAG_NAME, Button) -} From 2148864fbb742f1fa2dbf003cc145ba10cc78238 Mon Sep 17 00:00:00 2001 From: timea-solid <4144203+timea-solid@users.noreply.github.com> Date: Wed, 1 Jul 2026 10:08:51 +0200 Subject: [PATCH 2/4] carefully deleting some v2 components --- package-lock.json | 17 +- src/lib/forms/rdfHelper.ts | 10 + src/types/custom-elements.d.ts | 60 ++ src/v2/components/actions/button/Button.ts | 353 ----------- src/v2/components/actions/button/README.md | 198 ------ src/v2/components/actions/button/index.ts | 3 - .../forms/combobox/Combobox.test.ts | 247 -------- src/v2/components/forms/combobox/Combobox.ts | 583 ------------------ src/v2/components/forms/combobox/README.md | 221 ------- .../forms/combobox/comboboxTypes.ts | 6 - src/v2/components/forms/combobox/index.ts | 9 - src/v2/components/forms/select/README.md | 204 ------ src/v2/components/forms/select/Select.test.ts | 209 ------- src/v2/components/forms/select/Select.ts | 583 ------------------ src/v2/components/forms/select/index.ts | 9 - src/v2/components/forms/shared/downArrow.ts | 10 - src/v2/components/forms/shared/keyboard.ts | 82 --- .../components/forms/shared/listboxStyles.ts | 96 --- .../forms/shared/listboxTemplate.ts | 56 -- src/v2/components/forms/shared/optionTypes.ts | 5 - 20 files changed, 83 insertions(+), 2878 deletions(-) create mode 100644 src/lib/forms/rdfHelper.ts create mode 100644 src/types/custom-elements.d.ts delete mode 100644 src/v2/components/actions/button/Button.ts delete mode 100644 src/v2/components/actions/button/README.md delete mode 100644 src/v2/components/actions/button/index.ts delete mode 100644 src/v2/components/forms/combobox/Combobox.test.ts delete mode 100644 src/v2/components/forms/combobox/Combobox.ts delete mode 100644 src/v2/components/forms/combobox/README.md delete mode 100644 src/v2/components/forms/combobox/comboboxTypes.ts delete mode 100644 src/v2/components/forms/combobox/index.ts delete mode 100644 src/v2/components/forms/select/README.md delete mode 100644 src/v2/components/forms/select/Select.test.ts delete mode 100644 src/v2/components/forms/select/Select.ts delete mode 100644 src/v2/components/forms/select/index.ts delete mode 100644 src/v2/components/forms/shared/downArrow.ts delete mode 100644 src/v2/components/forms/shared/keyboard.ts delete mode 100644 src/v2/components/forms/shared/listboxStyles.ts delete mode 100644 src/v2/components/forms/shared/listboxTemplate.ts delete mode 100644 src/v2/components/forms/shared/optionTypes.ts diff --git a/package-lock.json b/package-lock.json index bf84cc956..cb57468c5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,7 +7,6 @@ "": { "name": "solid-ui", "version": "3.1.3-6", - "hasInstallScript": true, "license": "MIT", "dependencies": { "@awesome.me/webawesome": "^3.9.0", @@ -17,9 +16,7 @@ "escape-html": "^1.0.3", "lit": "^3.3.3", "mime-types": "^3.0.2", - "pane-registry": "3.1.2-1", "rdflib": "2.4.0", - "solid-logic": "4.0.8-1", "solid-namespace": "^0.5.4", "uuid": "^14.0.0" }, @@ -5747,7 +5744,10 @@ "version": "0.2.3", "resolved": "https://registry.npmjs.org/@uvdsl/solid-oidc-client-browser/-/solid-oidc-client-browser-0.2.3.tgz", "integrity": "sha512-WzVlxv46EUSoqm7ovsWJRZq8KEI/CdpA9O1fXoiP8bihs2cNxPnet3YcqvIYWYMsTrf0zsR031l5s/BzQ9MEgA==", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "jose": "^5.9.6" } @@ -10014,7 +10014,10 @@ "version": "5.10.0", "resolved": "https://registry.npmjs.org/jose/-/jose-5.10.0.tgz", "integrity": "sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg==", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "funding": { "url": "https://github.com/sponsors/panva" } @@ -11743,7 +11746,10 @@ "version": "3.1.2-1", "resolved": "https://registry.npmjs.org/pane-registry/-/pane-registry-3.1.2-1.tgz", "integrity": "sha512-aibuyF8pAgHDnmywsMEKUddIcqC0oPF8fNx2TP7zRpRW9aYk2iv06td+br8wenbmu2V/l6Y6aJmCkckHWN/GpA==", - "license": "MIT" + "dev": true, + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/parent-module": { "version": "1.0.1", @@ -12950,7 +12956,10 @@ "version": "4.0.8-1", "resolved": "https://registry.npmjs.org/solid-logic/-/solid-logic-4.0.8-1.tgz", "integrity": "sha512-9Vtg9ukEaDTIvBPqECORWRtEJ3nTlEvInJzUCJZDp1qjN9VIdJG0mSiPTIP9RXOEOSAA+Q+cP3d7BzsNyI/RVA==", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@uvdsl/solid-oidc-client-browser": "^0.2.3", "solid-namespace": "^0.5.4" diff --git a/src/lib/forms/rdfHelper.ts b/src/lib/forms/rdfHelper.ts new file mode 100644 index 000000000..ebe845c24 --- /dev/null +++ b/src/lib/forms/rdfHelper.ts @@ -0,0 +1,10 @@ +import { LiveStore, NamedNode } from 'rdflib' + +export function getGraph (subject?: NamedNode) { + return subject?.doc ? subject.doc() : undefined +} + +export function getFormProperty (store: LiveStore, subject: NamedNode | undefined, property: NamedNode, graph?: any): NamedNode | undefined { + if (!subject) return undefined + return store.any(subject, property, null, graph) as NamedNode | undefined +} diff --git a/src/types/custom-elements.d.ts b/src/types/custom-elements.d.ts new file mode 100644 index 000000000..00ae89ef0 --- /dev/null +++ b/src/types/custom-elements.d.ts @@ -0,0 +1,60 @@ +/** + * This file is auto-generated by vite-config/components.ts. + * Do not edit this file directly. + */ + +import type Account from '../components/account/Account' +import type Avatar from '../components/avatar/Avatar' +import type Button from '../components/button/Button' +import type Combobox from '../components/combobox/Combobox' +import type ComboboxOption from '../components/combobox-option/ComboboxOption' +import type Dialog from '../components/dialog/Dialog' +import type DialogContent from '../components/dialog-content/DialogContent' +import type DialogFooter from '../components/dialog-footer/DialogFooter' +import type DialogHeader from '../components/dialog-header/DialogHeader' +import type DialogProvider from '../components/dialog-provider/DialogProvider' +import type DialogsRoot from '../components/dialogs-root/DialogsRoot' +import type Footer from '../components/footer/Footer' +import type Guard from '../components/guard/Guard' +import type Header from '../components/header/Header' +import type Input from '../components/input/Input' +import type LoginButton from '../components/login-button/LoginButton' +import type LoginModal from '../components/login-modal/LoginModal' +import type LogoutButton from '../components/logout-button/LogoutButton' +import type Menu from '../components/menu/Menu' +import type MenuItem from '../components/menu-item/MenuItem' +import type PhotoCapture from '../components/photo-capture/PhotoCapture' +import type Provider from '../components/provider/Provider' +import type Select from '../components/select/Select' +import type SelectOption from '../components/select-option/SelectOption' +import type SignupButton from '../components/signup-button/SignupButton' + +declare global { + interface HTMLElementTagNameMap { + 'solid-ui-account': Account + 'solid-ui-avatar': Avatar + 'solid-ui-button': Button + 'solid-ui-combobox': Combobox + 'solid-ui-combobox-option': ComboboxOption + 'solid-ui-dialog': Dialog + 'solid-ui-dialog-content': DialogContent + 'solid-ui-dialog-footer': DialogFooter + 'solid-ui-dialog-header': DialogHeader + 'solid-ui-dialog-provider': DialogProvider + 'solid-ui-dialogs-root': DialogsRoot + 'solid-ui-footer': Footer + 'solid-ui-guard': Guard + 'solid-ui-header': Header + 'solid-ui-input': Input + 'solid-ui-login-button': LoginButton + 'solid-ui-login-modal': LoginModal + 'solid-ui-logout-button': LogoutButton + 'solid-ui-menu': Menu + 'solid-ui-menu-item': MenuItem + 'solid-ui-photo-capture': PhotoCapture + 'solid-ui-provider': Provider + 'solid-ui-select': Select + 'solid-ui-select-option': SelectOption + 'solid-ui-signup-button': SignupButton + } +} diff --git a/src/v2/components/actions/button/Button.ts b/src/v2/components/actions/button/Button.ts deleted file mode 100644 index 30c27fe3a..000000000 --- a/src/v2/components/actions/button/Button.ts +++ /dev/null @@ -1,353 +0,0 @@ -import { LitElement, html, css, nothing } from 'lit' - -export class Button extends LitElement { - static properties = { - label: { type: String, reflect: true }, - type: { type: String, reflect: true }, - disabled: { type: Boolean, reflect: true }, - selected: { type: Boolean, reflect: true }, - ariaLabel: { type: String, attribute: 'aria-label' }, - name: { type: String, reflect: true }, - value: { type: String, reflect: true }, - variant: { type: String, reflect: true }, - size: { type: String, reflect: true }, - theme: { type: String, reflect: true }, - fullWidth: { type: Boolean, attribute: 'full-width', reflect: true }, - icon: { type: String, reflect: true }, - iconPosition: { type: String, attribute: 'icon-position', reflect: true }, - contentAlign: { type: String, attribute: 'content-align', reflect: true }, - handleClick: { attribute: false }, - _hasSlottedIcon: { state: true } - } - - static styles = css` - :host { - display: inline-flex; - align-items: center; - justify-content: center; - --button-background: var(--color-background, #f8f9fb); - --button-text: var(--color-text-subheading, #101828); - --button-border: var(--color-border-button, var(--gray-300, #D1D5DC)); - --button-hover-background: var(--color-surface-subtle, rgba(15, 23, 43, 0.04)); - --button-hover-border: var(--color-border-button-hover, var(--gray-400, #99A1AF)); - --button-hover-text: var(--color-text-subheading, #101828); - --button-selected-background: var(--color-surface-selected, var(--color-surface-action, var(--color-primary, #7C4DFF))); - --button-selected-text: var(--color-text-selected, var(--color-text-on-action, var(--white, #FFF))); - --button-selected-border: var(--color-border-selected, var(--color-border-action, var(--color-primary, #7C4DFF))); - --button-icon-color: currentColor; - --button-focus-ring: var(--color-focus-ring, var(--color-primary, #7C4DFF)); - --button-height-sm: 1.875rem; - --button-height-md: var(--min-touch-target, 44px); - --button-height-lg: calc(var(--min-touch-target, 44px) + 0.5rem); - --button-padding-x-sm: var(--spacing-xs, 0.75rem); - --button-padding-x-md: var(--spacing-sm, 0.9375rem); - --button-padding-x-lg: var(--spacing-md, 1.25rem); - --button-font-size-sm: var(--font-size-sm, 0.875rem); - --button-font-size-md: var(--font-size-md, 1rem); - --button-font-size-lg: var(--font-size-lg, 1.125rem); - --button-icon-size-sm: var(--icon-xxxs, 0.75rem); - --button-icon-size-md: var(--icon-xxs, 1rem); - --button-icon-size-lg: var(--icon-xxs, 1rem); - --button-padding-sm: 0 var(--button-padding-x-sm); - --button-padding-md: 0 var(--button-padding-x-md); - --button-padding-lg: 0 var(--button-padding-x-lg); - --button-border-width: 1px; - --button-border-radius: var(--border-radius-base, 0.3125rem); - --button-font-weight: var(--font-weight-bold, 600); - --button-line-height: 1; - --button-justify-content: center; - --button-content-justify-content: var(--button-justify-content); - --button-label-text-align: center; - --button-box-shadow: none; - --button-hover-box-shadow: var(--button-box-shadow); - --button-active-box-shadow: var(--button-hover-box-shadow); - --button-active-transform: translateY(1px); - --button-focus-outline: 2px solid var(--button-focus-ring); - --button-focus-outline-offset: 2px; - --button-focus-box-shadow: none; - --button-transition: transform 0.2s ease, background-color 0.2s ease, color 0.2s ease, border-color 0.2s ease; - } - - :host([hidden]) { - display: none; - } - - :host([theme='dark']) { - --button-background: var(--color-background, #242a31); - --button-text: var(--color-text-subheading, #f8f9fb); - --button-border: var(--color-border, #46515b); - --button-hover-background: var(--color-surface-subtle, rgba(15, 23, 43, 0.04)); - --button-hover-border: var(--color-border, #46515b); - --button-hover-text: var(--color-text-subheading, #f8f9fb); - } - - :host([variant='primary']) { - --button-background: var(--color-surface-action, var(--color-primary, #7C4DFF)); - --button-text: var(--color-text-on-action, var(--white, #FFF)); - --button-border: var(--color-border-action, var(--color-primary, #7C4DFF)); - --button-hover-background: var(--color-surface-action-hover, #6d3cf2); - --button-hover-border: var(--color-border-action, var(--color-primary, #7C4DFF)); - --button-hover-text: var(--color-text-on-action, var(--white, #FFF)); - } - - :host([variant='icon']) { - --button-padding-x-sm: var(--spacing-xxs, 0.3125rem); - --button-padding-x-md: var(--spacing-base, 0.5rem); - --button-padding-x-lg: var(--spacing-2xs, 0.625rem); - } - - :host([full-width]) { - width: 100%; - } - - :host([content-align='start']) { - --button-justify-content: flex-start; - --button-content-justify-content: flex-start; - --button-label-text-align: left; - } - - :host([content-align='end']) { - --button-justify-content: flex-end; - --button-content-justify-content: flex-end; - --button-label-text-align: right; - } - - :host([selected]) { - --button-background: var(--button-selected-background); - --button-text: var(--button-selected-text); - --button-border: var(--button-selected-border); - } - - .button { - display: inline-flex; - width: 100%; - min-height: var(--button-height-md); - padding: var(--button-padding-md); - align-items: center; - justify-content: var(--button-justify-content); - gap: var(--spacing-xxs, 0.375rem); - border-radius: var(--button-border-radius); - background: var(--button-background); - border: var(--button-border-width) solid var(--button-border); - box-shadow: var(--button-box-shadow); - color: var(--button-text); - cursor: pointer; - font: inherit; - font-size: var(--button-font-size-md); - font-weight: var(--button-font-weight); - line-height: var(--button-line-height); - white-space: nowrap; - text-decoration: none; - box-sizing: border-box; - transition: var(--button-transition); - } - - :host([size='sm']) .button { - min-height: var(--button-height-sm); - padding: var(--button-padding-sm); - font-size: var(--button-font-size-sm); - } - - :host([size='lg']) .button { - min-height: var(--button-height-lg); - padding: var(--button-padding-lg); - font-size: var(--button-font-size-lg); - } - - .button:hover:not(:disabled) { - background: var(--button-hover-background); - border-color: var(--button-hover-border, var(--button-border)); - box-shadow: var(--button-hover-box-shadow, var(--button-box-shadow)); - color: var(--button-hover-text); - } - - :host([selected]) .button:hover:not(:disabled) { - background: var(--button-hover-background); - border-color: var(--button-selected-border); - color: var(--button-selected-text); - } - - .button:focus-visible { - outline: var(--button-focus-outline); - outline-offset: var(--button-focus-outline-offset); - box-shadow: var(--button-focus-box-shadow); - } - - .button:disabled { - opacity: 0.6; - cursor: not-allowed; - transform: none; - } - - .button__content { - display: inline-flex; - align-items: center; - justify-content: var(--button-content-justify-content); - gap: inherit; - width: 100%; - } - - :host([icon-position='end']) .button__content { - flex-direction: row-reverse; - } - - .button__icon { - width: var(--button-icon-size-md); - height: var(--button-icon-size-md); - display: inline-flex; - align-items: center; - justify-content: center; - color: var(--button-icon-color); - flex-shrink: 0; - } - - :host([size='sm']) .button__icon { - width: var(--button-icon-size-sm); - height: var(--button-icon-size-sm); - } - - :host([size='lg']) .button__icon { - width: var(--button-icon-size-lg); - height: var(--button-icon-size-lg); - } - - .button__icon ::slotted(*) { - width: 100%; - height: 100%; - display: block; - } - - .button__icon-image { - width: 100%; - height: 100%; - object-fit: contain; - } - - .button__label { - display: inline-flex; - align-items: center; - justify-content: var(--button-content-justify-content); - min-width: 0; - width: 100%; - text-align: var(--button-label-text-align); - } - - :host([variant='icon']) .button__label { - display: none; - } - - .button:active { - transform: var(--button-active-transform); - box-shadow: var(--button-active-box-shadow, var(--button-hover-box-shadow, var(--button-box-shadow))); - } - ` - - declare label: string - declare type: 'button' | 'submit' | 'reset' - declare disabled: boolean - declare selected: boolean - declare ariaLabel: string - declare name: string - declare value: string - declare variant: 'primary' | 'secondary' | 'icon' - declare size: 'sm' | 'md' | 'lg' - declare theme: 'light' | 'dark' - declare fullWidth: boolean - declare icon: string - declare iconPosition: 'start' | 'end' - declare contentAlign: 'start' | 'center' | 'end' - declare handleClick?: (event: MouseEvent) => void - declare _hasSlottedIcon: boolean - private _iconSlotObserver?: MutationObserver - - constructor () { - super() - this.label = '' - this.type = 'button' - this.disabled = false - this.selected = false - this.ariaLabel = '' - this.name = '' - this.value = '' - this.variant = 'secondary' - this.size = 'md' - this.theme = 'light' - this.fullWidth = false - this.icon = '' - this.iconPosition = 'start' - this.contentAlign = 'center' - this.handleClick = undefined - this._hasSlottedIcon = false - } - - connectedCallback () { - super.connectedCallback() - this._syncSlottedIconPresence() - - this._iconSlotObserver = new MutationObserver(() => { - this._syncSlottedIconPresence() - }) - - this._iconSlotObserver.observe(this, { - childList: true, - attributes: true, - attributeFilter: ['slot'] - }) - } - - disconnectedCallback () { - this._iconSlotObserver?.disconnect() - this._iconSlotObserver = undefined - super.disconnectedCallback() - } - - private _handleButtonClick (event: MouseEvent) { - this.handleClick?.(event) - } - - private _handleIconSlotChange (event: Event) { - const slot = event.target as HTMLSlotElement - this._hasSlottedIcon = slot.assignedNodes({ flatten: true }).length > 0 - } - - private _syncSlottedIconPresence () { - this._hasSlottedIcon = this.querySelector('[slot="icon"]') !== null - } - - private _renderIcon () { - if (!this._hasSlottedIcon && !this.icon) { - return nothing - } - - return html` - - - ${!this._hasSlottedIcon && this.icon - ? html`` - : nothing} - - ` - } - - render () { - return html` - - ` - } -} diff --git a/src/v2/components/actions/button/README.md b/src/v2/components/actions/button/README.md deleted file mode 100644 index db7d74ca9..000000000 --- a/src/v2/components/actions/button/README.md +++ /dev/null @@ -1,198 +0,0 @@ -# solid-ui-button component - -A Lit-based base button component for shared actions across panes and apps. It stays semantic by rendering a native ` - - - ` - } -} diff --git a/src/v2/components/forms/combobox/README.md b/src/v2/components/forms/combobox/README.md deleted file mode 100644 index 5a585bf62..000000000 --- a/src/v2/components/forms/combobox/README.md +++ /dev/null @@ -1,221 +0,0 @@ -# solid-ui-combobox component - -A Lit-based custom element that renders a styled combobox with a text input and a custom popup listbox. It supports async suggestion loading through a consumer-provided `suggestionProvider`, keyboard navigation, `input` and `change` events, and keeps the currently selected option at the top of the popup when opened. - -## Installation - -```bash -npm install solid-ui -``` - -## Usage in a bundled project (webpack, Vite, Rollup, etc.) - -```javascript -import { Combobox } from 'solid-ui/components/forms/combobox' -``` - -The legacy flat import path `solid-ui/components/combobox` still works, but the grouped `forms/combobox` path is the preferred long-term entrypoint. - -```html - - - -``` - -## Usage in a plain HTML page (CDN / script tag) - -```html - - - - - -``` - -## TypeScript - -```typescript -import { Combobox } from 'solid-ui/components/forms/combobox' - -const combobox = document.querySelector('solid-ui-combobox') as Combobox - -combobox.suggestionProvider = async (query) => { - return [ - { label: `Result for ${query}`, value: query.toLowerCase() } - ] -} - -combobox.addEventListener( - 'change', - (e: CustomEvent<{ value: string; label: string; option?: { label: string; value: string } }>) => { - console.log(e.detail.value) - } -) -``` - -The component works with suggestion objects shaped like: - -```typescript -type ComboboxSuggestion = { - label: string - value: string - disabled?: boolean - publicId?: string - meta?: Record -} -``` - -## API - -### Properties / attributes - -| Property | Attribute | Type | Default | Description | -|----------|-----------|------|---------|-------------| -| `label` | `label` | `string` | `Select an option` | Visible label rendered above the input. If omitted, provide an `aria-label` for accessibility. | -| `placeholder` | `placeholder` | `string` | `Type to search` | Placeholder text shown inside the input when it is empty. | -| `theme` | `theme` | `'light' \| 'dark'` | `'light'` | Sets the colour theme. | -| `options` | `options` | `ComboboxSuggestion[]` | `[]` | Current list of suggestions shown in the popup. In practice this should be set as a property from JavaScript rather than as an HTML attribute. | -| `layout` | `layout` | `'desktop' \| 'mobile'` | `'desktop'` | Layout mode reserved for integration with other responsive components. | -| `value` | `value` | `string` | `''` | The currently selected suggestion value. If it matches a suggestion, that suggestion is shown in the input and moved to the top of the popup when opened. | -| `inputValue` | none | `string` | `''` | Current raw text shown in the input field. This updates as the user types. | -| `suggestionProvider` | none | `(query: string) => Promise` | `undefined` | Optional async function supplied by the consumer. It receives the current input text and returns normalized suggestions for the popup. | - -### Events - -| Event | Detail | Description | -|-------|--------|-------------| -| `input` | `{ value: string }` | Fired when the user types in the input. Useful when the consumer wants to observe free text in addition to providing a `suggestionProvider`. | -| `change` | `{ value: string; label: string; option?: ComboboxSuggestion }` | Fired when the user selects a suggestion from the popup or confirms a keyboard selection. | - -### CSS custom properties - -These can be set on `solid-ui-combobox`, on a container element, or on `:root`. - -| Variable | Fallback | Description | -|----------|----------|-------------| -| `--popup-background` | `--color-background` | Popup surface background. | -| `--popup-text` | `--color-text` | Popup text colour. | -| `--popup-border` | `--color-border` / `#E5E7EB` | Popup border colour. | -| `--popup-shadow` | `--box-shadow-sm` / `0 1px 4px ...` | Popup shadow. | -| `--input-background` | `--color-background` | Input and popup background. | -| `--input-text` | `--color-text` | Input text colour. | -| `--input-border` | `--color-text` | Input border colour. | -| `--label-color` | `--grey-purple-700` | Label text colour. | -| `--placeholder-color` | `--grey-purple-700` | Placeholder text colour. | -| `--item-text` | `--color-text` | Option text colour. | -| `--item-selected-text` | `--color-primary` / `#7c4dff` | Active option text colour. | -| `--item-hover-background` | `--lavender-300` / `#e6dcff` | Hover background for option rows. | -| `--item-selected-background` | `--lavender-400` / `#cbb9ff` | Active option background. | - -The component also inherits common design-system tokens such as `--border-radius-base`, `--border-radius-md`, `--color-background`, `--color-border`, `--color-text`, `--color-primary`, `--box-shadow-sm`, `--lavender-300`, and `--lavender-400`. - -### CSS shadow parts - -These parts can be styled from a consuming repo using `::part(...)`. - -| Part | Description | -|------|-------------| -| `input` | The text input inside the combobox field. | -| `listbox` | The `