@sondor-technology/sondor-components 1.0.7 → 1.0.9
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.
- package/dist/components/calendar/calendar.d.ts +36 -0
- package/dist/components/calendar/calendar.d.ts.map +1 -0
- package/dist/components/calendar/calendar.js +449 -0
- package/dist/components/calendar/calendar.js.map +1 -0
- package/dist/components/cron-builder/cron-builder.d.ts +37 -0
- package/dist/components/cron-builder/cron-builder.d.ts.map +1 -0
- package/dist/components/cron-builder/cron-builder.js +522 -0
- package/dist/components/cron-builder/cron-builder.js.map +1 -0
- package/dist/components/input/input.d.ts +1 -0
- package/dist/components/input/input.d.ts.map +1 -1
- package/dist/components/input/input.js +35 -12
- package/dist/components/input/input.js.map +1 -1
- package/dist/components/pin-input/pin-input.d.ts +33 -0
- package/dist/components/pin-input/pin-input.d.ts.map +1 -0
- package/dist/components/pin-input/pin-input.js +253 -0
- package/dist/components/pin-input/pin-input.js.map +1 -0
- package/dist/components/popover/popover.d.ts +26 -0
- package/dist/components/popover/popover.d.ts.map +1 -0
- package/dist/components/popover/popover.js +196 -0
- package/dist/components/popover/popover.js.map +1 -0
- package/dist/components/select/select.d.ts +4 -1
- package/dist/components/select/select.d.ts.map +1 -1
- package/dist/components/select/select.js +26 -4
- package/dist/components/select/select.js.map +1 -1
- package/dist/components/time-picker/time-picker.d.ts +38 -0
- package/dist/components/time-picker/time-picker.d.ts.map +1 -0
- package/dist/components/time-picker/time-picker.js +290 -0
- package/dist/components/time-picker/time-picker.js.map +1 -0
- package/dist/components/toggle-group/toggle-button.d.ts +23 -0
- package/dist/components/toggle-group/toggle-button.d.ts.map +1 -0
- package/dist/components/toggle-group/toggle-button.js +114 -0
- package/dist/components/toggle-group/toggle-button.js.map +1 -0
- package/dist/components/toggle-group/toggle-group.d.ts +38 -0
- package/dist/components/toggle-group/toggle-group.d.ts.map +1 -0
- package/dist/components/toggle-group/toggle-group.js +270 -0
- package/dist/components/toggle-group/toggle-group.js.map +1 -0
- package/dist/components/tooltip/tooltip.d.ts +33 -0
- package/dist/components/tooltip/tooltip.d.ts.map +1 -0
- package/dist/components/tooltip/tooltip.js +293 -0
- package/dist/components/tooltip/tooltip.js.map +1 -0
- package/dist/styles/index.css +2 -0
- package/package.json +33 -1
- package/src/components/calendar/calendar.ts +458 -0
- package/src/components/cron-builder/cron-builder.ts +529 -0
- package/src/components/input/input.ts +38 -14
- package/src/components/pin-input/pin-input.ts +249 -0
- package/src/components/popover/popover.ts +192 -0
- package/src/components/select/select.ts +25 -63
- package/src/components/time-picker/time-picker.ts +286 -0
- package/src/components/toggle-group/toggle-button.ts +103 -0
- package/src/components/toggle-group/toggle-group.ts +264 -0
- package/src/components/tooltip/tooltip.ts +286 -0
- package/src/styles/theme.css +3 -0
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import {LitElement, html, css, PropertyValues} from "lit";
|
|
2
|
+
import {customElement, property, queryAll, state} from "lit/decorators.js";
|
|
3
|
+
import {classMap} from "lit/directives/class-map.js";
|
|
4
|
+
import {resetStyles} from "../../styles/reset.js";
|
|
5
|
+
|
|
6
|
+
@customElement("st-pin-input")
|
|
7
|
+
export class StPinInput extends LitElement {
|
|
8
|
+
static formAssociated = true;
|
|
9
|
+
private _internals: ElementInternals;
|
|
10
|
+
|
|
11
|
+
@property({type: String, reflect: true}) name = "";
|
|
12
|
+
@property({type: String}) label = "";
|
|
13
|
+
@property({type: Number}) length = 6;
|
|
14
|
+
@property({type: String}) value = "";
|
|
15
|
+
|
|
16
|
+
// Obscures the input for secure PINs
|
|
17
|
+
@property({type: Boolean}) mask = false;
|
|
18
|
+
|
|
19
|
+
@property({type: Boolean, reflect: true}) disabled = false;
|
|
20
|
+
@property({type: Boolean, reflect: true}) required = false;
|
|
21
|
+
@property({type: Boolean, reflect: true}) invalid = false;
|
|
22
|
+
@property({type: String}) error = "";
|
|
23
|
+
|
|
24
|
+
@queryAll(".pin-box") private _inputs!: NodeListOf<HTMLInputElement>;
|
|
25
|
+
|
|
26
|
+
@state() private _touched = false;
|
|
27
|
+
|
|
28
|
+
constructor() {
|
|
29
|
+
super();
|
|
30
|
+
this._internals = this.attachInternals();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
static styles = [
|
|
34
|
+
resetStyles,
|
|
35
|
+
css`
|
|
36
|
+
:host {
|
|
37
|
+
display: block;
|
|
38
|
+
font-family: inherit;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
label {
|
|
42
|
+
display: block;
|
|
43
|
+
font-size: var(--st-font-size-sm);
|
|
44
|
+
font-weight: 500;
|
|
45
|
+
color: var(--st-color-text-muted);
|
|
46
|
+
margin-bottom: var(--st-spacing-xs);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.pin-container {
|
|
50
|
+
display: flex;
|
|
51
|
+
gap: var(--st-spacing-sm);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.pin-box {
|
|
55
|
+
width: 48px;
|
|
56
|
+
height: 56px;
|
|
57
|
+
text-align: center;
|
|
58
|
+
font-size: var(--st-font-size-xl);
|
|
59
|
+
font-weight: var(--st-font-weight-semibold);
|
|
60
|
+
color: var(--st-color-text);
|
|
61
|
+
background: var(--st-color-surface);
|
|
62
|
+
border: 1px solid var(--st-color-border);
|
|
63
|
+
border-radius: var(--st-radius-md);
|
|
64
|
+
box-sizing: border-box;
|
|
65
|
+
transition:
|
|
66
|
+
border-color 0.2s,
|
|
67
|
+
box-shadow 0.2s,
|
|
68
|
+
background-color 0.2s;
|
|
69
|
+
/* iOS optimization for numeric pads */
|
|
70
|
+
caret-color: var(--st-color-primary);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.pin-box:focus {
|
|
74
|
+
outline: none;
|
|
75
|
+
border-color: var(--st-color-primary);
|
|
76
|
+
box-shadow: 0 0 0 2px var(--st-color-primary-muted, rgba(59, 130, 246, 0.25));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.pin-box:disabled {
|
|
80
|
+
background: var(--st-color-surface-muted, #f1f5f9);
|
|
81
|
+
color: var(--st-color-text-muted);
|
|
82
|
+
cursor: not-allowed;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.is-invalid .pin-box {
|
|
86
|
+
border-color: var(--st-color-danger, #dc2626);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.error-msg {
|
|
90
|
+
display: block;
|
|
91
|
+
margin-top: var(--st-spacing-xs);
|
|
92
|
+
font-size: var(--st-font-size-xs);
|
|
93
|
+
color: var(--st-color-danger, #dc2626);
|
|
94
|
+
}
|
|
95
|
+
`,
|
|
96
|
+
];
|
|
97
|
+
|
|
98
|
+
override updated(changedProperties: PropertyValues) {
|
|
99
|
+
super.updated(changedProperties);
|
|
100
|
+
if (
|
|
101
|
+
changedProperties.has("value") ||
|
|
102
|
+
changedProperties.has("required") ||
|
|
103
|
+
changedProperties.has("error")
|
|
104
|
+
) {
|
|
105
|
+
this._internals.setFormValue(this.value || null);
|
|
106
|
+
this._syncValidity();
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
private _syncValidity() {
|
|
111
|
+
this.invalid = false;
|
|
112
|
+
|
|
113
|
+
if (this.error) {
|
|
114
|
+
this._internals.setValidity({customError: true}, this.error);
|
|
115
|
+
this.invalid = true;
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (this.required && (this.value.length < this.length || this.value.includes(" "))) {
|
|
120
|
+
this._internals.setValidity({valueMissing: true}, `Please enter all ${this.length} digits.`);
|
|
121
|
+
if (this._touched) this.invalid = true;
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
this._internals.setValidity({});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
public checkValidity() {
|
|
129
|
+
return this._internals.checkValidity();
|
|
130
|
+
}
|
|
131
|
+
public reportValidity() {
|
|
132
|
+
this._touched = true;
|
|
133
|
+
this._syncValidity();
|
|
134
|
+
return this._internals.reportValidity();
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
private _updateValue() {
|
|
138
|
+
const chars = Array.from(this._inputs).map((input) => input.value || " ");
|
|
139
|
+
this.value = chars.join("");
|
|
140
|
+
|
|
141
|
+
this.dispatchEvent(new Event("input", {bubbles: true, composed: true}));
|
|
142
|
+
|
|
143
|
+
// Only fire 'change' if the pin is completely filled
|
|
144
|
+
if (!this.value.includes(" ")) {
|
|
145
|
+
this.dispatchEvent(new Event("change", {bubbles: true, composed: true}));
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Handle typing and auto-advancing
|
|
150
|
+
private _handleInput(e: InputEvent, index: number) {
|
|
151
|
+
this._touched = true;
|
|
152
|
+
const input = e.target as HTMLInputElement;
|
|
153
|
+
|
|
154
|
+
// If the user typed a character, move to the next input
|
|
155
|
+
if (input.value && index < this.length - 1) {
|
|
156
|
+
this._inputs[index + 1].focus();
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
this._updateValue();
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Handle backspace rewinding and arrow keys
|
|
163
|
+
private _handleKeyDown(e: KeyboardEvent, index: number) {
|
|
164
|
+
const input = e.target as HTMLInputElement;
|
|
165
|
+
|
|
166
|
+
if (e.key === "Backspace") {
|
|
167
|
+
// If current box is empty, jump to previous box and clear it
|
|
168
|
+
if (!input.value && index > 0) {
|
|
169
|
+
e.preventDefault();
|
|
170
|
+
this._inputs[index - 1].value = "";
|
|
171
|
+
this._inputs[index - 1].focus();
|
|
172
|
+
this._updateValue();
|
|
173
|
+
}
|
|
174
|
+
} else if (e.key === "ArrowLeft" && index > 0) {
|
|
175
|
+
e.preventDefault();
|
|
176
|
+
this._inputs[index - 1].focus();
|
|
177
|
+
} else if (e.key === "ArrowRight" && index < this.length - 1) {
|
|
178
|
+
e.preventDefault();
|
|
179
|
+
this._inputs[index + 1].focus();
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Handle cross-field pasting (e.g. pasting "123456" into the first box)
|
|
184
|
+
private _handlePaste(e: ClipboardEvent, startIndex: number) {
|
|
185
|
+
e.preventDefault();
|
|
186
|
+
this._touched = true;
|
|
187
|
+
|
|
188
|
+
const pasteData = e.clipboardData?.getData("text") || "";
|
|
189
|
+
// Clean the string (optional: restrict to numbers if you want strict numeric PINs)
|
|
190
|
+
const chars = pasteData.replace(/\s/g, "").split("");
|
|
191
|
+
|
|
192
|
+
if (!chars.length) return;
|
|
193
|
+
|
|
194
|
+
let currentIndex = startIndex;
|
|
195
|
+
for (const char of chars) {
|
|
196
|
+
if (currentIndex < this.length) {
|
|
197
|
+
this._inputs[currentIndex].value = char;
|
|
198
|
+
currentIndex++;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Focus the last filled input (or the very last input if we filled them all)
|
|
203
|
+
const focusIndex = Math.min(currentIndex, this.length - 1);
|
|
204
|
+
this._inputs[focusIndex].focus();
|
|
205
|
+
|
|
206
|
+
this._updateValue();
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
render() {
|
|
210
|
+
// Break the current value into an array of characters for rendering
|
|
211
|
+
const values = this.value.padEnd(this.length, " ").split("");
|
|
212
|
+
|
|
213
|
+
const isInvalid = this.invalid || !!this.error;
|
|
214
|
+
|
|
215
|
+
return html`
|
|
216
|
+
${this.label ? html`<label>${this.label}</label>` : ""}
|
|
217
|
+
|
|
218
|
+
<div class=${classMap({"pin-container": true, "is-invalid": isInvalid})}>
|
|
219
|
+
${values
|
|
220
|
+
.slice(0, this.length)
|
|
221
|
+
.map(
|
|
222
|
+
(char, index) => html`
|
|
223
|
+
<input
|
|
224
|
+
class="pin-box"
|
|
225
|
+
aria-label="Digit ${index + 1}"
|
|
226
|
+
type=${this.mask ? "password" : "text"}
|
|
227
|
+
inputmode="numeric"
|
|
228
|
+
autocomplete="${"one-time-code" as any}"
|
|
229
|
+
maxlength="1"
|
|
230
|
+
.value=${char.trim()}
|
|
231
|
+
?disabled=${this.disabled}
|
|
232
|
+
@input=${(e: InputEvent) => this._handleInput(e, index)}
|
|
233
|
+
@keydown=${(e: KeyboardEvent) => this._handleKeyDown(e, index)}
|
|
234
|
+
@paste=${(e: ClipboardEvent) => this._handlePaste(e, index)}
|
|
235
|
+
/>
|
|
236
|
+
`,
|
|
237
|
+
)}
|
|
238
|
+
</div>
|
|
239
|
+
|
|
240
|
+
${isInvalid && this.error ? html`<span class="error-msg">${this.error}</span>` : ""}
|
|
241
|
+
`;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
declare global {
|
|
246
|
+
interface HTMLElementTagNameMap {
|
|
247
|
+
"st-pin-input": StPinInput;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import {LitElement, html, css, PropertyValues} from "lit";
|
|
2
|
+
import {customElement, property, query, state, queryAssignedElements} from "lit/decorators.js";
|
|
3
|
+
import {resetStyles} from "../../styles/reset.js";
|
|
4
|
+
|
|
5
|
+
export type PopoverPosition = "top" | "bottom" | "left" | "right";
|
|
6
|
+
|
|
7
|
+
@customElement("st-popover")
|
|
8
|
+
export class StPopover extends LitElement {
|
|
9
|
+
@property({type: String}) position: PopoverPosition = "bottom";
|
|
10
|
+
@property({type: Number}) offset = 8;
|
|
11
|
+
@property({type: Boolean, reflect: true}) disabled = false;
|
|
12
|
+
|
|
13
|
+
@state() private _isOpen = false;
|
|
14
|
+
|
|
15
|
+
@query(".trigger-wrapper") private _triggerWrapper!: HTMLElement;
|
|
16
|
+
@query("#popover-content") private _popoverEl!: HTMLElement;
|
|
17
|
+
|
|
18
|
+
@queryAssignedElements({slot: "trigger"}) private _triggerElements!: HTMLElement[];
|
|
19
|
+
|
|
20
|
+
static styles = [
|
|
21
|
+
resetStyles,
|
|
22
|
+
css`
|
|
23
|
+
:host {
|
|
24
|
+
display: inline-block;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.trigger-wrapper {
|
|
28
|
+
display: inline-block;
|
|
29
|
+
cursor: pointer;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/* Native popover styles */
|
|
33
|
+
[popover] {
|
|
34
|
+
position: fixed;
|
|
35
|
+
margin: 0;
|
|
36
|
+
padding: 0;
|
|
37
|
+
border: none;
|
|
38
|
+
background: transparent;
|
|
39
|
+
overflow: visible;
|
|
40
|
+
/* 🚀 FIX: Erase the browser's native centering inset so our JS coordinates work */
|
|
41
|
+
inset: auto;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.popover-surface {
|
|
45
|
+
background: var(--st-color-surface);
|
|
46
|
+
border: 1px solid var(--st-color-border);
|
|
47
|
+
border-radius: var(--st-radius-md);
|
|
48
|
+
box-shadow:
|
|
49
|
+
0 10px 15px -3px rgba(0, 0, 0, 0.1),
|
|
50
|
+
0 4px 6px -2px rgba(0, 0, 0, 0.05);
|
|
51
|
+
padding: var(--st-spacing-md);
|
|
52
|
+
color: var(--st-color-text);
|
|
53
|
+
|
|
54
|
+
opacity: 0;
|
|
55
|
+
transform: scale(0.95);
|
|
56
|
+
transition:
|
|
57
|
+
opacity 0.15s ease,
|
|
58
|
+
transform 0.15s ease;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
[popover]:popover-open .popover-surface {
|
|
62
|
+
opacity: 1;
|
|
63
|
+
transform: scale(1);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
[popover]::backdrop {
|
|
67
|
+
background: transparent;
|
|
68
|
+
}
|
|
69
|
+
`,
|
|
70
|
+
];
|
|
71
|
+
|
|
72
|
+
override updated(changedProperties: PropertyValues) {
|
|
73
|
+
super.updated(changedProperties);
|
|
74
|
+
if (changedProperties.has("_isOpen")) {
|
|
75
|
+
this._triggerElements?.forEach((el) => {
|
|
76
|
+
el.setAttribute("aria-expanded", String(this._isOpen));
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
private _handleTriggerSlotChange() {
|
|
82
|
+
this._triggerElements?.forEach((el) => {
|
|
83
|
+
el.setAttribute("aria-haspopup", "dialog");
|
|
84
|
+
el.setAttribute("aria-expanded", String(this._isOpen));
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
private _togglePopover() {
|
|
89
|
+
if (this.disabled) return;
|
|
90
|
+
this._isOpen ? this.hide() : this.show();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
public show() {
|
|
94
|
+
if (this.disabled || this._isOpen) return;
|
|
95
|
+
|
|
96
|
+
// 🚀 FIX: Update state synchronously so the _reposition check passes instantly
|
|
97
|
+
this._isOpen = true;
|
|
98
|
+
|
|
99
|
+
this._popoverEl.showPopover();
|
|
100
|
+
this._reposition();
|
|
101
|
+
|
|
102
|
+
window.addEventListener("resize", this._reposition);
|
|
103
|
+
window.addEventListener("scroll", this._reposition, true);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
public hide() {
|
|
107
|
+
if (!this._isOpen) return;
|
|
108
|
+
|
|
109
|
+
// 🚀 FIX: Update state synchronously
|
|
110
|
+
this._isOpen = false;
|
|
111
|
+
|
|
112
|
+
this._popoverEl.hidePopover();
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
private _handleNativeToggle(e: Event) {
|
|
116
|
+
const isOpening = (e as any).newState === "open";
|
|
117
|
+
this._isOpen = isOpening;
|
|
118
|
+
|
|
119
|
+
// This handles the cleanup when the user clicks outside or hits ESC
|
|
120
|
+
if (!isOpening) {
|
|
121
|
+
window.removeEventListener("resize", this._reposition);
|
|
122
|
+
window.removeEventListener("scroll", this._reposition, true);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
private _reposition = () => {
|
|
127
|
+
if (!this._isOpen || !this._popoverEl || !this._triggerWrapper) return;
|
|
128
|
+
|
|
129
|
+
const triggerRect = this._triggerWrapper.getBoundingClientRect();
|
|
130
|
+
const popoverRect = this._popoverEl.getBoundingClientRect();
|
|
131
|
+
|
|
132
|
+
let top = 0;
|
|
133
|
+
let left = 0;
|
|
134
|
+
|
|
135
|
+
switch (this.position) {
|
|
136
|
+
case "bottom":
|
|
137
|
+
top = triggerRect.bottom + this.offset;
|
|
138
|
+
left = triggerRect.left + triggerRect.width / 2 - popoverRect.width / 2;
|
|
139
|
+
break;
|
|
140
|
+
case "top":
|
|
141
|
+
top = triggerRect.top - popoverRect.height - this.offset;
|
|
142
|
+
left = triggerRect.left + triggerRect.width / 2 - popoverRect.width / 2;
|
|
143
|
+
break;
|
|
144
|
+
case "right":
|
|
145
|
+
top = triggerRect.top + triggerRect.height / 2 - popoverRect.height / 2;
|
|
146
|
+
left = triggerRect.right + this.offset;
|
|
147
|
+
break;
|
|
148
|
+
case "left":
|
|
149
|
+
top = triggerRect.top + triggerRect.height / 2 - popoverRect.height / 2;
|
|
150
|
+
left = triggerRect.left - popoverRect.width - this.offset;
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const padding = 8;
|
|
155
|
+
if (left < padding) left = padding;
|
|
156
|
+
if (left + popoverRect.width > window.innerWidth - padding) {
|
|
157
|
+
left = window.innerWidth - popoverRect.width - padding;
|
|
158
|
+
}
|
|
159
|
+
if (top < padding) top = padding;
|
|
160
|
+
if (top + popoverRect.height > window.innerHeight - padding) {
|
|
161
|
+
top = window.innerHeight - popoverRect.height - padding;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
this._popoverEl.style.top = `${top}px`;
|
|
165
|
+
this._popoverEl.style.left = `${left}px`;
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
render() {
|
|
169
|
+
return html`
|
|
170
|
+
<div
|
|
171
|
+
class="trigger-wrapper"
|
|
172
|
+
@click=${this._togglePopover}
|
|
173
|
+
@keydown=${(e: KeyboardEvent) =>
|
|
174
|
+
e.key === "Enter" || e.key === " " ? this._togglePopover() : null}
|
|
175
|
+
>
|
|
176
|
+
<slot name="trigger" @slotchange=${this._handleTriggerSlotChange}></slot>
|
|
177
|
+
</div>
|
|
178
|
+
|
|
179
|
+
<div id="popover-content" popover="auto" @toggle=${this._handleNativeToggle}>
|
|
180
|
+
<div class="popover-surface">
|
|
181
|
+
<slot name="content"></slot>
|
|
182
|
+
</div>
|
|
183
|
+
</div>
|
|
184
|
+
`;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
declare global {
|
|
189
|
+
interface HTMLElementTagNameMap {
|
|
190
|
+
"st-popover": StPopover;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
@@ -1,71 +1,14 @@
|
|
|
1
1
|
import {LitElement, html, css, PropertyValues} from "lit";
|
|
2
2
|
import {customElement, property, state, query} from "lit/decorators.js";
|
|
3
3
|
import {classMap} from "lit/directives/class-map.js";
|
|
4
|
+
import {ifDefined} from "lit/directives/if-defined.js";
|
|
4
5
|
|
|
5
6
|
export interface SelectOption {
|
|
6
7
|
label: string;
|
|
7
8
|
value: string;
|
|
8
9
|
}
|
|
9
10
|
|
|
10
|
-
export type SelectAutocomplete =
|
|
11
|
-
| "on"
|
|
12
|
-
| "off"
|
|
13
|
-
| "name"
|
|
14
|
-
| "honorific-prefix"
|
|
15
|
-
| "given-name"
|
|
16
|
-
| "additional-name"
|
|
17
|
-
| "family-name"
|
|
18
|
-
| "honorific-suffix"
|
|
19
|
-
| "nickname"
|
|
20
|
-
| "email"
|
|
21
|
-
| "username"
|
|
22
|
-
| "new-password"
|
|
23
|
-
| "current-password"
|
|
24
|
-
| "one-time-code"
|
|
25
|
-
| "organization"
|
|
26
|
-
| "organization-title"
|
|
27
|
-
| "url"
|
|
28
|
-
| "photo"
|
|
29
|
-
| "webauthn"
|
|
30
|
-
| "street-address"
|
|
31
|
-
| "address-line1"
|
|
32
|
-
| "address-line2"
|
|
33
|
-
| "address-line3"
|
|
34
|
-
| "address-level1"
|
|
35
|
-
| "address-level2"
|
|
36
|
-
| "address-level3"
|
|
37
|
-
| "address-level4"
|
|
38
|
-
| "country"
|
|
39
|
-
| "country-name"
|
|
40
|
-
| "postal-code"
|
|
41
|
-
| "cc-name"
|
|
42
|
-
| "cc-given-name"
|
|
43
|
-
| "cc-additional-name"
|
|
44
|
-
| "cc-family-name"
|
|
45
|
-
| "cc-number"
|
|
46
|
-
| "cc-exp"
|
|
47
|
-
| "cc-exp-month"
|
|
48
|
-
| "cc-exp-year"
|
|
49
|
-
| "cc-csc"
|
|
50
|
-
| "cc-type"
|
|
51
|
-
| "transaction-currency"
|
|
52
|
-
| "transaction-amount"
|
|
53
|
-
| "language"
|
|
54
|
-
| "bday"
|
|
55
|
-
| "bday-day"
|
|
56
|
-
| "bday-month"
|
|
57
|
-
| "bday-year"
|
|
58
|
-
| "sex"
|
|
59
|
-
| "tel"
|
|
60
|
-
| "tel-country-code"
|
|
61
|
-
| "tel-national"
|
|
62
|
-
| "tel-area-code"
|
|
63
|
-
| "tel-local"
|
|
64
|
-
| "tel-local-prefix"
|
|
65
|
-
| "tel-local-suffix"
|
|
66
|
-
| "tel-extension"
|
|
67
|
-
| "impp"
|
|
68
|
-
| (string & {});
|
|
11
|
+
export type SelectAutocomplete = "on" | "off" | "name" | "email" | (string & {});
|
|
69
12
|
|
|
70
13
|
@customElement("st-select")
|
|
71
14
|
export class StSelect extends LitElement {
|
|
@@ -74,6 +17,7 @@ export class StSelect extends LitElement {
|
|
|
74
17
|
|
|
75
18
|
@property({type: String}) name = "";
|
|
76
19
|
@property({type: String}) label = "";
|
|
20
|
+
@property({type: Boolean, attribute: "hide-label"}) hideLabel = false;
|
|
77
21
|
@property({type: String}) value = "";
|
|
78
22
|
@property({type: Boolean}) required = false;
|
|
79
23
|
@property({type: Boolean}) multiple = false;
|
|
@@ -93,6 +37,9 @@ export class StSelect extends LitElement {
|
|
|
93
37
|
@query(".combobox") private buttonElement!: HTMLElement;
|
|
94
38
|
@query(".search-input") private searchInputEl?: HTMLInputElement;
|
|
95
39
|
|
|
40
|
+
private _labelId = `select-label-${Math.random().toString(36).substring(2, 9)}`;
|
|
41
|
+
private _listboxId = `select-listbox-${Math.random().toString(36).substring(2, 9)}`;
|
|
42
|
+
|
|
96
43
|
static styles = css`
|
|
97
44
|
:host {
|
|
98
45
|
display: block;
|
|
@@ -108,6 +55,17 @@ export class StSelect extends LitElement {
|
|
|
108
55
|
margin-bottom: var(--st-spacing-xs);
|
|
109
56
|
}
|
|
110
57
|
|
|
58
|
+
.sr-only {
|
|
59
|
+
position: absolute;
|
|
60
|
+
width: 1px;
|
|
61
|
+
height: 1px;
|
|
62
|
+
padding: 0;
|
|
63
|
+
margin: -1px;
|
|
64
|
+
overflow: hidden;
|
|
65
|
+
clip: rect(0, 0, 0, 0);
|
|
66
|
+
border: 0;
|
|
67
|
+
}
|
|
68
|
+
|
|
111
69
|
.combobox {
|
|
112
70
|
display: flex;
|
|
113
71
|
justify-content: space-between;
|
|
@@ -413,7 +371,11 @@ export class StSelect extends LitElement {
|
|
|
413
371
|
: this.options;
|
|
414
372
|
|
|
415
373
|
return html`
|
|
416
|
-
${this.label
|
|
374
|
+
${this.label
|
|
375
|
+
? html`<label id=${this._labelId} class=${classMap({"sr-only": this.hideLabel})}
|
|
376
|
+
>${this.label}</label
|
|
377
|
+
>`
|
|
378
|
+
: ""}
|
|
417
379
|
|
|
418
380
|
<button
|
|
419
381
|
type="button"
|
|
@@ -426,8 +388,8 @@ export class StSelect extends LitElement {
|
|
|
426
388
|
role="combobox"
|
|
427
389
|
aria-haspopup="listbox"
|
|
428
390
|
aria-expanded="${this.open}"
|
|
429
|
-
aria-labelledby
|
|
430
|
-
aria-controls
|
|
391
|
+
aria-labelledby=${ifDefined(this.label ? this._labelId : undefined)}
|
|
392
|
+
aria-controls=${this._listboxId}
|
|
431
393
|
@click=${this.toggleOpen}
|
|
432
394
|
@keydown=${this.handleButtonKeydown}
|
|
433
395
|
>
|
|
@@ -470,7 +432,7 @@ export class StSelect extends LitElement {
|
|
|
470
432
|
: ""}
|
|
471
433
|
|
|
472
434
|
<ul
|
|
473
|
-
id
|
|
435
|
+
id=${this._listboxId}
|
|
474
436
|
class="listbox"
|
|
475
437
|
role="listbox"
|
|
476
438
|
aria-multiselectable="${this.multiple}"
|