mirror of
https://github.com/shoelace-style/webawesome.git
synced 2026-01-20 15:54:15 +00:00
Compare commits
9 Commits
combobox-f
...
slider-fix
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
18e5612564 | ||
|
|
3d395653fc | ||
|
|
99c6e68aa4 | ||
|
|
b3844ef77f | ||
|
|
a50648c6e8 | ||
|
|
b6b82fb0ac | ||
|
|
fa073e8d21 | ||
|
|
e0f6ff11ec | ||
|
|
6403588285 |
@@ -105,6 +105,7 @@
|
||||
"keydown",
|
||||
"keyframes",
|
||||
"keymaker",
|
||||
"Kickstarter",
|
||||
"Konnor",
|
||||
"Kool",
|
||||
"labelledby",
|
||||
@@ -117,6 +118,7 @@
|
||||
"lowercasing",
|
||||
"Lucide",
|
||||
"maxlength",
|
||||
"mdash",
|
||||
"Menlo",
|
||||
"menuitemcheckbox",
|
||||
"menuitemradio",
|
||||
@@ -130,6 +132,7 @@
|
||||
"mouseout",
|
||||
"mouseup",
|
||||
"multiselectable",
|
||||
"nbsp",
|
||||
"nextjs",
|
||||
"nocheck",
|
||||
"noindex",
|
||||
@@ -179,6 +182,7 @@
|
||||
"shadowrootmode",
|
||||
"Shortcode",
|
||||
"Shortcodes",
|
||||
"signup",
|
||||
"sitedir",
|
||||
"slotchange",
|
||||
"smartquotes",
|
||||
|
||||
18
package-lock.json
generated
18
package-lock.json
generated
@@ -14053,7 +14053,8 @@
|
||||
"devDependencies": {
|
||||
"@wc-toolkit/jsx-types": "^1.3.0",
|
||||
"eleventy-plugin-git-commit-date": "^0.1.3",
|
||||
"esbuild": "^0.25.11"
|
||||
"esbuild": "^0.25.11",
|
||||
"npm-check-updates": "^19.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.17.0"
|
||||
@@ -14526,6 +14527,21 @@
|
||||
"node": "^18 || >=20"
|
||||
}
|
||||
},
|
||||
"packages/webawesome-pro/node_modules/npm-check-updates": {
|
||||
"version": "19.1.2",
|
||||
"resolved": "https://registry.npmjs.org/npm-check-updates/-/npm-check-updates-19.1.2.tgz",
|
||||
"integrity": "sha512-FNeFCVgPOj0fz89hOpGtxP2rnnRHR7hD2E8qNU8SMWfkyDZXA/xpgjsL3UMLSo3F/K13QvJDnbxPngulNDDo/g==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"ncu": "build/cli.js",
|
||||
"npm-check-updates": "build/cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0",
|
||||
"npm": ">=8.12.1"
|
||||
}
|
||||
},
|
||||
"packages/webawesome/node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.25.11",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.11.tgz",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "@webawesome/monorepo",
|
||||
"private": true,
|
||||
"description": "A forward-thinking library of web components.",
|
||||
"version": "3.0.0-alpha.13",
|
||||
"version": "3.0.0",
|
||||
"homepage": "https://webawesome.com/",
|
||||
"author": "Web Awesome",
|
||||
"license": "MIT",
|
||||
@@ -85,4 +85,4 @@
|
||||
"prettier --write"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -81,7 +81,15 @@
|
||||
<li><span class="is-planned wa-split">Charts <span><a href="https://github.com/shoelace-style/webawesome/issues/1073" target="_blank">{{ plannedBadge("A Web Awesome Kickstarter stretch goal!") }}</a>{{ proBadge({ description: "This will require access to Web Awesome Pro" }) }}</span></span></li>
|
||||
<li><a href="/docs/components/checkbox/">Checkbox</a></li>
|
||||
<li><a href="/docs/components/color-picker/">Color Picker</a></li>
|
||||
<li><span class="is-planned wa-split">Combobox <span><a href="https://github.com/shoelace-style/webawesome/issues/1074" target="_blank">{{ plannedBadge("A Web Awesome Kickstarter stretch goal!") }}</a>{{ proBadge({ description: "This will require access to Web Awesome Pro" }) }}</span></span></li>
|
||||
<li>
|
||||
<span class="wa-split">
|
||||
<span>
|
||||
<a href="/docs/components/combobox">Combobox</a>
|
||||
<wa-icon name="flask" aria-hidden="true" class="icon-shrink"></wa-icon>
|
||||
</span>
|
||||
{{ proBadge() }}
|
||||
</span>
|
||||
</li>
|
||||
<li><a href="/docs/components/comparison/">Comparison</a></li>
|
||||
<li>
|
||||
<a class="wa-cluster wa-gap-xs" href="/docs/components/copy-button/">
|
||||
@@ -90,7 +98,7 @@
|
||||
</a>
|
||||
</li>
|
||||
<li><span class="is-planned wa-split">Data Grid <span><a href="https://github.com/shoelace-style/webawesome/issues/1072" target="_blank">{{ plannedBadge("A Web Awesome Kickstarter stretch goal!") }}</a>{{ proBadge({ description: "This will require access to Web Awesome Pro" }) }}</span></span></li>
|
||||
<li><span class="is-planned wa-split">Datepicker <span><a href="https://github.com/shoelace-style/webawesome/issues/1075" target="_blank">{{ plannedBadge("A Web Awesome Kickstarter stretch goal!") }}</a>{{ proBadge({ description: "This will require access to Web Awesome Pro" }) }}</span></span></li>
|
||||
<li><span class="is-planned wa-split">Date Picker <span><a href="https://github.com/shoelace-style/webawesome/issues/1075" target="_blank">{{ plannedBadge("A Web Awesome Kickstarter stretch goal!") }}</a>{{ proBadge({ description: "This will require access to Web Awesome Pro" }) }}</span></span></li>
|
||||
<li><a href="/docs/components/details/">Details</a></li>
|
||||
<li><a href="/docs/components/dialog/">Dialog</a></li>
|
||||
<li><a href="/docs/components/divider/">Divider</a></li>
|
||||
|
||||
@@ -8,10 +8,13 @@
|
||||
<wa-badge variant="neutral">Since {{ component.since }}</wa-badge>
|
||||
<wa-badge
|
||||
{% if component.status == 'stable' %}variant="brand"{% endif %}
|
||||
{% if component.status == 'experimental' %}variant="warning"{% endif %}
|
||||
{% if component.status == 'experimental' %}variant="warning" appearance="filled"{% endif %}
|
||||
>
|
||||
{{ component.status }}
|
||||
</wa-badge>
|
||||
{% if isProComponent %}
|
||||
<wa-badge class="pro">Pro</wa-badge>
|
||||
{% endif %}
|
||||
</div>
|
||||
<p class="component-summary">
|
||||
{{ component.summary | inlineMarkdown | safe }}
|
||||
@@ -20,6 +23,37 @@
|
||||
|
||||
{# Component API #}
|
||||
{% block afterContent %}
|
||||
{# Importing #}
|
||||
<h2>Importing</h2>
|
||||
<p>
|
||||
Autoloading components via <a href="/docs/#using-a-project">projects</a> is the recommended way to import components. If you prefer to do it manually, use one of the following code snippets.
|
||||
</p>
|
||||
|
||||
{% set componentName = component.tagName | stripPrefix %}
|
||||
{% set componentPath = ["components/", componentName, "/", componentName, ".js"] | join("") %}
|
||||
<wa-tab-group label="How would you like to import this component?">
|
||||
<wa-tab panel="cdn">CDN</wa-tab>
|
||||
<wa-tab panel="npm">npm</wa-tab>
|
||||
<wa-tab panel="react">React</wa-tab>
|
||||
<wa-tab-panel name="cdn">
|
||||
<p>
|
||||
Let your project code do the work! <a href="/signup">Sign up for free</a> to use a project with your very own CDN — it's the fastest and easiest way to use Web Awesome.
|
||||
</p>
|
||||
</wa-tab-panel>
|
||||
<wa-tab-panel name="npm">
|
||||
<p>
|
||||
To manually import this component from NPM, use the following code.
|
||||
</p>
|
||||
<pre><code class="language-js">import '@awesome.me/webawesome/dist/{{ componentPath }}';</code></pre>
|
||||
</wa-tab-panel>
|
||||
<wa-tab-panel name="react">
|
||||
<p>
|
||||
To manually import this component from React, use the following code.
|
||||
</p>
|
||||
<pre><code class="language-js">import {{ component.name }} from '@awesome.me/webawesome/dist/react/{{ componentName }}';</code></pre>
|
||||
</wa-tab-panel>
|
||||
</wa-tab-group>
|
||||
|
||||
{# Slots #}
|
||||
{% if component.slots.length %}
|
||||
<h2>Slots</h2>
|
||||
@@ -270,38 +304,6 @@
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
||||
{# Importing #}
|
||||
<h2>Importing</h2>
|
||||
<p>
|
||||
Autoloading components via <a href="/docs/#using-a-project">projects</a> is the recommended way to import components. If you prefer to do it manually, use one of the following code snippets.
|
||||
</p>
|
||||
|
||||
|
||||
{% set componentName = component.tagName | stripPrefix %}
|
||||
{% set componentPath = ["components/", componentName, "/", componentName, ".js"] | join("") %}
|
||||
<wa-tab-group label="How would you like to import this component?">
|
||||
<wa-tab panel="cdn">CDN</wa-tab>
|
||||
<wa-tab panel="npm">npm</wa-tab>
|
||||
<wa-tab panel="react">React</wa-tab>
|
||||
<wa-tab-panel name="cdn">
|
||||
<p>
|
||||
Let your project code do the work! <a href="/signup">Sign up for free</a> to use a project with your very own CDN — it's the fastest and easiest way to use Web Awesome.
|
||||
</p>
|
||||
</wa-tab-panel>
|
||||
<wa-tab-panel name="npm">
|
||||
<p>
|
||||
To manually import this component from NPM, use the following code.
|
||||
</p>
|
||||
<pre><code class="language-js">import '@awesome.me/webawesome/dist/{{ componentPath }}';</code></pre>
|
||||
</wa-tab-panel>
|
||||
<wa-tab-panel name="react">
|
||||
<p>
|
||||
To manually import this component from React, use the following code.
|
||||
</p>
|
||||
<pre><code class="language-js">import {{ component.name }} from '@awesome.me/webawesome/dist/react/{{ componentName }}';</code></pre>
|
||||
</wa-tab-panel>
|
||||
</wa-tab-group>
|
||||
|
||||
<wa-divider></wa-divider>
|
||||
|
||||
<div class="component-help">
|
||||
|
||||
@@ -285,9 +285,10 @@ Remember that custom tags are rendered in a shadow root. To style them, you can
|
||||
const name = option.querySelector('wa-icon[slot="start"]').name;
|
||||
|
||||
// You can return a string, a Lit Template, or an HTMLElement here
|
||||
// Important: include data-value so the tag can be removed properly!
|
||||
return `
|
||||
<wa-tag with-remove>
|
||||
<wa-icon name="${name}" style="padding-inline-end: .5rem;"></wa-icon>
|
||||
<wa-tag with-remove data-value="${option.value}">
|
||||
<wa-icon name="${name}"></wa-icon>
|
||||
${option.label}
|
||||
</wa-tag>
|
||||
`;
|
||||
@@ -299,6 +300,10 @@ Remember that custom tags are rendered in a shadow root. To style them, you can
|
||||
Be sure you trust the content you are outputting! Passing unsanitized user input to `getTag()` can result in XSS vulnerabilities.
|
||||
:::
|
||||
|
||||
:::info
|
||||
When using custom tags with `with-remove`, you must include the `data-value` attribute set to the option's value. This allows the select to identify which option to deselect when the tag's remove button is clicked.
|
||||
:::
|
||||
|
||||
### Lazy loading options
|
||||
|
||||
Lazy loading options works similarly to native `<select>` elements. The select component handles various scenarios intelligently:
|
||||
|
||||
@@ -13,6 +13,9 @@ Components with the <wa-badge variant="warning">Experimental</wa-badge> badge sh
|
||||
|
||||
## Next
|
||||
|
||||
- Added `<wa-combobox>` as an experimental pro component [issue:1074]
|
||||
- Added version 2.0.0 of the [official Web Awesome Figma Design Kit](/docs/resources/figma)
|
||||
- Added npm support for Web Awesome Pro
|
||||
- Added `layers.css` to define cascade layer order and updated palettes, themes, native styles, and utilities to import the new rule for more fail-safe modularity [pr:1793]
|
||||
- Fixed a bug in `<wa-slider>` that caused some touch devices to end up with the incorrect value [issue:1703]
|
||||
- Fixed a bug in `<wa-card>` that prevented some slots from being detected correctly [discuss:1450]
|
||||
@@ -21,7 +24,12 @@ Components with the <wa-badge variant="warning">Experimental</wa-badge> badge sh
|
||||
- Fixed a bug in `<wa-tree-item>` that caused the spinner to not show when lazy loading [issue:1678]
|
||||
- Fixed a bug in `<wa-dropdown>` that caused the browser to hang when cancelling the `wa-hide` event [issue:1483]
|
||||
- Fixed a bug in `<wa-dropdown-item>` that prevented the icon dependency from being imported [issue:1825]
|
||||
- Fixed a bug in `<wa-select>` that prevented clicks on the tag's remove button from removing options in multiple mode
|
||||
- Fixed a bug in `<wa-select>` that caused tags to appear in alphabetical order instead of selection order when using `multiple`
|
||||
- Improved performance of `<wa-icon>` so initial rendering occurs faster, especially with multiple icons on the page [issue:1729]
|
||||
- Improved `<wa-slider>` to not throw an error when string values are passed to the `min`, `max`, and `step` properties [issue:1823]
|
||||
- Fixed a bug in Web Awesome form controls that caused `<wa-input form="foo">` to set the form property to equal `"foo"` instead of returning an `HTMLFormElement` breaking platform expectations. [pr:1815]
|
||||
- Fixed a bug in `<wa-button>` causing it to not copy over attributes for form submissions. [pr:1815]
|
||||
- Improved performance of all components by fixing how CSS is imported and reused [issue:1812]
|
||||
- Modified the default `transition` styles of `<wa-dropdown-item>` to use design tokens [pr:1693]
|
||||
|
||||
@@ -507,4 +515,4 @@ Many of these changes and improvements were the direct result of feedback from u
|
||||
|
||||
</details>
|
||||
|
||||
Did we miss something? [Let us know!](https://github.com/shoelace-style/webawesome/discussions)
|
||||
Did we miss something? [Let us know!](https://github.com/shoelace-style/webawesome/discussions)
|
||||
@@ -67,7 +67,7 @@
|
||||
"check-updates": "npm-check-updates --cooldown 7 --interactive --format group",
|
||||
"print-version": "echo $npm_package_version",
|
||||
"tag-version": "git tag -a \"v$(npm run print-version | tail -n1)\" -m \"tag v$(npm run print-version | tail -n1)\"",
|
||||
"postversion": "npm run tag-version"
|
||||
"postversion": "npm run tag-version && node ./scripts/update-root-version.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.17.0"
|
||||
|
||||
27
packages/webawesome/scripts/update-root-version.js
Executable file
27
packages/webawesome/scripts/update-root-version.js
Executable file
@@ -0,0 +1,27 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import * as fs from 'node:fs';
|
||||
import * as path from 'node:path';
|
||||
import * as url from 'url';
|
||||
|
||||
const __dirname = url.fileURLToPath(new URL('.', import.meta.url));
|
||||
|
||||
const monorepoRoot = path.resolve(__dirname, '..', '..', '..');
|
||||
const rootPackageJSONFile = path.join(monorepoRoot, 'package.json');
|
||||
const webawesomePackageJSONFile = path.join(path.resolve(__dirname, '..'), 'package.json');
|
||||
|
||||
const rootPackageJSON = JSON.parse(fs.readFileSync(rootPackageJSONFile));
|
||||
const webawesomePackageJSON = JSON.parse(fs.readFileSync(webawesomePackageJSONFile));
|
||||
|
||||
const currentVersion = webawesomePackageJSON.version;
|
||||
rootPackageJSON.version = currentVersion;
|
||||
|
||||
fs.writeFileSync(rootPackageJSONFile, JSON.stringify(rootPackageJSON, null, 2));
|
||||
|
||||
const versionsFile = path.join(monorepoRoot, 'VERSIONS.txt');
|
||||
const versions = fs.readFileSync(versions).split(/\r?\n/);
|
||||
|
||||
// TODO: Make this smart and understand semver and "insert" in the correct spot instead of appending.
|
||||
if (!versions.includes(currentVersion)) {
|
||||
fs.appendFileSync(webawesomePackageJSON.version);
|
||||
}
|
||||
@@ -115,7 +115,6 @@ export default class WaButton extends WebAwesomeFormAssociatedElement {
|
||||
* The "form owner" to associate the button with. If omitted, the closest containing form will be used instead. The
|
||||
* value of this attribute must be an id of a form in the same document or shadow root as the button.
|
||||
*/
|
||||
@property({ reflect: true }) form: string | null = null;
|
||||
|
||||
/** Used to override the form owner's `action` attribute. */
|
||||
@property({ attribute: 'formaction' }) formAction: string;
|
||||
@@ -135,24 +134,27 @@ export default class WaButton extends WebAwesomeFormAssociatedElement {
|
||||
|
||||
private constructLightDOMButton() {
|
||||
const button = document.createElement('button');
|
||||
|
||||
for (const attribute of this.attributes) {
|
||||
if (attribute.name === 'style') {
|
||||
// Skip style attributes as they *shouldn't* be necessary
|
||||
continue;
|
||||
}
|
||||
button.setAttribute(attribute.name, attribute.value);
|
||||
}
|
||||
|
||||
button.type = this.type;
|
||||
button.style.position = 'absolute';
|
||||
button.style.width = '0';
|
||||
button.style.height = '0';
|
||||
button.style.clipPath = 'inset(50%)';
|
||||
button.style.overflow = 'hidden';
|
||||
button.style.whiteSpace = 'nowrap';
|
||||
button.style.position = 'absolute !important';
|
||||
button.style.width = '0 !important';
|
||||
button.style.height = '0 !important';
|
||||
button.style.clipPath = 'inset(50%) !important';
|
||||
button.style.overflow = 'hidden !important';
|
||||
button.style.whiteSpace = 'nowrap !important';
|
||||
if (this.name) {
|
||||
button.name = this.name;
|
||||
}
|
||||
button.value = this.value || '';
|
||||
|
||||
['form', 'formaction', 'formenctype', 'formmethod', 'formnovalidate', 'formtarget'].forEach(attr => {
|
||||
if (this.hasAttribute(attr)) {
|
||||
button.setAttribute(attr, this.getAttribute(attr)!);
|
||||
}
|
||||
});
|
||||
|
||||
return button;
|
||||
}
|
||||
|
||||
|
||||
@@ -108,13 +108,6 @@ export default class WaCheckbox extends WebAwesomeFormAssociatedElement {
|
||||
@property({ type: Boolean, reflect: true, attribute: 'checked' }) defaultChecked: boolean =
|
||||
this.hasAttribute('checked');
|
||||
|
||||
/**
|
||||
* By default, form controls are associated with the nearest containing `<form>` element. This attribute allows you
|
||||
* to place the form control outside of a form and associate it with the form that has this `id`. The form must be in
|
||||
* the same document or shadow root for this to work.
|
||||
*/
|
||||
@property({ reflect: true }) form = null;
|
||||
|
||||
/** Makes the checkbox a required field. */
|
||||
@property({ type: Boolean, reflect: true }) required = false;
|
||||
|
||||
|
||||
@@ -220,13 +220,6 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement {
|
||||
*/
|
||||
@property() swatches: string | string[] = '';
|
||||
|
||||
/**
|
||||
* By default, form controls are associated with the nearest containing `<form>` element. This attribute allows you
|
||||
* to place the form control outside of a form and associate it with the form that has this `id`. The form must be in
|
||||
* the same document or shadow root for this to work.
|
||||
*/
|
||||
@property({ reflect: true }) form = null;
|
||||
|
||||
/** Makes the color picker a required field. */
|
||||
@property({ type: Boolean, reflect: true }) required = false;
|
||||
|
||||
|
||||
@@ -140,13 +140,6 @@ export default class WaInput extends WebAwesomeFormAssociatedElement {
|
||||
/** Hides the browser's built-in increment/decrement spin buttons for number inputs. */
|
||||
@property({ attribute: 'without-spin-buttons', type: Boolean }) withoutSpinButtons = false;
|
||||
|
||||
/**
|
||||
* By default, form controls are associated with the nearest containing `<form>` element. This attribute allows you
|
||||
* to place the form control outside of a form and associate it with the form that has this `id`. The form must be in
|
||||
* the same document or shadow root for this to work.
|
||||
*/
|
||||
@property({ reflect: true }) form = null;
|
||||
|
||||
/** Makes the input a required field. */
|
||||
@property({ type: Boolean, reflect: true }) required = false;
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import getText from '../../internal/get-text.js';
|
||||
import WebAwesomeElement from '../../internal/webawesome-element.js';
|
||||
import { LocalizeController } from '../../utilities/localize.js';
|
||||
import '../icon/icon.js';
|
||||
import type WaSelect from '../select/select.js';
|
||||
import styles from './option.styles.js';
|
||||
|
||||
/**
|
||||
@@ -109,7 +110,7 @@ export default class WaOption extends WebAwesomeElement {
|
||||
this.updateDefaultLabel();
|
||||
|
||||
if (this.isInitialized) {
|
||||
// When the label changes, tell the controller to update
|
||||
// When the label changes, tell the parent <wa-select> to update
|
||||
customElements.whenDefined('wa-select').then(() => {
|
||||
const controller = this.closest('wa-select');
|
||||
if (controller) {
|
||||
@@ -117,6 +118,16 @@ export default class WaOption extends WebAwesomeElement {
|
||||
controller.selectionChanged?.();
|
||||
}
|
||||
});
|
||||
|
||||
// When the label changes, tell the parent <wa-combobox> to update
|
||||
customElements.whenDefined('wa-combobox').then(() => {
|
||||
// We cast to <wa-select> because it shares the same API as combobox
|
||||
const controller = this.closest<WaSelect>('wa-combobox');
|
||||
if (controller) {
|
||||
controller.handleDefaultSlotChange();
|
||||
controller.selectionChanged?.();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.isInitialized = true;
|
||||
}
|
||||
@@ -134,7 +145,8 @@ export default class WaOption extends WebAwesomeElement {
|
||||
|
||||
protected willUpdate(changedProperties: PropertyValues<this>): void {
|
||||
if (changedProperties.has('defaultSelected')) {
|
||||
if (!this.closest('wa-select')?.hasInteracted) {
|
||||
// We cast to <wa-select> because it shares the same API as combobox
|
||||
if (!this.closest<WaSelect>('wa-combobox, wa-select')?.hasInteracted) {
|
||||
const oldVal = this.selected;
|
||||
this.selected = this.defaultSelected;
|
||||
this.requestUpdate('selected', oldVal);
|
||||
|
||||
@@ -39,11 +39,6 @@ export default class WaRadio extends WebAwesomeFormAssociatedElement {
|
||||
/** @internal Used by radio group to force disable radios while preserving their original disabled state. */
|
||||
@state() forceDisabled = false;
|
||||
|
||||
/**
|
||||
* The string pointing to a form's id.
|
||||
*/
|
||||
@property({ reflect: true }) form: string | null = null;
|
||||
|
||||
/** The radio's value. When selected, the radio group will receive this value. */
|
||||
@property({ reflect: true }) value: string;
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import { WaAfterHideEvent } from '../../events/after-hide.js';
|
||||
import { WaAfterShowEvent } from '../../events/after-show.js';
|
||||
import { WaClearEvent } from '../../events/clear.js';
|
||||
import { WaHideEvent } from '../../events/hide.js';
|
||||
import type { WaRemoveEvent } from '../../events/remove.js';
|
||||
import { WaRemoveEvent } from '../../events/remove.js';
|
||||
import { WaShowEvent } from '../../events/show.js';
|
||||
import { animateWithClass } from '../../internal/animate.js';
|
||||
import { waitForEvent } from '../../internal/event.js';
|
||||
@@ -99,6 +99,7 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
|
||||
|
||||
private readonly hasSlotController = new HasSlotController(this, 'hint', 'label');
|
||||
private readonly localize = new LocalizeController(this);
|
||||
private selectionOrder: Map<string, number> = new Map();
|
||||
private typeToSelectString = '';
|
||||
private typeToSelectTimeout: number;
|
||||
|
||||
@@ -256,13 +257,6 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
|
||||
*/
|
||||
@property({ attribute: 'with-hint', type: Boolean }) withHint = false;
|
||||
|
||||
/**
|
||||
* By default, form controls are associated with the nearest containing `<form>` element. This attribute allows you
|
||||
* to place the form control outside of a form and associate it with the form that has this `id`. The form must be in
|
||||
* the same document or shadow root for this to work.
|
||||
*/
|
||||
@property({ reflect: true }) form = null;
|
||||
|
||||
/** The select's required attribute. */
|
||||
@property({ type: Boolean, reflect: true }) required = false;
|
||||
|
||||
@@ -285,6 +279,8 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
|
||||
?pill=${this.pill}
|
||||
size=${this.size}
|
||||
with-remove
|
||||
data-value=${option.value}
|
||||
@wa-remove=${(event: WaRemoveEvent) => this.handleTagRemove(event, option)}
|
||||
>
|
||||
${option.label}
|
||||
</wa-tag>
|
||||
@@ -520,6 +516,7 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
|
||||
event.stopPropagation();
|
||||
|
||||
if (this.value !== null) {
|
||||
this.selectionOrder.clear();
|
||||
this.setSelectedOptions([]);
|
||||
this.displayInput.focus({ preventScroll: true });
|
||||
|
||||
@@ -603,24 +600,20 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
|
||||
|
||||
if (this.disabled) return;
|
||||
|
||||
// Mark as interacted so selectionChanged() uses the correct filter logic
|
||||
this.hasInteracted = true;
|
||||
this.valueHasChanged = true;
|
||||
|
||||
// Use the directly provided option if available (from getTag method)
|
||||
let option = directOption;
|
||||
|
||||
// If no direct option was provided, find the option from the event path
|
||||
// If no direct option was provided, find the option from the data-value attribute
|
||||
if (!option) {
|
||||
const tagElement = (event.target as Element).closest('wa-tag[part~=tag]');
|
||||
const tagElement = (event.target as Element).closest('wa-tag[data-value]') as HTMLElement | null;
|
||||
|
||||
if (tagElement) {
|
||||
// Find the index of this tag among all tags
|
||||
const tagsContainer = this.shadowRoot?.querySelector('[part="tags"]');
|
||||
if (tagsContainer) {
|
||||
const allTags = Array.from(tagsContainer.children);
|
||||
const index = allTags.indexOf(tagElement as HTMLElement);
|
||||
|
||||
if (index >= 0 && index < this.selectedOptions.length) {
|
||||
option = this.selectedOptions[index];
|
||||
}
|
||||
}
|
||||
const value = tagElement.dataset.value;
|
||||
option = this.selectedOptions.find(opt => opt.value === value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -707,7 +700,7 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
|
||||
const options = this.getAllOptions();
|
||||
|
||||
// Update selected options cache
|
||||
this.selectedOptions = options.filter(el => {
|
||||
const newSelectedOptions = options.filter(el => {
|
||||
if (!this.hasInteracted && !this.valueHasChanged) {
|
||||
const defaultValue = this.defaultValue;
|
||||
const defaultValues = Array.isArray(defaultValue) ? defaultValue : [defaultValue];
|
||||
@@ -717,6 +710,32 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
|
||||
return el.selected;
|
||||
});
|
||||
|
||||
// Update the selection order map
|
||||
const newSelectedValues = new Set(newSelectedOptions.map(el => el.value));
|
||||
|
||||
// Remove deselected options from the order map
|
||||
for (const value of this.selectionOrder.keys()) {
|
||||
if (!newSelectedValues.has(value)) {
|
||||
this.selectionOrder.delete(value);
|
||||
}
|
||||
}
|
||||
|
||||
// Add newly selected options
|
||||
const maxOrder = this.selectionOrder.size > 0 ? Math.max(...this.selectionOrder.values()) : -1;
|
||||
let nextOrder = maxOrder + 1;
|
||||
for (const option of newSelectedOptions) {
|
||||
if (!this.selectionOrder.has(option.value)) {
|
||||
this.selectionOrder.set(option.value, nextOrder++);
|
||||
}
|
||||
}
|
||||
|
||||
// Sort options by selection order
|
||||
this.selectedOptions = newSelectedOptions.sort((a, b) => {
|
||||
const orderA = this.selectionOrder.get(a.value) ?? 0;
|
||||
const orderB = this.selectionOrder.get(b.value) ?? 0;
|
||||
return orderA - orderB;
|
||||
});
|
||||
|
||||
let selectedValues = new Set(this.selectedOptions.map(el => el.value));
|
||||
|
||||
// Toggle values present in the DOM from this.value, while preserving options NOT present in the DOM (for lazy loading)
|
||||
@@ -888,6 +907,7 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
|
||||
}
|
||||
|
||||
formResetCallback() {
|
||||
this.selectionOrder.clear();
|
||||
this.value = this.defaultValue;
|
||||
super.formResetCallback();
|
||||
this.handleValueChange();
|
||||
|
||||
@@ -167,12 +167,6 @@ export default class WaSlider extends WebAwesomeFormAssociatedElement {
|
||||
/** The starting value from which to draw the slider's fill, which is based on its current value. */
|
||||
@property({ attribute: 'indicator-offset', type: Number }) indicatorOffset: number;
|
||||
|
||||
/**
|
||||
* The form to associate this control with. If omitted, the closest containing `<form>` will be used. The value of
|
||||
* this attribute must be an ID of a form in the same document or shadow root.
|
||||
*/
|
||||
@property({ reflect: true }) form = null;
|
||||
|
||||
/** The minimum value allowed. */
|
||||
@property({ type: Number }) min: number = 0;
|
||||
|
||||
@@ -437,8 +431,14 @@ export default class WaSlider extends WebAwesomeFormAssociatedElement {
|
||||
/** Clamps a number to min/max while ensuring it's a valid step interval. */
|
||||
private clampAndRoundToStep(value: number) {
|
||||
const stepPrecision = (String(this.step).split('.')[1] || '').replace(/0+$/g, '').length;
|
||||
value = Math.round(value / this.step) * this.step;
|
||||
value = clamp(value, this.min, this.max);
|
||||
|
||||
// Ensure we're working with numbers (in case the user passes strings to the respective properties)
|
||||
const step = Number(this.step);
|
||||
const min = Number(this.min);
|
||||
const max = Number(this.max);
|
||||
|
||||
value = Math.round(value / step) * step;
|
||||
value = clamp(value, min, max);
|
||||
|
||||
return parseFloat(value.toFixed(stepPrecision));
|
||||
}
|
||||
|
||||
@@ -80,13 +80,6 @@ export default class WaSwitch extends WebAwesomeFormAssociatedElement {
|
||||
@property({ type: Boolean, attribute: 'checked', reflect: true }) defaultChecked: boolean =
|
||||
this.hasAttribute('checked');
|
||||
|
||||
/**
|
||||
* By default, form controls are associated with the nearest containing `<form>` element. This attribute allows you
|
||||
* to place the form control outside of a form and associate it with the form that has this `id`. The form must be in
|
||||
* the same document or shadow root for this to work.
|
||||
*/
|
||||
@property({ reflect: true }) form = null;
|
||||
|
||||
/** Makes the switch a required field. */
|
||||
@property({ type: Boolean, reflect: true }) required = false;
|
||||
|
||||
|
||||
@@ -107,13 +107,6 @@ export default class WaTextarea extends WebAwesomeFormAssociatedElement {
|
||||
/** Makes the textarea readonly. */
|
||||
@property({ type: Boolean, reflect: true }) readonly = false;
|
||||
|
||||
/**
|
||||
* By default, form controls are associated with the nearest containing `<form>` element. This attribute allows you
|
||||
* to place the form control outside of a form and associate it with the form that has this `id`. The form must be in
|
||||
* the same document or shadow root for this to work.
|
||||
*/
|
||||
@property({ reflect: true }) form = null;
|
||||
|
||||
/** Makes the textarea a required field. */
|
||||
@property({ type: Boolean, reflect: true }) required = false;
|
||||
|
||||
|
||||
@@ -23,7 +23,8 @@ export interface WebAwesomeFormControl extends WebAwesomeElement {
|
||||
checked?: boolean;
|
||||
defaultSelected?: boolean;
|
||||
selected?: boolean;
|
||||
form?: string | null;
|
||||
get form(): HTMLFormElement | null;
|
||||
set form(val: string);
|
||||
|
||||
value?: unknown;
|
||||
|
||||
@@ -203,6 +204,23 @@ export class WebAwesomeFormAssociatedElement
|
||||
return this.internals.form;
|
||||
}
|
||||
|
||||
/**
|
||||
* By default, form controls are associated with the nearest containing `<form>` element. This attribute allows you
|
||||
* to place the form control outside of a form and associate it with the form that has this `id`. The form must be in
|
||||
* the same document or shadow root for this to work.
|
||||
*/
|
||||
set form(val: string) {
|
||||
if (val) {
|
||||
this.setAttribute('form', val);
|
||||
} else {
|
||||
this.removeAttribute('form');
|
||||
}
|
||||
}
|
||||
|
||||
get form(): HTMLFormElement | null {
|
||||
return this.internals.form;
|
||||
}
|
||||
|
||||
@property({ attribute: false, state: true, type: Object })
|
||||
get validity() {
|
||||
return this.internals.validity;
|
||||
|
||||
Reference in New Issue
Block a user