ngwr 12.1.0 → 12.2.0

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.
@@ -16,7 +16,7 @@ import { InjectionToken } from '@angular/core';
16
16
  * Distinct from `Version` exported by `@angular/core`, which reports the
17
17
  * loaded Angular runtime version (e.g. `'22.0.0'`), not ngwr's.
18
18
  */
19
- const NGWR_VERSION = '12.1.0';
19
+ const NGWR_VERSION = '12.2.0';
20
20
 
21
21
  /**
22
22
  * @license
@@ -1 +1 @@
1
- {"version":3,"file":"ngwr-version.mjs","sources":["../../../projects/lib/version/version.ts","../../../projects/lib/version/version.token.ts","../../../projects/lib/version/ngwr-version.ts"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\n/**\n * The ngwr library version string, kept in sync with\n * `projects/lib/package.json` by `scripts/release-prepare.ts`.\n *\n * Prefer this over reading `package.json` at runtime — that requires a JSON\n * import or `require`, neither of which tree-shakes well in Angular builds.\n *\n * Distinct from `Version` exported by `@angular/core`, which reports the\n * loaded Angular runtime version (e.g. `'22.0.0'`), not ngwr's.\n */\nexport const NGWR_VERSION = '12.1.0';\n","/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { InjectionToken } from '@angular/core';\n\nimport { NGWR_VERSION } from './version';\n\n/**\n * Injection token holding the ngwr library version string.\n *\n * Default factory returns {@link NGWR_VERSION}. Override it in tests or in\n * apps that want to surface a custom build identifier.\n *\n * @example\n * ```ts\n * @Component({...})\n * export class FooterComponent {\n * readonly version = inject(NGWR_VERSION_TOKEN);\n * }\n * ```\n */\nexport const NGWR_VERSION_TOKEN = new InjectionToken<string>('NGWR_VERSION_TOKEN', {\n providedIn: 'root',\n factory: () => NGWR_VERSION,\n});\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":";;AAAA;;;;;AAKG;AAEH;;;;;;;;;AASG;AACI,MAAM,YAAY,GAAG;;ACjB5B;;;;;AAKG;AAMH;;;;;;;;;;;;;AAaG;MACU,kBAAkB,GAAG,IAAI,cAAc,CAAS,oBAAoB,EAAE;AACjF,IAAA,UAAU,EAAE,MAAM;AAClB,IAAA,OAAO,EAAE,MAAM,YAAY;AAC5B,CAAA;;AC5BD;;AAEG;;;;"}
1
+ {"version":3,"file":"ngwr-version.mjs","sources":["../../../projects/lib/version/version.ts","../../../projects/lib/version/version.token.ts","../../../projects/lib/version/ngwr-version.ts"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\n/**\n * The ngwr library version string, kept in sync with\n * `projects/lib/package.json` by `scripts/release-prepare.ts`.\n *\n * Prefer this over reading `package.json` at runtime — that requires a JSON\n * import or `require`, neither of which tree-shakes well in Angular builds.\n *\n * Distinct from `Version` exported by `@angular/core`, which reports the\n * loaded Angular runtime version (e.g. `'22.0.0'`), not ngwr's.\n */\nexport const NGWR_VERSION = '12.2.0';\n","/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { InjectionToken } from '@angular/core';\n\nimport { NGWR_VERSION } from './version';\n\n/**\n * Injection token holding the ngwr library version string.\n *\n * Default factory returns {@link NGWR_VERSION}. Override it in tests or in\n * apps that want to surface a custom build identifier.\n *\n * @example\n * ```ts\n * @Component({...})\n * export class FooterComponent {\n * readonly version = inject(NGWR_VERSION_TOKEN);\n * }\n * ```\n */\nexport const NGWR_VERSION_TOKEN = new InjectionToken<string>('NGWR_VERSION_TOKEN', {\n providedIn: 'root',\n factory: () => NGWR_VERSION,\n});\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":";;AAAA;;;;;AAKG;AAEH;;;;;;;;;AASG;AACI,MAAM,YAAY,GAAG;;ACjB5B;;;;;AAKG;AAMH;;;;;;;;;;;;;AAaG;MACU,kBAAkB,GAAG,IAAI,cAAc,CAAS,oBAAoB,EAAE;AACjF,IAAA,UAAU,EAAE,MAAM;AAClB,IAAA,OAAO,EAAE,MAAM,YAAY;AAC5B,CAAA;;AC5BD;;AAEG;;;;"}
@@ -0,0 +1,243 @@
1
+ @use 'sass:list';
2
+ @use 'sass:map';
3
+
4
+ // --- Anchored-panel arrow ----------------------------------------------------
5
+ //
6
+ // A small square rotated 45°, half tucked under the panel so only the tip shows.
7
+ // Shared by every overlay that points AT its trigger: `wr-popover`, its tooltip
8
+ // mode, and `wr-popconfirm`.
9
+ //
10
+ // **Why this lives in `ngwr/overlay` and not in `ngwr/theme`.** `theme/styles`
11
+ // is the token layer plus mixins any element can use — `smooth-br`,
12
+ // `touch-target`, `focus-ring`. This is not one of those. It only means anything
13
+ // against a CDK pane that carries a `--<placement>` modifier written by
14
+ // `wrPopoverPositions()` / a directive's `panelClass`, and the eight-to-twelve
15
+ // placement names it keys off are `WR_POPOVER_POSITIONS`. That is the overlay
16
+ // layer's vocabulary, and `overlay/styles` is already where the cross-overlay
17
+ // presentation lives (`.wr-overlay-sheet`, `.wr-overlay-backdrop`). Promoting it
18
+ // to `ngwr/theme` would advertise it to every component in the catalog as
19
+ // something general, which it is not.
20
+ //
21
+ // It is deliberately NOT `@forward`ed from `overlay/styles/_index.scss`, so
22
+ // `@use 'ngwr/overlay'` gains no new public API and this file emits no CSS of
23
+ // its own — a component `@use`s it by relative path and pays only for the rules
24
+ // its own `anchored-arrow()` call generates.
25
+
26
+ /// Side of the square's box. Half of it shows; the rest is under the panel.
27
+ $size: 6px;
28
+ $half: 3px;
29
+
30
+ /// Distance from the panel corner for the `-start` / `-end` variants.
31
+ $inset: 0.625rem;
32
+
33
+ /// Every placement an anchored panel can take, mapped to
34
+ /// (which panel edge the arrow hangs off, where along that edge it sits).
35
+ ///
36
+ /// A `--top` pane means the panel sits ABOVE the trigger, so the arrow hangs off
37
+ /// the panel's BOTTOM edge.
38
+ $placements: (
39
+ top: (
40
+ bottom,
41
+ center,
42
+ ),
43
+ top-start: (
44
+ bottom,
45
+ start,
46
+ ),
47
+ top-end: (
48
+ bottom,
49
+ end,
50
+ ),
51
+ bottom: (
52
+ top,
53
+ center,
54
+ ),
55
+ bottom-start: (
56
+ top,
57
+ start,
58
+ ),
59
+ bottom-end: (
60
+ top,
61
+ end,
62
+ ),
63
+ left: (
64
+ right,
65
+ center,
66
+ ),
67
+ left-start: (
68
+ right,
69
+ start,
70
+ ),
71
+ left-end: (
72
+ right,
73
+ end,
74
+ ),
75
+ right: (
76
+ left,
77
+ center,
78
+ ),
79
+ right-start: (
80
+ left,
81
+ start,
82
+ ),
83
+ right-end: (
84
+ left,
85
+ end,
86
+ ),
87
+ );
88
+
89
+ /// Positions the arrow against `$edge`, offset outwards by `$pull`.
90
+ ///
91
+ /// `left` / `right` name the panel edge in LTR terms only. Every side-anchored
92
+ /// and `-start` / `-end` placement is expressed as CDK `originX: start | end` —
93
+ /// plain `top` / `bottom` use `center`, which mirrors to itself — and CDK
94
+ /// resolves start / end against `Directionality`. So under `dir="rtl"` the panel
95
+ /// lands on the mirrored side of the trigger while the modifier class keeps its
96
+ /// LTR name, and the arrow has to travel with the panel. Same for the `-start` /
97
+ /// `-end` alignment along a horizontal edge.
98
+ ///
99
+ /// The class names the placement the panel ACTUALLY took, not the requested one,
100
+ /// wherever the caller feeds `withPositions()` a fallback list whose links each
101
+ /// carry their own `panelClass` — so a flip swaps the modifier and this moves the
102
+ /// arrow with it.
103
+ @mixin at($edge, $align, $pull) {
104
+ @if $edge == left {
105
+ inset-inline-start: -$pull;
106
+ } @else if $edge == right {
107
+ inset-inline-end: -$pull;
108
+ } @else {
109
+ #{$edge}: -$pull;
110
+ }
111
+
112
+ @if $edge == bottom or $edge == top {
113
+ @if $align == center {
114
+ // Both halves of the centring pair go logical together, so the arrow
115
+ // stays on the midpoint whichever way the axis runs.
116
+ inset-inline-start: 50%;
117
+ margin-inline-start: -$half;
118
+ } @else if $align == start {
119
+ inset-inline-start: $inset;
120
+ } @else {
121
+ inset-inline-end: $inset;
122
+ }
123
+ } @else {
124
+ @if $align == center {
125
+ top: 50%;
126
+ margin-top: -$half;
127
+ } @else if $align == start {
128
+ top: $inset;
129
+ } @else {
130
+ bottom: $inset;
131
+ }
132
+ }
133
+ }
134
+
135
+ /// The two faces of the rotated square that stay visible outside the panel.
136
+ ///
137
+ /// These stay physical, and it is not an oversight. Which two borders paint is
138
+ /// decided by `transform: rotate(45deg)`, not by reading order: the rotation
139
+ /// turns the right border into the down-right face and the bottom one into the
140
+ /// down-left face, so `right + bottom` is what outlines a tip pointing DOWN.
141
+ /// Swapping only the inline half would paint two faces that are tucked under
142
+ /// the panel. A mirrored arrow needs the rotation mirrored as well — more than
143
+ /// a property rewrite, so it is left for the RTL pass that owns the TS side.
144
+ @mixin faces($edge, $color) {
145
+ @if $edge == bottom {
146
+ // rtl-ok: down-right face of a 45°-rotated square — fixed by the rotation
147
+ border-right: 1px solid $color;
148
+ border-bottom: 1px solid $color;
149
+ } @else if $edge == top {
150
+ // rtl-ok: up-left face of a 45°-rotated square — fixed by the rotation
151
+ border-left: 1px solid $color;
152
+ border-top: 1px solid $color;
153
+ } @else if $edge == right {
154
+ border-top: 1px solid $color;
155
+ // rtl-ok: down-right face of a 45°-rotated square — fixed by the rotation
156
+ border-right: 1px solid $color;
157
+ } @else {
158
+ border-bottom: 1px solid $color;
159
+ // rtl-ok: up-left face of a 45°-rotated square — fixed by the rotation
160
+ border-left: 1px solid $color;
161
+ }
162
+ }
163
+
164
+ /// Draws the arrow for an anchored overlay, at every placement it can take.
165
+ ///
166
+ /// @param {String} $pane - the CDK pane class the directive sets, without the
167
+ /// modifier: `.wr-popover-overlay`, `.wr-popconfirm-overlay`.
168
+ /// @param {String} $panel - selector for the painted surface INSIDE the pane,
169
+ /// relative to it: `> *` where the pane wraps an arbitrary child,
170
+ /// `> .wr-popconfirm` / `.wr-tooltip` where it has a known one. The arrow is
171
+ /// that element's `::after`, so this is also the element made `position:
172
+ /// relative`.
173
+ /// @param {*} $background - fill for the square; match the panel's own so the
174
+ /// two read as one shape. A `var()` is fine and preferred.
175
+ /// @param {*} $border - the panel's border colour, or `null` for a panel with no
176
+ /// border. When set, the two outward faces are painted so the outline reads as
177
+ /// continuous, and the square is pulled one extra pixel outwards so its own
178
+ /// border lines up flush with the panel's instead of leaving the panel border
179
+ /// showing across the arrow's base. A borderless panel has nothing to line up
180
+ /// with, hence the smaller pull.
181
+ /// @param {Bool} $inert - `true` drops the arrow out of hit-testing. For a
182
+ /// tooltip, whose arrow overlaps the very control the tooltip describes, so a
183
+ /// hittable arrow eats clicks meant for it.
184
+ /// @param {List} $placements-used - which placements to emit, defaulting to all
185
+ /// twelve. A component whose position input offers four should pass four
186
+ /// rather than shipping eight rules that can never match.
187
+ @mixin anchored-arrow($pane, $panel, $background, $border: null, $inert: false, $placements-used: map.keys($placements)) {
188
+ #{$pane} {
189
+ #{$panel} {
190
+ // Containing block for the arrow.
191
+ position: relative;
192
+
193
+ &::after {
194
+ content: '';
195
+ position: absolute;
196
+ width: $size;
197
+ height: $size;
198
+ background: $background;
199
+ transform: rotate(45deg);
200
+
201
+ @if $inert {
202
+ pointer-events: none;
203
+ }
204
+ }
205
+ }
206
+
207
+ // Responsive bottom-sheet mode detaches the panel from its trigger and docks
208
+ // it to the bottom of the screen, so there is nothing left to point at.
209
+ // Emitted for every caller, not only the ones that support sheets today: the
210
+ // arrow is what would be wrong there, so the arrow is what should know.
211
+ &.wr-overlay-sheet #{$panel}::after {
212
+ content: none;
213
+ }
214
+ }
215
+
216
+ @each $placement in $placements-used {
217
+ $spec: map.get($placements, $placement);
218
+
219
+ @if $spec == null {
220
+ @error 'wr arrow: unknown placement `#{$placement}` — expected one of #{map.keys($placements)}.';
221
+ }
222
+
223
+ $edge: list.nth($spec, 1);
224
+ $align: list.nth($spec, 2);
225
+
226
+ // A bordered panel pulls one extra pixel, so the square's own border lines
227
+ // up flush with the panel's instead of leaving the panel border showing
228
+ // across the arrow's base. A borderless one has nothing to line up with.
229
+ $pull: $half;
230
+
231
+ @if $border != null {
232
+ $pull: $half + 1px;
233
+ }
234
+
235
+ #{$pane}--#{$placement} #{$panel}::after {
236
+ @include at($edge, $align, $pull);
237
+
238
+ @if $border != null {
239
+ @include faces($edge, $border);
240
+ }
241
+ }
242
+ }
243
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ngwr",
3
- "version": "12.1.0",
3
+ "version": "12.2.0",
4
4
  "bin": {
5
5
  "ngwr-mcp": "./mcp/server.js"
6
6
  },
