@sveltia/ui 0.63.0 → 0.64.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -220,9 +220,7 @@ button.ghost[aria-pressed=true] {
220
220
  background-color: var(--sui-button-ghost-background-color-pressed, var(--sui-selected-background-color));
221
221
  }
222
222
  button.link {
223
- outline: 0;
224
223
  margin: 0;
225
- border-radius: 0 !important;
226
224
  padding: 0 !important;
227
225
  height: auto !important;
228
226
  color: var(--sui-button-link-foreground-color, var(--sui-primary-accent-color-text));
@@ -237,9 +235,6 @@ button.link .label {
237
235
  :global(:is(:root, :host)[data-underline-links='true']) button.link .label {
238
236
  text-decoration: underline;
239
237
  }
240
- button.link:is(:hover, :focus, :active) .label {
241
- text-decoration: var(--sui-button-link-text-decoration-focus, underline);
242
- }
243
238
  button.small {
244
239
  border-radius: var(--sui-button-small-border-radius);
245
240
  padding: var(--sui-button-small-padding);
@@ -50,6 +50,13 @@
50
50
  /* eslint-enable prefer-const */
51
51
  } = $props();
52
52
 
53
+ /**
54
+ * Fallback `id`, so the option can always be referenced by `aria-activedescendant`. `<Group>`
55
+ * assigns one to each member as it activates, but within a `<Combobox>` that happens while the
56
+ * dropdown is still collapsed and no option has rendered yet.
57
+ */
58
+ const fallbackId = $props.id();
59
+
53
60
  /**
54
61
  * The registry provided by an ancestor `<Combobox>`. This is `undefined` when the option is used
55
62
  * standalone within a `<Listbox>`, in which case it always renders itself.
@@ -126,6 +133,7 @@
126
133
  <Button
127
134
  {...restProps}
128
135
  role="option"
136
+ id={restProps.id ?? fallbackId}
129
137
  tabindex="-1"
130
138
  aria-selected={selected}
131
139
  {label}
@@ -124,11 +124,63 @@
124
124
  onChange?.(new CustomEvent('Change', { detail }));
125
125
  };
126
126
 
127
+ /**
128
+ * The `<Listbox>` rendered for this combobox. It’s reached through {@link optionHost}, which owns
129
+ * it wherever the options currently live, rather than through the popup, which only holds it
130
+ * while the dropdown is expanded.
131
+ * @returns {HTMLElement | null | undefined} Listbox element.
132
+ */
133
+ const getListbox = () => optionHost?.querySelector('[role="listbox"]');
134
+
135
+ /**
136
+ * Make the selected option the starting point of the expanded dropdown: scroll it into view, and
137
+ * mark it as the focused member so the arrow keys carry on from there. A long option list is
138
+ * scrollable, and the dropdown would otherwise open at the top with the current selection nowhere
139
+ * to be seen — and the first arrow key would jump back up to the first option.
140
+ */
141
+ const revealSelectedOption = () => {
142
+ const listbox = getListbox();
143
+
144
+ const option = /** @type {HTMLElement | null | undefined} */ (
145
+ listbox?.querySelector('[role="option"][aria-selected="true"]')
146
+ );
147
+
148
+ if (!listbox || !option) {
149
+ return;
150
+ }
151
+
152
+ // `<Group>` tracks the focused member with this class, and moves it on from there
153
+ option.classList.add('focused');
154
+ listbox.setAttribute('aria-activedescendant', option.id);
155
+
156
+ option.scrollIntoView(true);
157
+ };
158
+
127
159
  // Let the options know whether they should render themselves
128
160
  $effect(() => {
129
161
  registry.expanded = isPopupOpen;
130
162
  });
131
163
 
164
+ // Reveal the selected option once the dropdown is expanded. The options are rendered and moved
165
+ // into the popup by the effects around this one, and the popup is then measured against the space
166
+ // below the anchor to get its height, so there is nothing to scroll until all of that has
167
+ // settled. The popup moves focus onto its first tab stop 100ms in, which scrolls that element
168
+ // into view in turn, so this has to come after it rather than be undone by it.
169
+ $effect(() => {
170
+ if (!isPopupOpen) {
171
+ return undefined;
172
+ }
173
+
174
+ const timer = globalThis.setTimeout(revealSelectedOption, 150);
175
+
176
+ return () => {
177
+ globalThis.clearTimeout(timer);
178
+ // The options are unmounted along with the popup, so the listbox is about to be pointing at
179
+ // an element that no longer exists
180
+ getListbox()?.removeAttribute('aria-activedescendant');
181
+ };
182
+ });
183
+
132
184
  // Move the options into the popup while it’s expanded, and back out before it’s unmounted. Only
133
185
  // the wrapper is moved, never its children, so Svelte keeps full ownership of the subtree.
134
186
  $effect(() => {
@@ -414,6 +466,7 @@
414
466
  }
415
467
 
416
468
  .combobox-inner {
469
+ flex: auto;
417
470
  display: flex;
418
471
  flex-direction: column;
419
472
  overflow: hidden;
@@ -7,7 +7,7 @@ export const SHIKI_VERSION: "4.4.3";
7
7
  /**
8
8
  * Version of this package, used to resolve the prebuilt Shiki engine chunk from a CDN.
9
9
  */
10
- export const UI_VERSION: "0.63.0";
10
+ export const UI_VERSION: "0.64.0";
11
11
  /**
12
12
  * Available syntax highlighting languages, sorted by display name.
13
13
  * @type {{ id: string, name: string, aliases?: string[] }[]}
@@ -10,7 +10,7 @@ export const SHIKI_VERSION = "4.4.3";
10
10
  /**
11
11
  * Version of this package, used to resolve the prebuilt Shiki engine chunk from a CDN.
12
12
  */
13
- export const UI_VERSION = "0.63.0";
13
+ export const UI_VERSION = "0.64.0";
14
14
 
15
15
  /**
16
16
  * Available syntax highlighting languages, sorted by display name.
@@ -247,6 +247,11 @@
247
247
  padding: 0;
248
248
  background-color: transparent;
249
249
  }
250
+ .content.combobox {
251
+ display: flex;
252
+ flex-direction: column;
253
+ overflow: hidden;
254
+ }
250
255
  .content.touch {
251
256
  position: static;
252
257
  border-width: 0 !important;
@@ -0,0 +1,113 @@
1
+ # Dialog action button labels
2
+ ok: OK
3
+ cancel: Cancelar
4
+ close: Cerrar
5
+
6
+ # Button to clear content, e.g. search input or calendar date
7
+ clear: Limpiar
8
+
9
+ # Text editor dialog buttons for link/image insertion
10
+ insert: Insertar
11
+ update: Actualizar
12
+ remove: Quitar
13
+
14
+ # Combobox dropdown toggle button aria-labels
15
+ collapse: Contraer
16
+ expand: Expandir
17
+
18
+ # Infobar/toast dismiss button label
19
+ dismiss: Descartar
20
+
21
+ # Calendar component
22
+ calendar:
23
+ # View switcher group aria-labels and navigation button aria-labels
24
+ year: Año
25
+ previous_decade: Década anterior
26
+ next_decade: Década siguiente
27
+ month: Mes
28
+ previous_month: Mes anterior
29
+ next_month: Mes siguiente
30
+ # “Today” button label in footer
31
+ today: Hoy
32
+
33
+ # Split button component
34
+ split_button:
35
+ # Wrapper aria-label, e.g. “Save Options” when primary button is “Save”
36
+ x_options: 'Opciones de {$name}'
37
+ # Dropdown toggle aria-label
38
+ more_options: Más opciones
39
+
40
+ # Combobox component
41
+ combobox:
42
+ # Placeholder when no option is selected
43
+ select_an_option: Selecciona una opción…
44
+ # Filter input aria-label
45
+ filter_options: Filtrar opciones
46
+ # Empty state message when filter has no matches
47
+ no_matching_options: No se encontraron opciones coincidentes
48
+
49
+ # Number input component
50
+ number_input:
51
+ # Spin button aria-labels for incrementing/decrementing the number value
52
+ increase: Aumentar
53
+ decrease: Disminuir
54
+
55
+ # Password input component
56
+ password_input:
57
+ # Visibility toggle button aria-labels
58
+ show_password: Mostrar la contraseña
59
+ hide_password: Ocultar la contraseña
60
+
61
+ # Secret input component (API keys, tokens, etc.)
62
+ secret_input:
63
+ # Visibility toggle button aria-labels
64
+ show_secret: Mostrar el secreto
65
+ hide_secret: Ocultar el secreto
66
+
67
+ # Select tags component (multi-select)
68
+ select_tags:
69
+ # Listbox aria-label
70
+ selected_options: Opciones seleccionadas
71
+ # Remove button aria-label, e.g. “Remove Option 1”
72
+ remove_x: 'Quitar {$name}'
73
+
74
+ # Text editor component
75
+ text_editor:
76
+ # Toolbar aria-labels
77
+ text_editor: Editor de texto
78
+ code_editor: Editor de código
79
+ # Block style menu button and menu aria-labels
80
+ text_style_options: Opciones de estilo de texto
81
+ show_text_style_options: Mostrar las opciones de estilo de texto
82
+ # Block style menu items
83
+ paragraph: Párrafo
84
+ heading_1: Encabezado 1
85
+ heading_2: Encabezado 2
86
+ heading_3: Encabezado 3
87
+ heading_4: Encabezado 4
88
+ heading_5: Encabezado 5
89
+ heading_6: Encabezado 6
90
+ bulleted_list: Lista con viñetas
91
+ numbered_list: Lista numerada
92
+ blockquote: Cita
93
+ code_block: Bloque de código
94
+ # Inline style button aria-labels
95
+ bold: Negrita
96
+ italic: Cursiva
97
+ strikethrough: Tachado
98
+ code: Código
99
+ link: Enlace
100
+ # Link dialog title
101
+ insert_link: Insertar enlace
102
+ update_link: Actualizar enlace
103
+ # Link dialog input field labels
104
+ text: Texto
105
+ url: URL
106
+ # Markdown mode toggle button aria-label
107
+ edit_in_markdown: Editar en Markdown
108
+ # Error message when rich text conversion fails
109
+ converter_error: No se pudo activar el modo de texto enriquecido. Usa el editor de texto sin formato en su lugar.
110
+ # Language selector aria-label
111
+ language: Idioma
112
+ # Plain text mode option in language selector
113
+ plain_text: Texto sin formato
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sveltia/ui",
3
- "version": "0.63.0",
3
+ "version": "0.64.0",
4
4
  "description": "A collection of Svelte components and utilities for building user interfaces.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -46,7 +46,7 @@
46
46
  "@lexical/selection": "^0.49.0",
47
47
  "@lexical/table": "^0.49.0",
48
48
  "@lexical/utils": "^0.49.0",
49
- "@sveltia/i18n": "^1.2.2",
49
+ "@sveltia/i18n": "^1.2.3",
50
50
  "@sveltia/utils": "^0.12.1",
51
51
  "lexical": "^0.49.0"
52
52
  },
@@ -64,22 +64,22 @@
64
64
  "eslint-plugin-import": "^2.32.0",
65
65
  "eslint-plugin-jsdoc": "^64.1.0",
66
66
  "eslint-plugin-package-json": "^1.7.1",
67
- "eslint-plugin-svelte": "^3.22.0",
68
- "globals": "^17.10.0",
67
+ "eslint-plugin-svelte": "^3.23.0",
68
+ "globals": "^17.11.0",
69
69
  "happy-dom": "^20.11.2",
70
70
  "oxlint": "^1.78.0",
71
71
  "postcss": "^8.5.26",
72
72
  "postcss-html": "^2.0.0",
73
73
  "prettier": "^3.9.6",
74
74
  "prettier-plugin-svelte": "^4.1.1",
75
- "rolldown": "^1.2.3",
75
+ "rolldown": "^1.2.4",
76
76
  "sass": "^1.102.0",
77
77
  "shiki": "^4.4.3",
78
78
  "stylelint": "^17.14.1",
79
79
  "stylelint-config-recommended-scss": "^17.0.1",
80
80
  "stylelint-scss": "^7.2.0",
81
- "svelte": "^5.56.8",
82
- "svelte-check": "^4.7.5",
81
+ "svelte": "^5.56.9",
82
+ "svelte-check": "^4.7.6",
83
83
  "svelte-preprocess": "^6.0.5",
84
84
  "tslib": "^2.8.1",
85
85
  "vite": "^8.2.1",