@crvouga/mockingbird-service-google-maps 1.7.0 → 1.7.1

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/CHANGELOG.md CHANGED
@@ -1,6 +1,15 @@
1
1
  # Changelog — @crvouga/mockingbird-service-google-maps
2
2
 
3
- ## 1.7.0 (2026-09-30)
3
+ ## 1.7.1 (2026-09-30)
4
+
5
+ ### Fixes and improvements
6
+
7
+ - keep validated address lines within 80 characters ([2ba2574](https://github.com/crvouga/mockingbird/commit/2ba2574450c6900155e7b25a09891052faf7ba6f))
8
+ - keep admin buttons pressed until the action finishes ([0aabfcb](https://github.com/crvouga/mockingbird/commit/0aabfcb5f392aa51e193f9c3f817d5877d92bc6a))
9
+ - size admin select fields like the text inputs beside them ([0fb3ef2](https://github.com/crvouga/mockingbird/commit/0fb3ef2ddbf43376c93a2ae6d87c6c9bf890dafc))
10
+ - keep admin section tabs wired when boot is renamed ([4eda25e](https://github.com/crvouga/mockingbird/commit/4eda25eacd59da048d196f7cc8369d7a02b47bfc))
11
+
12
+ ## 1.7.0 (2026-09-29)
4
13
 
5
14
  ### Features
6
15
 
@@ -2088,6 +2088,50 @@ function bootAdmin(config) {
2088
2088
  if (render)
2089
2089
  await render();
2090
2090
  }
2091
+ const delay = (ms) => new Promise((resolve2) => {
2092
+ window.setTimeout(resolve2, ms);
2093
+ });
2094
+ const confirmAction = async (button, pending, done, work) => {
2095
+ if (button.dataset.pending === "1")
2096
+ return;
2097
+ const label = button.textContent ?? "";
2098
+ const width = button.offsetWidth;
2099
+ button.dataset.pending = "1";
2100
+ button.classList.add("pressed");
2101
+ button.setAttribute("aria-busy", "true");
2102
+ button.style.minWidth = `${width}px`;
2103
+ button.textContent = pending;
2104
+ try {
2105
+ await work();
2106
+ button.textContent = done;
2107
+ await delay(1e3);
2108
+ } catch (error) {
2109
+ showError(error);
2110
+ } finally {
2111
+ button.textContent = label;
2112
+ button.style.minWidth = "";
2113
+ button.classList.remove("pressed");
2114
+ button.removeAttribute("aria-busy");
2115
+ delete button.dataset.pending;
2116
+ }
2117
+ };
2118
+ document.addEventListener("click", (event) => {
2119
+ const target = event.target;
2120
+ if (!(target instanceof Element))
2121
+ return;
2122
+ const button = target.closest("button");
2123
+ if (!(button instanceof HTMLButtonElement) || button.dataset.pending === "1")
2124
+ return;
2125
+ button.classList.add("pressed");
2126
+ const token = String(Date.now());
2127
+ button.dataset.press = token;
2128
+ window.setTimeout(() => {
2129
+ if (button.dataset.press !== token || button.dataset.pending === "1")
2130
+ return;
2131
+ button.classList.remove("pressed");
2132
+ delete button.dataset.press;
2133
+ }, 450);
2134
+ });
2091
2135
  el("namespace", HTMLSelectElement).addEventListener("change", () => {
2092
2136
  state.namespace = el("namespace", HTMLSelectElement).value;
2093
2137
  refresh().catch(showError);
@@ -2098,7 +2142,7 @@ function bootAdmin(config) {
2098
2142
  refresh().catch(showError);
2099
2143
  });
2100
2144
  el("refresh", HTMLButtonElement).addEventListener("click", () => {
2101
- refresh().catch(showError);
2145
+ confirmAction(el("refresh", HTMLButtonElement), "Refreshing...", "Updated", () => refresh()).catch(showError);
2102
2146
  });
2103
2147
  el("collections", HTMLElement).addEventListener("click", (event) => {
2104
2148
  const target = event.target;
@@ -2221,10 +2265,10 @@ function bootAdmin(config) {
2221
2265
  }).then(() => renderFaults()).catch(showError);
2222
2266
  });
2223
2267
  el("fault-clear", HTMLButtonElement).addEventListener("click", () => {
2224
- api("DELETE", "/faults").then(() => renderFaults()).catch(showError);
2268
+ confirmAction(el("fault-clear", HTMLButtonElement), "Clearing...", "Cleared", () => api("DELETE", "/faults").then(() => renderFaults())).catch(showError);
2225
2269
  });
2226
2270
  el("journal-clear", HTMLButtonElement).addEventListener("click", () => {
2227
- api("DELETE", "/requests").then(() => renderJournal()).catch(showError);
2271
+ confirmAction(el("journal-clear", HTMLButtonElement), "Clearing...", "Cleared", () => api("DELETE", "/requests").then(() => renderJournal())).catch(showError);
2228
2272
  });
2229
2273
  el("route-list", HTMLElement).addEventListener("click", (event) => {
2230
2274
  const target = event.target;
@@ -2339,9 +2383,13 @@ function bootAdmin(config) {
2339
2383
  var clientPrelude = [isRecord2, mountAdminBrand, startAdminBrand, faultPresetList, bootAdmin].map((fn) => fn.toString()).join("\n");
2340
2384
  var adminClientSource = (config) => {
2341
2385
  const payload = JSON.stringify(config).replace(/</g, "\\u003c");
2386
+ const entry = bootAdmin.name;
2387
+ if (!/^[A-Za-z_$][\w$]*$/.test(entry)) {
2388
+ throw new Error("admin client cannot call its boot function");
2389
+ }
2342
2390
  return `<script>
2343
2391
  ${clientPrelude}
2344
- bootAdmin(${payload});
2392
+ ${entry}(${payload});
2345
2393
  </script>`;
2346
2394
  };
2347
2395
 
@@ -2555,9 +2603,24 @@ var renderAdminDocument = (service) => {
2555
2603
  .controls { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
2556
2604
  label.field { display: flex; flex-direction: column; gap: 2px; font-size: 11px; color: var(--faint); }
2557
2605
  label.field input, label.field select, .controls input[type="password"] {
2558
- background: var(--bg-inset); border: 1px solid var(--line); border-radius: 8px;
2559
- padding: 7px 8px; min-height: 36px;
2560
- }
2606
+ background-color: var(--bg-inset); border: 1px solid var(--line); border-radius: 8px;
2607
+ padding: 7px 8px; min-height: 36px; line-height: 1.2;
2608
+ }
2609
+ /* A native menu ignores min-height, so Preset rendered about half as tall as
2610
+ the text fields in the same form. Paint it like those fields. */
2611
+ label.field select {
2612
+ -webkit-appearance: none; appearance: none;
2613
+ padding-right: 28px;
2614
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='none' stroke='%2371717a' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round' d='M2.5 4.5 6 8l3.5-3.5'/%3E%3C/svg%3E");
2615
+ background-repeat: no-repeat; background-position: right 10px center; background-size: 12px 12px;
2616
+ }
2617
+ .stack > .field > input,
2618
+ .stack > .field > select,
2619
+ .stack > .field > textarea,
2620
+ dialog .field > input,
2621
+ dialog .field > select,
2622
+ dialog .field > textarea { width: 100%; }
2623
+ .icon-btn, .btn, .btn-primary, .btn-quiet, .btn-danger, nav.side button { cursor: pointer; }
2561
2624
  .icon-btn, .btn, .btn-primary, .btn-quiet, .btn-danger {
2562
2625
  border-radius: 9px; min-height: 36px; padding: 0 12px; border: 1px solid var(--line);
2563
2626
  background: var(--bg-elev);
@@ -2565,6 +2628,14 @@ var renderAdminDocument = (service) => {
2565
2628
  .btn-primary { background: var(--accent); color: var(--accent-ink); border-color: transparent; }
2566
2629
  .btn-quiet { background: transparent; }
2567
2630
  .btn-danger { color: var(--err); }
2631
+ /* Held after mouseup. Clearing an empty journal changes nothing on screen,
2632
+ so the press itself has to stay visible. */
2633
+ .icon-btn:active, .btn:active, .btn-quiet:active, .btn-danger:active, nav.side button:active,
2634
+ .icon-btn.pressed, .btn.pressed, .btn-quiet.pressed, .btn-danger.pressed, nav.side button.pressed {
2635
+ background: var(--bg-inset); border-color: var(--line-strong); color: var(--ink);
2636
+ }
2637
+ .btn-danger:active, .btn-danger.pressed { background: var(--err-soft); color: var(--err); }
2638
+ .btn-primary:active, .btn-primary.pressed { filter: brightness(0.88); color: var(--accent-ink); }
2568
2639
  /* minmax(0, 1fr): a 1fr column will not shrink below its content, so a long
2569
2640
  record or the full nav row would widen the page past the viewport. */
2570
2641
  .layout {
@@ -2653,8 +2724,16 @@ var renderAdminDocument = (service) => {
2653
2724
  input, select, textarea { font-size: 16px; }
2654
2725
  .brand, .vendor, .controls { width: 100%; }
2655
2726
  label.field, label.field select, label.field input { flex: 1; min-width: 0; }
2656
- .layout { grid-template-columns: minmax(0, 1fr); }
2727
+ /* The layout fills the screen. A single-column grid stretches every auto
2728
+ row, so the tab bar grew with the leftover height and the buttons floated
2729
+ in the middle of it. The bar stays one row; the page below takes the rest. */
2730
+ .layout {
2731
+ grid-template-columns: minmax(0, 1fr);
2732
+ grid-template-rows: auto minmax(0, 1fr);
2733
+ align-content: start;
2734
+ }
2657
2735
  nav.side {
2736
+ align-self: start; height: auto;
2658
2737
  flex-direction: row; align-items: center; overflow-x: auto; min-width: 0;
2659
2738
  border-right: 0; border-bottom: 1px solid var(--line);
2660
2739
  padding: 8px;
@@ -3448,7 +3527,7 @@ var stateAdminRoutes = (routes) => {
3448
3527
  };
3449
3528
 
3450
3529
  // ../core/dist/version.js
3451
- var PACKAGE_VERSION = true ? "1.7.0" : UNRELEASED_VERSION;
3530
+ var PACKAGE_VERSION = true ? "1.7.1" : UNRELEASED_VERSION;
3452
3531
 
3453
3532
  // ../core/dist/signing.js
3454
3533
  var encoder = new TextEncoder();
@@ -5266,6 +5345,14 @@ var POSTAL_ADDRESS = /* @__PURE__ */ new Set([
5266
5345
  var MAX_INPUT_LENGTH = 280;
5267
5346
  var MAX_ADDRESS_LINES = 3;
5268
5347
  var MAX_ADDRESS_LINE_LENGTH = 80;
5348
+ var MAX_BARE_UNIT_LENGTH = 8;
5349
+ var postalLines = (parts) => {
5350
+ const pieces = parts.map((part) => part.trim()).filter(Boolean);
5351
+ if (pieces.length === 0) return [];
5352
+ const joined = pieces.join(" ");
5353
+ if (joined.length <= MAX_ADDRESS_LINE_LENGTH) return [joined];
5354
+ return pieces.map((part) => part.slice(0, MAX_ADDRESS_LINE_LENGTH)).slice(0, MAX_ADDRESS_LINES);
5355
+ };
5269
5356
  var isRecord6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
5270
5357
  var optionalString = (value, field) => {
5271
5358
  if (value === void 0 || value === null) return void 0;
@@ -5447,7 +5534,10 @@ var resolve = (input, rows) => {
5447
5534
  const second = rest.join(" ").trim();
5448
5535
  if (second) {
5449
5536
  const only = UNIT_ONLY.exec(second);
5450
- if (only) unit = parseUnit(only[1], only[2]);
5537
+ const value = only?.[2];
5538
+ if (only && value && (only[1] !== void 0 || value.length <= MAX_BARE_UNIT_LENGTH)) {
5539
+ unit = parseUnit(only[1], value);
5540
+ }
5451
5541
  }
5452
5542
  } else {
5453
5543
  const joined = input.addressLines.join(", ");
@@ -5610,13 +5700,14 @@ var validateAddress = (request, rows, options = {}) => {
5610
5700
  hasInferred = input.regionCode === void 0;
5611
5701
  unconfirmed = components.filter((c) => c.confirmationLevel !== "CONFIRMED").map((c) => c.componentType);
5612
5702
  const line = [streetText, unit ? unitText(unit) : ""].filter(Boolean).join(" ");
5703
+ const lines = postalLines([streetText, unit ? unitText(unit) : ""]);
5613
5704
  postalAddress = compact({
5614
5705
  regionCode: region,
5615
5706
  languageCode: "en",
5616
5707
  postalCode: input.postalCode,
5617
5708
  administrativeArea: state,
5618
5709
  locality: input.locality,
5619
- addressLines: line ? [line] : void 0
5710
+ addressLines: lines.length > 0 ? lines : void 0
5620
5711
  });
5621
5712
  formattedAddress2 = [
5622
5713
  line,
@@ -5700,7 +5791,7 @@ var validateAddress = (request, rows, options = {}) => {
5700
5791
  postalCode: zip,
5701
5792
  administrativeArea: row2.state,
5702
5793
  locality: row2.city,
5703
- addressLines: [line]
5794
+ addressLines: postalLines([street, r.unit ? unitText(r.unit) : ""])
5704
5795
  };
5705
5796
  formattedAddress2 = `${line}, ${row2.city}, ${row2.state} ${zip}, USA`;
5706
5797
  const place = r.inCorpus ? { placeId: corpusPlaceId(row2), lat: row2.lat, lng: row2.lng } : synthesize(street, row2);
@@ -6294,4 +6385,4 @@ export {
6294
6385
  keyCredential,
6295
6386
  GoogleMapsAPI
6296
6387
  };
6297
- //# sourceMappingURL=chunk-KOTM4DOF.js.map
6388
+ //# sourceMappingURL=chunk-AIMXHIOX.js.map