@@ -1,4 +1,5 @@
1
1
  @use '../../theme/styles' as theme;
2
+ @use '../../overlay/styles/arrow';
2
3
 
3
4
  .wr-popconfirm-overlay {
4
5
  > .wr-popconfirm {
@@ -13,6 +14,7 @@
13
14
  color: var(--wr-color-on-surface);
14
15
  font-family: var(--wr-font-family-base);
15
16
  animation: wr-overlay-in var(--wr-overlay-duration) var(--wr-overlay-ease);
17
+ // rtl-ok: scale-in growth corner — `transform-origin` takes no logical keywords
16
18
  transform-origin: top left;
17
19
  }
18
20
  }
@@ -30,3 +32,18 @@
30
32
  gap: 0.375rem;
31
33
  }
32
34
  }
35
+
36
+ // The arrow that ties the panel to the control it is asking about — the same one
37
+ // `wr-popover` draws, from the same mixin, because the two are the same kind of
38
+ // anchored non-modal panel and a second copy of the geometry would drift.
39
+ //
40
+ // Four placements, not twelve: `WrPopconfirmPosition` is `top | bottom | left |
41
+ // right`, and the eight `-start` / `-end` rules would be selectors that can
42
+ // never match. Widen this list if the input ever grows them.
43
+ @include arrow.anchored-arrow(
44
+ $pane: '.wr-popconfirm-overlay',
45
+ $panel: '> .wr-popconfirm',
46
+ $background: var(--wr-color-surface),
47
+ $border: var(--wr-color-outline),
48
+ $placements-used: (top, bottom, left, right)
49
+ );
@@ -1,196 +1,27 @@
1
- @use 'sass:list';
2
-
3
1
  @use '../../theme/styles' as theme;
