@sveltia/ui 0.7.4 → 0.7.5

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.
Files changed (33) hide show
  1. package/package/components/button/button.svelte +8 -0
  2. package/package/components/button/button.svelte.d.ts +5 -0
  3. package/package/components/button/select-button-group.svelte +3 -3
  4. package/package/components/checkbox/checkbox.svelte +10 -5
  5. package/package/components/dialog/dialog.svelte +2 -2
  6. package/package/components/disclosure/disclosure.svelte +0 -1
  7. package/package/components/drawer/drawer.svelte +2 -2
  8. package/package/components/drawer/drawer.svelte.d.ts +2 -2
  9. package/package/components/listbox/listbox.svelte +3 -3
  10. package/package/components/radio/radio-group.svelte +3 -3
  11. package/package/components/radio/radio-group.svelte.d.ts +2 -2
  12. package/package/components/select/combobox.svelte +46 -28
  13. package/package/components/select/combobox.svelte.d.ts +3 -2
  14. package/package/components/select/select.svelte +10 -9
  15. package/package/components/select/select.svelte.d.ts +4 -3
  16. package/package/components/slider/slider.svelte +3 -2
  17. package/package/components/switch/switch.svelte +6 -4
  18. package/package/components/text-field/markdown-editor.svelte +9 -6
  19. package/package/components/text-field/number-input.svelte +74 -40
  20. package/package/components/text-field/password-input.svelte +9 -4
  21. package/package/components/text-field/search-bar.svelte +4 -2
  22. package/package/components/text-field/text-area.svelte +6 -5
  23. package/package/components/text-field/text-input.svelte +16 -4
  24. package/package/components/text-field/text-input.svelte.d.ts +7 -2
  25. package/package/components/util/app-shell.svelte +8 -2
  26. package/package/components/util/popup.svelte +8 -3
  27. package/package/components/util/popup.svelte.d.ts +5 -0
  28. package/package/services/group.js +16 -0
  29. package/package/services/popup.d.ts +5 -1
  30. package/package/services/popup.js +22 -19
  31. package/package/styles/core.scss +4 -0
  32. package/package/styles/variables.scss +2 -2
  33. package/package.json +11 -11
@@ -32,12 +32,12 @@
32
32
  */
33
33
  export let readonly = false;
34
34
  /**
35
- * Whether to disable the widget. An alias of the `aria-required` attribute.
35
+ * Whether to mark the widget required. An alias of the `aria-required` attribute.
36
36
  * @type {boolean}
37
37
  */
38
38
  export let required = false;
39
39
  /**
40
- * Whether to disable the widget. An alias of the `aria-invalid` attribute.
40
+ * Whether to mark the widget invalid. An alias of the `aria-invalid` attribute.
41
41
  * @type {boolean}
42
42
  */
43
43
  export let invalid = false;
@@ -61,14 +61,19 @@
61
61
  */
62
62
  export let step = 1;
63
63
 
64
+ let edited = false;
65
+
64
66
  $: maximumFractionDigits = String(step).split('.')[1]?.length || 0;
65
67
  $: isMin = typeof min === 'number' && Number(value || 0) <= min;
66
68
  $: isMax = typeof max === 'number' && Number(value || 0) >= max;
67
69
 
68
70
  $: invalid =
69
- (value !== undefined && Number.isNaN(Number(value))) ||
70
- (typeof min === 'number' && Number(value || 0) < min) ||
71
- (typeof max === 'number' && Number(value || 0) > max);
71
+ (required && edited && (value === undefined || value === '')) ||
72
+ (value !== undefined &&
73
+ value !== '' &&
74
+ (Number.isNaN(Number(value)) ||
75
+ (typeof min === 'number' && Number(value || 0) < min) ||
76
+ (typeof max === 'number' && Number(value || 0) > max)));
72
77
 
73
78
  let component;
74
79
 
@@ -95,7 +100,32 @@
95
100
  };
96
101
  </script>
97
102
 
