@peektravel/app-utilities 0.2.4 → 0.2.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.
- package/dist/index.cjs +49 -25
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +212 -23
- package/dist/index.d.ts +212 -23
- package/dist/index.js +49 -25
- package/dist/index.js.map +1 -1
- package/dist/ui/index.cjs +142 -14
- package/dist/ui/index.cjs.map +1 -1
- package/dist/ui/index.d.cts +30 -3
- package/dist/ui/index.d.ts +30 -3
- package/dist/ui/index.js +142 -14
- package/dist/ui/index.js.map +1 -1
- package/package.json +1 -1
package/dist/ui/index.d.cts
CHANGED
|
@@ -665,6 +665,13 @@ declare class OdyInput extends OdyElement {
|
|
|
665
665
|
/** Current field value. */
|
|
666
666
|
get value(): string;
|
|
667
667
|
set value(next: string);
|
|
668
|
+
/**
|
|
669
|
+
* Reflect `value` into the live control in place — the native field already
|
|
670
|
+
* shows what the user typed, so rebuilding it (as a full re-render would)
|
|
671
|
+
* needlessly drops focus and caret. Every other observed attribute changes
|
|
672
|
+
* the chrome and still re-renders via the base implementation.
|
|
673
|
+
*/
|
|
674
|
+
attributeChangedCallback(name?: string, oldValue?: string | null, newValue?: string | null): void;
|
|
668
675
|
protected render(): void;
|
|
669
676
|
}
|
|
670
677
|
|
|
@@ -686,6 +693,13 @@ declare class OdyInlineInput extends OdyElement {
|
|
|
686
693
|
/** Current field value. */
|
|
687
694
|
get value(): string;
|
|
688
695
|
set value(next: string);
|
|
696
|
+
/**
|
|
697
|
+
* Reflect `value` into the live control in place — the native field already
|
|
698
|
+
* shows what the user typed, so rebuilding it (as a full re-render would)
|
|
699
|
+
* needlessly drops focus and caret. Every other observed attribute changes
|
|
700
|
+
* the chrome and still re-renders via the base implementation.
|
|
701
|
+
*/
|
|
702
|
+
attributeChangedCallback(name?: string, oldValue?: string | null, newValue?: string | null): void;
|
|
689
703
|
protected render(): void;
|
|
690
704
|
}
|
|
691
705
|
|
|
@@ -711,6 +725,13 @@ declare class OdySearchInput extends OdyElement {
|
|
|
711
725
|
/** Current field value. */
|
|
712
726
|
get value(): string;
|
|
713
727
|
set value(next: string);
|
|
728
|
+
/**
|
|
729
|
+
* Reflect `value` into the live control in place — the native field already
|
|
730
|
+
* shows what the user typed, so rebuilding it (as a full re-render would)
|
|
731
|
+
* needlessly drops focus, caret and the `--focused` state. Every other
|
|
732
|
+
* observed attribute changes the chrome and still re-renders via the base.
|
|
733
|
+
*/
|
|
734
|
+
attributeChangedCallback(name?: string, oldValue?: string | null, newValue?: string | null): void;
|
|
714
735
|
protected render(): void;
|
|
715
736
|
}
|
|
716
737
|
|
|
@@ -918,9 +939,15 @@ declare class OdyTabs extends OdyElement {
|
|
|
918
939
|
}
|
|
919
940
|
|
|
920
941
|
/**
|
|
921
|
-
* `<ody-copy-button>` — a button that copies its `value` to the clipboard
|
|
922
|
-
*
|
|
923
|
-
*
|
|
942
|
+
* `<ody-copy-button>` — a button that copies its `value` to the clipboard,
|
|
943
|
+
* showing a transient success (or error) state. Dispatches a `copy`
|
|
944
|
+
* CustomEvent with `{ value, ok }`.
|
|
945
|
+
*
|
|
946
|
+
* The copy uses a synchronous `document.execCommand('copy')` as the source of
|
|
947
|
+
* truth so it works inside cross-origin iframes (where the async Clipboard API
|
|
948
|
+
* is blocked by default) and legacy contexts. The async
|
|
949
|
+
* `navigator.clipboard.writeText` is used only as a best-effort enhancement
|
|
950
|
+
* when the synchronous path can't run.
|
|
924
951
|
*
|
|
925
952
|
* Attributes:
|
|
926
953
|
* - `value` — the text copied to the clipboard.
|
package/dist/ui/index.d.ts
CHANGED
|
@@ -665,6 +665,13 @@ declare class OdyInput extends OdyElement {
|
|
|
665
665
|
/** Current field value. */
|
|
666
666
|
get value(): string;
|
|
667
667
|
set value(next: string);
|
|
668
|
+
/**
|
|
669
|
+
* Reflect `value` into the live control in place — the native field already
|
|
670
|
+
* shows what the user typed, so rebuilding it (as a full re-render would)
|
|
671
|
+
* needlessly drops focus and caret. Every other observed attribute changes
|
|
672
|
+
* the chrome and still re-renders via the base implementation.
|
|
673
|
+
*/
|
|
674
|
+
attributeChangedCallback(name?: string, oldValue?: string | null, newValue?: string | null): void;
|
|
668
675
|
protected render(): void;
|
|
669
676
|
}
|
|
670
677
|
|
|
@@ -686,6 +693,13 @@ declare class OdyInlineInput extends OdyElement {
|
|
|
686
693
|
/** Current field value. */
|
|
687
694
|
get value(): string;
|
|
688
695
|
set value(next: string);
|
|
696
|
+
/**
|
|
697
|
+
* Reflect `value` into the live control in place — the native field already
|
|
698
|
+
* shows what the user typed, so rebuilding it (as a full re-render would)
|
|
699
|
+
* needlessly drops focus and caret. Every other observed attribute changes
|
|
700
|
+
* the chrome and still re-renders via the base implementation.
|
|
701
|
+
*/
|
|
702
|
+
attributeChangedCallback(name?: string, oldValue?: string | null, newValue?: string | null): void;
|
|
689
703
|
protected render(): void;
|
|
690
704
|
}
|
|
691
705
|
|
|
@@ -711,6 +725,13 @@ declare class OdySearchInput extends OdyElement {
|
|
|
711
725
|
/** Current field value. */
|
|
712
726
|
get value(): string;
|
|
713
727
|
set value(next: string);
|
|
728
|
+
/**
|
|
729
|
+
* Reflect `value` into the live control in place — the native field already
|
|
730
|
+
* shows what the user typed, so rebuilding it (as a full re-render would)
|
|
731
|
+
* needlessly drops focus, caret and the `--focused` state. Every other
|
|
732
|
+
* observed attribute changes the chrome and still re-renders via the base.
|
|
733
|
+
*/
|
|
734
|
+
attributeChangedCallback(name?: string, oldValue?: string | null, newValue?: string | null): void;
|
|
714
735
|
protected render(): void;
|
|
715
736
|
}
|
|
716
737
|
|
|
@@ -918,9 +939,15 @@ declare class OdyTabs extends OdyElement {
|
|
|
918
939
|
}
|
|
919
940
|
|
|
920
941
|
/**
|
|
921
|
-
* `<ody-copy-button>` — a button that copies its `value` to the clipboard
|
|
922
|
-
*
|
|
923
|
-
*
|
|
942
|
+
* `<ody-copy-button>` — a button that copies its `value` to the clipboard,
|
|
943
|
+
* showing a transient success (or error) state. Dispatches a `copy`
|
|
944
|
+
* CustomEvent with `{ value, ok }`.
|
|
945
|
+
*
|
|
946
|
+
* The copy uses a synchronous `document.execCommand('copy')` as the source of
|
|
947
|
+
* truth so it works inside cross-origin iframes (where the async Clipboard API
|
|
948
|
+
* is blocked by default) and legacy contexts. The async
|
|
949
|
+
* `navigator.clipboard.writeText` is used only as a best-effort enhancement
|
|
950
|
+
* when the synchronous path can't run.
|
|
924
951
|
*
|
|
925
952
|
* Attributes:
|
|
926
953
|
* - `value` — the text copied to the clipboard.
|
package/dist/ui/index.js
CHANGED
|
@@ -157,6 +157,9 @@ function escapeHtml(value) {
|
|
|
157
157
|
function classes(...parts) {
|
|
158
158
|
return parts.filter(Boolean).join(" ");
|
|
159
159
|
}
|
|
160
|
+
function reflectControlValue(control, value) {
|
|
161
|
+
if (control && control.value !== value) control.value = value;
|
|
162
|
+
}
|
|
160
163
|
function define(tag, ctor) {
|
|
161
164
|
if (typeof customElements === "undefined") return;
|
|
162
165
|
if (!customElements.get(tag)) customElements.define(tag, ctor);
|
|
@@ -1350,6 +1353,28 @@ var OdyInput = class extends OdyElement {
|
|
|
1350
1353
|
this.#value = next;
|
|
1351
1354
|
this.setAttribute("value", next);
|
|
1352
1355
|
}
|
|
1356
|
+
/**
|
|
1357
|
+
* Reflect `value` into the live control in place — the native field already
|
|
1358
|
+
* shows what the user typed, so rebuilding it (as a full re-render would)
|
|
1359
|
+
* needlessly drops focus and caret. Every other observed attribute changes
|
|
1360
|
+
* the chrome and still re-renders via the base implementation.
|
|
1361
|
+
*/
|
|
1362
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
1363
|
+
if (name === "value") {
|
|
1364
|
+
if (oldValue === newValue) return;
|
|
1365
|
+
const value = newValue ?? "";
|
|
1366
|
+
reflectControlValue(
|
|
1367
|
+
this.querySelector(
|
|
1368
|
+
".ody-input__field, .ody-input__textarea"
|
|
1369
|
+
),
|
|
1370
|
+
value
|
|
1371
|
+
);
|
|
1372
|
+
this.#syncCounter(value);
|
|
1373
|
+
this.#syncClearButton(value);
|
|
1374
|
+
return;
|
|
1375
|
+
}
|
|
1376
|
+
super.attributeChangedCallback();
|
|
1377
|
+
}
|
|
1353
1378
|
render() {
|
|
1354
1379
|
const size = this.attr("size", "base");
|
|
1355
1380
|
const isTextarea = this.flag("textarea");
|
|
@@ -1379,8 +1404,7 @@ var OdyInput = class extends OdyElement {
|
|
|
1379
1404
|
`ody-input__container--${size}`
|
|
1380
1405
|
);
|
|
1381
1406
|
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" : ""} />`;
|
|
1382
|
-
const
|
|
1383
|
-
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>` : "";
|
|
1407
|
+
const clearEl = this.#clearEnabled(value) ? this.#clearButtonHtml() : "";
|
|
1384
1408
|
const showCounter = !isReadonly && !isDisabled && maxlength !== "";
|
|
1385
1409
|
const messages = classes(
|
|
1386
1410
|
caption && "has-caption",
|
|
@@ -1402,7 +1426,6 @@ var OdyInput = class extends OdyElement {
|
|
|
1402
1426
|
#onInput = (event) => {
|
|
1403
1427
|
event.stopPropagation();
|
|
1404
1428
|
const value = event.target.value;
|
|
1405
|
-
this.#syncCounter(value);
|
|
1406
1429
|
this.value = value;
|
|
1407
1430
|
this.dispatchEvent(new CustomEvent("input", { detail: { value }, bubbles: true }));
|
|
1408
1431
|
};
|
|
@@ -1422,6 +1445,26 @@ var OdyInput = class extends OdyElement {
|
|
|
1422
1445
|
const max = this.attr("maxlength");
|
|
1423
1446
|
if (counter && max) counter.textContent = `${value.length} / ${max}`;
|
|
1424
1447
|
}
|
|
1448
|
+
/** Whether the clear button should be shown for the given value. */
|
|
1449
|
+
#clearEnabled(value) {
|
|
1450
|
+
return !this.flag("no-clear") && !this.flag("disabled") && !this.flag("readonly") && value !== "";
|
|
1451
|
+
}
|
|
1452
|
+
/** Markup for the clear button (shared by render and the in-place sync). */
|
|
1453
|
+
#clearButtonHtml() {
|
|
1454
|
+
return `<button type="button" class="btn ody-input__clear-button" aria-label="${this.localized("clear-label", "clear")}">${iconSvg("close", "icon__svg clear-icon")}</button>`;
|
|
1455
|
+
}
|
|
1456
|
+
/** Add or remove the clear button in place as the value gains/loses content. */
|
|
1457
|
+
#syncClearButton(value) {
|
|
1458
|
+
const existing = this.querySelector(".ody-input__clear-button");
|
|
1459
|
+
if (this.#clearEnabled(value)) {
|
|
1460
|
+
if (existing) return;
|
|
1461
|
+
const control = this.querySelector(".ody-input__field, .ody-input__textarea");
|
|
1462
|
+
control?.insertAdjacentHTML("afterend", this.#clearButtonHtml());
|
|
1463
|
+
this.querySelector(".ody-input__clear-button")?.addEventListener("click", this.#onClear);
|
|
1464
|
+
} else {
|
|
1465
|
+
existing?.remove();
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1425
1468
|
};
|
|
1426
1469
|
define("ody-input", OdyInput);
|
|
1427
1470
|
|
|
@@ -1453,6 +1496,28 @@ var OdyInlineInput = class extends OdyElement {
|
|
|
1453
1496
|
this.#value = next;
|
|
1454
1497
|
this.setAttribute("value", next);
|
|
1455
1498
|
}
|
|
1499
|
+
/**
|
|
1500
|
+
* Reflect `value` into the live control in place — the native field already
|
|
1501
|
+
* shows what the user typed, so rebuilding it (as a full re-render would)
|
|
1502
|
+
* needlessly drops focus and caret. Every other observed attribute changes
|
|
1503
|
+
* the chrome and still re-renders via the base implementation.
|
|
1504
|
+
*/
|
|
1505
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
1506
|
+
if (name === "value") {
|
|
1507
|
+
if (oldValue === newValue) return;
|
|
1508
|
+
const value = newValue ?? "";
|
|
1509
|
+
reflectControlValue(
|
|
1510
|
+
this.querySelector(
|
|
1511
|
+
".ody-inline-input__field, .ody-inline-input__textarea"
|
|
1512
|
+
),
|
|
1513
|
+
value
|
|
1514
|
+
);
|
|
1515
|
+
this.#syncCounter(value);
|
|
1516
|
+
this.#syncClearButton(value);
|
|
1517
|
+
return;
|
|
1518
|
+
}
|
|
1519
|
+
super.attributeChangedCallback();
|
|
1520
|
+
}
|
|
1456
1521
|
render() {
|
|
1457
1522
|
const size = this.attr("size", "base");
|
|
1458
1523
|
const isTextarea = this.flag("textarea");
|
|
@@ -1482,8 +1547,7 @@ var OdyInlineInput = class extends OdyElement {
|
|
|
1482
1547
|
`ody-inline-input__group--${size}`
|
|
1483
1548
|
);
|
|
1484
1549
|
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" : ""} />`;
|
|
1485
|
-
const
|
|
1486
|
-
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>` : "";
|
|
1550
|
+
const clearEl = this.#clearEnabled(value) ? this.#clearButtonHtml() : "";
|
|
1487
1551
|
const showCounter = !isReadonly && !isDisabled && maxlength !== "";
|
|
1488
1552
|
const footerNeeded = showCounter || caption !== "" || warning !== "" || info !== "";
|
|
1489
1553
|
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>` : "";
|
|
@@ -1500,7 +1564,6 @@ var OdyInlineInput = class extends OdyElement {
|
|
|
1500
1564
|
#onInput = (event) => {
|
|
1501
1565
|
event.stopPropagation();
|
|
1502
1566
|
const value = event.target.value;
|
|
1503
|
-
this.#syncCounter(value);
|
|
1504
1567
|
this.value = value;
|
|
1505
1568
|
this.dispatchEvent(new CustomEvent("input", { detail: { value }, bubbles: true }));
|
|
1506
1569
|
};
|
|
@@ -1519,6 +1582,26 @@ var OdyInlineInput = class extends OdyElement {
|
|
|
1519
1582
|
const max = this.attr("maxlength");
|
|
1520
1583
|
if (counter && max) counter.textContent = `${value.length} / ${max}`;
|
|
1521
1584
|
}
|
|
1585
|
+
/** Whether the clear button should be shown for the given value. */
|
|
1586
|
+
#clearEnabled(value) {
|
|
1587
|
+
return !this.flag("no-clear") && !this.flag("disabled") && !this.flag("readonly") && value !== "";
|
|
1588
|
+
}
|
|
1589
|
+
/** Markup for the clear button (shared by render and the in-place sync). */
|
|
1590
|
+
#clearButtonHtml() {
|
|
1591
|
+
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>`;
|
|
1592
|
+
}
|
|
1593
|
+
/** Add or remove the clear button in place as the value gains/loses content. */
|
|
1594
|
+
#syncClearButton(value) {
|
|
1595
|
+
const existing = this.querySelector(".ody-inline-input__clear-button");
|
|
1596
|
+
if (this.#clearEnabled(value)) {
|
|
1597
|
+
if (existing) return;
|
|
1598
|
+
const control = this.querySelector(".ody-inline-input__field, .ody-inline-input__textarea");
|
|
1599
|
+
control?.insertAdjacentHTML("afterend", this.#clearButtonHtml());
|
|
1600
|
+
this.querySelector(".ody-inline-input__clear-button")?.addEventListener("click", this.#onClear);
|
|
1601
|
+
} else {
|
|
1602
|
+
existing?.remove();
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
1522
1605
|
};
|
|
1523
1606
|
define("ody-inline-input", OdyInlineInput);
|
|
1524
1607
|
|
|
@@ -1542,6 +1625,20 @@ var OdySearchInput = class extends OdyElement {
|
|
|
1542
1625
|
this.#value = next;
|
|
1543
1626
|
this.setAttribute("value", next);
|
|
1544
1627
|
}
|
|
1628
|
+
/**
|
|
1629
|
+
* Reflect `value` into the live control in place — the native field already
|
|
1630
|
+
* shows what the user typed, so rebuilding it (as a full re-render would)
|
|
1631
|
+
* needlessly drops focus, caret and the `--focused` state. Every other
|
|
1632
|
+
* observed attribute changes the chrome and still re-renders via the base.
|
|
1633
|
+
*/
|
|
1634
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
1635
|
+
if (name === "value") {
|
|
1636
|
+
if (oldValue === newValue) return;
|
|
1637
|
+
reflectControlValue(this.querySelector(".ody-input__field"), newValue ?? "");
|
|
1638
|
+
return;
|
|
1639
|
+
}
|
|
1640
|
+
super.attributeChangedCallback();
|
|
1641
|
+
}
|
|
1545
1642
|
render() {
|
|
1546
1643
|
const size = this.attr("size", "base");
|
|
1547
1644
|
const isDisabled = this.flag("disabled");
|
|
@@ -2052,16 +2149,47 @@ var OdyCopyButton = class extends OdyElement {
|
|
|
2052
2149
|
}
|
|
2053
2150
|
#onClick = () => {
|
|
2054
2151
|
const value = this.attr("value");
|
|
2055
|
-
const
|
|
2056
|
-
if (!
|
|
2057
|
-
|
|
2058
|
-
|
|
2152
|
+
const ok = this.#execCopy(value);
|
|
2153
|
+
if (!ok) {
|
|
2154
|
+
const clipboard = typeof navigator !== "undefined" ? navigator.clipboard : void 0;
|
|
2155
|
+
if (clipboard && typeof clipboard.writeText === "function") {
|
|
2156
|
+
void clipboard.writeText(value).then(
|
|
2157
|
+
() => void 0,
|
|
2158
|
+
() => void 0
|
|
2159
|
+
);
|
|
2160
|
+
}
|
|
2059
2161
|
}
|
|
2060
|
-
|
|
2061
|
-
() => this.#feedback("success", value),
|
|
2062
|
-
() => this.#feedback("error", value)
|
|
2063
|
-
);
|
|
2162
|
+
this.#feedback(ok ? "success" : "error", value);
|
|
2064
2163
|
};
|
|
2164
|
+
/**
|
|
2165
|
+
* Synchronous clipboard write. Must be called within the user-gesture window
|
|
2166
|
+
* (i.e. directly from the click handler, not from an async continuation).
|
|
2167
|
+
* Works in cross-origin iframes and legacy contexts where the async Clipboard
|
|
2168
|
+
* API is unavailable or blocked.
|
|
2169
|
+
*/
|
|
2170
|
+
#execCopy(value) {
|
|
2171
|
+
if (!value || typeof document === "undefined") return false;
|
|
2172
|
+
const el = document.createElement("textarea");
|
|
2173
|
+
el.value = value;
|
|
2174
|
+
el.setAttribute("readonly", "");
|
|
2175
|
+
el.style.position = "fixed";
|
|
2176
|
+
el.style.top = "0";
|
|
2177
|
+
el.style.left = "0";
|
|
2178
|
+
el.style.opacity = "0";
|
|
2179
|
+
document.body.appendChild(el);
|
|
2180
|
+
el.select();
|
|
2181
|
+
try {
|
|
2182
|
+
el.setSelectionRange(0, value.length);
|
|
2183
|
+
} catch {
|
|
2184
|
+
}
|
|
2185
|
+
let ok = false;
|
|
2186
|
+
try {
|
|
2187
|
+
ok = document.execCommand("copy");
|
|
2188
|
+
} catch {
|
|
2189
|
+
}
|
|
2190
|
+
document.body.removeChild(el);
|
|
2191
|
+
return ok;
|
|
2192
|
+
}
|
|
2065
2193
|
#feedback(state, value) {
|
|
2066
2194
|
this.#state = state;
|
|
2067
2195
|
this.render();
|