4
2
  @use '../../overlay/styles'; // shared .wr-overlay-sheet / .wr-overlay-backdrop for responsive mode
5
-
6
- // --- Arrow -----------------------------------------------------------------
7
- //
8
- // A small square rotated 45°, half tucked under the panel so only the tip shows.
9
- // Tooltip and popover share the geometry; the popover additionally carries the
10
- // panel's border on the two outward faces so the outline reads as continuous.
11
- //
12
- // Placement is driven by the resolved position modifier the directive puts on the
13
- // overlay host (`--top` means the panel sits ABOVE the trigger, so the arrow hangs
14
- // off the panel's BOTTOM edge). All 12 positions are covered — the hand-written
15
- // tooltip rules this replaces only handled 8, so `top-start` / `top-end` /
16
- // `bottom-start` / `bottom-end` tooltips have been shipping with no arrow at all.
17
- $_arrow: 6px;
18
- $_arrow-half: 3px;
19
- $_arrow-inset: 0.625rem; // distance from the panel corner for the -start / -end variants
20
-
21
- // edge = which side of the panel the arrow hangs off; align = where along that side.
22
- $_arrow-placement: (
23
- top: (
24
- bottom,
25
- center,
26
- ),
27
- top-start: (
28
- bottom,
29
- start,
30
- ),
31
- top-end: (
32
- bottom,
33
- end,
34
- ),
35
- bottom: (
36
- top,
37
- center,
38
- ),
39
- bottom-start: (
40
- top,
41
- start,
42
- ),
43
- bottom-end: (
44
- top,
45
- end,
46
- ),
47
- left: (
48
- right,
49
- center,
50
- ),
51
- left-start: (
52
- right,
53
- start,
54
- ),
55
- left-end: (
56
- right,
57
- end,
58
- ),
59
- right: (
60
- left,
61
- center,
62
- ),
63
- right-start: (
64
- left,
65
- start,
66
- ),
67
- right-end: (
68
- left,
69
- end,
70
- ),
71
- );
72
-
73
- /// Positions the arrow against `$edge`, offset outwards by `$pull`.
74
- ///
75
- /// `left` / `right` name the panel edge in LTR terms only. Every side-anchored
76
- /// and `-start` / `-end` entry in `WR_POPOVER_POSITIONS` is expressed as CDK
77
- /// `originX: start | end` — plain `top` / `bottom` use `center`, which mirrors
78
- /// to itself — and CDK resolves start / end against `Directionality`. So under
79
- /// `dir="rtl"` the panel lands on the mirrored side of the trigger while the
80
- /// modifier class keeps its LTR name, and the arrow has to travel with the
81
- /// panel. Same for the `-start` / `-end` alignment along a horizontal edge.
82
- ///
83
- /// The class names the placement the panel ACTUALLY took, not the requested one:
84
- /// each entry in `WR_POPOVER_FALLBACKS` carries its own `panelClass`, so a flip
85
- /// swaps the modifier and this loop moves the arrow with it.
86
- @mixin _arrow-at($edge, $align, $pull) {
87
- @if $edge == left {
88
- inset-inline-start: -$pull;
89
- } @else if $edge == right {
90
- inset-inline-end: -$pull;
91
- } @else {
92
- #{$edge}: -$pull;
93
- }
94
-
95
- @if $edge == bottom or $edge == top {
96
- @if $align == center {
97
- // Both halves of the centring pair go logical together, so the arrow
98
- // stays on the midpoint whichever way the axis runs.
99
- inset-inline-start: 50%;
100
- margin-inline-start: -$_arrow-half;
101
- } @else if $align == start {
102
- inset-inline-start: $_arrow-inset;
103
- } @else {
104
- inset-inline-end: $_arrow-inset;
105
- }
106
- } @else {
107
- @if $align == center {
108
- top: 50%;
109
- margin-top: -$_arrow-half;
110
- } @else if $align == start {
111
- top: $_arrow-inset;
112
- } @else {
113
- bottom: $_arrow-inset;
114
- }
115
- }
116
- }
117
-
118
- /// The two faces of the rotated square that stay visible outside the panel.
119
- ///
120
- /// These stay physical, and it is not an oversight. Which two borders paint is
121
- /// decided by `transform: rotate(45deg)`, not by reading order: the rotation
122
- /// turns the right border into the down-right face and the bottom one into the
123
- /// down-left face, so `right + bottom` is what outlines a tip pointing DOWN.
124
- /// Swapping only the inline half would paint two faces that are tucked under
125
- /// the panel. A mirrored arrow needs the rotation mirrored as well — more than
126
- /// a property rewrite, so it is left for the RTL pass that owns the TS side.
127
- @mixin _arrow-border($edge) {
128
- @if $edge == bottom {
129
- // rtl-ok: down-right face of a 45°-rotated square — fixed by the rotation
130
- border-right: 1px solid var(--wr-color-outline);
131
- border-bottom: 1px solid var(--wr-color-outline);
132
- } @else if $edge == top {
133
- // rtl-ok: up-left face of a 45°-rotated square — fixed by the rotation
134
- border-left: 1px solid var(--wr-color-outline);
135
- border-top: 1px solid var(--wr-color-outline);
136
- } @else if $edge == right {
137
- border-top: 1px solid var(--wr-color-outline);
138
- // rtl-ok: down-right face of a 45°-rotated square — fixed by the rotation
139
- border-right: 1px solid var(--wr-color-outline);
140
- } @else {
141
- border-bottom: 1px solid var(--wr-color-outline);
142
- // rtl-ok: up-left face of a 45°-rotated square — fixed by the rotation
143
- border-left: 1px solid var(--wr-color-outline);
144
- }
145
- }
146
-
147
- .wr-popover-overlay {
148
- > * {
149
- // Containing block for the arrow below.
150
- position: relative;
151
- display: block;
152
- min-width: 8rem;
153
- max-width: 22rem;
154
- background: var(--wr-color-surface);
155
- border: 1px solid var(--wr-color-outline);
156
- @include theme.smooth-br(var(--wr-border-radius-base));
157
- box-shadow: var(--wr-shadow-overlay);
158
- font-family: var(--wr-font-family-base);
159
- color: var(--wr-color-on-surface);
160
- animation: wr-overlay-in var(--wr-overlay-duration) var(--wr-overlay-ease);
161
- // rtl-ok: scale-in growth corner — `transform-origin` takes no logical keywords
162
- transform-origin: top left;
163
-
164
- &::after {
165
- content: '';
166
- position: absolute;
167
- width: $_arrow;
168
- height: $_arrow;
169
- background: var(--wr-color-surface);
170
- transform: rotate(45deg);
171
- }
172
- }
173
-
174
- // Responsive bottom-sheet mode has no anchor to point at, so no arrow.
175
- &.wr-overlay-sheet > *::after {
176
- content: none;
177
- }
178
- }
179
-
180
- @each $position, $spec in $_arrow-placement {
181
- $edge: list.nth($spec, 1);
182
- $align: list.nth($spec, 2);
183
-
184
- // Pulled one extra pixel so the square's own border lines up flush with the
185
- // panel's, instead of leaving the panel border showing across the arrow's base.
186
- .wr-popover-overlay--#{$position} > *::after {
187
- @include _arrow-at($edge, $align, $_arrow-half + 1px);
188
- @include _arrow-border($edge);
189
- }
190
-
191
- .wr-tooltip-overlay--#{$position} .wr-tooltip::after {
192
- @include _arrow-at($edge, $align, $_arrow-half);
193
- }
3
+ @use '../../overlay/styles/arrow';
4
+
5
+ // The arrow — a rotated square tucked half under the panel — used to be written
6
+ // out here: geometry, a twelve-placement map and two mixins. `wr-popconfirm` is
7
+ // the same kind of anchored panel and had none of it, so rather than a second
8
+ // copy the whole thing moved to `overlay/styles/_arrow.scss` and both components
9
+ // call `arrow.anchored-arrow()`. The two calls at the foot of this file emit the
10
+ // rules that were here, for the same twelve placements.
11
+
12
+ .wr-popover-overlay > * {
13
+ display: block;
14
+ min-width: 8rem;
15
+ max-width: 22rem;
16
+ background: var(--wr-color-surface);
17
+ border: 1px solid var(--wr-color-outline);
18
+ @include theme.smooth-br(var(--wr-border-radius-base));
19
+ box-shadow: var(--wr-shadow-overlay);
20
+ font-family: var(--wr-font-family-base);
21
+ color: var(--wr-color-on-surface);
22
+ animation: wr-overlay-in var(--wr-overlay-duration) var(--wr-overlay-ease);
23
+ // rtl-ok: scale-in growth corner — `transform-origin` takes no logical keywords
24
+ transform-origin: top left;
194
25
  }
