dv-web-components 1.0.4 → 1.0.6

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.
@@ -49,7 +49,7 @@ template.innerHTML = `
49
49
  </div>
50
50
  `;
51
51
 
52
- class AppCard extends HTMLElement {
52
+ export class AppCard extends HTMLElement {
53
53
  constructor() {
54
54
  super();
55
55
  this.attachShadow({ mode: 'open' });
@@ -1,3 +1,2 @@
1
- declare const template: HTMLTemplateElement;
2
- declare class AppCard extends HTMLElement {
1
+ export class AppCard extends HTMLElement {
3
2
  }
@@ -1 +1,4 @@
1
- export {};
1
+ export { AppCard } from "./card-component/index.js";
2
+ export { AppAlert } from "./alert-component/index.js";
3
+ export { AppInput } from "./input-component/index.js";
4
+ export { AppSelect } from "./select-component/index.js";
@@ -0,0 +1,27 @@
1
+ export class AppInput extends HTMLElement {
2
+ static formAssociated: boolean;
3
+ static get observedAttributes(): string[];
4
+ connectedCallback(): void;
5
+ disconnectedCallback(): void;
6
+ attributeChangedCallback(name: any, oldValue: any, newValue: any): void;
7
+ set value(val: any);
8
+ get value(): any;
9
+ set name(val: string);
10
+ get name(): string;
11
+ set type(val: string);
12
+ get type(): string;
13
+ set disabled(val: boolean);
14
+ get disabled(): boolean;
15
+ set required(val: boolean);
16
+ get required(): boolean;
17
+ set label(val: string);
18
+ get label(): string;
19
+ formResetCallback(): void;
20
+ formDisabledCallback(disabled: any): void;
21
+ checkValidity(): any;
22
+ reportValidity(): any;
23
+ get validity(): any;
24
+ get validationMessage(): any;
25
+ focus(options: any): void;
26
+ #private;
27
+ }
@@ -1,5 +1,16 @@
1
1
  export const AppAlert: import("@lit/react").ReactWebComponent<AppAlertElement, {
2
2
  onClose: string;
3
3
  }>;
4
- export const AppCard: import("@lit/react").ReactWebComponent<HTMLElement, {}>;
4
+ export const AppCard: import("@lit/react").ReactWebComponent<AppCardElement, {}>;
5
+ export const AppInput: import("@lit/react").ReactWebComponent<AppInputElement, {
6
+ onInput: string;
7
+ onChange: string;
8
+ }>;
9
+ export const AppSelect: import("@lit/react").ReactWebComponent<AppSelectElement, {
10
+ onInput: string;
11
+ onChange: string;
12
+ }>;
5
13
  import { AppAlert as AppAlertElement } from '../alert-component/index.js';
14
+ import { AppCard as AppCardElement } from '../card-component/index.js';
15
+ import { AppInput as AppInputElement } from '../input-component/index.js';
16
+ import { AppSelect as AppSelectElement } from '../select-component/index.js';
@@ -0,0 +1,27 @@
1
+ export class AppSelect extends HTMLElement {
2
+ static formAssociated: boolean;
3
+ static get observedAttributes(): string[];
4
+ connectedCallback(): void;
5
+ disconnectedCallback(): void;
6
+ attributeChangedCallback(name: any, oldValue: any, newValue: any): void;
7
+ set value(val: any);
8
+ get value(): any;
9
+ set options(val: any);
10
+ get options(): any;
11
+ set name(val: string);
12
+ get name(): string;
13
+ set disabled(val: boolean);
14
+ get disabled(): boolean;
15
+ set required(val: boolean);
16
+ get required(): boolean;
17
+ set label(val: string);
18
+ get label(): string;
19
+ formResetCallback(): void;
20
+ formDisabledCallback(disabled: any): void;
21
+ checkValidity(): any;
22
+ reportValidity(): any;
23
+ get validity(): any;
24
+ get validationMessage(): any;
25
+ focus(options: any): void;
26
+ #private;
27
+ }
package/index.js CHANGED
@@ -1,2 +1,4 @@
1
- import './card-component/index.js';
2
- import './alert-component/index.js';
1
+ export { AppCard } from './card-component/index.js';
2
+ export { AppAlert } from './alert-component/index.js';
3
+ export { AppInput } from './input-component/index.js';
4
+ export { AppSelect } from './select-component/index.js';
@@ -0,0 +1,273 @@
1
+ const template = document.createElement('template');
2
+ template.innerHTML = `
3
+ <style>
4
+ :host {
5
+ display: block;
6
+ margin-block: 0.75rem;
7
+ font-family: inherit;
8
+ }
9
+
10
+ *, *::before, *::after {
11
+ box-sizing: border-box;
12
+ }
13
+
14
+ .form-control {
15
+ display: flex;
16
+ flex-direction: column;
17
+ gap: 0.375rem;
18
+ }
19
+
20
+ .label {
21
+ font-size: 0.875rem;
22
+ font-weight: 500;
23
+ color: var(--input-label-color, #cbd5e1);
24
+ transition: color 0.2s ease;
25
+ }
26
+
27
+ .input-wrapper {
28
+ position: relative;
29
+ display: flex;
30
+ align-items: center;
31
+ background-color: var(--input-bg, #0f172a);
32
+ border: 1px solid var(--input-border, #334155);
33
+ border-radius: var(--input-radius, 0.5rem);
34
+ transition: border-color 0.2s ease, box-shadow 0.2s ease;
35
+ }
36
+
37
+ .input-wrapper:focus-within {
38
+ border-color: var(--input-focus-border, #38bdf8);
39
+ box-shadow: 0 0 0 3px var(--input-focus-ring, rgba(56, 189, 248, 0.2));
40
+ }
41
+
42
+ .input-field {
43
+ width: 100%;
44
+ padding: 0.625rem 0.875rem;
45
+ background: transparent;
46
+ border: none;
47
+ outline: none;
48
+ font-family: inherit;
49
+ font-size: 0.95rem;
50
+ color: var(--input-color, #f8fafc);
51
+ }
52
+
53
+ .input-field::placeholder {
54
+ color: var(--input-placeholder-color, #64748b);
55
+ }
56
+
57
+ :host([disabled]) {
58
+ opacity: 0.6;
59
+ cursor: not-allowed;
60
+ }
61
+
62
+ :host([disabled]) .input-field {
63
+ cursor: not-allowed;
64
+ pointer-events: none;
65
+ }
66
+
67
+ :host(:invalid) .input-wrapper,
68
+ :host([invalid]) .input-wrapper {
69
+ border-color: var(--input-error-border, #ef4444);
70
+ }
71
+ </style>
72
+
73
+ <div class="form-control" part="form-control">
74
+ <label class="label" part="label" id="label-text">
75
+ <slot name="label"></slot>
76
+ </label>
77
+ <div class="input-wrapper" part="wrapper">
78
+ <slot name="prefix"></slot>
79
+ <input class="input-field" part="input" />
80
+ <slot name="suffix"></slot>
81
+ </div>
82
+ </div>
83
+ `;
84
+
85
+ export class AppInput extends HTMLElement {
86
+ static formAssociated = true;
87
+
88
+ #internals = null;
89
+ #inputElement = null;
90
+ #labelText = null;
91
+ #initialValue = '';
92
+
93
+ static get observedAttributes() {
94
+ return ['value', 'name', 'type', 'placeholder', 'disabled', 'required', 'label'];
95
+ }
96
+
97
+ constructor() {
98
+ super();
99
+ this.#internals = this.attachInternals();
100
+ this.attachShadow({ mode: 'open' });
101
+ this.shadowRoot.appendChild(template.content.cloneNode(true));
102
+
103
+ this.#inputElement = this.shadowRoot.querySelector('.input-field');
104
+ this.#labelText = this.shadowRoot.querySelector('#label-text');
105
+ }
106
+
107
+ connectedCallback() {
108
+ this.#initialValue = this.getAttribute('value') || '';
109
+ this.#updateInputState();
110
+
111
+ this.#inputElement.addEventListener('input', this.#handleInput);
112
+ this.#inputElement.addEventListener('change', this.#handleChange);
113
+ }
114
+
115
+ disconnectedCallback() {
116
+ this.#inputElement?.removeEventListener('input', this.#handleInput);
117
+ this.#inputElement?.removeEventListener('change', this.#handleChange);
118
+ }
119
+
120
+ attributeChangedCallback(name, oldValue, newValue) {
121
+ if (oldValue === newValue) return;
122
+ this.#updateInputState();
123
+ }
124
+
125
+ #updateInputState() {
126
+ if (!this.#inputElement) return;
127
+
128
+ const val = this.getAttribute('value') || '';
129
+ if (this.#inputElement.value !== val) {
130
+ this.#inputElement.value = val;
131
+ }
132
+ this.#internals.setFormValue(val);
133
+
134
+ this.#inputElement.type = this.getAttribute('type') || 'text';
135
+ this.#inputElement.placeholder = this.getAttribute('placeholder') || '';
136
+ this.#inputElement.disabled = this.hasAttribute('disabled');
137
+ this.#inputElement.required = this.hasAttribute('required');
138
+
139
+ if (this.hasAttribute('name')) {
140
+ this.#inputElement.name = this.getAttribute('name');
141
+ }
142
+
143
+ const labelAttr = this.getAttribute('label');
144
+ if (labelAttr && !this.querySelector('[slot="label"]')) {
145
+ this.#labelText.textContent = labelAttr;
146
+ this.#labelText.style.display = 'block';
147
+ } else if (!this.querySelector('[slot="label"]')) {
148
+ this.#labelText.style.display = 'none';
149
+ }
150
+ }
151
+
152
+ #handleInput = (event) => {
153
+ event.stopPropagation();
154
+ const val = this.#inputElement.value;
155
+ this.#internals.setFormValue(val);
156
+
157
+ this.dispatchEvent(
158
+ new CustomEvent('input', {
159
+ bubbles: true,
160
+ composed: true,
161
+ detail: { value: val }
162
+ })
163
+ );
164
+ };
165
+
166
+ #handleChange = (event) => {
167
+ event.stopPropagation();
168
+ const val = this.#inputElement.value;
169
+ this.#internals.setFormValue(val);
170
+
171
+ this.dispatchEvent(
172
+ new CustomEvent('change', {
173
+ bubbles: true,
174
+ composed: true,
175
+ detail: { value: val }
176
+ })
177
+ );
178
+ };
179
+
180
+ // Getters & Setters sincronizados
181
+ get value() {
182
+ return this.#inputElement ? this.#inputElement.value : this.getAttribute('value') || '';
183
+ }
184
+
185
+ set value(val) {
186
+ const stringVal = val == null ? '' : String(val);
187
+ if (this.#inputElement) {
188
+ this.#inputElement.value = stringVal;
189
+ }
190
+ this.#internals.setFormValue(stringVal);
191
+ }
192
+
193
+ get name() {
194
+ return this.getAttribute('name') || '';
195
+ }
196
+
197
+ set name(val) {
198
+ if (val) this.setAttribute('name', val);
199
+ else this.removeAttribute('name');
200
+ }
201
+
202
+ get type() {
203
+ return this.getAttribute('type') || 'text';
204
+ }
205
+
206
+ set type(val) {
207
+ this.setAttribute('type', val);
208
+ }
209
+
210
+ get disabled() {
211
+ return this.hasAttribute('disabled');
212
+ }
213
+
214
+ set disabled(val) {
215
+ if (Boolean(val)) this.setAttribute('disabled', '');
216
+ else this.removeAttribute('disabled');
217
+ }
218
+
219
+ get required() {
220
+ return this.hasAttribute('required');
221
+ }
222
+
223
+ set required(val) {
224
+ if (Boolean(val)) this.setAttribute('required', '');
225
+ else this.removeAttribute('required');
226
+ }
227
+
228
+ get label() {
229
+ return this.getAttribute('label') || '';
230
+ }
231
+
232
+ set label(val) {
233
+ if (val) this.setAttribute('label', val);
234
+ else this.removeAttribute('label');
235
+ }
236
+
237
+ // Soporte nativo de formularios (<form>)
238
+ formResetCallback() {
239
+ this.value = this.#initialValue;
240
+ }
241
+
242
+ formDisabledCallback(disabled) {
243
+ this.disabled = disabled;
244
+ }
245
+
246
+ checkValidity() {
247
+ return this.#internals.checkValidity();
248
+ }
249
+
250
+ reportValidity() {
251
+ return this.#internals.reportValidity();
252
+ }
253
+
254
+ get validity() {
255
+ return this.#internals.validity;
256
+ }
257
+
258
+ get validationMessage() {
259
+ return this.#internals.validationMessage;
260
+ }
261
+
262
+ focus(options) {
263
+ this.#inputElement?.focus(options);
264
+ }
265
+
266
+ blur() {
267
+ this.#inputElement?.blur();
268
+ }
269
+ }
270
+
271
+ if (!customElements.get('app-input')) {
272
+ customElements.define('app-input', AppInput);
273
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dv-web-components",
3
- "version": "1.0.4",
3
+ "version": "1.0.6",
4
4
  "description": "Librería de Web Components nativos y accesibles",
5
5
  "type": "module",
6
6
  "main": "./index.js",
@@ -26,6 +26,8 @@
26
26
  "index.js",
27
27
  "alert-component",
28
28
  "card-component",
29
+ "input-component",
30
+ "select-component",
29
31
  "react",
30
32
  "dist"
31
33
  ],
package/react/index.js CHANGED
@@ -2,6 +2,8 @@ import React from 'react';
2
2
  import { createComponent } from '@lit/react';
3
3
  import { AppAlert as AppAlertElement } from '../alert-component/index.js';
4
4
  import { AppCard as AppCardElement } from '../card-component/index.js';
5
+ import { AppInput as AppInputElement } from '../input-component/index.js';
6
+ import { AppSelect as AppSelectElement } from '../select-component/index.js';
5
7
 
6
8
  // Envolvemos el Web Component y lo convertimos en un Componente React real
7
9
  export const AppAlert = createComponent({
@@ -9,7 +11,7 @@ export const AppAlert = createComponent({
9
11
  elementClass: AppAlertElement,
10
12
  react: React,
11
13
  events: {
12
- onClose: 'app-close' // 👈 Mapea el CustomEvent nativo 'app-close' a una prop React 'onClose'
14
+ onClose: 'app-close' // Mapea el CustomEvent nativo 'app-close' a una prop React 'onClose'
13
15
  }
14
16
  });
15
17
 
@@ -18,3 +20,23 @@ export const AppCard = createComponent({
18
20
  elementClass: AppCardElement,
19
21
  react: React
20
22
  });
23
+
24
+ export const AppInput = createComponent({
25
+ tagName: 'app-input',
26
+ elementClass: AppInputElement,
27
+ react: React,
28
+ events: {
29
+ onInput: 'input',
30
+ onChange: 'change'
31
+ }
32
+ });
33
+
34
+ export const AppSelect = createComponent({
35
+ tagName: 'app-select',
36
+ elementClass: AppSelectElement,
37
+ react: React,
38
+ events: {
39
+ onInput: 'input',
40
+ onChange: 'change'
41
+ }
42
+ });
@@ -0,0 +1,374 @@
1
+ const template = document.createElement('template');
2
+ template.innerHTML = `
3
+ <style>
4
+ :host {
5
+ display: block;
6
+ margin-block: 0.75rem;
7
+ font-family: inherit;
8
+ }
9
+
10
+ *, *::before, *::after {
11
+ box-sizing: border-box;
12
+ }
13
+
14
+ .form-control {
15
+ display: flex;
16
+ flex-direction: column;
17
+ gap: 0.375rem;
18
+ }
19
+
20
+ .label {
21
+ font-size: 0.875rem;
22
+ font-weight: 500;
23
+ color: var(--select-label-color, #cbd5e1);
24
+ transition: color 0.2s ease;
25
+ }
26
+
27
+ .select-wrapper {
28
+ position: relative;
29
+ display: flex;
30
+ align-items: center;
31
+ background-color: var(--select-bg, #0f172a);
32
+ border: 1px solid var(--select-border, #334155);
33
+ border-radius: var(--select-radius, 0.5rem);
34
+ transition: border-color 0.2s ease, box-shadow 0.2s ease;
35
+ }
36
+
37
+ .select-wrapper:focus-within {
38
+ border-color: var(--select-focus-border, #38bdf8);
39
+ box-shadow: 0 0 0 3px var(--select-focus-ring, rgba(56, 189, 248, 0.2));
40
+ }
41
+
42
+ .select-field {
43
+ width: 100%;
44
+ padding: 0.625rem 2.5rem 0.625rem 0.875rem;
45
+ background: transparent;
46
+ border: none;
47
+ outline: none;
48
+ font-family: inherit;
49
+ font-size: 0.95rem;
50
+ color: var(--select-color, #f8fafc);
51
+ cursor: pointer;
52
+ appearance: none;
53
+ -webkit-appearance: none;
54
+ }
55
+
56
+ .select-field option {
57
+ background-color: #0f172a;
58
+ color: #f8fafc;
59
+ }
60
+
61
+ .arrow-icon {
62
+ position: absolute;
63
+ right: 0.875rem;
64
+ pointer-events: none;
65
+ color: var(--select-arrow-color, #94a3b8);
66
+ display: flex;
67
+ align-items: center;
68
+ justify-content: center;
69
+ }
70
+
71
+ /* Ocultamos el slot de opciones del light DOM para no duplicar la visualización */
72
+ ::slotted(option) {
73
+ display: none;
74
+ }
75
+
76
+ :host([disabled]) {
77
+ opacity: 0.6;
78
+ cursor: not-allowed;
79
+ }
80
+
81
+ :host([disabled]) .select-field {
82
+ cursor: not-allowed;
83
+ pointer-events: none;
84
+ }
85
+
86
+ :host(:invalid) .select-wrapper,
87
+ :host([invalid]) .select-wrapper {
88
+ border-color: var(--select-error-border, #ef4444);
89
+ }
90
+ </style>
91
+
92
+ <div class="form-control" part="form-control">
93
+ <label class="label" part="label" id="label-text">
94
+ <slot name="label"></slot>
95
+ </label>
96
+ <div class="select-wrapper" part="wrapper">
97
+ <select class="select-field" part="select"></select>
98
+ <span class="arrow-icon" part="arrow">
99
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
100
+ <polyline points="6 9 12 15 18 9"></polyline>
101
+ </svg>
102
+ </span>
103
+ <!-- Slot para capturar las etiquetas <option> que pase el usuario -->
104
+ <slot style="display: none;"></slot>
105
+ </div>
106
+ </div>
107
+ `;
108
+
109
+ export class AppSelect extends HTMLElement {
110
+ static formAssociated = true;
111
+
112
+ #internals = null;
113
+ #selectElement = null;
114
+ #labelText = null;
115
+ #slotElement = null;
116
+ #observer = null;
117
+ #initialValue = '';
118
+ #optionsData = null;
119
+
120
+ static get observedAttributes() {
121
+ return ['value', 'name', 'disabled', 'required', 'placeholder', 'label'];
122
+ }
123
+
124
+ constructor() {
125
+ super();
126
+ this.#internals = this.attachInternals();
127
+ this.attachShadow({ mode: 'open' });
128
+ this.shadowRoot.appendChild(template.content.cloneNode(true));
129
+
130
+ this.#selectElement = this.shadowRoot.querySelector('.select-field');
131
+ this.#labelText = this.shadowRoot.querySelector('#label-text');
132
+ this.#slotElement = this.shadowRoot.querySelector('slot:not([name])');
133
+ }
134
+
135
+ connectedCallback() {
136
+ this.#initialValue = this.getAttribute('value') || '';
137
+ this.#syncOptions();
138
+ this.#updateState();
139
+
140
+ this.#selectElement.addEventListener('change', this.#handleChange);
141
+ this.#slotElement?.addEventListener('slotchange', this.#syncOptions);
142
+
143
+ // Observar inserción dinámica de <option> en el Light DOM
144
+ this.#observer = new MutationObserver(() => this.#syncOptions());
145
+ this.#observer.observe(this, { childList: true, subtree: true, characterData: true });
146
+ }
147
+
148
+ disconnectedCallback() {
149
+ this.#selectElement?.removeEventListener('change', this.#handleChange);
150
+ this.#slotElement?.removeEventListener('slotchange', this.#syncOptions);
151
+ this.#observer?.disconnect();
152
+ }
153
+
154
+ attributeChangedCallback(name, oldValue, newValue) {
155
+ if (oldValue === newValue) return;
156
+ this.#updateState();
157
+ }
158
+
159
+ #syncOptions = () => {
160
+ if (!this.#selectElement) return;
161
+
162
+ // Si las opciones vinieron por propiedad JS (array)
163
+ if (Array.isArray(this.#optionsData)) {
164
+ this.#renderOptionsFromArray(this.#optionsData);
165
+ return;
166
+ }
167
+
168
+ // Si las opciones vienen por Light DOM (<option> hijos)
169
+ const options = Array.from(this.querySelectorAll('option'));
170
+ this.#selectElement.innerHTML = '';
171
+
172
+ const placeholder = this.getAttribute('placeholder');
173
+ if (placeholder) {
174
+ const phOption = document.createElement('option');
175
+ phOption.value = '';
176
+ phOption.textContent = placeholder;
177
+ phOption.disabled = true;
178
+ phOption.selected = !this.value;
179
+ this.#selectElement.appendChild(phOption);
180
+ }
181
+
182
+ options.forEach((opt) => {
183
+ const clone = document.createElement('option');
184
+ clone.value = opt.value;
185
+ clone.textContent = opt.textContent;
186
+ clone.disabled = opt.disabled;
187
+ if (opt.value === this.value) {
188
+ clone.selected = true;
189
+ }
190
+ this.#selectElement.appendChild(clone);
191
+ });
192
+
193
+ if (this.value) {
194
+ this.#selectElement.value = this.value;
195
+ } else if (this.#selectElement.value) {
196
+ this.#internals.setFormValue(this.#selectElement.value);
197
+ }
198
+ };
199
+
200
+ #renderOptionsFromArray(items) {
201
+ this.#selectElement.innerHTML = '';
202
+
203
+ const placeholder = this.getAttribute('placeholder');
204
+ if (placeholder) {
205
+ const phOption = document.createElement('option');
206
+ phOption.value = '';
207
+ phOption.textContent = placeholder;
208
+ phOption.disabled = true;
209
+ phOption.selected = !this.value;
210
+ this.#selectElement.appendChild(phOption);
211
+ }
212
+
213
+ items.forEach((item) => {
214
+ const opt = document.createElement('option');
215
+ if (typeof item === 'string') {
216
+ opt.value = item;
217
+ opt.textContent = item;
218
+ } else {
219
+ opt.value = item.value;
220
+ opt.textContent = item.label ?? item.value;
221
+ opt.disabled = Boolean(item.disabled);
222
+ }
223
+ if (opt.value === this.value) {
224
+ opt.selected = true;
225
+ }
226
+ this.#selectElement.appendChild(opt);
227
+ });
228
+
229
+ if (this.value) {
230
+ this.#selectElement.value = this.value;
231
+ }
232
+ }
233
+
234
+ #updateState() {
235
+ if (!this.#selectElement) return;
236
+
237
+ const val = this.getAttribute('value') || '';
238
+ if (val && this.#selectElement.value !== val) {
239
+ this.#selectElement.value = val;
240
+ }
241
+ this.#internals.setFormValue(this.#selectElement.value || val);
242
+
243
+ this.#selectElement.disabled = this.hasAttribute('disabled');
244
+ this.#selectElement.required = this.hasAttribute('required');
245
+
246
+ if (this.hasAttribute('name')) {
247
+ this.#selectElement.name = this.getAttribute('name');
248
+ }
249
+
250
+ const labelAttr = this.getAttribute('label');
251
+ if (labelAttr && !this.querySelector('[slot="label"]')) {
252
+ this.#labelText.textContent = labelAttr;
253
+ this.#labelText.style.display = 'block';
254
+ } else if (!this.querySelector('[slot="label"]')) {
255
+ this.#labelText.style.display = 'none';
256
+ }
257
+ }
258
+
259
+ #handleChange = (event) => {
260
+ event.stopPropagation();
261
+ const val = this.#selectElement.value;
262
+ this.#internals.setFormValue(val);
263
+
264
+ this.dispatchEvent(
265
+ new CustomEvent('change', {
266
+ bubbles: true,
267
+ composed: true,
268
+ detail: { value: val }
269
+ })
270
+ );
271
+ this.dispatchEvent(
272
+ new CustomEvent('input', {
273
+ bubbles: true,
274
+ composed: true,
275
+ detail: { value: val }
276
+ })
277
+ );
278
+ };
279
+
280
+ // Getters & Setters
281
+ get value() {
282
+ return this.#selectElement ? this.#selectElement.value : this.getAttribute('value') || '';
283
+ }
284
+
285
+ set value(val) {
286
+ const stringVal = val == null ? '' : String(val);
287
+ if (this.#selectElement) {
288
+ this.#selectElement.value = stringVal;
289
+ }
290
+ this.#internals.setFormValue(stringVal);
291
+ }
292
+
293
+ get options() {
294
+ return this.#optionsData;
295
+ }
296
+
297
+ set options(val) {
298
+ this.#optionsData = val;
299
+ this.#syncOptions();
300
+ }
301
+
302
+ get name() {
303
+ return this.getAttribute('name') || '';
304
+ }
305
+
306
+ set name(val) {
307
+ if (val) this.setAttribute('name', val);
308
+ else this.removeAttribute('name');
309
+ }
310
+
311
+ get disabled() {
312
+ return this.hasAttribute('disabled');
313
+ }
314
+
315
+ set disabled(val) {
316
+ if (Boolean(val)) this.setAttribute('disabled', '');
317
+ else this.removeAttribute('disabled');
318
+ }
319
+
320
+ get required() {
321
+ return this.hasAttribute('required');
322
+ }
323
+
324
+ set required(val) {
325
+ if (Boolean(val)) this.setAttribute('required', '');
326
+ else this.removeAttribute('required');
327
+ }
328
+
329
+ get label() {
330
+ return this.getAttribute('label') || '';
331
+ }
332
+
333
+ set label(val) {
334
+ if (val) this.setAttribute('label', val);
335
+ else this.removeAttribute('label');
336
+ }
337
+
338
+ // Soporte nativo de formularios
339
+ formResetCallback() {
340
+ this.value = this.#initialValue;
341
+ }
342
+
343
+ formDisabledCallback(disabled) {
344
+ this.disabled = disabled;
345
+ }
346
+
347
+ checkValidity() {
348
+ return this.#internals.checkValidity();
349
+ }
350
+
351
+ reportValidity() {
352
+ return this.#internals.reportValidity();
353
+ }
354
+
355
+ get validity() {
356
+ return this.#internals.validity;
357
+ }
358
+
359
+ get validationMessage() {
360
+ return this.#internals.validationMessage;
361
+ }
362
+
363
+ focus(options) {
364
+ this.#selectElement?.focus(options);
365
+ }
366
+
367
+ blur() {
368
+ this.#selectElement?.blur();
369
+ }
370
+ }
371
+
372
+ if (!customElements.get('app-select')) {
373
+ customElements.define('app-select', AppSelect);
374
+ }