@7365admin1/layer-common 3.2.2-staging.195 → 3.2.2-staging.196

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.
@@ -924,8 +924,18 @@ select option {
924
924
  inset: 0;
925
925
  width: var(--toggle-w);
926
926
  height: var(--toggle-h);
927
- /* Vuetify's own slide. The design moves the thumb, not this box. */
928
- transform: none;
927
+ /*
928
+ * Vuetify's own slide. The design moves the thumb, not this box - but the
929
+ * reset never landed: Vuetify ships it as
930
+ * `.v-locale--is-ltr .v-switch .v-selection-control__input` (THREE classes)
931
+ * and, when checked, `.v-locale--is-ltr .v-switch .v-selection-control--dirty
932
+ * .v-selection-control__input` (FOUR), both of which outrank this two-class
933
+ * rule. So every `.app-switch` in the layer drew its thumb 10px out: measured
934
+ * OFF thumb at `track.x - 8` (design: +2) and ON at `track.x + 28`
935
+ * (design: +18). `!important` rather than stacking four `.app-switch`
936
+ * selectors to out-specify it.
937
+ */
938
+ transform: none !important;
929
939
  }
930
940
 
931
941
  /* The 40px hover/ripple disc is bigger than the whole control in this shape. */
@@ -603,6 +603,18 @@
603
603
  border-radius: var(--r-card);
604
604
  box-shadow: var(--shadow);
605
605
  overflow: hidden;
606
+ /*
607
+ * `.table-card__loading` below is `position: absolute`, and nothing in the
608
+ * chain above it - not `.app-card`, not `.table-card` - established a
609
+ * containing block, so the bar resolved against the VIEWPORT: measured
610
+ * 1440x2 pinned to `top: 0` of the window while its card sat at `y: 61.5`.
611
+ * Every `TableMain` screen in all 11 apps drew its busy bar across the top
612
+ * of the browser instead of the top of the card. This is the anchor it
613
+ * always needed. Safe by construction: the card is already
614
+ * `overflow: hidden`, so nothing inside it could have been anchored to an
615
+ * ancestor OUTSIDE it and still be visible.
616
+ */
617
+ position: relative;
606
618
  }
607
619
 