195
26
 
196
27
  //
@@ -235,24 +66,35 @@ $_arrow-placement: (
235
66
  // rtl-ok: scale-in growth corner — `transform-origin` takes no logical keywords
236
67
  transform-origin: top left;
237
68
 
238
- // Arrow — geometry from the shared placement loop at the top of this file.
239
- &::after {
240
- content: '';
241
- position: absolute;
242
- width: $_arrow;
243
- height: $_arrow;
244
- background: var(--wr-tooltip-bg);
245
- transform: rotate(45deg);
246
- // The one piece that stays inert: the arrow points AT the trigger and
247
- // overlaps it, so a hittable arrow would eat clicks meant for the control
248
- // the tooltip describes.
249
- pointer-events: none;
250
- }
69
+ // The arrow itself comes from `arrow.anchored-arrow()` below. `position:
70
+ // relative` is restated by that call and kept here too, so the class still
71
+ // stands on its own for anyone styling `.wr-tooltip` directly — it is public
72
+ // API, and the arrow is the pane's business rather than the chip's.
251
73
  }
252
74
 
253
- // Arrow placement for both modes is generated from `$_arrow-placement` at the top
254
- // of this file — the per-position rules that used to live here are emitted there,
255
- // byte-identical for the 8 positions they covered plus the 4 they missed.
75
+ // Arrow for both modes.
76
+ //
77
+ // Popover: the pane wraps whatever the author projected, so the panel selector
78
+ // is `> *`, and the square carries the panel's border on its two outward faces
79
+ // so the outline reads as continuous.
80
+ @include arrow.anchored-arrow(
81
+ $pane: '.wr-popover-overlay',
82
+ $panel: '> *',
83
+ $background: var(--wr-color-surface),
84
+ $border: var(--wr-color-outline)
85
+ );
86
+
87
+ // Tooltip: a known child, and no border faces — the chip's own border colour is
88
+ // overridable per instance through `--wr-tooltip-border`, so painting the faces
89
+ // from the token here would desync the moment anyone retints one. The arrow
90
+ // points AT the trigger and overlaps it, so it is the one inert piece: a
91
+ // hittable arrow would eat clicks meant for the control the tooltip describes.
92
+ @include arrow.anchored-arrow(
93
+ $pane: '.wr-tooltip-overlay',
94
+ $panel: '.wr-tooltip',
95
+ $background: var(--wr-tooltip-bg),
96
+ $inert: true
97
+ );
256
98
 
