@ajuarezso/sui-design-system 0.15.0 → 0.15.2

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.
@@ -9777,6 +9777,38 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
9777
9777
  * selector de país es una feature nueva (selector de país); SUI tiene
9778
9778
  * [prefix] como string estático.
9779
9779
  */
9780
+ /**
9781
+ * ¿El tap va a parar a algo que abre el teclado?
9782
+ *
9783
+ * Se mira hacia arriba desde el target y no solo el target: en un `sui-input`
9784
+ * el toque suele caer en el wrapper, el label o el ícono, y ninguno de esos es
9785
+ * el `<input>`. Sin subir por los ancestros, tocar el borde de otro campo se
9786
+ * leía como tocar el vacío.
9787
+ *
9788
+ * `readonly` no cuenta —no abre teclado— pero un `disabled` tampoco recibe el
9789
+ * evento, así que no hace falta filtrarlo.
9790
+ */
9791
+ function recibeTexto(target) {
9792
+ let el = target instanceof HTMLElement
9793
+ ? target
9794
+ : target.parentElement;
9795
+ while (el) {
9796
+ const tag = el.tagName;
9797
+ if (tag === 'TEXTAREA' || tag === 'SELECT')
9798
+ return true;
9799
+ if (tag === 'INPUT' && !el.readOnly)
9800
+ return true;
9801
+ if (el.isContentEditable)
9802
+ return true;
9803
+ /* El host del componente: alcanza con reconocerlo aunque el `<input>` de
9804
+ adentro todavía no exista (los `type` con control propio lo montan
9805
+ condicionalmente). */
9806
+ if (tag === 'SUI-INPUT' || tag === 'SUI-TEXTAREA')
9807
+ return true;
9808
+ el = el.parentElement;
9809
+ }
9810
+ return false;
9811
+ }
9780
9812
  class SuiInput {
9781
9813
  // ── Core inputs (existing API — DO NOT BREAK) ────────────────────────────
9782
9814
  size = input('medium', ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
@@ -9872,15 +9904,22 @@ class SuiInput {
9872
9904
  * hay teclado abierto, por eso escuchamos también `touchstart` (nativo iOS)
9873
9905
  * y `mousedown` (web fallback). Cualquiera de los tres dispara el blur.
9874
9906
  *
9875
- * Solo hace blur cuando el elemento enfocado vive DENTRO de este host: si el
9876
- * foco está en otro lado (otro input, un textarea, nada), acá no hay teclado
9877
- * que cerrar y tocarlo es un bug. El listener es a nivel `document`, así que
9878
- * cada instancia de la página recibe todos los taps: con un blur incondicional
9879
- * de `inputEl`, tocar el texto de un input cerraba el teclado porque los OTROS
9880
- * sui-input de la pantalla hacían blur de los suyos —y en iOS un blur con el
9881
- * teclado abierto lo cierra aunque el elemento no tenga el foco. Se veía en
9882
- * cualquier formulario con más de un campo y no en el buscador del home, que
9883
- * tiene uno solo.
9907
+ * Dos condiciones lo apagan, y cada una salió de un bug real en iOS:
9908
+ *
9909
+ * 1. El foco tiene que vivir DENTRO de este host. El listener es a nivel
9910
+ * `document`, así que cada instancia de la página recibe todos los taps:
9911
+ * con un blur incondicional, tocar el texto de un input cerraba el teclado
9912
+ * porque los OTROS sui-input hacían blur de los suyos —y en iOS un blur con
9913
+ * el teclado abierto lo cierra aunque el elemento no tenga el foco.
9914
+ *
9915
+ * 2. El tap NO puede ir a otro campo de texto. Ahí el navegador mueve el foco
9916
+ * solo, y adelantarle un blur en `pointerdown` cierra el teclado un
9917
+ * instante antes de que el campo nuevo lo vuelva a abrir. Ese parpadeo se
9918
+ * come el segundo toque de un doble tap: estando en un campo, no se podía
9919
+ * seleccionar una palabra de otro.
9920
+ *
9921
+ * Lo que queda es lo único que este listener tenía que hacer: tocar espacio
9922
+ * muerto de la pantalla baja el teclado.
9884
9923
  */
9885
9924
  onDocumentTap(event) {
9886
9925
  if (!this.closeOnOutsideClick())
@@ -9889,6 +9928,8 @@ class SuiInput {
9889
9928
  const target = event.target;
9890
9929
  if (!target || host.contains(target))
9891
9930
  return;
9931
+ if (recibeTexto(target))
9932
+ return;
9892
9933
  const active = document.activeElement;
9893
9934
  if (active && host.contains(active))
9894
9935
  active.blur();
@@ -11352,10 +11393,18 @@ class SuiModal {
11352
11393
  const c = document.documentElement.classList;
11353
11394
  return c.contains('platform-ios') || c.contains('platform-android');
11354
11395
  }
11396
+ /** Se lee al construir CADA modal y no dentro del computed: `enApp()` mira el
11397
+ * DOM y no es una señal, así que un `computed` la evaluaría una sola vez y se
11398
+ * quedaría con esa respuesta para siempre. Si el host marca la plataforma
11399
+ * después de ese primer render —o si el DS se evalúa antes que el arranque de
11400
+ * la app—, el modal quedaría centrado dentro de la app y nadie se enteraría.
11401
+ * Leerlo por instancia da la respuesta vigente cuando el modal nace, que es
11402
+ * cuando importa. */
11403
+ enAppAlNacer = SuiModal.enApp();
11355
11404
  /** `fullscreen` ya resuelto: es lo que miran la plantilla y el host. */
11356
11405
  esFullscreen = computed(() => {
11357
11406
  const v = this.fullscreen();
11358
- return v === 'auto' ? SuiModal.enApp() : v === true;
11407
+ return v === 'auto' ? this.enAppAlNacer : v === true;
11359
11408
  }, ...(ngDevMode ? [{ debugName: "esFullscreen" }] : /* istanbul ignore next */ []));
11360
11409
  /** Quita el padding del body para contenido full-bleed (mapas, imágenes
11361
11410
  grandes). El consumer es responsable de su propio spacing interno. */