608
620
  /**
@@ -129,7 +129,30 @@
129
129
  </template>
130
130
 
131
131
  <template #left>
132
- <v-btn fab icon density="comfortable" variant="text" @click="emits('refresh')">
132
+ <!--
133
+ THE REFRESH BUTTON HAD NO BUSY STATE.
134
+
135
+ It emitted `refresh` and then looked identical for the whole
136
+ request. When the reload returned the same rows, NOTHING on the
137
+ screen changed - so there was no way to tell whether the click
138
+ had registered, which is exactly what QA reported on Work Orders
139
+ and on the Duty Officer Book (both fire the request; neither
140
+ showed it). `loading` is the caller's own in-flight flag, the
141
+ same one the bar above already reads: while it is true the button
142
+ is disabled, so the click is acknowledged and a second one cannot
143
+ stack another request on the first.
144
+
145
+ A caller that passes no `loading` renders exactly what it renders
146
+ today (the prop defaults to false).
147
+ -->
148
+ <v-btn
149
+ fab
150
+ icon
151
+ density="comfortable"
152
+ variant="text"
153
+ :disabled="loading"
154
+ @click="emits('refresh')"
155
+ >
133
156
  <v-icon>mdi-refresh</v-icon>
134
157
  </v-btn>
135
158
  <slot name="prepend-additional" />
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@7365admin1/layer-common",
3
3
  "license": "MIT",
4
4
  "type": "module",
5
- "version": "3.2.2-staging.195",
5
+ "version": "3.2.2-staging.196",
6
6
  "author": "7365admin1",
7
7
  "main": "./nuxt.config.ts",
8
8
  "//files": "What a consumer extending this layer actually loads. Without this npm ships the whole working tree - the changesets, the CI workflows, the render harness in tools/ and any scratch directory that happened to exist at publish time. Nuxt resolves a layer by directory, so every runtime directory below has to stay listed; adding a new top-level runtime directory means adding it here too.",
@@ -0,0 +1,84 @@
1
+ /**
2
+ * REGRESSION GUARDS FOR THE TABLE CARD'S BUSY SIGNAL.
3
+ *
4
+ * Both faults here are a CSS declaration and a template binding, not a pure
5
+ * function, and this repo has no component test runner (adding one is a
6
+ * dependency change). So these assert the SOURCE CONTRACT: the exact
7
+ * declarations whose absence caused the defect. They fail the moment someone
8
+ * deletes the anchor or the disabled binding again.
9
+ *
10
+ * The behavioural proof is the driven browser measurement recorded on the PR -
11
+ * bar 1440x2 at `top: 0` of the WINDOW before, 1406x2 at the card's own top
12
+ * edge after; refresh button `false -> false -> false` before,
13
+ * `false -> true -> false` after.
14
+ */
15
+ import { describe, it } from "node:test";
16
+ import assert from "node:assert/strict";
17
+ import { readFileSync } from "node:fs";
18
+ import { fileURLToPath } from "node:url";
19
+
20
+ const read = (rel: string) =>
21
+ readFileSync(fileURLToPath(new URL(`../${rel}`, import.meta.url)), "utf8");
22
+
23
+ /** The declaration block for a selector, comments stripped. */
24
+ const ruleFor = (css: string, selector: string) => {
25
+ const at = css.indexOf(`\n${selector} {`);
26
+ assert.notEqual(at, -1, `no rule for "${selector}"`);
27
+ const open = css.indexOf("{", at);
28
+ const close = css.indexOf("}", open);
29
+ return css
30
+ .slice(open + 1, close)
31
+ .replace(/\/\*[\s\S]*?\*\//g, "")
32
+ .trim();
33
+ };
34
+
35
+ describe("the loading bar's containing block", () => {
36
+ const screens = read("assets/css/screens.css");
37
+
38
+ it("anchors .table-card__loading to its card, not the viewport", () => {
39
+ // The bar is absolute. Without this the nearest positioned ancestor is
40
+ // the initial containing block, i.e. the browser window: the bar was
41
+ // measured 1440x2 at top:0 while its card sat 61.5px lower.
42
+ assert.match(ruleFor(screens, ".table-card"), /position:\s*relative/);
43
+ });
44
+
45
+ it("still positions the bar out of flow, so a load causes no 2px jump", () => {
46
+ assert.match(ruleFor(screens, ".table-card__loading"), /position:\s*absolute/);
47
+ });
48
+ });
49
+
50
+ describe("TableMain's refresh button", () => {
51
+ const sfc = read("components/TableMain.vue");
52
+ // The one `v-btn` carrying the refresh emit.
53
+ const refreshBtn = (() => {
54
+ const at = sfc.indexOf(`@click="emits('refresh')"`);
55
+ assert.notEqual(at, -1, "no refresh button in TableMain");
56
+ const open = sfc.lastIndexOf("<v-btn", at);
57
+ return sfc.slice(open, at);
58
+ })();
59
+
60
+ it("is disabled while the caller's request is in flight", () => {
61
+ // Without this, clicking refresh on a list that comes back identical
62
+ // changed zero pixels - the Work Orders / Duty Officer Book report.
63
+ assert.match(refreshBtn, /:disabled="loading"/);
64
+ });
65
+
66
+ it("reads the same `loading` prop the bar reads, so the two cannot diverge", () => {
67
+ assert.match(sfc, /v-if="loading"[\s\S]{0,200}table-card__loading/);
68
+ assert.match(sfc, /loading:\s*\{\s*type:\s*Boolean,\s*default:\s*false,?\s*\}/);
69
+ });
70
+ });
71
+
72
+ describe(".app-switch's thumb", () => {
73
+ const primitives = read("assets/css/primitives.css");
74
+
75
+ it("beats Vuetify's 3- and 4-class slide on the input", () => {
76
+ // `.v-locale--is-ltr .v-switch .v-selection-control__input` and its
77
+ // `--dirty` twin outrank a 2-class rule, so the plain reset never
78
+ // applied and every thumb sat 10px out of its track.
79
+ assert.match(
80
+ ruleFor(primitives, ".app-switch .v-selection-control__input"),
81
+ /transform:\s*none\s*!important/,
82
+ );
83
+ });
84
+ });