kopia lustrzana https://github.com/shoelace-style/shoelace
Merge branch 'next' into current
commit
a40d2ca1b5
|
@ -79,6 +79,7 @@ module.exports = {
|
|||
'@typescript-eslint/no-unnecessary-boolean-literal-compare': 'warn',
|
||||
'@typescript-eslint/no-unnecessary-condition': 'off',
|
||||
'@typescript-eslint/no-unnecessary-qualifier': 'warn',
|
||||
'@typescript-eslint/no-unnecessary-type-assertion': 'off',
|
||||
'@typescript-eslint/non-nullable-type-assertion-style': 'warn',
|
||||
'@typescript-eslint/prefer-for-of': 'warn',
|
||||
'@typescript-eslint/prefer-optional-chain': 'warn',
|
||||
|
|
|
@ -9,7 +9,7 @@ A forward-thinking library of web components.
|
|||
- Built with accessibility in mind ♿️
|
||||
- Open source 😸
|
||||
|
||||
Designed in New Hampshire by [Cory LaViska](https://twitter.com/claviska).
|
||||
Designed in New Hampshire by [Cory LaViska](https://twitter.com/cory_laviska).
|
||||
|
||||
---
|
||||
|
||||
|
@ -77,6 +77,6 @@ Shoelace is an open source project and contributions are encouraged! If you're i
|
|||
|
||||
## License
|
||||
|
||||
Shoelace was created by [Cory LaViska](https://twitter.com/claviska) and is available under the terms of the MIT license.
|
||||
Shoelace was created by [Cory LaViska](https://twitter.com/cory_laviska) and is available under the terms of the MIT license.
|
||||
|
||||
Whether you're building Shoelace or building something _with_ Shoelace — have fun creating! 🥾
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
"autoloading",
|
||||
"autoplay",
|
||||
"bezier",
|
||||
"Bokmål",
|
||||
"boxicons",
|
||||
"CACHEABLE",
|
||||
"callout",
|
||||
|
@ -166,6 +167,7 @@
|
|||
"valpha",
|
||||
"valuenow",
|
||||
"valuetext",
|
||||
"vuejs",
|
||||
"WEBP",
|
||||
"Webpacker",
|
||||
"wordmark"
|
||||
|
|
|
@ -283,7 +283,7 @@
|
|||
</tbody>
|
||||
</table>
|
||||
|
||||
<p><em>Learn more about <a href="{{ rootUrl('/getting-started/usage#custom-properties') }}">customizing CSS custom properties</a>.</em></p>
|
||||
<p><em>Learn more about <a href="{{ rootUrl('/getting-started/customizing#custom-properties') }}">customizing CSS custom properties</a>.</em></p>
|
||||
{% endif %}
|
||||
|
||||
{# CSS Parts #}
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
<li><a href="/frameworks/react">React</a></li>
|
||||
<li><a href="/frameworks/vue">Vue</a></li>
|
||||
<li><a href="/frameworks/angular">Angular</a></li>
|
||||
<li><a href="/frameworks/svelte">Svelte</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import * as Turbo from 'https://cdn.jsdelivr.net/npm/@hotwired/turbo@7.3.0/+esm';
|
||||
import * as Turbo from 'https://cdn.jsdelivr.net/npm/@hotwired/turbo@8.0.10/+esm';
|
||||
|
||||
(() => {
|
||||
if (!window.scrollPositions) {
|
||||
|
@ -6,13 +6,13 @@ import * as Turbo from 'https://cdn.jsdelivr.net/npm/@hotwired/turbo@7.3.0/+esm'
|
|||
}
|
||||
|
||||
function preserveScroll() {
|
||||
document.querySelectorAll('[data-preserve-scroll').forEach(element => {
|
||||
document.querySelectorAll('[data-preserve-scroll]').forEach(element => {
|
||||
scrollPositions[element.id] = element.scrollTop;
|
||||
});
|
||||
}
|
||||
|
||||
function restoreScroll(event) {
|
||||
document.querySelectorAll('[data-preserve-scroll').forEach(element => {
|
||||
document.querySelectorAll('[data-preserve-scroll]').forEach(element => {
|
||||
element.scrollTop = scrollPositions[element.id];
|
||||
});
|
||||
|
||||
|
|
|
@ -504,17 +504,17 @@ Remember that custom tags are rendered in a shadow root. To style them, you can
|
|||
|
||||
### Lazy loading options
|
||||
|
||||
Lazy loading options is very hard to get right. `<wa-select>` largely follows how a native `<select>` works.
|
||||
Lazy loading options is very hard to get right. `<sl-select>` largely follows how a native `<select>` works.
|
||||
|
||||
Here are the following conditions:
|
||||
|
||||
- If a `<wa-select>` is created without any options, but is given a `value` attribute, its `value` will be `""`, and then when options are added, if any of the options have a value equal to the `<wa-select>` value, the value of the `<wa-select>` will equal that of the option.
|
||||
- If a `<sl-select>` is created without any options, but is given a `value` attribute, its `value` will be `""`, and then when options are added, if any of the options have a value equal to the `<sl-select>` value, the value of the `<sl-select>` will equal that of the option.
|
||||
|
||||
EX: `<wa-select value="foo">` will have a value of `""` until `<wa-option value="foo">Foo</wa-option>` connects, at which point its value will become `"foo"` when submitting.
|
||||
EX: `<sl-select value="foo">` will have a value of `""` until `<sl-option value="foo">Foo</sl-option>` connects, at which point its value will become `"foo"` when submitting.
|
||||
|
||||
- If a `<wa-select multiple>` with an initial value has multiple values, but only some of the options are present, it will only respect the options that are present, and if a selected option is loaded in later, _AND_ the value of the select has not changed via user interaction or direct property assignment, it will add the selected option to the form value and to the `.value` of the select.
|
||||
- If a `<sl-select multiple>` with an initial value has multiple values, but only some of the options are present, it will only respect the options that are present, and if a selected option is loaded in later, _AND_ the value of the select has not changed via user interaction or direct property assignment, it will add the selected option to the form value and to the `.value` of the select.
|
||||
|
||||
This can be hard to conceptualize, so heres a fairly large example showing how lazy loaded options work with `<wa-select>` and `<wa-select multiple>` when given initial value attributes. Feel free to play around with it in a codepen.
|
||||
This can be hard to conceptualize, so heres a fairly large example showing how lazy loaded options work with `<sl-select>` and `<sl-select multiple>` when given initial value attributes. Feel free to play around with it in a codepen.
|
||||
|
||||
```html:preview
|
||||
<form id="lazy-options-example">
|
||||
|
|
|
@ -31,7 +31,7 @@ const App = () => (
|
|||
|
||||
### Sizes
|
||||
|
||||
Use the `size` attribute to change a tab's size.
|
||||
Use the `size` attribute to change a tag's size.
|
||||
|
||||
```html:preview
|
||||
<sl-tag size="small">Small</sl-tag>
|
||||
|
@ -53,7 +53,7 @@ const App = () => (
|
|||
|
||||
### Pill
|
||||
|
||||
Use the `pill` attribute to give tabs rounded edges.
|
||||
Use the `pill` attribute to give tags rounded edges.
|
||||
|
||||
```html:preview
|
||||
<sl-tag size="small" pill>Small</sl-tag>
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
---
|
||||
meta:
|
||||
title: Svelte
|
||||
description: Tips for using Shoelace in your Svelte app.
|
||||
---
|
||||
|
||||
# Svelte
|
||||
|
||||
Svelte [plays nice](https://custom-elements-everywhere.com/#svelte) with custom elements, so you can use Shoelace in your Svelte apps with ease.
|
||||
|
||||
## Installation
|
||||
|
||||
To add Shoelace to your Svelte app, install the package from npm.
|
||||
|
||||
```bash
|
||||
npm install @shoelace-style/shoelace
|
||||
```
|
||||
|
||||
Next, [include a theme](/getting-started/themes) and set the [base path](/getting-started/installation#setting-the-base-path) for icons and other assets. In this example, we'll import the light theme and use the CDN as a base path.
|
||||
|
||||
```jsx
|
||||
// main.js or main.ts
|
||||
import '@shoelace-style/shoelace/dist/themes/light.css';
|
||||
import { setBasePath } from '@shoelace-style/shoelace/dist/utilities/base-path';
|
||||
|
||||
setBasePath('https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@%VERSION%/%CDNDIR%/');
|
||||
```
|
||||
|
||||
:::tip
|
||||
If you'd rather not use the CDN for assets, you can create a build task that copies `node_modules/@shoelace-style/shoelace/dist/assets` into a public folder in your app. Then you can point the base path to that folder instead.
|
||||
:::
|
||||
|
||||
## Usage
|
||||
|
||||
### QR code generator example
|
||||
|
||||
```jsx
|
||||
<h1>Live editing</h1>
|
||||
|
||||
<sl-input label="Message" value={message} oninput={event => message = event.target.value}></sl-input>
|
||||
|
||||
<sl-alert open>
|
||||
<sl-icon slot="icon" name="info-circle"></sl-icon>
|
||||
{message}
|
||||
</sl-alert>
|
||||
|
||||
<script>
|
||||
import '@shoelace-style/shoelace/dist/components/alert/alert.js'
|
||||
import '@shoelace-style/shoelace/dist/components/input/input.js';
|
||||
|
||||
let message = $state('')
|
||||
</script>
|
||||
```
|
||||
|
||||
### Two-way Binding
|
||||
|
||||
One caveat is there's currently Svelte only supports `bind:value` directive in `<input>`, `<textarea>` and `<select>`, but you can still achieve two-way binding manually.
|
||||
|
||||
```jsx
|
||||
// This doesn't work
|
||||
<sl-input bind:value="name"></sl-input>
|
||||
// This works, but it's a bit longer
|
||||
<sl-input value={name} oninput={event => message = event.target.value}></sl-input>
|
||||
```
|
||||
|
||||
:::tip
|
||||
Are you using Shoelace with Svelte? [Help us improve this page!](https://github.com/shoelace-style/shoelace/blob/next/docs/frameworks/svelte.md)
|
||||
:::
|
||||
|
||||
### Slots
|
||||
|
||||
Slots in Shoelace/web components are functionally the same as basic slots in Svelte. Slots can be assigned to elements using the `slot` attribute followed by the name of the slot it is being assigned to.
|
||||
|
||||
Here is an example:
|
||||
|
||||
```jsx
|
||||
<sl-drawer label="Drawer" placement="start" class="drawer-placement-start" bind:open={drawerIsOpen}>
|
||||
This drawer slides in from the start.
|
||||
<div slot="footer">
|
||||
<sl-button variant="primary" onclick={() => (drawerIsOpen = false)}>
|
||||
Close
|
||||
</sl-button>
|
||||
</div>
|
||||
</sl-drawer>
|
||||
```
|
|
@ -106,7 +106,7 @@ If you need to support IE11 or pre-Chromium Edge, this library isn't for you. Al
|
|||
|
||||
## License
|
||||
|
||||
Shoelace was created in New Hampshire by [Cory LaViska](https://twitter.com/claviska). It's available under the terms of the [MIT license](https://github.com/shoelace-style/shoelace/blob/next/LICENSE.md).
|
||||
Shoelace was created in New Hampshire by [Cory LaViska](https://twitter.com/cory_laviska). It's available under the terms of the [MIT license](https://github.com/shoelace-style/shoelace/blob/next/LICENSE.md).
|
||||
|
||||
## Attribution
|
||||
|
||||
|
|
|
@ -12,9 +12,28 @@ Components with the <sl-badge variant="warning" pill>Experimental</sl-badge> bad
|
|||
|
||||
New versions of Shoelace are released as-needed and generally occur when a critical mass of changes have accumulated. At any time, you can see what's coming in the next release by visiting [next.shoelace.style](https://next.shoelace.style).
|
||||
|
||||
## 2.19.0
|
||||
|
||||
- Added Norwegian translations for Bokmål and Nynorsk [#2268]
|
||||
- Added Ukrainian translation [#2270]
|
||||
- Added community maintained docs for Svelte [#2262]
|
||||
- Fixed a bug in `<sl-select>` when setting the value property before the element connected. [#2255]
|
||||
- Fixed a bug in `<sl-select>` where it was using the wrong tag name. [#2287]
|
||||
- Fixed a bug in `<sl-carousel>` that caused the navigation icons to be reversed
|
||||
- Fixed a bug in `<sl-carousel>` that caused interactive elements to be activated when dragging [#2196]
|
||||
- Fixed a bug in `<sl-carousel>` that caused out of order slides when used inside a resize observer [#2260]
|
||||
- Fixed a bug in `<sl-rating>` that allowed tabbing into the rating when readonly [#2271]
|
||||
- Fixed a bug in `<sl-select>` where it was using the wrong tag name,. [#2287]
|
||||
- Fixed a bug in `<sl-select>` that prevented label changes in `<sl-option>` from updating the controller [#1971]
|
||||
- Fixed a bug in `<sl-select>` that caused the placeholder to display incorrectly when using `placeholder` and `multiple` [#2292]
|
||||
- Fixed a bug in `<sl-textarea>` that caused a console warning in Firefox when typing [#2107]
|
||||
- Improved accessibility of `<sl-split-panel>` by adding support for <kbd>Enter</kbd> to align with ARIA APG's [window splitter pattern](https://www.w3.org/WAI/ARIA/apg/patterns/windowsplitter/) [#2234]
|
||||
- Improved performance of `<sl-range>` by skipping positioning logic when tooltip isn't shown [#2064]
|
||||
- Updated many dependencies to their latest versions
|
||||
|
||||
## 2.18.0
|
||||
|
||||
- Added Finnish translations [#2211]
|
||||
- Added Finnish translation [#2211]
|
||||
- Added the `.focus` function to `<sl-radio-group>` [#2192]
|
||||
- Fixed a bug in `<sl-tab-group>` when removed from the DOM too quickly. [#2218]
|
||||
- Fixed a bug with `<sl-select>` not respecting its initial value. [#2204]
|
||||
|
|
|
@ -49,7 +49,7 @@ You can post questions on Stack Overflow using [the "shoelace" tag](https://stac
|
|||
|
||||
## Twitter
|
||||
|
||||
Follow [@shoelace_style](https://twitter.com/shoelace_style) on Twitter for general updates and announcements about Shoelace. This is a great place to say "hi" or to share something you're working on. You're also welcome to follow [@claviska](https://twitter.com/claviska), the creator, for tweets about web components, web development, and life.
|
||||
Follow [@shoelace_style](https://twitter.com/shoelace_style) on Twitter for general updates and announcements about Shoelace. This is a great place to say "hi" or to share something you're working on. You're also welcome to follow [@cory_laviska](https://twitter.com/cory_laviska), the creator, for tweets about web components, web development, and life.
|
||||
|
||||
**Please avoid using Twitter for support questions.** The [discussion forum](https://github.com/shoelace-style/shoelace/discussions) is a much better place to share code snippets, screenshots, and other troubleshooting info. You'll have much better luck there, as more users will have a chance to help you.
|
||||
|
||||
|
|
Plik diff jest za duży
Load Diff
97
package.json
97
package.json
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "@shoelace-style/shoelace",
|
||||
"description": "A forward-thinking library of web components.",
|
||||
"version": "2.18.0",
|
||||
"version": "2.19.0",
|
||||
"homepage": "https://github.com/shoelace-style/shoelace",
|
||||
"author": "Cory LaViska",
|
||||
"license": "MIT",
|
||||
|
@ -64,84 +64,83 @@
|
|||
"test:component": "web-test-runner -- --watch --group",
|
||||
"test:watch": "web-test-runner --watch --group default",
|
||||
"spellcheck": "cspell \"**/*.{js,ts,json,html,css,md}\" --no-progress",
|
||||
"list-outdated-dependencies": "npm-check-updates --format repo --peer",
|
||||
"update-dependencies": "npm-check-updates --peer -u && npm install"
|
||||
"check-updates": "npx npm-check-updates --interactive --format group"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.17.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ctrl/tinycolor": "^4.0.2",
|
||||
"@floating-ui/dom": "^1.5.3",
|
||||
"@lit/react": "^1.0.0",
|
||||
"@shoelace-style/animations": "^1.1.0",
|
||||
"@shoelace-style/localize": "^3.1.2",
|
||||
"composed-offset-position": "^0.0.4",
|
||||
"lit": "^3.0.0",
|
||||
"@ctrl/tinycolor": "^4.1.0",
|
||||
"@floating-ui/dom": "^1.6.12",
|
||||
"@lit/react": "^1.0.6",
|
||||
"@shoelace-style/animations": "^1.2.0",
|
||||
"@shoelace-style/localize": "^3.2.1",
|
||||
"composed-offset-position": "^0.0.6",
|
||||
"lit": "^3.2.1",
|
||||
"qr-creator": "^1.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@11ty/eleventy": "^2.0.1",
|
||||
"@custom-elements-manifest/analyzer": "^0.8.4",
|
||||
"@open-wc/testing": "^3.2.0",
|
||||
"@types/mocha": "^10.0.2",
|
||||
"@types/react": "^18.2.28",
|
||||
"@custom-elements-manifest/analyzer": "^0.10.3",
|
||||
"@open-wc/testing": "^4.0.0",
|
||||
"@types/mocha": "^10.0.10",
|
||||
"@types/react": "^18.3.12",
|
||||
"@typescript-eslint/eslint-plugin": "^6.7.5",
|
||||
"@typescript-eslint/parser": "^6.7.5",
|
||||
"@web/dev-server-esbuild": "^0.3.6",
|
||||
"@web/test-runner": "^0.18.0",
|
||||
"@web/dev-server-esbuild": "^1.0.3",
|
||||
"@web/test-runner": "^0.19.0",
|
||||
"@web/test-runner-commands": "^0.9.0",
|
||||
"@web/test-runner-playwright": "^0.11.0",
|
||||
"bootstrap-icons": "^1.11.1",
|
||||
"browser-sync": "^2.29.3",
|
||||
"bootstrap-icons": "^1.11.3",
|
||||
"browser-sync": "^3.0.3",
|
||||
"chalk": "^5.3.0",
|
||||
"change-case": "^4.1.2",
|
||||
"command-line-args": "^5.2.1",
|
||||
"comment-parser": "^1.4.0",
|
||||
"cspell": "^6.18.1",
|
||||
"custom-element-jet-brains-integration": "^1.4.0",
|
||||
"custom-element-vs-code-integration": "^1.2.1",
|
||||
"custom-element-vuejs-integration": "^1.0.0",
|
||||
"del": "^7.1.0",
|
||||
"command-line-args": "^6.0.1",
|
||||
"comment-parser": "^1.4.1",
|
||||
"cspell": "^8.16.1",
|
||||
"custom-element-jet-brains-integration": "^1.6.2",
|
||||
"custom-element-vs-code-integration": "^1.4.1",
|
||||
"custom-element-vuejs-integration": "^1.3.3",
|
||||
"del": "^8.0.0",
|
||||
"download": "^8.0.0",
|
||||
"esbuild": "^0.19.4",
|
||||
"esbuild": "^0.24.0",
|
||||
"esbuild-plugin-replace": "^1.4.0",
|
||||
"eslint": "^8.51.0",
|
||||
"eslint-plugin-chai-expect": "^3.0.0",
|
||||
"eslint-plugin-chai-expect": "^3.1.0",
|
||||
"eslint-plugin-chai-friendly": "^0.7.2",
|
||||
"eslint-plugin-import": "^2.28.1",
|
||||
"eslint-plugin-lit": "^1.9.1",
|
||||
"eslint-plugin-lit-a11y": "^4.1.0",
|
||||
"eslint-plugin-import": "^2.31.0",
|
||||
"eslint-plugin-lit": "^1.15.0",
|
||||
"eslint-plugin-lit-a11y": "^4.1.4",
|
||||
"eslint-plugin-markdown": "^3.0.1",
|
||||
"eslint-plugin-sort-imports-es6-autofix": "^0.6.0",
|
||||
"eslint-plugin-wc": "^2.0.4",
|
||||
"eslint-plugin-wc": "^2.2.0",
|
||||
"front-matter": "^4.0.2",
|
||||
"get-port": "^7.0.0",
|
||||
"globby": "^13.2.2",
|
||||
"husky": "^8.0.3",
|
||||
"jsdom": "^22.1.0",
|
||||
"lint-staged": "^14.0.1",
|
||||
"get-port": "^7.1.0",
|
||||
"globby": "^14.0.2",
|
||||
"husky": "^9.1.7",
|
||||
"jsdom": "^25.0.1",
|
||||
"lint-staged": "^15.2.10",
|
||||
"lunr": "^2.3.9",
|
||||
"markdown-it-container": "^3.0.0",
|
||||
"markdown-it-ins": "^3.0.1",
|
||||
"markdown-it-container": "^4.0.0",
|
||||
"markdown-it-ins": "^4.0.0",
|
||||
"markdown-it-kbd": "^2.2.2",
|
||||
"markdown-it-mark": "^3.0.1",
|
||||
"markdown-it-mark": "^4.0.0",
|
||||
"markdown-it-replace-it": "^1.0.0",
|
||||
"npm-check-updates": "^16.14.6",
|
||||
"ora": "^7.0.1",
|
||||
"pascal-case": "^3.1.2",
|
||||
"plop": "^4.0.0",
|
||||
"prettier": "^3.0.3",
|
||||
"npm-check-updates": "^17.1.11",
|
||||
"ora": "^8.1.1",
|
||||
"pascal-case": "^4.0.0",
|
||||
"plop": "^4.0.1",
|
||||
"prettier": "^3.4.1",
|
||||
"prismjs": "^1.29.0",
|
||||
"react": "^18.2.0",
|
||||
"react": "^18.3.1",
|
||||
"recursive-copy": "^2.0.14",
|
||||
"sinon": "^16.1.0",
|
||||
"sinon": "^19.0.2",
|
||||
"smartquotes": "^2.3.2",
|
||||
"source-map": "^0.7.4",
|
||||
"strip-css-comments": "^5.0.0",
|
||||
"tslib": "^2.6.2",
|
||||
"typescript": "^5.2.2",
|
||||
"user-agent-data-types": "^0.3.1"
|
||||
"tslib": "^2.8.1",
|
||||
"typescript": "^5.7.2",
|
||||
"user-agent-data-types": "^0.4.2"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{ts,js}": [
|
||||
|
|
|
@ -92,6 +92,7 @@ export default class SlCarousel extends ShoelaceElement {
|
|||
@state() dragging = false;
|
||||
|
||||
private autoplayController = new AutoplayController(this, () => this.next());
|
||||
private dragStartPosition: [number, number] = [-1, -1];
|
||||
private readonly localize = new LocalizeController(this);
|
||||
private mutationObserver: MutationObserver;
|
||||
private pendingSlideChange = false;
|
||||
|
@ -151,6 +152,20 @@ export default class SlCarousel extends ShoelaceElement {
|
|||
) as SlCarouselItem[];
|
||||
}
|
||||
|
||||
private handleClick(event: MouseEvent) {
|
||||
if (this.dragging && this.dragStartPosition[0] > 0 && this.dragStartPosition[1] > 0) {
|
||||
const deltaX = Math.abs(this.dragStartPosition[0] - event.clientX);
|
||||
const deltaY = Math.abs(this.dragStartPosition[1] - event.clientY);
|
||||
const delta = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
|
||||
|
||||
// Prevents clicks on interactive elements while dragging if the click is within a small range. This prevents
|
||||
// accidental drags from interfering with intentional clicks.
|
||||
if (delta >= 10) {
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private handleKeyDown(event: KeyboardEvent) {
|
||||
if (['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Home', 'End'].includes(event.key)) {
|
||||
const target = event.target as HTMLElement;
|
||||
|
@ -208,6 +223,7 @@ export default class SlCarousel extends ShoelaceElement {
|
|||
// Start dragging if it hasn't yet
|
||||
this.scrollContainer.style.setProperty('scroll-snap-type', 'none');
|
||||
this.dragging = true;
|
||||
this.dragStartPosition = [event.clientX, event.clientY];
|
||||
}
|
||||
|
||||
this.scrollContainer.scrollBy({
|
||||
|
@ -255,6 +271,7 @@ export default class SlCarousel extends ShoelaceElement {
|
|||
scrollContainer.style.removeProperty('scroll-snap-type');
|
||||
|
||||
this.dragging = false;
|
||||
this.dragStartPosition = [-1, -1];
|
||||
this.handleScrollEnd();
|
||||
});
|
||||
};
|
||||
|
@ -364,10 +381,10 @@ export default class SlCarousel extends ShoelaceElement {
|
|||
this.createClones();
|
||||
}
|
||||
|
||||
this.synchronizeSlides();
|
||||
|
||||
// Because the DOM may be changed, restore the scroll position to the active slide
|
||||
this.goToSlide(this.activeSlide, 'auto');
|
||||
|
||||
this.synchronizeSlides();
|
||||
}
|
||||
|
||||
private createClones() {
|
||||
|
@ -512,7 +529,7 @@ export default class SlCarousel extends ShoelaceElement {
|
|||
const currentPage = this.getCurrentPage();
|
||||
const prevEnabled = this.canScrollPrev();
|
||||
const nextEnabled = this.canScrollNext();
|
||||
const isLtr = this.localize.dir() === 'rtl';
|
||||
const isLtr = this.localize.dir() === 'ltr';
|
||||
|
||||
return html`
|
||||
<div part="base" class="carousel">
|
||||
|
@ -533,6 +550,7 @@ export default class SlCarousel extends ShoelaceElement {
|
|||
@mousedown="${this.handleMouseDragStart}"
|
||||
@scroll="${this.handleScroll}"
|
||||
@scrollend=${this.handleScrollEnd}
|
||||
@click=${this.handleClick}
|
||||
>
|
||||
<slot></slot>
|
||||
</div>
|
||||
|
|
|
@ -1053,7 +1053,7 @@ export default class SlColorPicker extends ShoelaceElement implements ShoelaceFo
|
|||
<sl-dropdown
|
||||
class="color-dropdown"
|
||||
aria-disabled=${this.disabled ? 'true' : 'false'}
|
||||
.containing-element=${this}
|
||||
.containingElement=${this}
|
||||
?disabled=${this.disabled}
|
||||
?hoist=${this.hoist}
|
||||
@sl-after-hide=${this.handleAfterHide}
|
||||
|
|
|
@ -114,7 +114,7 @@ export default class SlDialog extends ShoelaceElement {
|
|||
super.disconnectedCallback();
|
||||
this.modal.deactivate();
|
||||
unlockBodyScrolling(this);
|
||||
this.closeWatcher?.destroy();
|
||||
this.removeOpenListeners();
|
||||
}
|
||||
|
||||
private requestClose(source: 'close-button' | 'keyboard' | 'overlay') {
|
||||
|
|
|
@ -131,7 +131,7 @@ export default class SlDrawer extends ShoelaceElement {
|
|||
disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
unlockBodyScrolling(this);
|
||||
this.closeWatcher?.destroy();
|
||||
this.removeOpenListeners();
|
||||
}
|
||||
|
||||
private requestClose(source: 'close-button' | 'keyboard' | 'overlay') {
|
||||
|
|
|
@ -130,8 +130,8 @@ export class SubmenuController implements ReactiveController {
|
|||
} else {
|
||||
this.enableSubmenu(false);
|
||||
this.host.updateComplete.then(() => {
|
||||
if (menuItems![0] instanceof HTMLElement) {
|
||||
menuItems![0].focus();
|
||||
if (menuItems[0] instanceof HTMLElement) {
|
||||
menuItems[0].focus();
|
||||
}
|
||||
});
|
||||
this.host.requestUpdate();
|
||||
|
|
|
@ -31,7 +31,6 @@ export default class SlOption extends ShoelaceElement {
|
|||
static styles: CSSResultGroup = [componentStyles, styles];
|
||||
static dependencies = { 'sl-icon': SlIcon };
|
||||
|
||||
private cachedTextLabel: string;
|
||||
// @ts-expect-error - Controller is currently unused
|
||||
private readonly localize = new LocalizeController(this);
|
||||
|
||||
|
@ -58,19 +57,13 @@ export default class SlOption extends ShoelaceElement {
|
|||
}
|
||||
|
||||
private handleDefaultSlotChange() {
|
||||
const textLabel = this.getTextLabel();
|
||||
|
||||
// Ignore the first time the label is set
|
||||
if (typeof this.cachedTextLabel === 'undefined') {
|
||||
this.cachedTextLabel = textLabel;
|
||||
return;
|
||||
}
|
||||
|
||||
// When the label changes, emit a slotchange event so parent controls see it
|
||||
if (textLabel !== this.cachedTextLabel) {
|
||||
this.cachedTextLabel = textLabel;
|
||||
this.emit('slotchange', { bubbles: true, composed: false, cancelable: false });
|
||||
}
|
||||
// When the label changes, tell the controller to update
|
||||
customElements.whenDefined('sl-select').then(() => {
|
||||
const controller = this.closest('sl-select');
|
||||
if (controller) {
|
||||
controller.handleDefaultSlotChange();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private handleMouseEnter() {
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import '../../../dist/shoelace.js';
|
||||
import { aTimeout, expect, fixture, html, waitUntil } from '@open-wc/testing';
|
||||
import sinon from 'sinon';
|
||||
import { aTimeout, expect, fixture, html } from '@open-wc/testing';
|
||||
import type SlOption from './option.js';
|
||||
|
||||
describe('<sl-option>', () => {
|
||||
|
@ -32,17 +31,6 @@ describe('<sl-option>', () => {
|
|||
expect(el.getAttribute('aria-disabled')).to.equal('true');
|
||||
});
|
||||
|
||||
it('emits the slotchange event when the label changes', async () => {
|
||||
const el = await fixture<SlOption>(html` <sl-option>Text</sl-option> `);
|
||||
const slotChangeHandler = sinon.spy();
|
||||
|
||||
el.addEventListener('slotchange', slotChangeHandler);
|
||||
el.textContent = 'New Text';
|
||||
await waitUntil(() => slotChangeHandler.calledOnce);
|
||||
|
||||
expect(slotChangeHandler).to.have.been.calledOnce;
|
||||
});
|
||||
|
||||
it('should convert non-string values to string', async () => {
|
||||
const el = await fixture<SlOption>(html` <sl-option>Text</sl-option> `);
|
||||
|
||||
|
|
|
@ -272,8 +272,8 @@ export default class SlPopup extends ShoelaceElement {
|
|||
}
|
||||
|
||||
private start() {
|
||||
// We can't start the positioner without an anchor
|
||||
if (!this.anchorEl) {
|
||||
// We can't start the positioner without an anchor or when the popup is inactive
|
||||
if (!this.anchorEl || !this.active) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,10 +1,33 @@
|
|||
import '../../../dist/shoelace.js';
|
||||
import { expect, fixture, html } from '@open-wc/testing';
|
||||
import type SlPopup from './popup.js';
|
||||
|
||||
describe('<sl-popup>', () => {
|
||||
let element: SlPopup;
|
||||
|
||||
it('should render a component', async () => {
|
||||
const el = await fixture(html` <sl-popup></sl-popup> `);
|
||||
|
||||
expect(el).to.exist;
|
||||
});
|
||||
|
||||
it('should properly handle positioning when active changes', async () => {
|
||||
element = await fixture('<sl-popup></sl-popup>');
|
||||
|
||||
element.active = true;
|
||||
await element.updateComplete;
|
||||
|
||||
// SImulate a scroll event
|
||||
const event = new Event('scroll');
|
||||
window.dispatchEvent(event);
|
||||
|
||||
element.active = false;
|
||||
await element.updateComplete;
|
||||
|
||||
// The component should not throw an error when the window is scrolled
|
||||
expect(() => {
|
||||
element.active = true;
|
||||
window.dispatchEvent(event);
|
||||
}).not.to.throw();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -215,7 +215,7 @@ export default class SlRange extends ShoelaceElement implements ShoelaceFormCont
|
|||
|
||||
this.syncProgress(percent);
|
||||
|
||||
if (this.tooltip !== 'none') {
|
||||
if (this.tooltip !== 'none' && this.hasTooltip) {
|
||||
// Ensure updates are drawn before we sync the tooltip
|
||||
this.updateComplete.then(() => this.syncTooltip(percent));
|
||||
}
|
||||
|
|
|
@ -240,7 +240,7 @@ export default class SlRating extends ShoelaceElement {
|
|||
aria-valuenow=${this.value}
|
||||
aria-valuemin=${0}
|
||||
aria-valuemax=${this.max}
|
||||
tabindex=${this.disabled ? '-1' : '0'}
|
||||
tabindex=${this.disabled || this.readonly ? '-1' : '0'}
|
||||
@click=${this.handleClick}
|
||||
@keydown=${this.handleKeyDown}
|
||||
@mouseenter=${this.handleMouseEnter}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { animateTo, stopAnimations } from '../../internal/animate.js';
|
||||
import { classMap } from 'lit/directives/class-map.js';
|
||||
import { defaultValue } from '../../internal/default-value.js';
|
||||
import { FormControlController } from '../../internal/form.js';
|
||||
import { getAnimation, setDefaultAnimation } from '../../utilities/animation-registry.js';
|
||||
import { HasSlotController } from '../../internal/slot.js';
|
||||
|
@ -102,21 +101,35 @@ export default class SlSelect extends ShoelaceElement implements ShoelaceFormCon
|
|||
/** The name of the select, submitted as a name/value pair with form data. */
|
||||
@property() name = '';
|
||||
|
||||
private _value: string | string[] = '';
|
||||
|
||||
get value() {
|
||||
return this._value;
|
||||
}
|
||||
|
||||
/**
|
||||
* The current value of the select, submitted as a name/value pair with form data. When `multiple` is enabled, the
|
||||
* value attribute will be a space-delimited list of values based on the options selected, and the value property will
|
||||
* be an array. **For this reason, values must not contain spaces.**
|
||||
*/
|
||||
@property({
|
||||
converter: {
|
||||
fromAttribute: (value: string) => value.split(' '),
|
||||
toAttribute: (value: string[]) => value.join(' ')
|
||||
@state()
|
||||
set value(val: string | string[]) {
|
||||
if (this.multiple) {
|
||||
val = Array.isArray(val) ? val : val.split(' ');
|
||||
} else {
|
||||
val = Array.isArray(val) ? val.join(' ') : val;
|
||||
}
|
||||
})
|
||||
value: string | string[] = '';
|
||||
|
||||
if (this._value === val) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.valueHasChanged = true;
|
||||
this._value = val;
|
||||
}
|
||||
|
||||
/** The default value of the form control. Primarily used for resetting the form control. */
|
||||
@defaultValue() defaultValue: string | string[] = '';
|
||||
@property({ attribute: 'value' }) defaultValue: string | string[] = '';
|
||||
|
||||
/** The select's size. */
|
||||
@property({ reflect: true }) size: 'small' | 'medium' | 'large' = 'medium';
|
||||
|
@ -451,6 +464,8 @@ export default class SlSelect extends ShoelaceElement implements ShoelaceFormCon
|
|||
private handleClearClick(event: MouseEvent) {
|
||||
event.stopPropagation();
|
||||
|
||||
this.valueHasChanged = true;
|
||||
|
||||
if (this.value !== '') {
|
||||
this.setSelectedOptions([]);
|
||||
this.displayInput.focus({ preventScroll: true });
|
||||
|
@ -501,9 +516,10 @@ export default class SlSelect extends ShoelaceElement implements ShoelaceFormCon
|
|||
}
|
||||
}
|
||||
|
||||
private handleDefaultSlotChange() {
|
||||
if (!customElements.get('wa-option')) {
|
||||
customElements.whenDefined('wa-option').then(() => this.handleDefaultSlotChange());
|
||||
/* @internal - used by options to update labels */
|
||||
public handleDefaultSlotChange() {
|
||||
if (!customElements.get('sl-option')) {
|
||||
customElements.whenDefined('sl-option').then(() => this.handleDefaultSlotChange());
|
||||
}
|
||||
|
||||
const allOptions = this.getAllOptions();
|
||||
|
@ -521,6 +537,8 @@ export default class SlSelect extends ShoelaceElement implements ShoelaceFormCon
|
|||
private handleTagRemove(event: SlRemoveEvent, option: SlOption) {
|
||||
event.stopPropagation();
|
||||
|
||||
this.valueHasChanged = true;
|
||||
|
||||
if (!this.disabled) {
|
||||
this.toggleOptionSelection(option, false);
|
||||
|
||||
|
@ -597,6 +615,9 @@ export default class SlSelect extends ShoelaceElement implements ShoelaceFormCon
|
|||
// Update selected options cache
|
||||
this.selectedOptions = options.filter(el => el.selected);
|
||||
|
||||
// Keep a reference to the previous `valueHasChanged`. Changes made here don't count has changing the value.
|
||||
const cachedValueHasChanged = this.valueHasChanged;
|
||||
|
||||
// Update the value and display label
|
||||
if (this.multiple) {
|
||||
this.value = this.selectedOptions.map(el => el.value);
|
||||
|
@ -612,12 +633,14 @@ export default class SlSelect extends ShoelaceElement implements ShoelaceFormCon
|
|||
this.value = selectedOption?.value ?? '';
|
||||
this.displayLabel = selectedOption?.getTextLabel?.() ?? '';
|
||||
}
|
||||
this.valueHasChanged = cachedValueHasChanged;
|
||||
|
||||
// Update validity
|
||||
this.updateComplete.then(() => {
|
||||
this.formControlController.updateValidity();
|
||||
});
|
||||
}
|
||||
|
||||
protected get tags() {
|
||||
return this.selectedOptions.map((option, index) => {
|
||||
if (index < this.maxOptionsVisible || this.maxOptionsVisible <= 0) {
|
||||
|
@ -648,8 +671,29 @@ export default class SlSelect extends ShoelaceElement implements ShoelaceFormCon
|
|||
}
|
||||
}
|
||||
|
||||
@watch('value', { waitUntilFirstUpdate: true })
|
||||
attributeChangedCallback(name: string, oldVal: string | null, newVal: string | null) {
|
||||
super.attributeChangedCallback(name, oldVal, newVal);
|
||||
|
||||
/** This is a backwards compatibility call. In a new major version we should make a clean separation between "value" the attribute mapping to "defaultValue" property and "value" the property not reflecting. */
|
||||
if (name === 'value') {
|
||||
const cachedValueHasChanged = this.valueHasChanged;
|
||||
this.value = this.defaultValue;
|
||||
|
||||
// Set it back to false since this isn't an interaction.
|
||||
this.valueHasChanged = cachedValueHasChanged;
|
||||
}
|
||||
}
|
||||
|
||||
@watch(['defaultValue', 'value'], { waitUntilFirstUpdate: true })
|
||||
handleValueChange() {
|
||||
if (!this.valueHasChanged) {
|
||||
const cachedValueHasChanged = this.valueHasChanged;
|
||||
this.value = this.defaultValue;
|
||||
|
||||
// Set it back to false since this isn't an interaction.
|
||||
this.valueHasChanged = cachedValueHasChanged;
|
||||
}
|
||||
|
||||
const allOptions = this.getAllOptions();
|
||||
const value = Array.isArray(this.value) ? this.value : [this.value];
|
||||
|
||||
|
|
|
@ -176,7 +176,7 @@ export default css`
|
|||
margin-inline-end: var(--sl-input-spacing-small);
|
||||
}
|
||||
|
||||
.select--small.select--multiple .select__prefix::slotted(*) {
|
||||
.select--small.select--multiple:not(.select--placeholder-visible) .select__prefix::slotted(*) {
|
||||
margin-inline-start: var(--sl-input-spacing-small);
|
||||
}
|
||||
|
||||
|
@ -205,11 +205,11 @@ export default css`
|
|||
margin-inline-end: var(--sl-input-spacing-medium);
|
||||
}
|
||||
|
||||
.select--medium.select--multiple .select__prefix::slotted(*) {
|
||||
.select--medium.select--multiple:not(.select--placeholder-visible) .select__prefix::slotted(*) {
|
||||
margin-inline-start: var(--sl-input-spacing-medium);
|
||||
}
|
||||
|
||||
.select--medium.select--multiple .select__combobox {
|
||||
.select--medium.select--multiple:not(.select--placeholder-visible) .select__combobox {
|
||||
padding-inline-start: 0;
|
||||
padding-block: 3px;
|
||||
}
|
||||
|
@ -234,11 +234,11 @@ export default css`
|
|||
margin-inline-end: var(--sl-input-spacing-large);
|
||||
}
|
||||
|
||||
.select--large.select--multiple .select__prefix::slotted(*) {
|
||||
.select--large.select--multiple:not(.select--placeholder-visible) .select__prefix::slotted(*) {
|
||||
margin-inline-start: var(--sl-input-spacing-large);
|
||||
}
|
||||
|
||||
.select--large.select--multiple .select__combobox {
|
||||
.select--large.select--multiple:not(.select--placeholder-visible) .select__combobox {
|
||||
padding-inline-start: 0;
|
||||
padding-block: 4px;
|
||||
}
|
||||
|
|
|
@ -506,7 +506,9 @@ describe('<sl-select>', () => {
|
|||
expect(displayInput.value).to.equal('Option 1');
|
||||
|
||||
option.textContent = 'updated';
|
||||
await oneEvent(option, 'slotchange');
|
||||
|
||||
await aTimeout(250);
|
||||
await option.updateComplete;
|
||||
await el.updateComplete;
|
||||
|
||||
expect(displayInput.value).to.equal('updated');
|
||||
|
@ -738,6 +740,52 @@ describe('<sl-select>', () => {
|
|||
expect(new FormData(form).getAll('select')).to.have.members(['foo', 'bar', 'baz']);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* @see {https://github.com/shoelace-style/shoelace/issues/2254}
|
||||
*/
|
||||
it('Should account for if `value` changed before connecting', async () => {
|
||||
const select = await fixture<SlSelect>(html`
|
||||
<sl-select label="Search By" multiple clearable .value=${['foo', 'bar']}>
|
||||
<sl-option value="foo">Foo</sl-option>
|
||||
<sl-option value="bar">Bar</sl-option>
|
||||
</sl-select>
|
||||
`);
|
||||
|
||||
// just for safe measure.
|
||||
await aTimeout(10);
|
||||
|
||||
expect(select.value).to.deep.equal(['foo', 'bar']);
|
||||
});
|
||||
|
||||
/**
|
||||
* @see {https://github.com/shoelace-style/shoelace/issues/2254}
|
||||
*/
|
||||
it('Should still work if using the value attribute', async () => {
|
||||
const select = await fixture<SlSelect>(html`
|
||||
<sl-select label="Search By" multiple clearable value="foo bar">
|
||||
<sl-option value="foo">Foo</sl-option>
|
||||
<sl-option value="bar">Bar</sl-option>
|
||||
</sl-select>
|
||||
`);
|
||||
|
||||
// just for safe measure.
|
||||
await aTimeout(10);
|
||||
|
||||
expect(select.value).to.deep.equal(['foo', 'bar']);
|
||||
|
||||
await clickOnElement(select);
|
||||
await select.updateComplete;
|
||||
await clickOnElement(select.querySelector("[value='foo']")!);
|
||||
|
||||
await select.updateComplete;
|
||||
await aTimeout(10);
|
||||
expect(select.value).to.deep.equal(['bar']);
|
||||
|
||||
select.setAttribute('value', 'foo bar');
|
||||
await aTimeout(10);
|
||||
expect(select.value).to.deep.equal(['foo', 'bar']);
|
||||
});
|
||||
});
|
||||
|
||||
runFormControlBaseTests('sl-select');
|
||||
|
|
|
@ -37,7 +37,9 @@ export default class SlSplitPanel extends ShoelaceElement {
|
|||
static styles: CSSResultGroup = [componentStyles, styles];
|
||||
|
||||
private cachedPositionInPixels: number;
|
||||
private isCollapsed = false;
|
||||
private readonly localize = new LocalizeController(this);
|
||||
private positionBeforeCollapsing = 0;
|
||||
private resizeObserver: ResizeObserver;
|
||||
private size: number;
|
||||
|
||||
|
@ -159,7 +161,7 @@ export default class SlSplitPanel extends ShoelaceElement {
|
|||
return;
|
||||
}
|
||||
|
||||
if (['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Home', 'End'].includes(event.key)) {
|
||||
if (['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Home', 'End', 'Enter'].includes(event.key)) {
|
||||
let newPosition = this.position;
|
||||
const incr = (event.shiftKey ? 10 : 1) * (this.primary === 'end' ? -1 : 1);
|
||||
|
||||
|
@ -181,6 +183,24 @@ export default class SlSplitPanel extends ShoelaceElement {
|
|||
newPosition = this.primary === 'end' ? 0 : 100;
|
||||
}
|
||||
|
||||
// Collapse/expand the primary panel when enter is pressed
|
||||
if (event.key === 'Enter') {
|
||||
if (this.isCollapsed) {
|
||||
newPosition = this.positionBeforeCollapsing;
|
||||
this.isCollapsed = false;
|
||||
} else {
|
||||
const positionBeforeCollapsing = this.position;
|
||||
|
||||
newPosition = 0;
|
||||
|
||||
// Wait for position to update before setting the collapsed state
|
||||
requestAnimationFrame(() => {
|
||||
this.isCollapsed = true;
|
||||
this.positionBeforeCollapsing = positionBeforeCollapsing;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
this.position = clamp(newPosition, 0, 100);
|
||||
}
|
||||
}
|
||||
|
@ -206,6 +226,8 @@ export default class SlSplitPanel extends ShoelaceElement {
|
|||
@watch('position')
|
||||
handlePositionChange() {
|
||||
this.cachedPositionInPixels = this.percentageToPixels(this.position);
|
||||
this.isCollapsed = false;
|
||||
this.positionBeforeCollapsing = 0;
|
||||
this.positionInPixels = this.percentageToPixels(this.position);
|
||||
this.emit('sl-reposition');
|
||||
}
|
||||
|
|
|
@ -31,11 +31,8 @@ export default css`
|
|||
outline: transparent;
|
||||
}
|
||||
|
||||
:host(:focus-visible):not([disabled]) {
|
||||
color: var(--sl-color-primary-600);
|
||||
}
|
||||
|
||||
:host(:focus-visible) {
|
||||
color: var(--sl-color-primary-600);
|
||||
outline: var(--sl-focus-ring);
|
||||
outline-offset: calc(-1 * var(--sl-focus-ring-width) - var(--sl-focus-ring-offset));
|
||||
}
|
||||
|
|
|
@ -202,7 +202,7 @@ export default class SlTextarea extends ShoelaceElement implements ShoelaceFormC
|
|||
this.input.style.height = 'auto';
|
||||
this.input.style.height = `${this.input.scrollHeight}px`;
|
||||
} else {
|
||||
(this.input.style.height as string | undefined) = undefined;
|
||||
this.input.style.height = '';
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -35,7 +35,7 @@ export const defaultValue =
|
|||
if (name === attributeName) {
|
||||
const converter = options.converter || defaultConverter;
|
||||
const fromAttribute =
|
||||
typeof converter === 'function' ? converter : converter?.fromAttribute ?? defaultConverter.fromAttribute;
|
||||
typeof converter === 'function' ? converter : (converter?.fromAttribute ?? defaultConverter.fromAttribute);
|
||||
|
||||
const newValue: unknown = fromAttribute!(value, options.type);
|
||||
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
import { registerTranslation } from '../utilities/localize.js';
|
||||
import type { Translation } from '../utilities/localize.js';
|
||||
|
||||
const translation: Translation = {
|
||||
$code: 'nb',
|
||||
$name: 'Norwegian Bokmål',
|
||||
$dir: 'ltr',
|
||||
|
||||
carousel: 'Karusell',
|
||||
clearEntry: 'Tøm felt',
|
||||
close: 'Lukk',
|
||||
copied: 'Kopiert',
|
||||
copy: 'Kopier',
|
||||
currentValue: 'Nåværende verdi',
|
||||
error: 'Feil',
|
||||
goToSlide: (slide, count) => `Gå til visning ${slide} av ${count}`,
|
||||
hidePassword: 'Skjul passord',
|
||||
loading: 'Laster',
|
||||
nextSlide: 'Neste visning',
|
||||
numOptionsSelected: num => {
|
||||
if (num === 0) return 'Ingen alternativer valgt';
|
||||
if (num === 1) return 'Ett alternativ valgt';
|
||||
return `${num} alternativer valgt`;
|
||||
},
|
||||
previousSlide: 'Forrige visning',
|
||||
progress: 'Fremdrift',
|
||||
remove: 'Fjern',
|
||||
resize: 'Endre størrelse',
|
||||
scrollToEnd: 'Rull til slutten',
|
||||
scrollToStart: 'Rull til starten',
|
||||
selectAColorFromTheScreen: 'Velg en farge fra skjermen',
|
||||
showPassword: 'Vis passord',
|
||||
slideNum: slide => `Visning ${slide}`,
|
||||
toggleColorFormat: 'Bytt fargeformat'
|
||||
};
|
||||
|
||||
registerTranslation(translation);
|
||||
|
||||
export default translation;
|
|
@ -0,0 +1,39 @@
|
|||
import { registerTranslation } from '../utilities/localize.js';
|
||||
import type { Translation } from '../utilities/localize.js';
|
||||
|
||||
const translation: Translation = {
|
||||
$code: 'nn',
|
||||
$name: 'Norwegian Nynorsk',
|
||||
$dir: 'ltr',
|
||||
|
||||
carousel: 'Karusell',
|
||||
clearEntry: 'Tøm felt',
|
||||
close: 'Lukk',
|
||||
copied: 'Kopiert',
|
||||
copy: 'Kopier',
|
||||
currentValue: 'Nåverande verdi',
|
||||
error: 'Feil',
|
||||
goToSlide: (slide, count) => `Gå til visning ${slide} av ${count}`,
|
||||
hidePassword: 'Gøym passord',
|
||||
loading: 'Lastar',
|
||||
nextSlide: 'Neste visning',
|
||||
numOptionsSelected: num => {
|
||||
if (num === 0) return 'Ingen alternativ valt';
|
||||
if (num === 1) return 'Eitt alternativ valt';
|
||||
return `${num} alternativ valt`;
|
||||
},
|
||||
previousSlide: 'Førre visning',
|
||||
progress: 'Framdrift',
|
||||
remove: 'Fjern',
|
||||
resize: 'Endre storleik',
|
||||
scrollToEnd: 'Rull til slutten',
|
||||
scrollToStart: 'Rull til starten',
|
||||
selectAColorFromTheScreen: 'Vel ein farge frå skjermen',
|
||||
showPassword: 'Vis passord',
|
||||
slideNum: slide => `Visning ${slide}`,
|
||||
toggleColorFormat: 'Byt fargeformat'
|
||||
};
|
||||
|
||||
registerTranslation(translation);
|
||||
|
||||
export default translation;
|
|
@ -0,0 +1,41 @@
|
|||
import { registerTranslation } from '../utilities/localize.js';
|
||||
import type { Translation } from '../utilities/localize.js';
|
||||
|
||||
const translation: Translation = {
|
||||
$code: 'uk',
|
||||
$name: 'Українська',
|
||||
$dir: 'ltr',
|
||||
|
||||
carousel: 'Карусель',
|
||||
clearEntry: 'Очистити поле',
|
||||
close: 'Закрити',
|
||||
copied: 'Скопійовано',
|
||||
copy: 'Скопіювати',
|
||||
currentValue: 'Поточне значення',
|
||||
error: 'Збій',
|
||||
goToSlide: (slide, count) => `Перейти до слайда №${slide} з ${count}`,
|
||||
hidePassword: 'Приховати пароль',
|
||||
loading: 'Завантаження',
|
||||
nextSlide: 'Наступний слайд',
|
||||
numOptionsSelected: num => {
|
||||
const n = num % 10;
|
||||
if (n === 0) return 'не вибрано варіантів';
|
||||
if (n === 1) return 'вибрано 1 варіант';
|
||||
if (n === 2 || n === 3 || n === 4) return `вибрано ${num} варіанти`;
|
||||
return `вибрано ${num} варіантів`;
|
||||
},
|
||||
previousSlide: 'Попередній слайд',
|
||||
progress: 'Поступ',
|
||||
remove: 'Видалити',
|
||||
resize: 'Змінити розмір',
|
||||
scrollToEnd: 'Прокрутити в кінець',
|
||||
scrollToStart: 'Прокрутити на початок',
|
||||
selectAColorFromTheScreen: 'Виберіть колір на екрані',
|
||||
showPassword: 'Показати пароль',
|
||||
slideNum: slide => `Слайд ${slide}`,
|
||||
toggleColorFormat: 'Переключити кольорову модель'
|
||||
};
|
||||
|
||||
registerTranslation(translation);
|
||||
|
||||
export default translation;
|
Ładowanie…
Reference in New Issue