257
99
  @media (prefers-reduced-motion: reduce) {
258
100
  .wr-tooltip {
@@ -36,7 +36,15 @@
36
36
  &--rounded {
37
37
  --wr-select-radius: var(--wr-border-radius-pill);
38
38
  --wr-select-padding-x: 1rem;
39
- corner-shape: round; // override squircle from smooth-br for pills
39
+
40
+ // The squircle reset has to land on the element `smooth-br` ran on, and
41
+ // that is the trigger, not the host: `corner-shape` does not inherit, so a
42
+ // declaration here left the trigger drawing the inset curve the mixin's own
43
+ // gotcha warns about instead of a stadium. `wr-btn` / `wr-input` put their
44
+ // modifier and their `smooth-br` on one element, which is why they paint.
45
+ .wr-select__trigger {
46
+ corner-shape: round;
47
+ }
40
48
  }
41
49
 
42
50
  &--disabled {
@@ -128,7 +136,15 @@
128
136
  // Search-mode trigger: inline text input + chevron + clear
129
137
  &__trigger--search {
130
138
  cursor: text;
131
- padding: 0;
139
+ // The FIELD owns the padding here — it is the click target and has to be
140
+ // typable edge to edge — but the chevron and the × are siblings of it, not
141
+ // children, so a flat `padding: 0` left the chevron sitting on the border.
142
+ // Hand the TRAILING inset back to the trigger: the chevron is now inset by
143
+ // the same amount the placeholder is, and the field keeps the leading edge.
144
+ // Logical properties, so the inset moves to the left in `dir="rtl"` on its
145
+ // own — no physical side is named.
146
+ padding-block: 0;
147
+ padding-inline: 0 calc(var(--wr-select-padding-x) * var(--wr-density-x, 1));
132
148
 
133
149
  &:focus-within {
134
150
  border-color: var(--wr-color-primary);
@@ -218,7 +234,12 @@
218
234
  // Roomier on the label side, tighter where the remove (×) sits.
219
235
  padding-block: 0.125rem;
220
236
  padding-inline: 0.5rem 0.25rem;
221
- border-radius: 999px;
237
+ // Not a pill: a chip is a shape nested INSIDE the trigger, so it repeats the
238
+ // trigger's own corner and scales with `size` along with it (5 / 6 / 7px for
239
+ // sm / md / lg). `--rounded` sets `--wr-select-radius` to the pill token, so
240
+ // a pill select still gets pill chips — the shape follows the control rather
241
+ // than being decided twice.
242
+ border-radius: var(--wr-select-radius);
222
243
  background: var(--wr-color-primary-soft);
223
244
  // `-ink`, not the raw intent: the chip paints the hue on its own soft tint,
224
245
  // where the base lands at 4.17:1.
@@ -241,7 +262,12 @@
241
262
  justify-content: center;
242
263
  width: 1rem;
243
264
  height: 1rem;
244
- border-radius: 999px;
265
+ // Concentric with the chip: the remove is inset from the chip's edge by the
266
+ // chip's 2px block padding, and an inner corner only nests cleanly when it
267
+ // is the outer corner minus that inset. `max()` keeps the value legal for a
268
+ // consumer who sets `--wr-select-radius: 0` — a negative radius would
269
+ // invalidate the declaration outright.
270
+ border-radius: max(0px, calc(var(--wr-select-radius) - 2px));
245
271
  cursor: pointer;
246
272
  transition: background 0.12s ease;
247
273
 
@@ -224,7 +224,10 @@
224
224
  &__badge {
225
225
  flex: 0 0 auto;
226
226
  padding: 0.0625rem 0.375rem;
227
- border-radius: var(--wr-border-radius-pill);
227
+ // The row's corner, not a pill: the badge sits inside `__entry` / `__item`,
228
+ // which both read `--wr-border-radius-sm`, and a stadium nested in a 6px row
229
+ // reads as a foreign shape rather than a part of it.
230
+ border-radius: var(--wr-border-radius-sm);
228
231
  background: var(--wr-color-fill-strong);
229
232
  color: var(--wr-color-on-surface-muted);
230
233
  font-size: var(--wr-text-xs);