98
- <div class="sui number-input {className}" class:disabled hidden={hidden || undefined}>
103
+ <div
104
+ class="sui number-input {className}"
105
+ class:disabled
106
+ class:readonly
107
+ hidden={hidden || undefined}
108
+ >
109
+ <div class="buttons">
110
+ <Button
111
+ iconic
112
+ disabled={disabled || readonly || Number.isNaN(Number(value)) || isMax}
113
+ on:click={() => {
114
+ increase();
115
+ }}
116
+ >
117
+ <Icon slot="start-icon" name="expand_less" label={$_('_sui.number_input.increase')} />
118
+ </Button>
119
+ <Button
120
+ iconic
121
+ disabled={disabled || readonly || Number.isNaN(Number(value)) || isMin}
122
+ on:click={() => {
123
+ decrease();
124
+ }}
125
+ >
126
+ <Icon slot="start-icon" name="expand_more" label={$_('_sui.number_input.decrease')} />
127
+ </Button>
128
+ </div>
99
129
  <TextInput
100
130
  bind:this={component}
101
131
  bind:value
@@ -108,6 +138,7 @@
108
138
  aria-valuenow={Number(value || 0)}
109
139
  aria-valuemin={min}
110
140
  aria-valuemax={max}
141
+ inputmode={maximumFractionDigits > 0 ? 'decimal' : 'numeric'}
111
142
  {...$$restProps}
112
143
  on:keydown={(event) => {
113
144
  const { key, ctrlKey, metaKey, altKey, shiftKey } = event;
@@ -122,28 +153,14 @@
122
153
  event.preventDefault();
123
154
  increase();
124
155
  }
156
+
157
+ if (!edited) {
158
+ edited = true;
159
+ }
125
160
  }}
126
161
  on:keypress
127
162
  on:input
128
163
  />
129
- <Button
130
- iconic
131
- disabled={disabled || Number.isNaN(Number(value)) || isMin}
132
- on:click={() => {
133
- decrease();
134
- }}
135
- >
136
- <Icon slot="start-icon" name="arrow_downward" label={$_('_sui.number_input.decrease')} />
137
- </Button>
138
- <Button
139
- iconic
140
- disabled={disabled || Number.isNaN(Number(value)) || isMax}
141
- on:click={() => {
142
- increase();
143
- }}
144
- >
145
- <Icon slot="start-icon" name={'arrow_upward'} label={$_('_sui.number_input.increase')} />
146
- </Button>
147
164
  </div>
148
165
 
149
166
  <style>.number-input {
@@ -151,29 +168,46 @@
151
168
  display: inline-flex;
152
169
  align-items: center;
153
170
  }
