@leavepulse/ui 0.22.12 → 0.22.14

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.
Files changed (28) hide show
  1. package/dist/chunks/{LpAddressInput-BWuIOt_4.js → LpAddressInput-DoiamNCt.js} +3 -3
  2. package/dist/chunks/{LpAutocomplete-DIgXmtCA.js → LpAutocomplete-DExlIZe_.js} +2 -1
  3. package/dist/chunks/{LpConfirmDialog-B_3ML33f.js → LpConfirmDialog-CmgeAdCh.js} +1 -1
  4. package/dist/chunks/{LpFileTree-DWx77B1F.js → LpFileTree-Cw50eG_y.js} +2 -2
  5. package/dist/chunks/{LpInput-BauZRyzm.js → LpInput-DYc9O-db.js} +2 -1
  6. package/dist/chunks/{LpModal-CF7G_0D_.js → LpModal-CNNSf5pv.js} +8 -9
  7. package/dist/chunks/{LpNumberField-C5ar-OwE.js → LpNumberField-B3JuQFol.js} +1 -0
  8. package/dist/chunks/{LpPasswordInput-DIHFw4u0.js → LpPasswordInput-BvF2omhn.js} +1 -1
  9. package/dist/chunks/{LpPhoneInput-LQvVFGMw.js → LpPhoneInput-BxuDyXp3.js} +2 -1
  10. package/dist/components/LpAddressInput.vue.js +1 -1
  11. package/dist/components/LpAutocomplete.vue.js +1 -1
  12. package/dist/components/LpConfirmDialog.vue.js +1 -1
  13. package/dist/components/LpFileTree.vue.js +1 -1
  14. package/dist/components/LpInput.vue.js +1 -1
  15. package/dist/components/LpModal.vue.js +1 -1
  16. package/dist/components/LpNumberField.vue.js +1 -1
  17. package/dist/components/LpPasswordInput.vue.js +1 -1
  18. package/dist/components/LpPhoneInput.vue.js +1 -1
  19. package/dist/components/LpShortcutsDialog.vue.js +1 -1
  20. package/dist/index.js +9 -9
  21. package/package.json +1 -1
  22. package/src/components/LpAutocomplete.vue +2 -1
  23. package/src/components/LpFileTree.vue +6 -4
  24. package/src/components/LpInput.vue +2 -1
  25. package/src/components/LpModal.vue +12 -9
  26. package/src/components/LpNumberField.vue +1 -0
  27. package/src/components/LpPhoneInput.vue +2 -1
  28. package/src/tokens/utilities.css +32 -0
@@ -1,12 +1,12 @@
1
1
  import { defineComponent, defineAsyncComponent, computed, ref, onMounted, watch, openBlock, createElementBlock, createVNode, withCtx, createElementVNode, unref, createBlock, createCommentVNode, createTextVNode, toDisplayString, renderSlot, normalizeClass } from "vue";
2
2
  import { OSM_TILES, createNominatimProvider, emptyAddress, formatAddress } from "../components/address.js";
3
3
  import { flagEmoji, loadCountries } from "../components/countries.js";
4
- import { _ as _sfc_main$2 } from "./LpAutocomplete-DIgXmtCA.js";
4
+ import { _ as _sfc_main$2 } from "./LpAutocomplete-DExlIZe_.js";
5
5
  import { _ as _sfc_main$3 } from "./LpButton-5VXXCjwF.js";
6
6
  import { _ as _sfc_main$1 } from "./LpFormField-Bn-ZwC6z.js";
7
7
  import { _ as _sfc_main$4 } from "./LpIcon-CCnX5_2j.js";
8
- import { _ as _sfc_main$5 } from "./LpInput-BauZRyzm.js";
9
- import { _ as _sfc_main$7 } from "./LpModal-CF7G_0D_.js";
8
+ import { _ as _sfc_main$5 } from "./LpInput-DYc9O-db.js";
9
+ import { _ as _sfc_main$7 } from "./LpModal-CNNSf5pv.js";
10
10
  import { _ as _sfc_main$6 } from "./LpSelect-D5mU4cZO.js";
11
11
  const _hoisted_1 = { class: "flex flex-col gap-3" };
12
12
  const _hoisted_2 = { class: "flex items-stretch gap-2" };
