spiderly 19.11.8 → 19.12.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.
- package/agent/docs/angular-customization/index.md +80 -5
- package/agent/docs/angular-customization/references/helper-functions.generated.md +2 -0
- package/agent/docs/e2e-testing/index.md +10 -15
- package/agent/skills/add-entity/SKILL.md +12 -1
- package/fesm2022/spiderly.mjs +2090 -271
- package/fesm2022/spiderly.mjs.map +1 -1
- package/lib/components/spiderly-data-table/spiderly-data-table.component.d.ts +426 -53
- package/lib/components/spiderly-data-view/spiderly-data-view.component.d.ts +8 -1
- package/lib/directives/spiderly-filter-template.directive.d.ts +34 -0
- package/lib/directives/spiderly-overflow-title.directive.d.ts +33 -0
- package/lib/entities/filter-rule.d.ts +5 -7
- package/lib/filters/allowed-operators.d.ts +23 -0
- package/lib/filters/filter-store.d.ts +255 -0
- package/lib/filters/spiderly-filter-bar.component.d.ts +132 -0
- package/lib/services/helper-functions.d.ts +14 -0
- package/package.json +1 -1
- package/public-api.d.ts +5 -0
- package/styles/components/spiderly-data-table/spiderly-data-table.component.scss +170 -4
- package/styles/components/spiderly-data-view/spiderly-data-view.component.scss +16 -0
- package/styles/filters/spiderly-filter-bar.component.scss +163 -0
- package/styles/styles/layout/_content.scss +1 -1
- package/styles/styles/layout/_main.scss +15 -1
- package/styles/styles/layout/_menu.scss +1 -1
- package/styles/styles/layout/_mixins.scss +46 -0
- package/styles/styles/layout/_topbar.scss +4 -4
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
@use "../../styles/layout/mixins" as *;
|
|
2
|
+
|
|
1
3
|
.table-header {
|
|
2
4
|
display: flex;
|
|
3
5
|
justify-content: space-between;
|
|
@@ -48,12 +50,9 @@
|
|
|
48
50
|
cursor: pointer;
|
|
49
51
|
}
|
|
50
52
|
.column-chooser-reset {
|
|
53
|
+
@include bare-button();
|
|
51
54
|
margin-top: 4px;
|
|
52
|
-
padding: 0;
|
|
53
|
-
border: none;
|
|
54
|
-
background: none;
|
|
55
55
|
color: var(--p-primary-color);
|
|
56
|
-
cursor: pointer;
|
|
57
56
|
align-self: flex-start;
|
|
58
57
|
font-size: 0.875rem;
|
|
59
58
|
|
|
@@ -62,8 +61,44 @@
|
|
|
62
61
|
}
|
|
63
62
|
}
|
|
64
63
|
|
|
64
|
+
// Teleported with its popover, so top level — the same rule the chooser follows above.
|
|
65
|
+
.column-menu {
|
|
66
|
+
display: flex;
|
|
67
|
+
flex-direction: column;
|
|
68
|
+
min-width: 11rem;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.column-menu-item {
|
|
72
|
+
display: flex;
|
|
73
|
+
align-items: center;
|
|
74
|
+
gap: 8px;
|
|
75
|
+
// The label takes the slack so a trailing check sits at the far edge rather than beside the
|
|
76
|
+
// text, where it would read as part of it.
|
|
77
|
+
.column-menu-item-label {
|
|
78
|
+
flex: 1;
|
|
79
|
+
}
|
|
80
|
+
@include bare-button();
|
|
81
|
+
text-align: left;
|
|
82
|
+
padding: 6px 10px;
|
|
83
|
+
border-radius: var(--p-content-border-radius);
|
|
84
|
+
|
|
85
|
+
&:hover:not(:disabled) {
|
|
86
|
+
background: var(--p-content-hover-background);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
&:disabled {
|
|
90
|
+
color: var(--p-text-muted-color);
|
|
91
|
+
cursor: default;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
65
95
|
:host {
|
|
66
96
|
// In-template content only — rules for teleported overlay content go at top level (see above).
|
|
97
|
+
|
|
98
|
+
// onPageChange scrolls this wrapper back into view; the mixin carries the offset contract.
|
|
99
|
+
.spiderly-table-container {
|
|
100
|
+
@include scroll-target-below-top-chrome();
|
|
101
|
+
}
|
|
67
102
|
// The `.clickable` affordance is shared: clickable rows (navigateOnRowClick) and opt-in cells
|
|
68
103
|
// (Column.onCellClick) both get a pointer cursor; cells additionally underline on hover.
|
|
69
104
|
::ng-deep .clickable {
|
|
@@ -73,11 +108,142 @@
|
|
|
73
108
|
text-decoration: underline;
|
|
74
109
|
}
|
|
75
110
|
|
|
111
|
+
// Only ink on hover or focus: a chevron on every header at rest is noise across nineteen of
|
|
112
|
+
// them, and the header already carries a name and a sort icon.
|
|
113
|
+
.column-menu-button {
|
|
114
|
+
@include bare-button();
|
|
115
|
+
color: var(--p-text-muted-color);
|
|
116
|
+
padding: 0 2px;
|
|
117
|
+
opacity: 0;
|
|
118
|
+
font-size: 0.75rem;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
th:hover .column-menu-button,
|
|
122
|
+
.column-menu-button:focus-visible {
|
|
123
|
+
opacity: 1;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Tabs, above the bar: a view names the question, the bar shows what it resolved to. Shopify
|
|
127
|
+
// puts saved order views in the same place for the same reason.
|
|
128
|
+
.table-views {
|
|
129
|
+
display: flex;
|
|
130
|
+
flex-wrap: wrap;
|
|
131
|
+
gap: 4px;
|
|
132
|
+
padding-top: 8px;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.table-view {
|
|
136
|
+
@include bare-button();
|
|
137
|
+
color: var(--p-text-muted-color);
|
|
138
|
+
padding: 4px 10px;
|
|
139
|
+
border-radius: var(--p-content-border-radius);
|
|
140
|
+
|
|
141
|
+
&:hover {
|
|
142
|
+
background: var(--p-content-hover-background);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.table-view-active {
|
|
147
|
+
color: var(--p-primary-color);
|
|
148
|
+
font-weight: 600;
|
|
149
|
+
background: var(--p-content-hover-background);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// The left edge that survives a horizontal scroll. Geometry only here: the opaque background
|
|
153
|
+
// lives on the BODY cells below, because the header th already gets an opaque
|
|
154
|
+
// header.cell.background from PrimeNG's own th rule. Painting the row palette on the whole
|
|
155
|
+
// class put the pinned th on the row colour and let the hover rule tint it whenever the pointer
|
|
156
|
+
// crossed ANY header cell — the locked column reacting on its own, again ("na hover ostalih
|
|
157
|
+
// hoveruje i locked kolone", Filip, 2026-09-05) — and overrode PrimeNG's per-cell sortable
|
|
158
|
+
// header hover, which is the hover a th should have.
|
|
159
|
+
.frozen-column {
|
|
160
|
+
position: sticky;
|
|
161
|
+
z-index: 1;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Body cells: the background must be OPAQUE and must track hover and striping, or rows show
|
|
165
|
+
// through the pinned cells as they pass underneath — which is the one thing that makes a sticky
|
|
166
|
+
// column look broken rather than merely unstyled. It inherits rather than naming a colour so
|
|
167
|
+
// PrimeNG's own row states keep working.
|
|
168
|
+
td.frozen-column {
|
|
169
|
+
background: var(--p-datatable-row-background, var(--p-content-background));
|
|
170
|
+
// The row fades between its two backgrounds and this repainted instantly, so the pinned
|
|
171
|
+
// columns changed shade a beat before the rest of the row — which reads as the locked column
|
|
172
|
+
// reacting on its own rather than as one row highlighting (Filip, on /tags). Same duration
|
|
173
|
+
// token PrimeNG's own row uses, so the two are one movement.
|
|
174
|
+
transition: background-color var(--p-transition-duration, 0.2s);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
tr:hover > td.frozen-column {
|
|
178
|
+
background: var(--p-datatable-row-hover-background, inherit);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// The seam. NN/g calls for a shadow so the pinned columns read as floating ABOVE the rest
|
|
182
|
+
// rather than as the first two columns of a table that happens not to move.
|
|
183
|
+
//
|
|
184
|
+
// `:last-of-type` shipped here first and drew NOTHING: it means "last th among its siblings",
|
|
185
|
+
// and the frozen cells are the FIRST ones — it was written for "the last frozen column", which
|
|
186
|
+
// is a concept the markup never expressed. The class does.
|
|
187
|
+
.frozen-column-last {
|
|
188
|
+
box-shadow: 2px 0 4px -2px rgb(0 0 0 / 15%);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// The grab handle on a header's right edge. The th is position:relative through PrimeNG's own
|
|
192
|
+
// styles; the strip is wider than the line it draws so it can actually be hit.
|
|
193
|
+
th {
|
|
194
|
+
position: relative;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
.column-resizer {
|
|
198
|
+
position: absolute;
|
|
199
|
+
top: 0;
|
|
200
|
+
right: 0;
|
|
201
|
+
width: 8px;
|
|
202
|
+
height: 100%;
|
|
203
|
+
cursor: col-resize;
|
|
204
|
+
user-select: none;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// The select-all counter. A smaller step than the body text because it is a parenthetical, and
|
|
208
|
+
// because it is what decides how narrow the checkbox column can be.
|
|
209
|
+
.selection-count {
|
|
210
|
+
font-size: 0.75rem;
|
|
211
|
+
color: var(--p-text-muted-color);
|
|
212
|
+
}
|
|
213
|
+
|
|
76
214
|
// Belt to onSelectionCellMouseDown's preventDefault (shift+click text-selection suppression).
|
|
77
215
|
td.selection-cell {
|
|
78
216
|
user-select: none;
|
|
79
217
|
}
|
|
80
218
|
|
|
219
|
+
// The operator's per-column wrap (header menu) travels as inherited custom properties, because
|
|
220
|
+
// the library cannot style a projected spiderlyCellTemplate directly (CLAUDE.md → Column
|
|
221
|
+
// widths). Unset, the var()s below fall back to the clamp; a wrapped td flips both, and any
|
|
222
|
+
// consumer clamp written against the same vars follows — that is the whole contract. Only these
|
|
223
|
+
// two: text-overflow needs no property, since it has no effect once overflow is visible.
|
|
224
|
+
td.cell-wrap {
|
|
225
|
+
--spiderly-cell-overflow: visible;
|
|
226
|
+
--spiderly-cell-white-space: normal;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// The cost the fixed layout would otherwise hand over: text no longer widens its column, so
|
|
230
|
+
// it wraps inside it and a long value grows the row — which pushes every row below it, a worse
|
|
231
|
+
// shift than the horizontal one the fixed layout removes. One line, ellipsis, whole value still
|
|
232
|
+
// in the DOM to select and copy. The clamp stays the default precisely because a row that grows
|
|
233
|
+
// by content costs more than the line saves — wrapping is a choice someone makes about one
|
|
234
|
+
// column (td.cell-wrap above), not a shape every table inherits.
|
|
235
|
+
.cell-text {
|
|
236
|
+
display: block;
|
|
237
|
+
overflow: var(--spiderly-cell-overflow, hidden);
|
|
238
|
+
text-overflow: ellipsis;
|
|
239
|
+
white-space: var(--spiderly-cell-white-space, nowrap);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// PrimeNG-rendered markup, so ::ng-deep is the only way in; the mixin carries the contract.
|
|
243
|
+
::ng-deep .p-datatable-mask {
|
|
244
|
+
@include table-pending-veil();
|
|
245
|
+
}
|
|
246
|
+
|
|
81
247
|
::ng-deep {
|
|
82
248
|
.p-datatable-thead {
|
|
83
249
|
position: unset !important;
|
|
@@ -1,3 +1,19 @@
|
|
|
1
|
+
@use "../../styles/layout/mixins" as *;
|
|
2
|
+
|
|
3
|
+
:host {
|
|
4
|
+
// onPageChange scrolls this wrapper back into view; the mixin carries the offset contract.
|
|
5
|
+
.spiderly-table-container {
|
|
6
|
+
@include scroll-target-below-top-chrome();
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// PrimeNG-rendered markup, so ::ng-deep is the only way in. Kept under :host deliberately:
|
|
10
|
+
// the block further down is at SCSS top level, which strips encapsulation entirely and leaks
|
|
11
|
+
// its rules to every component in the consuming app. Do not extend that one.
|
|
12
|
+
::ng-deep .p-datatable-mask {
|
|
13
|
+
@include table-pending-veil();
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
1
17
|
::ng-deep {
|
|
2
18
|
.p-datatable-tbody {
|
|
3
19
|
display: flex;
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
@use "../styles/layout/mixins" as *;
|
|
2
|
+
|
|
3
|
+
// Not teleported, so `:host` scoping holds here — unlike the column chooser, whose rules must sit
|
|
4
|
+
// at top level because PrimeNG moves its popover into document.body (Angular/CLAUDE.md → overlay
|
|
5
|
+
// styling). Colours come from the PrimeNG theme's variables so the bar follows the admin's theme
|
|
6
|
+
// rather than carrying a second palette.
|
|
7
|
+
:host {
|
|
8
|
+
display: block;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
.filter-bar {
|
|
13
|
+
display: flex;
|
|
14
|
+
flex-wrap: wrap;
|
|
15
|
+
align-items: center;
|
|
16
|
+
gap: 8px;
|
|
17
|
+
padding: 8px 0 0;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.filter-chip {
|
|
21
|
+
display: inline-flex;
|
|
22
|
+
align-items: center;
|
|
23
|
+
gap: 2px;
|
|
24
|
+
border: 1px solid var(--p-content-border-color);
|
|
25
|
+
border-radius: 999px;
|
|
26
|
+
background: var(--p-content-background);
|
|
27
|
+
// The chip is two buttons in one pill; the padding lives on them so both halves are fully
|
|
28
|
+
// clickable rather than leaving a dead ring around the text.
|
|
29
|
+
padding: 0;
|
|
30
|
+
font-size: 0.875rem;
|
|
31
|
+
line-height: 1.4;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.filter-chip-edit {
|
|
35
|
+
@include bare-button();
|
|
36
|
+
display: inline-flex;
|
|
37
|
+
align-items: center;
|
|
38
|
+
gap: 4px;
|
|
39
|
+
padding: 4px 4px 4px 10px;
|
|
40
|
+
border-radius: 999px 0 0 999px;
|
|
41
|
+
|
|
42
|
+
&:hover .filter-chip-value {
|
|
43
|
+
text-decoration: underline;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.filter-chip-label {
|
|
48
|
+
color: var(--p-text-muted-color);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.filter-chip-value {
|
|
52
|
+
font-weight: 600;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.filter-chip-remove {
|
|
56
|
+
@include bare-button();
|
|
57
|
+
color: var(--p-text-muted-color);
|
|
58
|
+
font-size: 1.05rem;
|
|
59
|
+
line-height: 1;
|
|
60
|
+
padding: 4px 9px 5px 4px;
|
|
61
|
+
border-radius: 0 999px 999px 0;
|
|
62
|
+
|
|
63
|
+
&:hover {
|
|
64
|
+
color: var(--p-primary-color);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// PrimeNG's button owns the look; only placement belongs here.
|
|
69
|
+
.filter-add {
|
|
70
|
+
flex: none;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// The add menu is teleported into document.body with its popover, so its rules must sit at TOP
|
|
74
|
+
// LEVEL — `:host` nesting dies on teleport, exactly as the column chooser's do (Angular/CLAUDE.md
|
|
75
|
+
// → overlay styling). The editor is in-template and could be nested, but the two are kept together
|
|
76
|
+
// so nobody has to remember which is which.
|
|
77
|
+
.filter-add-menu {
|
|
78
|
+
display: flex;
|
|
79
|
+
flex-direction: column;
|
|
80
|
+
align-items: stretch;
|
|
81
|
+
gap: 2px;
|
|
82
|
+
min-width: 14rem;
|
|
83
|
+
// Nineteen filters is taller than a popover should ever be; the list scrolls and the search box
|
|
84
|
+
// above it does not move with it.
|
|
85
|
+
max-height: 20rem;
|
|
86
|
+
overflow-y: auto;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.filter-add-search {
|
|
90
|
+
margin-bottom: 4px;
|
|
91
|
+
position: sticky;
|
|
92
|
+
top: 0;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.filter-add-option {
|
|
96
|
+
@include bare-button();
|
|
97
|
+
text-align: left;
|
|
98
|
+
padding: 6px 10px;
|
|
99
|
+
border-radius: var(--p-content-border-radius);
|
|
100
|
+
|
|
101
|
+
&:hover {
|
|
102
|
+
background: var(--p-content-hover-background);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Its own row under the chips, full width, so the controls have room and always appear in one
|
|
107
|
+
// predictable place.
|
|
108
|
+
// Every control here is sized through its own `size="small"` input, never a hand-written
|
|
109
|
+
// `p-inputtext-sm`: each PrimeNG component has its OWN small class (p-select-sm,
|
|
110
|
+
// p-multiselect-sm), so the input class silently did nothing on a select and left it a head
|
|
111
|
+
// taller than the box beside it.
|
|
112
|
+
.filter-editor {
|
|
113
|
+
display: flex;
|
|
114
|
+
margin-top: 8px;
|
|
115
|
+
flex-wrap: wrap;
|
|
116
|
+
align-items: center;
|
|
117
|
+
gap: 8px;
|
|
118
|
+
border: 1px solid var(--p-content-border-color);
|
|
119
|
+
border-radius: var(--p-content-border-radius);
|
|
120
|
+
background: var(--p-content-background);
|
|
121
|
+
padding: 8px;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.filter-editor-label {
|
|
125
|
+
font-weight: 600;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.filter-editor-tick {
|
|
129
|
+
display: inline-flex;
|
|
130
|
+
align-items: center;
|
|
131
|
+
gap: 4px;
|
|
132
|
+
cursor: pointer;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.filter-bar-count {
|
|
136
|
+
color: var(--p-text-muted-color);
|
|
137
|
+
font-size: 0.875rem;
|
|
138
|
+
// Pushed away from the chips it summarises, not crowded against the last one.
|
|
139
|
+
margin-left: 4px;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.sort-chip {
|
|
143
|
+
display: inline-flex;
|
|
144
|
+
align-items: center;
|
|
145
|
+
gap: 4px;
|
|
146
|
+
color: var(--p-text-muted-color);
|
|
147
|
+
font-size: 0.875rem;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.sort-chip-key {
|
|
151
|
+
// The direction arrow is the non-colour channel; the priority is the reading order, which is
|
|
152
|
+
// the thing the scattered header icons could never show.
|
|
153
|
+
color: var(--p-text-color);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
.filter-bar-clear {
|
|
157
|
+
// Pushed to the far end, away from the chips: a destructive action next to the x it resembles
|
|
158
|
+
// is how a single chip gets removed by someone reaching for all of them. It is also the LAST
|
|
159
|
+
// child, because an auto margin here pushes everything after it to the right edge too — which
|
|
160
|
+
// is how the editor ended up across the screen from the click it answered.
|
|
161
|
+
margin-left: auto;
|
|
162
|
+
flex: none;
|
|
163
|
+
}
|
|
@@ -4,10 +4,24 @@
|
|
|
4
4
|
box-sizing: border-box;
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
+
// Declared through :where() so both vars carry ZERO specificity: a consumer's own
|
|
8
|
+
// `html { --spiderly-… }` then wins wherever it sits in the cascade, instead of the override
|
|
9
|
+
// working or not depending on stylesheet order. Full contract, and the fallback rule
|
|
10
|
+
// components must follow: Angular/CLAUDE.md → fixed-chrome offset.
|
|
11
|
+
:where(html) {
|
|
12
|
+
// Two names because they are two quantities that merely coincide in THIS shell: the topbar's
|
|
13
|
+
// own height, and how much fixed chrome covers the top of the viewport. A shell that pins a
|
|
14
|
+
// second bar under the topbar raises the inset without stretching the topbar.
|
|
15
|
+
--spiderly-topbar-height: 5rem;
|
|
16
|
+
--spiderly-viewport-top-inset: var(--spiderly-topbar-height);
|
|
17
|
+
}
|
|
18
|
+
|
|
7
19
|
html {
|
|
8
20
|
height: 100%;
|
|
9
21
|
font-size: $scale;
|
|
10
|
-
|
|
22
|
+
|
|
23
|
+
// No `scroll-behavior` here, deliberately (2026-08-29) — the argument against re-adding it
|
|
24
|
+
// is in Angular/CLAUDE.md → fixed-chrome offset.
|
|
11
25
|
}
|
|
12
26
|
|
|
13
27
|
body {
|
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
// A <button> stripped to bare text — the reset behind the library chrome's chips, view tabs,
|
|
2
|
+
// menu rows and plain-text actions. Only the reset lives here: colour, padding, radius and hover
|
|
3
|
+
// stay at the call site (declared AFTER the include, so they win), because what a bare button
|
|
4
|
+
// looks like is per surface — that it stops looking like a UA button is not.
|
|
5
|
+
@mixin bare-button() {
|
|
6
|
+
border: none;
|
|
7
|
+
background: none;
|
|
8
|
+
color: inherit;
|
|
9
|
+
font: inherit;
|
|
10
|
+
cursor: pointer;
|
|
11
|
+
padding: 0;
|
|
12
|
+
}
|
|
13
|
+
|
|
1
14
|
@mixin focused() {
|
|
2
15
|
outline: 0 none;
|
|
3
16
|
outline-offset: 0;
|
|
@@ -11,3 +24,36 @@
|
|
|
11
24
|
transition: box-shadow 0.2s;
|
|
12
25
|
box-shadow: inset 0 0 0 0.2rem var(--p-primary-400);
|
|
13
26
|
}
|
|
27
|
+
|
|
28
|
+
// Marks an element as a scroll-into-view target that must clear the shell's fixed top chrome.
|
|
29
|
+
// This is the ONE declaration of that offset: scrollElementIntoViewIfAboveViewport reads the
|
|
30
|
+
// computed value back rather than taking a number, so nothing in TypeScript knows the shell's
|
|
31
|
+
// dimensions. The 0px fallback is what stops a consumer running its own shell (no
|
|
32
|
+
// --spiderly-viewport-top-inset declared) from getting a phantom gap above the element.
|
|
33
|
+
@mixin scroll-target-below-top-chrome() {
|
|
34
|
+
scroll-margin-top: calc(var(--spiderly-viewport-top-inset, 0px) + 1rem);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// The overlay PrimeNG mounts over a table while a fetch is in flight (.p-datatable-mask).
|
|
38
|
+
// Its own theme token is a modal-strength scrim -- rgba(0,0,0,0.4) light, 0.6 dark -- which
|
|
39
|
+
// makes the rows underneath unreadable. Since the whole point of keeping the previous page on
|
|
40
|
+
// screen during a refetch is that it stays READABLE, the scrim is replaced by a light veil.
|
|
41
|
+
// Three parts, each load-bearing:
|
|
42
|
+
// - `color`, because SpinnerIcon paints fill="currentColor". Overriding only the background
|
|
43
|
+
// leaves a near-white spinner on a near-white veil.
|
|
44
|
+
// - `flex-start` plus a top pad, because the mask spans the whole datatable root: a centred
|
|
45
|
+
// spinner on a long list sits far below the fold, worst right after a page change has
|
|
46
|
+
// scrolled the reader to the table's top. The pad is deliberately approximate -- it only has
|
|
47
|
+
// to land the spinner inside the first screenful, not on any particular row. `position:
|
|
48
|
+
// sticky` was tried and cannot work here: the icon's containing block IS the mask.
|
|
49
|
+
// - A fallback on every var: Karma loads no PrimeNG theme, and a consumer need not either.
|
|
50
|
+
@mixin table-pending-veil() {
|
|
51
|
+
background: color-mix(
|
|
52
|
+
in srgb,
|
|
53
|
+
var(--p-content-background, #ffffff) 65%,
|
|
54
|
+
transparent
|
|
55
|
+
);
|
|
56
|
+
color: var(--p-primary-color, #334155);
|
|
57
|
+
align-items: flex-start;
|
|
58
|
+
padding-top: 6rem;
|
|
59
|
+
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
.layout-topbar {
|
|
5
5
|
position: fixed;
|
|
6
|
-
height:
|
|
6
|
+
height: var(--spiderly-topbar-height);
|
|
7
7
|
z-index: 997;
|
|
8
8
|
left: 0;
|
|
9
9
|
top: 0;
|
|
@@ -136,7 +136,7 @@
|
|
|
136
136
|
border-radius: var(--p-content-border-radius);
|
|
137
137
|
padding: 1rem;
|
|
138
138
|
right: 2rem;
|
|
139
|
-
top:
|
|
139
|
+
top: var(--spiderly-topbar-height);
|
|
140
140
|
min-width: 15rem;
|
|
141
141
|
display: none;
|
|
142
142
|
-webkit-animation: scalein 0.15s linear;
|
|
@@ -181,7 +181,7 @@
|
|
|
181
181
|
border-radius: var(--p-content-border-radius);
|
|
182
182
|
padding: 1rem;
|
|
183
183
|
right: 2rem;
|
|
184
|
-
top:
|
|
184
|
+
top: var(--spiderly-topbar-height);
|
|
185
185
|
min-width: 15rem;
|
|
186
186
|
-webkit-animation: scalein 0.15s linear;
|
|
187
187
|
animation: scalein 0.15s linear;
|
|
@@ -196,7 +196,7 @@
|
|
|
196
196
|
@media (max-width: 991px) {
|
|
197
197
|
width: 210px;
|
|
198
198
|
right: 1rem;
|
|
199
|
-
top:
|
|
199
|
+
top: calc(var(--spiderly-topbar-height) - 0.5rem);
|
|
200
200
|
}
|
|
201
201
|
|
|
202
202
|
.layout-topbar-profile-dropdown-button {
|