@peektravel/app-utilities 0.2.3 → 0.2.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.
package/dist/ui/index.cjs CHANGED
@@ -159,6 +159,9 @@ function escapeHtml(value) {
159
159
  function classes(...parts) {
160
160
  return parts.filter(Boolean).join(" ");
161
161
  }
162
+ function reflectControlValue(control, value) {
163
+ if (control && control.value !== value) control.value = value;
164
+ }
162
165
  function define(tag, ctor) {
163
166
  if (typeof customElements === "undefined") return;
164
167
  if (!customElements.get(tag)) customElements.define(tag, ctor);
@@ -1352,6 +1355,28 @@ var OdyInput = class extends OdyElement {
1352
1355
  this.#value = next;
1353
1356
  this.setAttribute("value", next);
1354
1357
  }
1358
+ /**
1359
+ * Reflect `value` into the live control in place — the native field already
1360
+ * shows what the user typed, so rebuilding it (as a full re-render would)
1361
+ * needlessly drops focus and caret. Every other observed attribute changes
1362
+ * the chrome and still re-renders via the base implementation.
1363
+ */
1364
+ attributeChangedCallback(name, oldValue, newValue) {
1365
+ if (name === "value") {
1366
+ if (oldValue === newValue) return;
1367
+ const value = newValue ?? "";
1368
+ reflectControlValue(
1369
+ this.querySelector(
1370
+ ".ody-input__field, .ody-input__textarea"
1371
+ ),
1372
+ value
1373
+ );
1374
+ this.#syncCounter(value);
1375
+ this.#syncClearButton(value);
1376
+ return;
1377
+ }
1378
+ super.attributeChangedCallback();
1379
+ }
1355
1380
  render() {
1356
1381
  const size = this.attr("size", "base");
1357
1382
  const isTextarea = this.flag("textarea");
@@ -1381,8 +1406,7 @@ var OdyInput = class extends OdyElement {
1381
1406
  `ody-input__container--${size}`
1382
1407
  );
1383
1408
  const field = isTextarea ? `<textarea class="ody-input__textarea" rows="3"${maxlengthAttr}${isReadonly ? " readonly" : ""}${isDisabled ? " disabled" : ""}>${this.esc(value)}</textarea>` : `<input class="ody-input__field" type="text" value="${this.esc(value)}" placeholder="${this.esc(this.attr("placeholder"))}"${maxlengthAttr}${isReadonly ? " readonly" : ""}${isDisabled ? " disabled" : ""} />`;
1384
- const clearEnabled = !this.flag("no-clear") && !isDisabled && !isReadonly && value !== "";
1385
- const clearEl = clearEnabled ? `<button type="button" class="btn ody-input__clear-button" aria-label="${this.localized("clear-label", "clear")}">${iconSvg("close", "icon__svg clear-icon")}</button>` : "";
1409
+ const clearEl = this.#clearEnabled(value) ? this.#clearButtonHtml() : "";
1386
1410
  const showCounter = !isReadonly && !isDisabled && maxlength !== "";
1387
1411
  const messages = classes(
1388
1412
  caption && "has-caption",
@@ -1404,7 +1428,6 @@ var OdyInput = class extends OdyElement {
1404
1428
  #onInput = (event) => {
1405
1429
  event.stopPropagation();
1406
1430
  const value = event.target.value;
1407
- this.#syncCounter(value);
1408
1431
  this.value = value;
1409
1432
  this.dispatchEvent(new CustomEvent("input", { detail: { value }, bubbles: true }));
1410
1433
  };
@@ -1424,6 +1447,26 @@ var OdyInput = class extends OdyElement {
1424
1447
  const max = this.attr("maxlength");
1425
1448
  if (counter && max) counter.textContent = `${value.length} / ${max}`;
1426
1449
  }
1450
+ /** Whether the clear button should be shown for the given value. */
1451
+ #clearEnabled(value) {
1452
+ return !this.flag("no-clear") && !this.flag("disabled") && !this.flag("readonly") && value !== "";
1453
+ }
1454
+ /** Markup for the clear button (shared by render and the in-place sync). */
1455
+ #clearButtonHtml() {
1456
+ return `<button type="button" class="btn ody-input__clear-button" aria-label="${this.localized("clear-label", "clear")}">${iconSvg("close", "icon__svg clear-icon")}</button>`;
1457
+ }
1458
+ /** Add or remove the clear button in place as the value gains/loses content. */
1459
+ #syncClearButton(value) {
1460
+ const existing = this.querySelector(".ody-input__clear-button");
1461
+ if (this.#clearEnabled(value)) {
1462
+ if (existing) return;
1463
+ const control = this.querySelector(".ody-input__field, .ody-input__textarea");
1464
+ control?.insertAdjacentHTML("afterend", this.#clearButtonHtml());
1465
+ this.querySelector(".ody-input__clear-button")?.addEventListener("click", this.#onClear);
1466
+ } else {
1467
+ existing?.remove();
1468
+ }
1469
+ }
1427
1470
  };
1428
1471
  define("ody-input", OdyInput);
1429
1472
 
@@ -1455,6 +1498,28 @@ var OdyInlineInput = class extends OdyElement {
1455
1498
  this.#value = next;
1456
1499
  this.setAttribute("value", next);
1457
1500
  }
1501
+ /**
1502
+ * Reflect `value` into the live control in place — the native field already
1503
+ * shows what the user typed, so rebuilding it (as a full re-render would)
1504
+ * needlessly drops focus and caret. Every other observed attribute changes
1505
+ * the chrome and still re-renders via the base implementation.
1506
+ */
1507
+ attributeChangedCallback(name, oldValue, newValue) {
1508
+ if (name === "value") {
1509
+ if (oldValue === newValue) return;
1510
+ const value = newValue ?? "";
1511
+ reflectControlValue(
1512
+ this.querySelector(
1513
+ ".ody-inline-input__field, .ody-inline-input__textarea"
1514
+ ),
1515
+ value
1516
+ );
1517
+ this.#syncCounter(value);
1518
+ this.#syncClearButton(value);
1519
+ return;
1520
+ }
1521
+ super.attributeChangedCallback();
1522
+ }
1458
1523
  render() {
1459
1524
  const size = this.attr("size", "base");
1460
1525
  const isTextarea = this.flag("textarea");
@@ -1484,8 +1549,7 @@ var OdyInlineInput = class extends OdyElement {
1484
1549
  `ody-inline-input__group--${size}`
1485
1550
  );
1486
1551
  const field = isTextarea ? `<textarea class="ody-inline-input__textarea" rows="3"${maxlengthAttr}${isReadonly ? " readonly" : ""}${isDisabled ? " disabled" : ""}>${this.esc(value)}</textarea>` : `<input class="ody-inline-input__field" type="text" value="${this.esc(value)}" placeholder="${this.esc(this.attr("placeholder"))}"${maxlengthAttr}${isReadonly ? " readonly" : ""}${isDisabled ? " disabled" : ""} />`;
1487
- const clearEnabled = !this.flag("no-clear") && !isDisabled && !isReadonly && value !== "";
1488
- const clearEl = clearEnabled ? `<button type="button" class="btn ody-inline-input__clear-button" aria-label="${this.localized("clear-label", "clear")}">${iconSvg("close", "icon__svg clear-icon")}</button>` : "";
1552
+ const clearEl = this.#clearEnabled(value) ? this.#clearButtonHtml() : "";
1489
1553
  const showCounter = !isReadonly && !isDisabled && maxlength !== "";
1490
1554
  const footerNeeded = showCounter || caption !== "" || warning !== "" || info !== "";
1491
1555
  const footer = footerNeeded ? `<div class="ody-inline-input-footer"><div class="ody-inline-input-footer__message">` + (caption ? `<span class="ody-inline-input-footer__message__caption">${this.esc(caption)}</span>` : "") + (warning ? `<span class="ody-inline-input-footer__message__warning">${this.esc(warning)}</span>` : "") + (info ? `<span class="ody-inline-input-footer__message__info">${this.esc(info)}</span>` : "") + `</div>` + (showCounter ? `<div class="ody-inline-input-footer__length-message">${value.length} / ${this.esc(maxlength)}</div>` : "") + `</div>` : "";
@@ -1502,7 +1566,6 @@ var OdyInlineInput = class extends OdyElement {
1502
1566
  #onInput = (event) => {
1503
1567
  event.stopPropagation();
1504
1568
  const value = event.target.value;
1505
- this.#syncCounter(value);
1506
1569
  this.value = value;
1507
1570
  this.dispatchEvent(new CustomEvent("input", { detail: { value }, bubbles: true }));
1508
1571
  };
@@ -1521,6 +1584,26 @@ var OdyInlineInput = class extends OdyElement {
1521
1584
  const max = this.attr("maxlength");
1522
1585
  if (counter && max) counter.textContent = `${value.length} / ${max}`;
1523
1586
  }
1587
+ /** Whether the clear button should be shown for the given value. */
1588
+ #clearEnabled(value) {
1589
+ return !this.flag("no-clear") && !this.flag("disabled") && !this.flag("readonly") && value !== "";
1590
+ }
1591
+ /** Markup for the clear button (shared by render and the in-place sync). */
1592
+ #clearButtonHtml() {
1593
+ return `<button type="button" class="btn ody-inline-input__clear-button" aria-label="${this.localized("clear-label", "clear")}">${iconSvg("close", "icon__svg clear-icon")}</button>`;
1594
+ }
1595
+ /** Add or remove the clear button in place as the value gains/loses content. */
1596
+ #syncClearButton(value) {
1597
+ const existing = this.querySelector(".ody-inline-input__clear-button");
1598
+ if (this.#clearEnabled(value)) {
1599
+ if (existing) return;
1600
+ const control = this.querySelector(".ody-inline-input__field, .ody-inline-input__textarea");
1601
+ control?.insertAdjacentHTML("afterend", this.#clearButtonHtml());
1602
+ this.querySelector(".ody-inline-input__clear-button")?.addEventListener("click", this.#onClear);
1603
+ } else {
1604
+ existing?.remove();
1605
+ }
1606
+ }
1524
1607
  };
1525
1608
  define("ody-inline-input", OdyInlineInput);
1526
1609
 
@@ -1544,6 +1627,20 @@ var OdySearchInput = class extends OdyElement {
1544
1627
  this.#value = next;
1545
1628
  this.setAttribute("value", next);
1546
1629
  }
1630
+ /**
1631
+ * Reflect `value` into the live control in place — the native field already
1632
+ * shows what the user typed, so rebuilding it (as a full re-render would)
1633
+ * needlessly drops focus, caret and the `--focused` state. Every other
1634
+ * observed attribute changes the chrome and still re-renders via the base.
1635
+ */
1636
+ attributeChangedCallback(name, oldValue, newValue) {
1637
+ if (name === "value") {
1638
+ if (oldValue === newValue) return;
1639
+ reflectControlValue(this.querySelector(".ody-input__field"), newValue ?? "");
1640
+ return;
1641
+ }
1642
+ super.attributeChangedCallback();
1643
+ }
1547
1644
  render() {
1548
1645
  const size = this.attr("size", "base");
1549
1646
  const isDisabled = this.flag("disabled");