154
- .number-input :global(input) {
171
+ .number-input :global(:not(:first-child) input) {
172
+ border-top-left-radius: 0;
173
+ border-bottom-left-radius: 0;
174
+ }
175
+ .number-input :global(:not(:last-child) input) {
155
176
  border-top-right-radius: 0;
156
177
  border-bottom-right-radius: 0;
157
178
  }
158
- .number-input :global(button) {
179
+ .number-input:not(.disabled) :global(button[aria-disabled="true"]) {
180
+ filter: grayscale(0) opacity(1);
181
+ }
182
+ .number-input:not(.disabled) :global(button[aria-disabled="true"]) :global(*) {
183
+ filter: grayscale(1) opacity(0.35);
184
+ }
185
+
186
+ .buttons {
187
+ display: flex;
188
+ flex-direction: column;
189
+ width: 24px;
190
+ height: var(--sui-textbox-height);
191
+ }
192
+ .buttons :global(button) {
159
193
  flex: none;
160
194
  border-width: 1px;
161
195
  border-color: var(--sui-textbox-border-color);
162
- width: var(--sui-textbox-height);
163
- }
164
- .number-input :global(button):first-of-type {
165
- border-radius: 0;
166
- border-width: 1px 0;
167
- }
168
- .number-input :global(button):last-of-type {
169
- border-radius: 0 4px 4px 0;
196
+ width: 100%;
197
+ height: 50%;
170
198
  }
171
- .number-input :global(button) :global(.icon) {
172
- font-size: var(--sui-font-size-xx-large);
199
+ .buttons :global(button):first-of-type {
200
+ border-top-right-radius: 0;
201
+ border-bottom-right-radius: 0;
202
+ border-bottom-left-radius: 0;
203
+ border-width: 1px 0 0 1px;
173
204
  }
174
- .number-input:not(.disabled) :global(button[aria-disabled="true"]) {
175
- filter: grayscale(0) opacity(1);
205
+ .buttons :global(button):last-of-type {
206
+ border-top-left-radius: 0;
207
+ border-top-right-radius: 0;
208
+ border-bottom-right-radius: 0;
209
+ border-width: 0 0 1px 1px;
176
210
  }
177
- .number-input:not(.disabled) :global(button[aria-disabled="true"]) :global(*) {
178
- filter: grayscale(1) opacity(0.35);
211
+ .buttons :global(button) :global(.icon) {
212
+ font-size: 20px;
179
213
  }</style>
@@ -33,12 +33,12 @@
33
33
  */
34
34
  export let readonly = false;
35
35
  /**
36
- * Whether to disable the widget. An alias of the `aria-required` attribute.
36
+ * Whether to mark the widget required. An alias of the `aria-required` attribute.
37
37
  * @type {boolean}
38
38
  */
39
39
  export let required = false;
40
40
  /**
41
- * Whether to disable the widget. An alias of the `aria-invalid` attribute.
41
+ * Whether to mark the widget invalid. An alias of the `aria-invalid` attribute.
42
42
  * @type {boolean}
43
43
  */
44
44
  export let invalid = false;
@@ -61,7 +61,12 @@
61
61
  }
62
62
  </script>
63
63
 
64
- <div class="sui password-input {className}" class:disabled hidden={hidden || undefined}>
64
+ <div
65
+ class="sui password-input {className}"
66
+ class:disabled
67
+ class:readonly
68
+ hidden={hidden || undefined}
69
+ >
65
70
  <TextInput
66
71
  bind:this={inputComponent}
67
72
  bind:value
@@ -78,7 +83,7 @@
78
83
  />
79
84
  <Button
80
85
  iconic
81
- {disabled}
86
+ disabled={disabled || readonly}
82
87
  pressed={passwordVisible}
83
88
  on:click={() => {
84
89
  passwordVisible = !passwordVisible;
@@ -35,12 +35,12 @@
35
35
  */
36
36
  export let readonly = false;
37
37
  /**
38
- * Whether to disable the widget. An alias of the `aria-required` attribute.
38
+ * Whether to mark the widget required. An alias of the `aria-required` attribute.
39
39
  * @type {boolean}
40
40
  */
41
41
  export let required = false;
42
42
  /**
43
- * Whether to disable the widget. An alias of the `aria-invalid` attribute.
43
+ * Whether to mark the widget invalid. An alias of the `aria-invalid` attribute.
44
44
  * @type {boolean}
45
45
  */
46
46
  export let invalid = false;
@@ -69,6 +69,7 @@
69
69
  <div
70
70
  class="sui search-bar {className}"
71
71
  class:disabled
72
+ class:readonly
72
73
  role="search"
73
74
  hidden={hidden || undefined}
74
75
  aria-hidden={hidden}
@@ -84,6 +85,7 @@
84
85
  {readonly}
85
86
  {required}
86
87
  {invalid}
88
+ inputmode="search"
87
89
  {...$$restProps}
88
90
  bind:this={inputComponent}
89
91
  on:input
@@ -28,12 +28,12 @@
28
28
  */
29
29
  export let readonly = false;
30
30
  /**
31
- * Whether to disable the widget. An alias of the `aria-required` attribute.
31
+ * Whether to mark the widget required. An alias of the `aria-required` attribute.
32
32
  * @type {boolean}
33
33
  */
34
34
  export let required = false;
35
35
  /**
36
- * Whether to disable the widget. An alias of the `aria-invalid` attribute.
36
+ * Whether to mark the widget invalid. An alias of the `aria-invalid` attribute.
37
37
  * @type {boolean}
38
38
  */
39
39
  export let invalid = false;
@@ -54,7 +54,7 @@
54
54
  export let autoResize = false;
55
55
  </script>
56
56
 
57
- <div class="sui text-area {className}" class:disabled hidden={hidden || undefined}>
57
+ <div class="sui text-area {className}" class:disabled class:readonly hidden={hidden || undefined}>
58
58
  <textarea
59
59
  {name}
60
60
  bind:value
@@ -107,8 +107,9 @@ textarea:focus,
107
107
  .clone:focus {
108
108
  border-color: var(--sui-primary-accent-color);
109
109
  }
110
- textarea:disabled,
111
- .clone:disabled {
110
+ textarea:disabled, textarea:read-only,
111
+ .clone:disabled,
112
+ .clone:read-only {
112
113
  background-color: var(--sui-disabled-background-color);
113
114
  }
114
115
 
@@ -42,12 +42,12 @@
42
42
  */
43
43
  export let readonly = false;
44
44
  /**
45
- * Whether to disable the widget. An alias of the `aria-required` attribute.
45
+ * Whether to mark the widget required. An alias of the `aria-required` attribute.
46
46
  * @type {boolean}
47
47
  */
48
48
  export let required = false;
49
49
  /**
50
- * Whether to disable the widget. An alias of the `aria-invalid` attribute.
50
+ * Whether to mark the widget invalid. An alias of the `aria-invalid` attribute.
51
51
  * @type {boolean}
52
52
  */
53
53
  export let invalid = false;
@@ -73,13 +73,19 @@
73
73
  * @type {boolean}
74
74
  */
75
75
  export let showInlineLabel = false;
76
+ /**
77
+ * The `inputmode` attribute on the `<input>`.
78
+ * @type {'text' | 'decimal' | 'numeric' | 'tel' | 'search' | 'email' | 'url'}
79
+ * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/inputmode
80
+ */
81
+ export let inputmode = 'text';
76
82
 
77
83
  const id = getRandomId('input');
78
84
 
79
85
  $: ariaLabel = $$restProps['aria-label'];
80
86
  </script>
81
87
 
82
- <div class="sui text-input {className}" class:disabled hidden={hidden || undefined}>
88
+ <div class="sui text-input {className}" class:disabled class:readonly hidden={hidden || undefined}>
83
89
  <input
84
90
  type="text"
85
91
  {role}
@@ -87,6 +93,7 @@
87
93
  tabindex={disabled ? -1 : 0}
88
94
  disabled={disabled || undefined}
89
95
  readonly={readonly || undefined}
96
+ {inputmode}
90
97
  aria-hidden={hidden}
91
98
  aria-disabled={disabled}
92
99
  aria-readonly={readonly}
@@ -116,6 +123,11 @@
116
123
  align-items: center;
117
124
  }
118
125
 
126
+ input:-webkit-autofill,
127
+ input:-webkit-autofill:focus {
128
+ transition: background-color 0s 600000s, color 0s 600000s;
129
+ }
130
+
119
131
  input {
120
132
  z-index: 1;
121
133
  display: inline-block;
@@ -140,7 +152,7 @@ input:read-only {
140
152
  color: var(--sui-tertiary-foreground-color);
141
153
  border-color: var(--sui-textbox-border-color) !important;
142
154
  }
143
- input:disabled {
155
+ input:disabled, input:read-only {
144
156
  background-color: var(--sui-disabled-background-color);
145
157
  }
146
158
  input[aria-invalid=true] {
@@ -17,9 +17,10 @@ export default class TextInput extends SvelteComponent<{
17
17
  hidden?: boolean;
18
18
  element?: HTMLInputElement;
19
19
  role?: "textbox" | "searchbox" | "combobox" | "spinbutton";
20
- keyShortcuts?: string;
21
20
  readonly?: boolean;
21
+ keyShortcuts?: string;
22
22
  showInlineLabel?: boolean;
23
+ inputmode?: "text" | "numeric" | "decimal" | "tel" | "search" | "email" | "url";
23
24
  }, {
24
25
  input: Event;
25
26
  keydown: KeyboardEvent;
@@ -65,6 +66,9 @@ export default class TextInput extends SvelteComponent<{
65
66
  /**accessor*/
66
67
  set showInlineLabel(arg: boolean);
67
68
  get showInlineLabel(): boolean;
69
+ /**accessor*/
70
+ set inputmode(arg: "text" | "numeric" | "decimal" | "tel" | "search" | "email" | "url");
71
+ get inputmode(): "text" | "numeric" | "decimal" | "tel" | "search" | "email" | "url";
68
72
  }
69
73
  export type TextInputProps = typeof __propDef.props;
70
74
  export type TextInputEvents = typeof __propDef.events;
@@ -82,9 +86,10 @@ declare const __propDef: {
82
86
  hidden?: boolean | undefined;
83
87
  element?: HTMLInputElement | undefined;
84
88
  role?: 'textbox' | 'searchbox' | 'combobox' | 'spinbutton';
85
- keyShortcuts?: string | undefined;
86
89
  readonly?: boolean;
90
+ keyShortcuts?: string | undefined;
87
91
  showInlineLabel?: boolean;
92
+ inputmode?: 'text' | 'decimal' | 'numeric' | 'tel' | 'search' | 'email' | 'url';
88
93
  };
89
94
  events: {
90
95
  input: Event;
@@ -96,7 +96,7 @@
96
96
  --sui-background-color-3-hsl: var(--sui-base-hue) 5% 96%;
97
97
  --sui-background-color-4-hsl: var(--sui-base-hue) 5% 94%;
98
98
  --sui-background-color-5-hsl: var(--sui-base-hue) 5% 74%;
99
- --sui-border-color-1-hsl: var(--sui-base-hue) 5% 54%;
99
+ --sui-border-color-1-hsl: var(--sui-base-hue) 5% 50%;
100
100
  --sui-border-color-2-hsl: var(--sui-base-hue) 5% 74%;
101
101
  --sui-border-color-3-hsl: var(--sui-base-hue) 5% 70%;
102
102
  --sui-shadow-color: var(--sui-base-hue) 10% 0%;
@@ -129,7 +129,7 @@
129
129
  --sui-background-color-3-hsl: var(--sui-base-hue) 10% 14%;
130
130
  --sui-background-color-4-hsl: var(--sui-base-hue) 10% 16%;
131
131
  --sui-background-color-5-hsl: var(--sui-base-hue) 10% 26%;
132
- --sui-border-color-1-hsl: var(--sui-base-hue) 10% 46%;
132
+ --sui-border-color-1-hsl: var(--sui-base-hue) 10% 50%;
133
133
  --sui-border-color-2-hsl: var(--sui-base-hue) 10% 26%;
134
134
  --sui-border-color-3-hsl: var(--sui-base-hue) 10% 22%;
135
135
  --sui-shadow-color: var(--sui-base-hue) 10% 0%;
@@ -397,7 +397,9 @@
397
397
  }
398
398
 
399
399
  :global(.disabled),
400
+ :global(.readonly),
400
401
  :global([aria-disabled=true]),
402
+ :global([aria-readonly=true]),
401
403
  :global([inert]) {
402
404
  cursor: default;
403
405
  pointer-events: none;
@@ -406,13 +408,17 @@
406
408
  filter: grayscale(1) opacity(0.35);
407
409
  }
408
410
  :global(.disabled) :global(*),
411
+ :global(.readonly) :global(*),
409
412
  :global([aria-disabled=true]) :global(*),
413
+ :global([aria-readonly=true]) :global(*),
410
414
  :global([inert]) :global(*) {
411
415
  filter: grayscale(0) opacity(1);
412
416
  }
413
417
 
414
418
  :global(.disabled) :global(*),
419
+ :global(.readonly) :global(*),
415
420
  :global([aria-disabled=true]) :global(*),
421
+ :global([aria-readonly=true]) :global(*),
416
422
  :global([inert]) :global(*) {
417
423
  cursor: default;
418
424
  pointer-events: none;
@@ -38,6 +38,11 @@
38
38
  */
39
39
  export let position = 'bottom-left';
40
40
 
41
+ /**
42
+ * Whether to keep the content when the dialog is not displayed.
43
+ */
44
+ export let keepContent = false;
45
+
41
46
  /**
42
47
  * Whether to show the popup at the center of the screen on mobile/tablet and ignore the defined
43
48
  * dropdown `position`.
@@ -155,7 +160,7 @@
155
160
  style:max-height={$style.height}
156
161
  style:visibility={$style.inset ? undefined : 'hidden'}
157
162
  >
158
- {#if showContent}
163
+ {#if keepContent || showContent}
159
164
  <slot />
160
165
  {/if}
161
166
  </div>
@@ -207,8 +212,8 @@
207
212
  outline-width: 0 !important;
208
213
  color: var(--sui-primary-foreground-color);
209
214
  background-color: var(--sui-secondary-background-color-translucent);
210
- -webkit-backdrop-filter: blur(16px);
211
- backdrop-filter: blur(16px);
215
+ -webkit-backdrop-filter: blur(32px);
216
+ backdrop-filter: blur(32px);
212
217
  box-shadow: 0 8px 16px var(--sui-popup-shadow-color);
213
218
  will-change: opacity, transform;
214
219
  transition-property: opacity, transform;
@@ -9,6 +9,7 @@ export default class Popup extends SvelteComponent<{
9
9
  position?: PopupPosition;
10
10
  anchor?: HTMLElement;
11
11
  content?: HTMLElement;
12
+ keepContent?: boolean;
12
13
  touchOptimized?: boolean;
13
14
  open?: import("svelte/store").Writable<boolean>;
14
15
  }, {
@@ -32,6 +33,9 @@ export default class Popup extends SvelteComponent<{
32
33
  set position(arg: PopupPosition);
33
34
  get position(): PopupPosition;
34
35
  /**accessor*/
36
+ set keepContent(arg: boolean);
37
+ get keepContent(): boolean;
38
+ /**accessor*/
35
39
  set touchOptimized(arg: boolean);
36
40
  get touchOptimized(): boolean;
37
41
  /**accessor*/
@@ -50,6 +54,7 @@ declare const __propDef: {
50
54
  position?: PopupPosition;
51
55
  anchor?: HTMLElement | undefined;
52
56
  content?: HTMLElement | undefined;
57
+ keepContent?: boolean;
53
58
  touchOptimized?: boolean;
54
59
  open?: import("svelte/store").Writable<boolean>;
55
60
  };
@@ -118,12 +118,28 @@ class Group {
118
118
  );
119
119
  }
120
120
 
121
+ /** @type {boolean} */
122
+ get isDisabled() {
123
+ return this.parent.matches('[aria-disabled="true"]');
124
+ }
125
+
126
+ /** @type {boolean} */
127
+ get isReadOnly() {
128
+ return this.parent.matches('[aria-readonly="true"]');
129
+ }
130
+
121
131
  /**
122
132
  * Select (and move focus to) the given target.
123
133
  * @param {(MouseEvent | KeyboardEvent)} event Triggered event.
124
134
  * @param {HTMLElement} newTarget Target element.
125
135
  */
126
136
  selectTarget(event, newTarget) {
137
+ if (this.isDisabled || this.isReadOnly) {
138
+ event.preventDefault();
139
+
140
+ return;
141
+ }
142
+
127
143
  const targetParentGroup = newTarget.closest(this.parentGroupSelector);
128
144
 
129
145
  this.activeMembers.forEach((element) => {
@@ -23,8 +23,12 @@ declare class Popup {
23
23
  popupElement: HTMLDialogElement;
24
24
  position: PopupPosition;
25
25
  id: string;
26
+ /** @type {boolean} */
27
+ get isDisabled(): boolean;
28
+ /** @type {boolean} */
29
+ get isReadOnly(): boolean;
26
30
  /**
27
- * Continue checking the position in case the window or parent element resizes.
31
+ * Check the position of the anchor element.
28
32
  */
29
33
  checkPosition(): void;
30
34
  }
@@ -48,26 +48,26 @@ class Popup {
48
48
  const top = position.startsWith('bottom-')
49
49
  ? `${Math.round(intersectionRect.bottom)}px`
50
50
  : position.endsWith('-top')
51
- ? `${Math.round(intersectionRect.top)}px`
52
- : 'auto';
51
+ ? `${Math.round(intersectionRect.top)}px`
52
+ : 'auto';
53
53
 
54
54
  const right = position.startsWith('left-')
55
55
  ? `${Math.round(rootBounds.width - intersectionRect.left)}px`
56
56
  : position.endsWith('-right')
57
- ? `${Math.round(rootBounds.width - intersectionRect.right)}px`
58
- : 'auto';
57
+ ? `${Math.round(rootBounds.width - intersectionRect.right)}px`
58
+ : 'auto';
59
59
 
60
60
  const bottom = position.startsWith('top-')
61
61
  ? `${Math.round(rootBounds.height - intersectionRect.top)}px`
62
62
  : position.endsWith('-bottom')
63
- ? `${Math.round(rootBounds.height - intersectionRect.bottom)}px`
64
- : 'auto';
63
+ ? `${Math.round(rootBounds.height - intersectionRect.bottom)}px`
64
+ : 'auto';
65
65
 
66
66
  const left = position.startsWith('right-')
67
67
  ? `${Math.round(intersectionRect.right)}px`
68
68
  : position.endsWith('-left')
69
- ? `${Math.round(intersectionRect.left)}px`
70
- : 'auto';
69
+ ? `${Math.round(intersectionRect.left)}px`
70
+ : 'auto';
71
71
 
72
72
  const anchorPopup = /** @type {HTMLElement} */ (this.anchorElement.closest('.popup'));
73
73
 
@@ -100,7 +100,7 @@ class Popup {
100
100
  this.popupElement.setAttribute('id', this.id);
101
101
 
102
102
  this.anchorElement.addEventListener('click', () => {
103
- if (!this.anchorElement.matches('[aria-disabled="true"]')) {
103
+ if (!this.isDisabled && !this.isReadOnly) {
104
104
  this.open.set(!get(this.open));
105
105
  }
106
106
  });
@@ -109,7 +109,7 @@ class Popup {
109
109
  const { key, ctrlKey, metaKey, shiftKey, altKey } = event;
110
110
  const hasModifier = shiftKey || altKey || ctrlKey || metaKey;
111
111
 
112
- if (['Enter', ' '].includes(key) && !hasModifier) {
112
+ if (!this.isDisabled && !this.isReadOnly && ['Enter', ' '].includes(key) && !hasModifier) {
113
113
  event.preventDefault();
114
114
  event.stopPropagation();
115
115
  this.open.set(!get(this.open));
@@ -154,19 +154,22 @@ class Popup {
154
154
  });
155
155
  }
156
156
 
157
+ /** @type {boolean} */
158
+ get isDisabled() {
159
+ return this.anchorElement.matches('[aria-disabled="true"]');
160
+ }
161
+
162
+ /** @type {boolean} */
163
+ get isReadOnly() {
164
+ return this.anchorElement.matches('[aria-readonly="true"]');
165
+ }
166
+
157
167
  /**
158
- * Continue checking the position in case the window or parent element resizes.
168
+ * Check the position of the anchor element.
159
169
  */
160
170
  checkPosition() {
171
+ this.observer.unobserve(this.anchorElement);
161
172
  this.observer.observe(this.anchorElement);
162
-
163
- window.requestAnimationFrame(() => {
164
- this.observer.unobserve(this.anchorElement);
165
-
166
- if (get(this.open)) {
167
- this.checkPosition();
168
- }
169
- });
170
173
  }
171
174
  }
172
175
 
@@ -101,7 +101,9 @@ dialog {
101
101
  }
102
102
 
103
103
  .disabled,
104
+ .readonly,
104
105
  [aria-disabled="true"],
106
+ [aria-readonly="true"],
105
107
  [inert] {
106
108
  cursor: default;
107
109
  pointer-events: none;
@@ -115,7 +117,9 @@ dialog {
115
117
  }
116
118
 
117
119
  .disabled *,
120
+ .readonly *,
118
121
  [aria-disabled="true"] *,
122
+ [aria-readonly="true"] *,
119
123
  [inert] * {
120
124
  cursor: default;
121
125
  pointer-events: none;
@@ -10,7 +10,7 @@
10
10
  --sui-background-color-3-hsl: var(--sui-base-hue) 5% 96%; // secondary
11
11
  --sui-background-color-4-hsl: var(--sui-base-hue) 5% 94%; // tertiary/disabled
12
12
  --sui-background-color-5-hsl: var(--sui-base-hue) 5% 74%; // highlight
13
- --sui-border-color-1-hsl: var(--sui-base-hue) 5% 54%; // control
13
+ --sui-border-color-1-hsl: var(--sui-base-hue) 5% 50%; // control
14
14
  --sui-border-color-2-hsl: var(--sui-base-hue) 5% 74%; // primary
15
15
  --sui-border-color-3-hsl: var(--sui-base-hue) 5% 70%; // secondary
16
16
  --sui-shadow-color: var(--sui-base-hue) 10% 0%;
@@ -45,7 +45,7 @@
45
45
  --sui-background-color-3-hsl: var(--sui-base-hue) 10% 14%; // secondary
46
46
  --sui-background-color-4-hsl: var(--sui-base-hue) 10% 16%; // tertiary/disabled
47
47
  --sui-background-color-5-hsl: var(--sui-base-hue) 10% 26%; // highlight
48
- --sui-border-color-1-hsl: var(--sui-base-hue) 10% 46%; // control
48
+ --sui-border-color-1-hsl: var(--sui-base-hue) 10% 50%; // control
49
49
  --sui-border-color-2-hsl: var(--sui-base-hue) 10% 26%; // primary
50
50
  --sui-border-color-3-hsl: var(--sui-base-hue) 10% 22%; // secondary
51
51
  --sui-shadow-color: var(--sui-base-hue) 10% 0%;