@@ -105,6 +105,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
105
105
  }, {
106
106
  default: withCtx(() => [
107
107
  createVNode(unref(ComboboxAnchor), {
108
+ "data-lp-ring-owner": "",
108
109
  class: normalizeClass(["flex w-full items-center gap-2 rounded-control border bg-surface-soft px-3 text-ink transition-colors duration-[var(--duration-fast)] focus-within:ring-2 focus-within:ring-ring data-[disabled]:cursor-not-allowed data-[disabled]:opacity-55", [
109
110
  anchorSize[__props.size],
110
111
  isInvalidState.value ? "border-danger focus-within:border-danger focus-within:ring-danger-soft" : "border-line focus-within:border-brand"
@@ -123,7 +124,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
123
124
  disabled: __props.disabled,
124
125
  "aria-invalid": isInvalidState.value || void 0,
125
126
  "auto-focus": "",
126
- class: "min-w-0 flex-1 bg-transparent outline-none placeholder:text-muted",
127
+ class: "lp-autofill min-w-0 flex-1 self-stretch bg-transparent outline-none placeholder:text-muted",
127
128
  onBeforeinput: unref(onBeforeInput),
128
129
  onPaste: unref(onPaste),
129
130
  onInput,
@@ -1,6 +1,6 @@
1
1
  import { defineComponent, openBlock, createBlock, withCtx, renderSlot, createVNode, createTextVNode, toDisplayString } from "vue";
2
2
  import { _ as _sfc_main$2 } from "./LpButton-5VXXCjwF.js";
3
- import { _ as _sfc_main$1 } from "./LpModal-CF7G_0D_.js";
3
+ import { _ as _sfc_main$1 } from "./LpModal-CNNSf5pv.js";
4
4
  const _sfc_main = /* @__PURE__ */ defineComponent({
5
5
  __name: "LpConfirmDialog",
6
6
  props: {
@@ -1,5 +1,5 @@
1
1
  import { defineComponent, ref, watch, computed, openBlock, createElementBlock, Fragment, renderList, normalizeStyle, createBlock, createVNode, withCtx, createElementVNode, renderSlot, mergeProps, normalizeProps, guardReactiveProps, createTextVNode, toDisplayString, createCommentVNode } from "vue";
2
- import { formatSize, sortNodes, computeStats, subtreeIds, ancestorIds } from "../components/fileTree.js";
2
+ import { computeStats, formatSize, sortNodes, subtreeIds, ancestorIds } from "../components/fileTree.js";
3
3
  import { _ as _sfc_main$1 } from "./LpEmptyState-CrfyRJUG.js";
4
4
  import { _ as _sfc_main$3 } from "./LpFileTreeNode-DFg4PhOK.js";
5
5
  import { _ as _sfc_main$2 } from "./LpScrollArea-D8xyKXcF.js";
@@ -67,6 +67,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
67
67
  return { byId: byId2, parentOfId };
68
68
  });
69
69
  const byId = computed(() => index.value.byId);
70
+ const stats = computed(() => computeStats(props.nodes, checkedIds.value));
70
71
  const summary = computed(() => {
71
72
  let files = 0;
72
73
  let dirs = 0;
@@ -146,7 +147,6 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
146
147
  }
147
148
  const requested = /* @__PURE__ */ new Set();
148
149
  const roots = computed(() => props.sort ? sortNodes(props.nodes) : props.nodes);
149
- const stats = computed(() => computeStats(props.nodes, checkedIds.value));
150
150
  const visible = computed(() => {
151
151
  const out = [];
152
152
  function walk(list) {
@@ -91,6 +91,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
91
91
  }, {
92
92
  default: withCtx(() => [
93
93
  createElementVNode("div", {
94
+ "data-lp-ring-owner": "",
94
95
  class: normalizeClass([shellClass.value, hasLeading.value ? "pl-2.5" : "", hasTrailing.value ? "pr-1.5" : "", hasField.value ? "" : rootClass.value]),
95
96
  style: normalizeStyle(hasField.value ? void 0 : rootStyle.value)
96
97
  }, [
@@ -113,7 +114,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
113
114
  disabled: __props.disabled,
114
115
  "aria-invalid": isInvalidState.value || void 0,
115
116
  class: [
116
- "min-w-0 flex-1 bg-transparent outline-none placeholder:text-muted",
117
+ "lp-autofill min-w-0 flex-1 self-stretch bg-transparent outline-none placeholder:text-muted",
117
118
  hasLeading.value ? "pl-2" : padX[__props.size ?? "md"],
118
119
  hasTrailing.value ? "" : padX[__props.size ?? "md"]
119
120
  ],
@@ -70,16 +70,15 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
70
70
  const target = panel.querySelector(selector);
71
71
  if (!target || target.hasAttribute("disabled")) continue;
72
72
  event.preventDefault();
73
- target.setAttribute("data-lp-focus-ring", "");
74
- target.addEventListener(
75
- "blur",
76
- () => target.removeAttribute("data-lp-focus-ring"),
77
- { once: true }
78
- );
79
- target.focus();
80
- if (target instanceof HTMLInputElement && target.type === "text") {
81
- target.select();
73
+ if (!target.closest("[data-lp-ring-owner]")) {
74
+ target.setAttribute("data-lp-focus-ring", "");
75
+ target.addEventListener(
76
+ "blur",
77
+ () => target.removeAttribute("data-lp-focus-ring"),
78
+ { once: true }
79
+ );
82
80
  }
81
+ target.focus();
83
82
  return;
84
83
  }
85
84
  }
@@ -19,6 +19,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
19
19
  max: __props.max,
20
20
  step: __props.step,
21
21
  disabled: __props.disabled,
22
+ "data-lp-ring-owner": "",
22
23
  class: "flex h-(--size-control-md) w-full items-center rounded-control border border-line bg-surface-soft transition-colors duration-[var(--duration-fast)] focus-within:border-brand focus-within:ring-2 focus-within:ring-ring data-[disabled]:cursor-not-allowed data-[disabled]:opacity-55",
23
24
  "onUpdate:modelValue": _cache[0] || (_cache[0] = (v) => _ctx.$emit("update:modelValue", v ?? null))
24
25
  }, {
@@ -1,6 +1,6 @@
1
1
  import { defineComponent, ref, openBlock, createBlock, createSlots, withCtx, createElementVNode, createVNode, renderSlot } from "vue";
2
2
  import { _ as _sfc_main$2 } from "./LpIcon-CCnX5_2j.js";
3
- import { _ as _sfc_main$1 } from "./LpInput-BauZRyzm.js";
3
+ import { _ as _sfc_main$1 } from "./LpInput-DYc9O-db.js";
4
4
  const _hoisted_1 = ["aria-label"];
5
5
  const __default__ = { inheritAttrs: false };
6
6
  const _sfc_main = /* @__PURE__ */ defineComponent({
@@ -86,6 +86,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
86
86
  };
87
87
  return (_ctx, _cache) => {
88
88
  return openBlock(), createElementBlock("div", {
89
+ "data-lp-ring-owner": "",
89
90
  class: normalizeClass(["flex w-full items-stretch rounded-control border bg-surface-soft text-ink transition-colors duration-[var(--duration-fast)] focus-within:ring-2 focus-within:ring-ring", [
90
91
  sizeClass[__props.size],
91
92
  __props.invalid ? "border-danger focus-within:border-danger focus-within:ring-danger-soft" : "border-line focus-within:border-brand",
@@ -183,7 +184,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
183
184
  type: "tel",
184
185
  inputmode: "tel",
185
186
  autocomplete: "tel",
186
- class: "min-w-0 flex-1 bg-transparent px-3 outline-none placeholder:text-muted",
187
+ class: "lp-autofill min-w-0 flex-1 self-stretch bg-transparent px-3 outline-none placeholder:text-muted",
187
188
  onBeforeinput: _cache[2] || (_cache[2] = //@ts-ignore
188
189
  (...args) => unref(onBeforeInput) && unref(onBeforeInput)(...args)),
189
190
  onPaste: _cache[3] || (_cache[3] = //@ts-ignore
@@ -1,4 +1,4 @@
1
- import { _ as _sfc_main } from "../chunks/LpAddressInput-BWuIOt_4.js";
1
+ import { _ as _sfc_main } from "../chunks/LpAddressInput-DoiamNCt.js";
2
2
  export {
3
3
  _sfc_main as default
4
4
  };
@@ -1,4 +1,4 @@
1
- import { _ as _sfc_main } from "../chunks/LpAutocomplete-DIgXmtCA.js";
1
+ import { _ as _sfc_main } from "../chunks/LpAutocomplete-DExlIZe_.js";
2
2
  export {
3
3
  _sfc_main as default
4
4
  };
@@ -1,4 +1,4 @@
1
- import { _ as _sfc_main } from "../chunks/LpConfirmDialog-B_3ML33f.js";
1
+ import { _ as _sfc_main } from "../chunks/LpConfirmDialog-CmgeAdCh.js";
2
2
  export {
3
3
  _sfc_main as default
4
4
  };
@@ -1,4 +1,4 @@
1
- import { _ as _sfc_main } from "../chunks/LpFileTree-DWx77B1F.js";
1
+ import { _ as _sfc_main } from "../chunks/LpFileTree-Cw50eG_y.js";
2
2
  export {
3
3
  _sfc_main as default
4
4
  };
@@ -1,4 +1,4 @@
1
- import { _ as _sfc_main } from "../chunks/LpInput-BauZRyzm.js";
1
+ import { _ as _sfc_main } from "../chunks/LpInput-DYc9O-db.js";
2
2
  export {
3
3
  _sfc_main as default
4
4
  };
@@ -1,4 +1,4 @@
1
- import { _ as _sfc_main } from "../chunks/LpModal-CF7G_0D_.js";
1
+ import { _ as _sfc_main } from "../chunks/LpModal-CNNSf5pv.js";
2
2
  export {
3
3
  _sfc_main as default
4
4
  };
@@ -1,4 +1,4 @@
1
- import { _ as _sfc_main } from "../chunks/LpNumberField-C5ar-OwE.js";
1
+ import { _ as _sfc_main } from "../chunks/LpNumberField-B3JuQFol.js";
2
2
  export {
3
3
  _sfc_main as default
4
4
  };
@@ -1,4 +1,4 @@
1
- import { _ as _sfc_main } from "../chunks/LpPasswordInput-DIHFw4u0.js";
1
+ import { _ as _sfc_main } from "../chunks/LpPasswordInput-BvF2omhn.js";
2
2
  export {
3
3
  _sfc_main as default
4
4
  };
@@ -1,4 +1,4 @@
1
- import { _ as _sfc_main } from "../chunks/LpPhoneInput-LQvVFGMw.js";
1
+ import { _ as _sfc_main } from "../chunks/LpPhoneInput-BxuDyXp3.js";
2
2
  export {
3
3
  _sfc_main as default
4
4
  };
@@ -1,5 +1,5 @@
1
1
  import { defineComponent, openBlock, createBlock, withCtx, createElementVNode, createElementBlock, Fragment, renderList, toDisplayString, unref } from "vue";
2
- import { _ as _sfc_main$1 } from "../chunks/LpModal-CF7G_0D_.js";
2
+ import { _ as _sfc_main$1 } from "../chunks/LpModal-CNNSf5pv.js";
3
3
  import { appleModifierLabel } from "../composables/useHotkeys.js";
4
4
  import { _ as _export_sfc } from "../chunks/_plugin-vue_export-helper-1tPrXgE0.js";
5
5
  import '../assets/LpShortcutsDialog-B7H-RZLQ.css';const _hoisted_1 = { class: "shortcut-groups" };
package/dist/index.js CHANGED
@@ -1,10 +1,10 @@
1
1
  import { _ } from "./chunks/LayoutCanvas-Dd20boUG.js";
2
- import { _ as _2 } from "./chunks/LpAddressInput-BWuIOt_4.js";
2
+ import { _ as _2 } from "./chunks/LpAddressInput-DoiamNCt.js";
3
3
  import { _ as _3 } from "./chunks/LpMapPicker-DFKrd1Ne.js";
4
4
  import { OSM_TILES, createNominatimProvider, emptyAddress, formatAddress, project, tileUrl, unproject } from "./components/address.js";
5
5
  import { _ as _4 } from "./chunks/LpAlert-BAYHDSS1.js";
6
6
  import { _ as _5 } from "./chunks/LpAppShell-D8CAP9oF.js";
7
- import { _ as _6 } from "./chunks/LpAutocomplete-DIgXmtCA.js";
7
+ import { _ as _6 } from "./chunks/LpAutocomplete-DExlIZe_.js";
8
8
  import { _ as _7 } from "./chunks/LpAvatar-B1Ez0ZAx.js";
9
9
  import { _ as _8 } from "./chunks/LpBadge-BPodVcJa.js";
10
10
  import { _ as _9 } from "./chunks/LpBreadcrumbs-B7Aqatzw.js";
@@ -14,7 +14,7 @@ import { _ as _12 } from "./chunks/LpCard-BmrTvWOe.js";
14
14
  import { _ as _13 } from "./chunks/LpCheckbox-BO0zuuPh.js";
15
15
  import { _ as _14 } from "./chunks/LpCodeBlock-CSiOSPs8.js";
16
16
  import { default as default2 } from "./components/LpCommandPalette.vue.js";
17
- import { _ as _15 } from "./chunks/LpConfirmDialog-B_3ML33f.js";
17
+ import { _ as _15 } from "./chunks/LpConfirmDialog-CmgeAdCh.js";
18
18
  import { _ as _16 } from "./chunks/LpContextMenu-D0mzXqA7.js";
19
19
  import { _ as _17 } from "./chunks/LpDatePicker-dY2q4dr4.js";
20
20
  import { _ as _18 } from "./chunks/LpDisclosure-DP6yfgfM.js";
@@ -22,23 +22,23 @@ import { _ as _19 } from "./chunks/LpDivider-CUX46KR0.js";
22
22
  import { _ as _20 } from "./chunks/LpDrawer-DAJN5rGK.js";
23
23
  import { _ as _21 } from "./chunks/LpDropdownMenu-DJpoqRCb.js";
24
24
  import { _ as _22 } from "./chunks/LpEmptyState-CrfyRJUG.js";
25
- import { _ as _23 } from "./chunks/LpFileTree-DWx77B1F.js";
25
+ import { _ as _23 } from "./chunks/LpFileTree-Cw50eG_y.js";
26
26
  import { ancestorIds, checkStateOf, checkedCount, computeStats, fileIcon, formatModified, formatSize, sortNodes, subtreeIds, subtreeSize } from "./components/fileTree.js";
27
27
  import { _ as _24 } from "./chunks/LpFormField-Bn-ZwC6z.js";
28
28
  import { _ as _25 } from "./chunks/LpIcon-CCnX5_2j.js";
29
29
  import { _ as _26 } from "./chunks/LpInfraNode-qIL3l7IO.js";
30
- import { _ as _27 } from "./chunks/LpInput-BauZRyzm.js";
30
+ import { _ as _27 } from "./chunks/LpInput-DYc9O-db.js";
31
31
  import { _ as _28 } from "./chunks/LpLightbox-Ci35Y_xP.js";
32
32
  import { fileNameOf } from "./components/lightbox.js";
33
33
  import { _ as _29 } from "./chunks/LpLink-DHwOEp4e.js";
34
34
  import { _ as _30 } from "./chunks/LpLogViewer-B1aLUVdz.js";
35
- import { _ as _31 } from "./chunks/LpModal-CF7G_0D_.js";
35
+ import { _ as _31 } from "./chunks/LpModal-CNNSf5pv.js";
36
36
  import { _ as _32 } from "./chunks/LpNotificationBell-DWDEkmdA.js";
37
- import { _ as _33 } from "./chunks/LpNumberField-C5ar-OwE.js";
37
+ import { _ as _33 } from "./chunks/LpNumberField-B3JuQFol.js";
38
38
  import { _ as _34 } from "./chunks/LpOtpInput-DqaT0Z75.js";
39
39
  import { _ as _35 } from "./chunks/LpPagination-CeGuNe_q.js";
40
- import { _ as _36 } from "./chunks/LpPasswordInput-DIHFw4u0.js";
41
- import { _ as _37 } from "./chunks/LpPhoneInput-LQvVFGMw.js";
40
+ import { _ as _36 } from "./chunks/LpPasswordInput-BvF2omhn.js";
41
+ import { _ as _37 } from "./chunks/LpPhoneInput-BxuDyXp3.js";
42
42
  import { flagEmoji, loadCountries, matchCountryByValue } from "./components/countries.js";
43
43
  import { _ as _38 } from "./chunks/LpPopover-8268UmlC.js";
44
44
  import { _ as _39 } from "./chunks/LpProgress-BhI0IbjC.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leavepulse/ui",
3
- "version": "0.22.12",
3
+ "version": "0.22.14",
4
4
  "type": "module",
5
5
  "description": "LeavePulse token-driven Vue component kit.",
6
6
  "repository": {
@@ -180,6 +180,7 @@ function clear() {
180
180
  class="relative"
181
181
  >
182
182
  <ComboboxAnchor
183
+ data-lp-ring-owner
183
184
  class="flex w-full items-center gap-2 rounded-control border bg-surface-soft px-3 text-ink transition-colors duration-[var(--duration-fast)] focus-within:ring-2 focus-within:ring-ring data-[disabled]:cursor-not-allowed data-[disabled]:opacity-55"
184
185
  :class="[
185
186
  anchorSize[size],
@@ -202,7 +203,7 @@ function clear() {
202
203
  :disabled="disabled"
203
204
  :aria-invalid="isInvalidState || undefined"
204
205
  auto-focus
205
- class="min-w-0 flex-1 bg-transparent outline-none placeholder:text-muted"
206
+ class="lp-autofill min-w-0 flex-1 self-stretch bg-transparent outline-none placeholder:text-muted"
206
207
  @beforeinput="onBeforeInput"
207
208
  @paste="onPaste"
208
209
  @input="onInput"
@@ -155,6 +155,12 @@ const index = computed(() => {
155
155
 
156
156
  const byId = computed(() => index.value.byId)
157
157
 
158
+ // One pass for the whole tree; every row reads its entry by id instead of
159
+ // walking its own subtree on each render. Declared before `summary`, which
160
+ // reads it and is watched eagerly — a `const` below that watcher is still in
161
+ // its temporal dead zone when the watcher first runs.
162
+ const stats = computed(() => computeStats(props.nodes, checkedIds.value))
163
+
158
164
  /**
159
165
  * What the ticked set adds up to. Directories are counted but not sized — their
160
166
  * bytes arrive through the files inside them, so summing both would double-count.
@@ -268,10 +274,6 @@ const requested = new Set<string>()
268
274
 
269
275
  const roots = computed(() => (props.sort ? sortNodes(props.nodes) : props.nodes))
270
276
 
271
- // One pass for the whole tree; every row reads its entry by id instead of
272
- // walking its own subtree on each render.
273
- const stats = computed(() => computeStats(props.nodes, checkedIds.value))
274
-
275
277
  /** Rows currently visible, in render order — the axis the arrow keys walk. */
276
278
  const visible = computed(() => {
277
279
  const out: FileNode[] = []
@@ -118,6 +118,7 @@ defineExpose({
118
118
  :style="hasField ? rootStyle : undefined"
119
119
  >
120
120
  <div
121
+ data-lp-ring-owner
121
122
  :class="[shellClass, hasLeading ? 'pl-2.5' : '', hasTrailing ? 'pr-1.5' : '', hasField ? '' : rootClass]"
122
123
  :style="hasField ? undefined : rootStyle"
123
124
  >
@@ -133,7 +134,7 @@ defineExpose({
133
134
  :disabled="disabled"
134
135
  :aria-invalid="isInvalidState || undefined"
135
136
  :class="[
136
- 'min-w-0 flex-1 bg-transparent outline-none placeholder:text-muted',
137
+ 'lp-autofill min-w-0 flex-1 self-stretch bg-transparent outline-none placeholder:text-muted',
137
138
  hasLeading ? 'pl-2' : padX[size ?? 'md'],
138
139
  hasTrailing ? '' : padX[size ?? 'md'],
139
140
  ]"
@@ -140,16 +140,19 @@ function onOpenAutoFocus(event: Event) {
140
140
  // `focus-visible` is the browser's own heuristic and cannot be forced, so
141
141
  // the ring is driven off an attribute the kit owns. Removed as soon as the
142
142
  // element loses focus, leaving normal focus-visible behaviour in charge.
143
- target.setAttribute("data-lp-focus-ring", "")
144
- target.addEventListener(
145
- "blur",
146
- () => target.removeAttribute("data-lp-focus-ring"),
147
- { once: true },
148
- )
149
- target.focus()
150
- if (target instanceof HTMLInputElement && target.type === "text") {
151
- target.select()
143
+ //
144
+ // Skipped for a control whose own shell already draws a ring on
145
+ // focus-within: our fields put the border and ring on the wrapper, so
146
+ // adding one to the bare <input> stacks a second ring inside the first.
147
+ if (!target.closest("[data-lp-ring-owner]")) {
148
+ target.setAttribute("data-lp-focus-ring", "")
149
+ target.addEventListener(
150
+ "blur",
151
+ () => target.removeAttribute("data-lp-focus-ring"),
152
+ { once: true },
153
+ )
152
154
  }
155
+ target.focus()
153
156
  return
154
157
  }
155
158
  }
@@ -28,6 +28,7 @@ defineEmits<{ (e: "update:modelValue", value: number | null): void }>()
28
28
  :max="max"
29
29
  :step="step"
30
30
  :disabled="disabled"
31
+ data-lp-ring-owner
31
32
  class="flex h-(--size-control-md) w-full items-center rounded-control border border-line bg-surface-soft transition-colors duration-[var(--duration-fast)] focus-within:border-brand focus-within:ring-2 focus-within:ring-ring data-[disabled]:cursor-not-allowed data-[disabled]:opacity-55"
32
33
  @update:model-value="(v) => $emit('update:modelValue', v ?? null)"
33
34
  >
@@ -150,6 +150,7 @@ const sizeClass = {
150
150
 
151
151
  <template>
152
152
  <div
153
+ data-lp-ring-owner
153
154
  class="flex w-full items-stretch rounded-control border bg-surface-soft text-ink transition-colors duration-[var(--duration-fast)] focus-within:ring-2 focus-within:ring-ring"
154
155
  :class="[
155
156
  sizeClass[size],
@@ -232,7 +233,7 @@ const sizeClass = {
232
233
  type="tel"
233
234
  inputmode="tel"
234
235
  autocomplete="tel"
235
- class="min-w-0 flex-1 bg-transparent px-3 outline-none placeholder:text-muted"
236
+ class="lp-autofill min-w-0 flex-1 self-stretch bg-transparent px-3 outline-none placeholder:text-muted"
236
237
  @beforeinput="onBeforeInput"
237
238
  @paste="onPaste"
238
239
  @input="onInput"
@@ -36,3 +36,35 @@
36
36
  outline: none;
37
37
  box-shadow: 0 0 0 2px var(--color-ring);
38
38
  }
39
+
40
+ /*
41
+ * Autofilled fields keep the field's own colours.
42
+ *
43
+ * A browser paints its own background on an autofilled control, and it wins
44
+ * over `background` outright. Our inputs draw their surface and border on the
45
+ * WRAPPER, so that paint lands on the bare <input> inside — a band of colour
46
+ * that stops short of the field's real height and reads as a rendering fault.
47
+ *
48
+ * The background cannot be overridden, so it is covered: a large inset shadow
49
+ * repaints the control in the field's own surface colour, and the text colour
50
+ * is forced back with `-webkit-text-fill-color`, which the autofill style also
51
+ * hijacks. The transition delay keeps Chrome from flashing its yellow during
52
+ * the fade it runs on load.
53
+ */
54
+ .lp-autofill:-webkit-autofill,
55
+ .lp-autofill:-webkit-autofill:hover,
56
+ .lp-autofill:-webkit-autofill:focus,
57
+ .lp-autofill:-webkit-autofill:active {
58
+ box-shadow: inset 0 0 0 1000px var(--color-surface-soft);
59
+ -webkit-text-fill-color: var(--color-ink);
60
+ caret-color: var(--color-ink);
61
+ transition: background-color 100000s ease-in-out 0s;
62
+ }
63
+
64
+ /* Firefox names it differently and does honour a background here. */
65
+ .lp-autofill:autofill,
66
+ .lp-autofill:-moz-autofill {
67
+ background-color: var(--color-surface-soft);
68
+ color: var(--color-ink);
69
+ filter: none;
70
+ }