@sveltia/ui 0.33.1 → 0.35.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.
@@ -260,7 +260,8 @@ button.pill {
260
260
  border-radius: 80px;
261
261
  padding: var(--sui-button-medium-pill-padding, 0 12px);
262
262
  }
263
- button.flex {
263
+ button.flex:not([hidden]) {
264
+ display: inline-flex;
264
265
  flex: auto;
265
266
  width: -moz-available;
266
267
  width: -webkit-fill-available;
@@ -23,7 +23,8 @@
23
23
 
24
24
  <div {...restProps} role="none" class="sui spacer {className}" class:flex></div>
25
25
 
26
- <style>.spacer.flex {
26
+ <style>.spacer.flex:not([hidden]) {
27
+ display: block;
27
28
  flex: auto;
28
29
  }
29
30
  .spacer:not(.flex) {
@@ -115,4 +115,7 @@
115
115
  <style>.code-editor {
116
116
  margin: var(--sui-focus-ring-width);
117
117
  width: calc(100% - var(--sui-focus-ring-width) * 2);
118
+ }
119
+ .code-editor.flex:not([hidden]) {
120
+ display: block;
118
121
  }</style>
@@ -128,6 +128,9 @@
128
128
  margin: var(--sui-focus-ring-width);
129
129
  width: calc(100% - var(--sui-focus-ring-width) * 2);
130
130
  }
131
+ .text-editor.flex:not([hidden]) {
132
+ display: block;
133
+ }
131
134
  .text-editor :global(.sui.text-area) {
132
135
  margin: 0 !important;
133
136
  width: 100% !important;
@@ -204,7 +204,8 @@
204
204
  margin: var(--sui-focus-ring-width);
205
205
  min-width: var(--sui-textbox-singleline-min-width);
206
206
  }
207
- .number-input.flex {
207
+ .number-input.flex:not([hidden]) {
208
+ display: inline-flex;
208
209
  width: -moz-available;
209
210
  width: -webkit-fill-available;
210
211
  width: stretch;
@@ -107,7 +107,8 @@
107
107
  margin: var(--sui-focus-ring-width);
108
108
  min-width: var(--sui-textbox-singleline-min-width);
109
109
  }
110
- .password-input.flex {
110
+ .password-input.flex:not([hidden]) {
111
+ display: inline-flex;
111
112
  width: -moz-available;
112
113
  width: -webkit-fill-available;
113
114
  width: stretch;
@@ -22,6 +22,7 @@
22
22
  * @property {string} [value] Input value.
23
23
  * @property {Snippet} [searchIcon] Search icon slot content.
24
24
  * @property {Snippet} [closeIcon] Close icon slot content.
25
+ * @property {() => void} [onClear] Callback invoked when the clear button is clicked.
25
26
  */
26
27
 
27
28
  /**
@@ -40,6 +41,7 @@
40
41
  children,
41
42
  searchIcon,
42
43
  closeIcon,
44
+ onClear,
43
45
  ...restProps
44
46
  /* eslint-enable prefer-const */
45
47
  } = $props();
@@ -98,11 +100,7 @@
98
100
  onclick={() => {
99
101
  value = '';
100
102
  inputElement?.focus();
101
- globalThis.requestIdleCallback(() => {
102
- inputElement?.dispatchEvent(new KeyboardEvent('input'));
103
- inputElement?.dispatchEvent(new KeyboardEvent('keypress'));
104
- inputElement?.dispatchEvent(new KeyboardEvent('change'));
105
- });
103
+ onClear?.();
106
104
  }}
107
105
  >
108
106
  {#snippet startIcon()}
@@ -123,7 +121,8 @@
123
121
  margin: var(--sui-focus-ring-width);
124
122
  min-width: var(--sui-textbox-singleline-min-width);
125
123
  }
126
- .search-bar.flex {
124
+ .search-bar.flex:not([hidden]) {
125
+ display: inline-flex;
127
126
  width: -moz-available;
128
127
  width: -webkit-fill-available;
129
128
  width: stretch;
@@ -24,6 +24,10 @@ declare const SearchBar: import("svelte").Component<TextInputProps & import("../
24
24
  * Close icon slot content.
25
25
  */
26
26
  closeIcon?: Snippet<[]> | undefined;
27
+ /**
28
+ * Callback invoked when the clear button is clicked.
29
+ */
30
+ onClear?: (() => void) | undefined;
27
31
  } & Record<string, any>, {
28
32
  focus: () => void;
29
33
  }, "value">;
@@ -40,6 +44,10 @@ type Props = {
40
44
  * Close icon slot content.
41
45
  */
42
46
  closeIcon?: Snippet<[]> | undefined;
47
+ /**
48
+ * Callback invoked when the clear button is clicked.
49
+ */
50
+ onClear?: (() => void) | undefined;
43
51
  };
44
52
  import type { TextInputProps } from '../../typedefs';
45
53
  import type { InputEventHandlers } from '../../typedefs';
@@ -0,0 +1,143 @@
1
+ <!--
2
+ @component
3
+ Similar to `PasswordInput`, but it doesn’t use `type="password"` to hide the input value. Instead,
4
+ it relies on CSS to visually conceal the input to prevent password managers from prompting to save
5
+ the password.
6
+ @see https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/-webkit-text-security
7
+ -->
8
+ <script>
9
+ import { _ } from 'svelte-i18n';
10
+ import Button from '../button/button.svelte';
11
+ import Icon from '../icon/icon.svelte';
12
+ import TextInput from './text-input.svelte';
13
+
14
+ /**
15
+ * @import { Snippet } from 'svelte';
16
+ * @import { CommonEventHandlers, InputEventHandlers, TextInputProps } from '../../typedefs';
17
+ */
18
+
19
+ /**
20
+ * @typedef {object} Props
21
+ * @property {string} [value] Input value.
22
+ * @property {Snippet} [visibilityIcon] Visibility icon slot content.
23
+ */
24
+
25
+ /**
26
+ * @type {TextInputProps & CommonEventHandlers & InputEventHandlers & Props & Record<string, any>}
27
+ */
28
+ let {
29
+ /* eslint-disable prefer-const */
30
+ value = $bindable(),
31
+ flex = false,
32
+ monospace = true,
33
+ class: className,
34
+ hidden = false,
35
+ disabled = false,
36
+ readonly = false,
37
+ required = false,
38
+ invalid = false,
39
+ children,
40
+ visibilityIcon,
41
+ ...restProps
42
+ /* eslint-enable prefer-const */
43
+ } = $props();
44
+
45
+ const id = $props.id();
46
+
47
+ /**
48
+ * Reference to the `<input>` element.
49
+ * @type {HTMLInputElement | undefined}
50
+ */
51
+ let inputElement = $state();
52
+ let show = $state(false);
53
+ </script>
54
+
55
+ <div
56
+ role="none"
57
+ class="sui secret-input {className}"
58
+ class:flex
59
+ class:disabled
60
+ class:readonly
61
+ class:show
62
+ {hidden}
63
+ >
64
+ <TextInput
65
+ bind:element={inputElement}
66
+ {...restProps}
67
+ {id}
68
+ bind:value
69
+ spellcheck="false"
70
+ {flex}
71
+ {monospace}
72
+ {hidden}
73
+ {disabled}
74
+ {readonly}
75
+ {required}
76
+ {invalid}
77
+ />
78
+ <Button
79
+ iconic
80
+ disabled={disabled || readonly}
81
+ pressed={show}
82
+ aria-label={$_(show ? '_sui.secret_input.hide_secret' : '_sui.secret_input.show_secret')}
83
+ aria-controls={id}
84
+ onclick={() => {
85
+ show = !show;
86
+ }}
87
+ >
88
+ {#snippet startIcon()}
89
+ {#if visibilityIcon}
90
+ {@render visibilityIcon()}
91
+ {:else}
92
+ <Icon name={show ? 'visibility_off' : 'visibility'} />
93
+ {/if}
94
+ {/snippet}
95
+ </Button>
96
+ </div>
97
+
98
+ <style>.secret-input {
99
+ display: inline-flex;
100
+ align-items: center;
101
+ margin: var(--sui-focus-ring-width);
102
+ min-width: var(--sui-textbox-singleline-min-width);
103
+ }
104
+ .secret-input.flex:not([hidden]) {
105
+ display: inline-flex;
106
+ width: -moz-available;
107
+ width: -webkit-fill-available;
108
+ width: stretch;
109
+ min-width: 0;
110
+ }
111
+ .secret-input.show :global(input) {
112
+ -webkit-text-security: none;
113
+ }
114
+ .secret-input :global(.text-input) {
115
+ flex: auto;
116
+ margin: 0 !important;
117
+ width: 0;
118
+ min-width: 0 !important;
119
+ }
120
+ .secret-input :global(input) {
121
+ border-start-end-radius: 0;
122
+ border-end-end-radius: 0;
123
+ -webkit-text-security: disc;
124
+ }
125
+ .secret-input :global(button) {
126
+ flex: none;
127
+ margin-block: 0;
128
+ margin-inline-start: -1px;
129
+ margin-inline-end: 0;
130
+ border-width: 1px;
131
+ border-color: var(--sui-textbox-border-color);
132
+ width: var(--sui-textbox-height);
133
+ aspect-ratio: 1/1;
134
+ }
135
+ .secret-input :global(button:last-child) {
136
+ border-start-start-radius: 0;
137
+ border-start-end-radius: 4px;
138
+ border-end-end-radius: 4px;
139
+ border-end-start-radius: 0;
140
+ }
141
+ .secret-input :global(button) :global(.icon) {
142
+ font-size: var(--sui-font-size-xx-large);
143
+ }</style>
@@ -0,0 +1,34 @@
1
+ export default SecretInput;
2
+ type SecretInput = {
3
+ $on?(type: string, callback: (e: any) => void): () => void;
4
+ $set?(props: Partial<TextInputProps & KeyboardEventHandlers & MouseEventHandlers & FocusEventHandlers & DragEventHandlers & InputEventHandlers & Props & Record<string, any>>): void;
5
+ };
6
+ /**
7
+ * Similar to `PasswordInput`, but it doesn’t use `type="password"` to hide the input value. Instead,
8
+ * it relies on CSS to visually conceal the input to prevent password managers from prompting to save
9
+ * the password.
10
+ * @see https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/-webkit-text-security
11
+ */
12
+ declare const SecretInput: import("svelte").Component<TextInputProps & import("../../typedefs").KeyboardEventHandlers & import("../../typedefs").MouseEventHandlers & import("../../typedefs").FocusEventHandlers & import("../../typedefs").DragEventHandlers & InputEventHandlers & {
13
+ /**
14
+ * Input value.
15
+ */
16
+ value?: string | undefined;
17
+ /**
18
+ * Visibility icon slot content.
19
+ */
20
+ visibilityIcon?: Snippet<[]> | undefined;
21
+ } & Record<string, any>, {}, "value">;
22
+ type Props = {
23
+ /**
24
+ * Input value.
25
+ */
26
+ value?: string | undefined;
27
+ /**
28
+ * Visibility icon slot content.
29
+ */
30
+ visibilityIcon?: Snippet<[]> | undefined;
31
+ };
32
+ import type { TextInputProps } from '../../typedefs';
33
+ import type { InputEventHandlers } from '../../typedefs';
34
+ import type { Snippet } from 'svelte';
@@ -87,7 +87,8 @@
87
87
  .text-area[hidden] {
88
88
  display: none;
89
89
  }
90
- .text-area.flex {
90
+ .text-area.flex:not([hidden]) {
91
+ display: inline-grid;
91
92
  width: -moz-available;
92
93
  width: -webkit-fill-available;
93
94
  width: stretch;
@@ -31,6 +31,7 @@
31
31
  inputmode = 'text',
32
32
  flex = false,
33
33
  monospace = false,
34
+ debounce = false,
34
35
  class: className,
35
36
  hidden = false,
36
37
  disabled = false,
@@ -39,11 +40,43 @@
39
40
  invalid = false,
40
41
  'aria-label': ariaLabel,
41
42
  children,
43
+ oninput,
42
44
  ...restProps
43
45
  /* eslint-enable prefer-const */
44
46
  } = $props();
45
47
 
46
48
  const id = $props.id();
49
+ const timeout = $derived(typeof debounce === 'number' ? debounce : 300);
50
+
51
+ let debounceTimer = 0;
52
+
53
+ $effect(() => () => {
54
+ clearTimeout(debounceTimer);
55
+ });
56
+
57
+ /**
58
+ * Update the `value` and call the `oninput` callback.
59
+ * @param {InputEvent} event The `input` event object.
60
+ */
61
+ const fireInput = (event) => {
62
+ value = /** @type {HTMLInputElement} */ (event.target).value;
63
+ oninput?.(event);
64
+ };
65
+
66
+ /**
67
+ * Handle the `input` event. If `debounce` is `true`, the event will be debounced by 300ms.
68
+ * @param {InputEvent} event The `input` event object.
69
+ */
70
+ const handleInput = (event) => {
71
+ if (debounce) {
72
+ clearTimeout(debounceTimer);
73
+ debounceTimer = setTimeout(() => {
74
+ fireInput(event);
75
+ }, timeout);
76
+ } else {
77
+ fireInput(event);
78
+ }
79
+ };
47
80
  </script>
48
81
 
49
82
  <div
@@ -58,7 +91,7 @@
58
91
  <input
59
92
  bind:this={element}
60
93
  {...restProps}
61
- bind:value
94
+ {value}
62
95
  type="text"
63
96
  {role}
64
97
  dir="auto"
@@ -73,6 +106,7 @@
73
106
  aria-readonly={readonly}
74
107
  aria-required={required}
75
108
  aria-invalid={invalid}
109
+ oninput={handleInput}
76
110
  use:activateKeyShortcuts={keyShortcuts}
77
111
  />
78
112
  {#if ariaLabel && showInlineLabel}
@@ -91,7 +125,8 @@
91
125
  margin: var(--sui-focus-ring-width);
92
126
  min-width: var(--sui-textbox-singleline-min-width);
93
127
  }
94
- .text-input.flex {
128
+ .text-input.flex:not([hidden]) {
129
+ display: inline-flex;
95
130
  width: -moz-available;
96
131
  width: -webkit-fill-available;
97
132
  width: stretch;
package/dist/index.d.ts CHANGED
@@ -67,6 +67,7 @@ export { default as TextEditor } from "./components/text-editor/text-editor.svel
67
67
  export { default as NumberInput } from "./components/text-field/number-input.svelte";
68
68
  export { default as PasswordInput } from "./components/text-field/password-input.svelte";
69
69
  export { default as SearchBar } from "./components/text-field/search-bar.svelte";
70
+ export { default as SecretInput } from "./components/text-field/secret-input.svelte";
70
71
  export { default as TextArea } from "./components/text-field/text-area.svelte";
71
72
  export { default as TextInput } from "./components/text-field/text-input.svelte";
72
73
  export { default as Toast } from "./components/toast/toast.svelte";
package/dist/index.js CHANGED
@@ -71,6 +71,7 @@ export { default as TextEditor } from './components/text-editor/text-editor.svel
71
71
  export { default as NumberInput } from './components/text-field/number-input.svelte';
72
72
  export { default as PasswordInput } from './components/text-field/password-input.svelte';
73
73
  export { default as SearchBar } from './components/text-field/search-bar.svelte';
74
+ export { default as SecretInput } from './components/text-field/secret-input.svelte';
74
75
  export { default as TextArea } from './components/text-field/text-area.svelte';
75
76
  export { default as TextInput } from './components/text-field/text-input.svelte';
76
77
  export { default as Toast } from './components/toast/toast.svelte';
@@ -35,6 +35,10 @@ export namespace strings {
35
35
  let show_password: string;
36
36
  let hide_password: string;
37
37
  }
38
+ namespace secret_input {
39
+ let show_secret: string;
40
+ let hide_secret: string;
41
+ }
38
42
  namespace select_tags {
39
43
  let selected_options: string;
40
44
  let remove_x: string;
@@ -35,6 +35,10 @@ export const strings = {
35
35
  show_password: 'Show Password',
36
36
  hide_password: 'Hide Password',
37
37
  },
38
+ secret_input: {
39
+ show_secret: 'Show Secret',
40
+ hide_secret: 'Hide Secret',
41
+ },
38
42
  select_tags: {
39
43
  selected_options: 'Selected options',
40
44
  remove_x: 'Remove {name}',
@@ -35,6 +35,10 @@ export namespace strings {
35
35
  let show_password: string;
36
36
  let hide_password: string;
37
37
  }
38
+ namespace secret_input {
39
+ let show_secret: string;
40
+ let hide_secret: string;
41
+ }
38
42
  namespace select_tags {
39
43
  let selected_options: string;
40
44
  let remove_x: string;
@@ -35,6 +35,10 @@ export const strings = {
35
35
  show_password: 'パスワードを表示',
36
36
  hide_password: 'パスワードを隠す',
37
37
  },
38
+ secret_input: {
39
+ show_secret: 'シークレットを表示',
40
+ hide_secret: 'シークレットを隠す',
41
+ },
38
42
  select_tags: {
39
43
  selected_options: '選択済みのオプション',
40
44
  remove_x: '{name} を削除',
@@ -448,6 +448,13 @@ export type TextInputProps = {
448
448
  * Whether to use a monospace font for the input text.
449
449
  */
450
450
  monospace?: boolean | undefined;
451
+ /**
452
+ * Whether to debounce the `input` event. If `true`, the
453
+ * `input` event will be debounced by 300ms. If a number is provided, it will be used as the
454
+ * debounce delay in milliseconds. This is useful for performance optimization when the input event
455
+ * triggers expensive operations like API calls. Default: `false`.
456
+ */
457
+ debounce?: number | boolean | undefined;
451
458
  /**
452
459
  * The `class` attribute on the wrapper element.
453
460
  */
package/dist/typedefs.js CHANGED
@@ -159,6 +159,10 @@
159
159
  * `inputmode` attribute on the `<input>`.
160
160
  * @property {boolean} [flex] Make the text input container flexible.
161
161
  * @property {boolean} [monospace] Whether to use a monospace font for the input text.
162
+ * @property {boolean | number} [debounce] Whether to debounce the `input` event. If `true`, the
163
+ * `input` event will be debounced by 300ms. If a number is provided, it will be used as the
164
+ * debounce delay in milliseconds. This is useful for performance optimization when the input event
165
+ * triggers expensive operations like API calls. Default: `false`.
162
166
  * @property {string} [class] The `class` attribute on the wrapper element.
163
167
  * @property {boolean} [hidden] Whether to hide the widget.
164
168
  * @property {boolean} [disabled] Whether to disable the widget. An alias of the `aria-disabled`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sveltia/ui",
3
- "version": "0.33.1",
3
+ "version": "0.35.0",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "repository": {
@@ -29,7 +29,7 @@
29
29
  },
30
30
  "devDependencies": {
31
31
  "@sveltejs/adapter-auto": "^7.0.1",
32
- "@sveltejs/kit": "^2.53.4",
32
+ "@sveltejs/kit": "^2.54.0",
33
33
  "@sveltejs/package": "^2.5.7",
34
34
  "@sveltejs/vite-plugin-svelte": "^6.2.4",
35
35
  "@vitest/coverage-v8": "^4.0.18",
@@ -40,17 +40,17 @@
40
40
  "eslint-plugin-import": "^2.32.0",
41
41
  "eslint-plugin-jsdoc": "^62.7.1",
42
42
  "eslint-plugin-svelte": "^2.46.1",
43
- "oxlint": "^1.51.0",
43
+ "oxlint": "^1.53.0",
44
44
  "postcss": "^8.5.8",
45
45
  "postcss-html": "^1.8.1",
46
46
  "prettier": "^3.8.1",
47
47
  "prettier-plugin-svelte": "^3.5.1",
48
- "sass": "^1.97.3",
48
+ "sass": "^1.98.0",
49
49
  "stylelint": "^17.4.0",
50
50
  "stylelint-config-recommended-scss": "^17.0.0",
51
51
  "stylelint-scss": "^7.0.0",
52
- "svelte": "^5.53.7",
53
- "svelte-check": "^4.4.4",
52
+ "svelte": "^5.53.11",
53
+ "svelte-check": "^4.4.5",
54
54
  "svelte-preprocess": "^6.0.3",
55
55
  "tslib": "^2.8.1",
56
56
  "vite": "^7.3.1",