@dataworkz/crystal 0.1.0-beta.1 → 0.1.0-beta.10

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dataworkz/crystal",
3
- "version": "0.1.0-beta.1",
3
+ "version": "0.1.0-beta.10",
4
4
  "description": "Dataworkz core UI for Angular: dw-* components, icons, toasts, design tokens, theme CSS and bundled fonts.",
5
5
  "license": "MIT",
6
6
  "author": "Dataworkz",
@@ -19,21 +19,19 @@
19
19
  "@angular/forms": "^20.3.0",
20
20
  "@angular/platform-browser": "^20.3.0",
21
21
  "@angular/router": "^20.3.0",
22
- "@primeng/themes": "^21.0.0",
23
22
  "ngx-markdown": "^20.0.0",
24
- "primeicons": "^7.0.0",
25
- "primeng": "^20.4.0",
26
23
  "rxjs": "~7.8.0"
27
24
  },
28
25
  "peerDependenciesMeta": {
29
- "@primeng/themes": {
30
- "optional": true
31
- },
32
26
  "ngx-markdown": {
33
27
  "optional": true
34
28
  }
35
29
  },
36
30
  "dependencies": {
31
+ "@primeng/themes": "^21.0.0",
32
+ "chart.js": "^4.5.1",
33
+ "primeicons": "^7.0.0",
34
+ "primeng": "~20.4.0",
37
35
  "tslib": "^2.8.1"
38
36
  },
39
37
  "exports": {
@@ -608,8 +608,48 @@ declare const DW_PRESET_OVERRIDES: {
608
608
  /** Dataworkz PrimeNG preset: Aura retargeted to the design system. */
609
609
  declare const DwPreset: _primeuix_themes_types.Preset<_primeuix_themes_aura_base.AuraBaseDesignTokens>;
610
610
 
611
- /** PrimeNG's dark mode follows the attribute ThemeService (and provideCrystal()) put on <html>. */
611
+ /** PrimeNG's dark mode follows the attribute ThemeService (and provideCrystal()) put on `<html>`. */
612
612
  declare const DW_DARK_MODE_SELECTOR = "[data-theme=\"dark\"]";
613
+ /** Panel class that sizes a select's option list for a small trigger (components/form.css). */
614
+ declare const DW_SELECT_PANEL_SM = "dw-select-panel--sm";
615
+ type SizedInstance = {
616
+ size?: () => string | null | undefined;
617
+ };
618
+ /**
619
+ * Crystal's pass-through defaults: a small `p-select` / `p-multiselect` gets small options even when its list is
620
+ * appended to `<body>` (where CSS can't see the trigger).
621
+ */
622
+ declare const DW_PRIMENG_PT: {
623
+ readonly select: {
624
+ readonly overlay: ({ instance }: {
625
+ instance?: SizedInstance;
626
+ }) => {
627
+ class: string;
628
+ } | {
629
+ class?: undefined;
630
+ };
631
+ };
632
+ readonly multiselect: {
633
+ readonly overlay: ({ instance }: {
634
+ instance?: SizedInstance;
635
+ }) => {
636
+ class: string;
637
+ } | {
638
+ class?: undefined;
639
+ };
640
+ };
641
+ };
642
+ /**
643
+ * PrimeNG's z-index bases, with tooltips raised to 5000: PrimeNG already stacks a tooltip above the overlays it manages
644
+ * (dialogs, drawers, popovers), and the higher base keeps it above layers that set their own z-index too (a panel or
645
+ * page chrome with `z-index` in CSS), so a tooltip is never hidden under what it describes.
646
+ */
647
+ declare const DW_PRIMENG_Z_INDEX: {
648
+ readonly modal: 1100;
649
+ readonly overlay: 1000;
650
+ readonly menu: 1000;
651
+ readonly tooltip: 5000;
652
+ };
613
653
  /**
614
654
  * PrimeNG with the Dataworkz preset, dark mode on `[data-theme="dark"]`. Pass app-specific PrimeNG settings
615
655
  * (ripple, zIndex, translation…) as `config`; its `theme.options` are merged over crystal's.
@@ -620,4 +660,4 @@ declare const DW_DARK_MODE_SELECTOR = "[data-theme=\"dark\"]";
620
660
  */
621
661
  declare function provideCrystalPrimeNG(config?: PrimeNGConfigType): EnvironmentProviders;
622
662
 
623
- export { AZURE_PRIMARY_RAMP, DW_COLOR_SCHEME, DW_DARK_MODE_SELECTOR, DW_PRESET_OVERRIDES, DwPreset, provideCrystalPrimeNG };
663
+ export { AZURE_PRIMARY_RAMP, DW_COLOR_SCHEME, DW_DARK_MODE_SELECTOR, DW_PRESET_OVERRIDES, DW_PRIMENG_PT, DW_PRIMENG_Z_INDEX, DW_SELECT_PANEL_SM, DwPreset, provideCrystalPrimeNG };
@@ -325,6 +325,9 @@
325
325
  .dw-icon-btn.is-active { background: var(--dw-accent-tint); color: var(--dw-accent); }
326
326
  /* Hovering the active (selected) button keeps it accented, a step stronger */
327
327
  .dw-icon-btn.is-active:hover:not(:disabled) { background: var(--dw-accent-tint-strong); color: var(--dw-accent); }
328
+ /* A selected danger toggle (e.g. a "not helpful" rating) keeps its danger colour instead of the accent. */
329
+ .dw-icon-btn--danger.is-active,
330
+ .dw-icon-btn--danger.is-active:hover:not(:disabled) { background: var(--dw-danger-tint); color: var(--dw-danger-solid); }
328
331
  .dw-icon-btn--inverse { color: var(--dw-text-muted); }
329
332
  .dw-icon-btn--inverse:hover:not(:disabled),
330
333
  .dw-icon-btn--inverse.is-active { background: color-mix(in srgb, var(--dw-text-inverse) 12%, transparent); color: var(--dw-text-inverse); }
@@ -156,3 +156,103 @@ dw-markdown.dw-prose { display: block; }
156
156
  /* ── dw-icon: app SVG glyphs, drawn in currentColor at the text size ── */
157
157
  .dw-icon { display: inline-flex; flex-shrink: 0; align-items: center; justify-content: center; line-height: 0; vertical-align: -0.125em; }
158
158
  .dw-icon svg { display: block; }
159
+ /* A PrimeIcons glyph (dw-icon pi="…") is a font glyph: it needs a line box, which the SVG case sets to 0. */
160
+ .dw-icon.pi { line-height: 1; }
161
+ .dw-icon--spin { animation: dw-icon-spin 1s linear infinite; }
162
+ @keyframes dw-icon-spin { to { transform: rotate(360deg); } }
163
+ @media (prefers-reduced-motion: reduce) { .dw-icon--spin, .dw-icon.pi-spin { animation-duration: 3s; } }
164
+
165
+ /* ── dw-accordion: panels that open and close under a header row (PrimeNG's p-accordion underneath) ──
166
+ PrimeNG reads its accordion design tokens as --p-accordion-* variables, so the look is set through them (no
167
+ specificity race with its own :hover / :first-child rules). flush (default): rows on the container, a divider
168
+ between panels; card: each panel a bordered card, the list spaced, a divider under an open header. */
169
+ .dw-accordion {
170
+ display: block;
171
+ min-width: 0;
172
+ --p-accordion-panel-border-width: 0;
173
+ --p-accordion-panel-border-color: var(--dw-border);
174
+ --p-accordion-header-padding: var(--dw-space-3) var(--dw-space-4);
175
+ --p-accordion-header-font-weight: var(--dw-font-weight-semibold);
176
+ --p-accordion-header-color: var(--dw-text-strong);
177
+ --p-accordion-header-hover-color: var(--dw-text-strong);
178
+ --p-accordion-header-active-color: var(--dw-text-strong);
179
+ --p-accordion-header-active-hover-color: var(--dw-text-strong);
180
+ --p-accordion-header-background: transparent;
181
+ --p-accordion-header-hover-background: var(--dw-surface-hover);
182
+ --p-accordion-header-active-background: transparent;
183
+ --p-accordion-header-active-hover-background: var(--dw-surface-hover);
184
+ --p-accordion-header-border-width: 0;
185
+ --p-accordion-header-border-color: var(--dw-border);
186
+ --p-accordion-header-border-radius: 0;
187
+ --p-accordion-header-first-border-width: 0;
188
+ --p-accordion-header-first-top-border-radius: 0;
189
+ --p-accordion-header-last-bottom-border-radius: 0;
190
+ --p-accordion-header-last-active-bottom-border-radius: 0;
191
+ --p-accordion-header-focus-ring-width: 0;
192
+ --p-accordion-header-focus-ring-shadow: inset var(--dw-focus-ring);
193
+ --p-accordion-content-background: transparent;
194
+ --p-accordion-content-color: var(--dw-text);
195
+ --p-accordion-content-padding: 0 var(--dw-space-4) var(--dw-space-4);
196
+ }
197
+
198
+ .dw-accordion .p-accordionheader {
199
+ gap: var(--dw-space-3);
200
+ font-size: var(--dw-font-size-14);
201
+ line-height: 1.4;
202
+ }
203
+
204
+ .dw-accordion__header {
205
+ display: flex;
206
+ flex: 1 1 auto;
207
+ align-items: center;
208
+ gap: var(--dw-space-2);
209
+ min-width: 0;
210
+ }
211
+
212
+ .dw-accordion__title { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
213
+
214
+ .dw-accordion__chevron {
215
+ flex: none;
216
+ font-size: var(--dw-font-size-12);
217
+ color: var(--dw-text-secondary);
218
+ transition: transform var(--dw-dur-enter) var(--dw-ease);
219
+ }
220
+
221
+ .dw-accordion .p-accordionpanel-active > .p-accordionheader .dw-accordion__chevron { transform: rotate(180deg); }
222
+ .dw-accordion .p-accordionpanel.p-disabled > .p-accordionheader { cursor: default; }
223
+
224
+ /* flush: a divider between panels */
225
+ .dw-accordion--flush .p-accordionpanel + .p-accordionpanel { --p-accordion-panel-border-width: 1px 0 0; }
226
+
227
+ /* card: each panel its own card; a closed one lifts on hover, an open one divides its header from its content */
228
+ .dw-accordion--card {
229
+ --p-accordion-header-hover-background: transparent;
230
+ --p-accordion-header-active-hover-background: transparent;
231
+ --p-accordion-header-border-radius: calc(var(--dw-radius-card) - 1px);
232
+ --p-accordion-header-first-top-border-radius: calc(var(--dw-radius-card) - 1px);
233
+ --p-accordion-header-last-bottom-border-radius: calc(var(--dw-radius-card) - 1px);
234
+ --p-accordion-content-padding: var(--dw-space-4);
235
+ }
236
+
237
+ .dw-accordion--card > .p-accordion { display: flex; flex-direction: column; gap: var(--dw-space-2); }
238
+
239
+ .dw-accordion--card .p-accordionpanel {
240
+ border: 1px solid var(--dw-border);
241
+ border-radius: var(--dw-radius-card);
242
+ background: var(--dw-surface);
243
+ transition: box-shadow var(--dw-dur-enter) var(--dw-ease);
244
+ }
245
+
246
+ .dw-accordion--card .p-accordionpanel:not(.p-accordionpanel-active):not(.p-disabled):hover { box-shadow: var(--dw-shadow-md); }
247
+
248
+ .dw-accordion--card .p-accordionpanel-active {
249
+ --p-accordion-header-border-width: 0 0 1px;
250
+ --p-accordion-header-first-border-width: 0 0 1px;
251
+ --p-accordion-header-border-radius: calc(var(--dw-radius-card) - 1px) calc(var(--dw-radius-card) - 1px) 0 0;
252
+ --p-accordion-header-last-active-bottom-border-radius: 0;
253
+ }
254
+
255
+ @media (prefers-reduced-motion: reduce) {
256
+ .dw-accordion__chevron,
257
+ .dw-accordion--card .p-accordionpanel { transition: none; }
258
+ }
@@ -138,6 +138,29 @@ body .p-treetable .p-treetable-tbody > tr.p-treetable-row-selected {
138
138
  border-left: 1px solid var(--dw-tree-spine);
139
139
  }
140
140
 
141
+ /* dw-tree: no box of its own (it sits on its container); --framed draws one. Node content fills the row, so a
142
+ dwTreeNode template can lay out across it. */
143
+ .dw-tree-view { display: block; min-width: 0; }
144
+ .dw-tree-view > .p-tree { padding: 0; border: 0; border-radius: 0; background: transparent; }
145
+ .dw-tree-view.dw-tree-view--framed > .p-tree {
146
+ padding: var(--dw-space-2);
147
+ border: 1px solid var(--dw-border-strong);
148
+ border-radius: var(--dw-radius-control);
149
+ background: var(--dw-surface);
150
+ }
151
+ .dw-tree-view .p-tree-node-label { flex: 1 1 auto; min-width: 0; }
152
+ /* --sm: compact rows, through PrimeNG's own tree variables (row ~28px instead of ~36px). */
153
+ .dw-tree-view.dw-tree-view--sm {
154
+ --p-tree-node-padding: var(--dw-space-1) var(--dw-space-2);
155
+ --p-tree-node-gap: var(--dw-space-1);
156
+ --p-tree-node-toggle-button-size: var(--dw-space-5);
157
+ --p-tree-gap: 0;
158
+ font-size: var(--dw-font-size-13);
159
+ }
160
+ /* --marker: the selected row's accent bar, as dw-nav-list [marker] (an inset shadow, so nothing shifts). Specific
161
+ enough to outlast PrimeNG's focus styles, which it loads after crystal. */
162
+ .dw-tree-view.dw-tree-view--marker .p-tree-node-content.p-tree-node-selected { box-shadow: inset 3px 0 0 var(--dw-accent); }
163
+
141
164
  /* ── Skeleton ── */
142
165
  .dw-skeleton {
143
166
  height: 20px;
@@ -225,14 +225,15 @@ dw-field.dw-field--inline:has(.dw-field__control :is(.p-inputtext-sm, .p-textare
225
225
  .dw-field__control > :first-child:not([dwFieldSuffix]) { flex: 1 1 auto; min-width: 0; }
226
226
  /* Switches, checkboxes and radios keep their own size — stretching one turns a toggle into a bar. */
227
227
  /* (Same specificity as the stretch rule above, plus the element, so it wins.) */
228
- .dw-field__control > :is(p-toggleswitch, p-checkbox, p-radiobutton):first-child:not([dwFieldSuffix]) { flex: none; }
228
+ .dw-field__control > :is(p-toggleswitch, p-checkbox, p-radiobutton, dw-switch, dw-checkbox, dw-radio):first-child:not([dwFieldSuffix]) { flex: none; }
229
229
  .dw-field__control > input,
230
230
  .dw-field__control > textarea,
231
231
  .dw-field__control > p-select,
232
232
  .dw-field__control > p-multiselect,
233
233
  .dw-field__control > p-inputnumber,
234
234
  .dw-field__control > p-datepicker,
235
- .dw-field__control > p-autocomplete { width: 100%; }
235
+ .dw-field__control > p-autocomplete,
236
+ .dw-field__control > :is(dw-select, dw-multiselect, dw-number-input, dw-date-picker, dw-autocomplete, dw-slider) { width: 100%; }
236
237
  .dw-field__error { display: inline-flex; align-items: center; gap: var(--dw-space-1); }
237
238
  /* Field errors are text only (the red text and border carry the state): no leading icon, even in
238
239
  hand-written error lines. */
@@ -391,12 +392,6 @@ dw-search-input.dw-search--block { width: 100%; }
391
392
  display: none;
392
393
  }
393
394
 
394
- /* PrimeNG's slider host is inline; give it the field's width. */
395
- p-slider {
396
- display: block;
397
- width: 100%;
398
- }
399
-
400
395
  /* pSize="small" inputs: base.css's `font: inherit` would keep them at 14px, so restate the
401
396
  small type here — 5px padding + 16px line + 1px borders = 28px, the small dw-button height. */
402
397
  /* Small selects match small inputs exactly (28px): their label gets the same 16px line
@@ -470,12 +465,14 @@ p-slider {
470
465
 
471
466
 
472
467
  /* Small selects (size="small"): their option list matches the 12px trigger instead of the default
473
- 14px rows. The list renders inside the select unless it is appended elsewhere (appendTo); a list
474
- appended to <body> takes the panel class `dw-select-panel--sm` for the same sizing. */
468
+ 14px rows. A list rendered inside the select matches `.p-select-sm`; a list appended elsewhere
469
+ (appendTo="body") gets `dw-select-panel--sm` from provideCrystalPrimeNG()'s pass-through, so no
470
+ screen sets it by hand. */
475
471
  .p-select-sm .p-select-option,
476
472
  .p-multiselect-sm .p-multiselect-option,
477
473
  .dw-select-panel--sm .p-select-option,
478
- .dw-select-panel--sm .p-multiselect-option {
474
+ .dw-select-panel--sm .p-multiselect-option,
475
+ .dw-autocomplete-panel--sm .p-autocomplete-option {
479
476
  padding: 5px 10px;
480
477
  font-size: var(--dw-font-size-12);
481
478
  line-height: var(--dw-line-height-16);
@@ -483,7 +480,8 @@ p-slider {
483
480
  .p-select-sm .p-select-empty-message,
484
481
  .p-multiselect-sm .p-multiselect-empty-message,
485
482
  .dw-select-panel--sm .p-select-empty-message,
486
- .dw-select-panel--sm .p-multiselect-empty-message {
483
+ .dw-select-panel--sm .p-multiselect-empty-message,
484
+ .dw-autocomplete-panel--sm .p-autocomplete-empty-message {
487
485
  padding: 5px 10px;
488
486
  font-size: var(--dw-font-size-12);
489
487
  }
@@ -635,7 +633,7 @@ body .dw-var-field .p-textarea {
635
633
  .dw-check-toggle.is-on .dw-check-toggle__label { color: var(--dw-text-strong); font-weight: var(--dw-font-weight-medium); }
636
634
 
637
635
  /* Compact calendar (inline in popovers): smaller day cells and type, quieter weekday row and a
638
- tighter time picker. <p-datepicker styleClass="dw-datepicker--compact" [inline]="true" />.
636
+ tighter time picker. <dw-date-picker [compact]="true" [inline]="true" />.
639
637
  Two classes per rule so they outrank PrimeNG's own (unlayered) datepicker styles. */
640
638
  .dw-datepicker--compact .p-datepicker-header { padding: 0 0 var(--dw-space-2); }
641
639
  .dw-datepicker--compact .p-datepicker-title,
@@ -675,3 +673,108 @@ body .dw-var-field .p-textarea {
675
673
  .config-form-body .toggle-label { margin: 0; cursor: pointer; }
676
674
  /* Host of a config form component inside .config-drawer-body: fill the drawer height. */
677
675
  .config-form-host { display: flex; flex: 1; flex-direction: column; min-height: 0; }
676
+
677
+ /* ── dw-select / dw-multiselect (components) ──
678
+ The host takes the screen's sizing (a width, `dw-w-full`, a grid track) and the PrimeNG control fills it; with no
679
+ width set, the host is as wide as the control would be on its own. */
680
+ .dw-select,
681
+ .dw-multiselect {
682
+ display: inline-flex;
683
+ min-width: 0;
684
+ max-width: 100%;
685
+ vertical-align: middle;
686
+ }
687
+ .dw-select > p-select,
688
+ .dw-multiselect > p-multiselect {
689
+ flex: 1 1 auto;
690
+ min-width: 0;
691
+ }
692
+ .dw-select.dw-select--fluid,
693
+ .dw-multiselect.dw-multiselect--fluid {
694
+ display: flex;
695
+ width: 100%;
696
+ }
697
+
698
+ /* ── dw-checkbox / dw-radio / dw-switch (components): the control and its label on one line (dw-check) ── */
699
+ .dw-checkbox__label--empty:empty,
700
+ .dw-radio__label--empty:empty { display: none; }
701
+
702
+ /* ── dw-number-input (component): sized by the screen like dw-select ── */
703
+ .dw-number-input { display: inline-flex; min-width: 0; max-width: 100%; vertical-align: middle; }
704
+ .dw-number-input > p-inputnumber { flex: 1 1 auto; min-width: 0; }
705
+ .dw-number-input.dw-number-input--fluid { display: flex; width: 100%; }
706
+
707
+ /* ── dw-date-picker (component): sized by the screen like dw-select; the inline calendar keeps its own width unless fluid ── */
708
+ .dw-date-picker { display: inline-flex; min-width: 0; max-width: 100%; vertical-align: middle; }
709
+ .dw-date-picker > p-datepicker { flex: 1 1 auto; min-width: 0; }
710
+ .dw-date-picker.dw-date-picker--fluid { display: flex; width: 100%; }
711
+ .dw-date-picker.dw-date-picker--inline > p-datepicker { flex: none; }
712
+ .dw-date-picker.dw-date-picker--inline.dw-date-picker--fluid > p-datepicker { flex: 1 1 auto; }
713
+ /* flush: the inline calendar loses its own box and sits on its container's surface (a popover or a panel pads it). */
714
+ .dw-date-picker.dw-date-picker--flush .p-datepicker-panel-inline {
715
+ padding: 0;
716
+ border: 0;
717
+ border-radius: 0;
718
+ background: transparent;
719
+ box-shadow: none;
720
+ }
721
+
722
+ /* ── dw-autocomplete (component): sized by the screen like dw-select; the text input fills it ──
723
+ (The extra elements outweigh PrimeNG's `width: 1%` on the input beside a dropdown button, whose styles load after ours.) */
724
+ .dw-autocomplete { display: inline-flex; min-width: 0; max-width: 100%; vertical-align: middle; }
725
+ .dw-autocomplete > p-autocomplete { display: flex; flex: 1 1 auto; min-width: 0; }
726
+ .dw-autocomplete > p-autocomplete.p-autocomplete > input.p-autocomplete-input { flex: 1 1 auto; width: auto; min-width: 0; }
727
+ .dw-autocomplete.dw-autocomplete--fluid { display: flex; width: 100%; }
728
+ /* Its list (appended to <body>, so styled by its panel classes): rows in the 14px body type, unless small. */
729
+ body .dw-autocomplete-panel:not(.dw-autocomplete-panel--sm) :is(.p-autocomplete-option, .p-autocomplete-empty-message) {
730
+ font-size: var(--dw-font-size-14);
731
+ }
732
+ /* panelWidth="field": exactly as wide as the field, as select lists are (see above): PrimeNG puts the field's width in
733
+ the overlay's inline min-width, a zero width lets it win, and long suggestions ellipsise. */
734
+ .p-overlay:has(> .p-overlay-content > .dw-autocomplete-panel--field) { width: 0; }
735
+ .dw-autocomplete-panel--field .p-autocomplete-option > * {
736
+ min-width: 0;
737
+ overflow: hidden;
738
+ text-overflow: ellipsis;
739
+ }
740
+ /* size="sm" gives it the small select rows (`.dw-autocomplete-panel--sm`, with the small select lists above). */
741
+
742
+ /* ── dw-slider (component): fills its container, in crystal's tokens ──
743
+ A 4px track in the meter's colours (dw-meter) and a 16px accent handle with a surface centre; the padding keeps the
744
+ handle clear of what sits above and below. PrimeNG draws it from these variables. */
745
+ .dw-slider {
746
+ --p-slider-track-size: 4px;
747
+ --p-slider-track-background: var(--dw-surface-pressed);
748
+ --p-slider-track-border-radius: var(--dw-radius-full);
749
+ --p-slider-range-background: var(--dw-accent);
750
+ --p-slider-handle-width: 16px;
751
+ --p-slider-handle-height: 16px;
752
+ --p-slider-handle-border-radius: var(--dw-radius-full);
753
+ --p-slider-handle-background: var(--dw-accent);
754
+ --p-slider-handle-hover-background: var(--dw-accent-hover);
755
+ --p-slider-handle-content-width: 8px;
756
+ --p-slider-handle-content-height: 8px;
757
+ --p-slider-handle-content-border-radius: var(--dw-radius-full);
758
+ --p-slider-handle-content-background: var(--dw-surface);
759
+ --p-slider-handle-content-hover-background: var(--dw-surface);
760
+ --p-slider-handle-content-shadow: none;
761
+ --p-slider-handle-focus-ring-width: 0;
762
+ --p-slider-handle-focus-ring-shadow: var(--dw-focus-ring);
763
+
764
+ display: block;
765
+ width: 100%;
766
+ padding-block: var(--dw-space-2);
767
+ }
768
+ /* PrimeNG shows a grab cursor on the handle; show it grabbing while held. */
769
+ .dw-slider .p-slider-handle:active { cursor: grabbing; }
770
+ .dw-slider.dw-slider--invalid {
771
+ --p-slider-range-background: var(--dw-danger-solid);
772
+ --p-slider-handle-background: var(--dw-danger-solid);
773
+ --p-slider-handle-hover-background: var(--dw-danger-solid);
774
+ --p-slider-handle-focus-ring-shadow: var(--dw-focus-ring-danger);
775
+ }
776
+ .dw-slider.dw-slider--vertical { display: inline-block; width: auto; height: 100%; padding-block: 0; padding-inline: var(--dw-space-2); }
777
+ .dw-slider.dw-slider--vertical > p-slider { height: 100%; }
778
+ /* p-slider used directly (until its screens move to dw-slider): a block as wide as its field. */
779
+ p-slider { display: block; width: 100%; }
780
+ .dw-slider--vertical > p-slider { width: var(--p-slider-track-size); }
@@ -215,7 +215,7 @@
215
215
  }
216
216
 
217
217
  /* Selected marker: an accent left border that follows the item's rounded corners. Only the agents
218
- list page's sidebar has it (dw-nav-list [marker], and its tags tree: dw-selected-marker); every
218
+ list page's sidebar has it (dw-nav-list [marker], and its tags tree: dw-tree [marker]); every
219
219
  other selection is the plain selected fill. An inset shadow, not a border, so the item's content
220
220
  does not shift when it is selected. */
221
221
  .dw-nav-list--marker .dw-nav-item.is-active,
@@ -547,6 +547,36 @@ dw-nav-list.dw-nav-list--minimizable { overflow: hidden; padding-bottom: 0; }
547
547
  .dw-steps__item.is-done .dw-steps__no { border-color: var(--dw-accent); background: var(--dw-accent); color: var(--dw-text-inverse); }
548
548
  .dw-steps__sep { flex: 0 0 48px; height: 1px; background: var(--dw-border-strong); }
549
549
 
550
+ /* ── dw-stepper: the component over .dw-steps. Each step is a button; a done step goes back,
551
+ an upcoming step of a linear stepper is disabled but drawn as upcoming (not faded).
552
+ --inline drops the sub-header strip (a wizard header); --md is the wizard size, --sm the strip above. ── */
553
+ .dw-stepper { display: block; min-width: 0; }
554
+ .dw-steps--inline { padding: 0; border-bottom: 0; }
555
+ .dw-steps__btn {
556
+ display: inline-flex;
557
+ align-items: center;
558
+ gap: var(--dw-space-2);
559
+ margin: calc(-1 * var(--dw-space-1));
560
+ padding: var(--dw-space-1);
561
+ border: 0;
562
+ border-radius: var(--dw-radius-md);
563
+ background: transparent;
564
+ color: inherit;
565
+ font-weight: inherit;
566
+ white-space: nowrap;
567
+ transition: color var(--dw-dur-micro) var(--dw-ease);
568
+ }
569
+ .dw-steps__btn:disabled,
570
+ .dw-steps__btn[aria-current='step'] { cursor: default; }
571
+ .dw-steps__btn:not(:disabled, [aria-current='step']):hover { color: var(--dw-text-link); }
572
+ .dw-steps__btn:focus-visible { outline: none; box-shadow: var(--dw-focus-ring); }
573
+ .dw-steps__no { flex: none; transition: background-color var(--dw-dur-micro) var(--dw-ease), border-color var(--dw-dur-micro) var(--dw-ease); }
574
+ .dw-steps__item.is-disabled { opacity: 0.5; }
575
+ .dw-steps__item.is-disabled .dw-steps__btn { cursor: not-allowed; }
576
+ .dw-steps__sep.is-done { background: var(--dw-accent); }
577
+ .dw-steps--md .dw-steps__no { width: 32px; height: 32px; border-width: 2px; font-size: var(--dw-font-size-14); }
578
+ .dw-steps--md .dw-steps__sep { height: 2px; }
579
+
550
580
  /* ── dw-rail-tabs: vertical icon-over-label tab strip at a panel edge ── */
551
581
  .dw-rail-tabs { display: flex; flex-shrink: 0; width: 64px; }
552
582
  .dw-rail-tabs__nav {
@@ -339,9 +339,24 @@ body .p-drawer-right {
339
339
  }
340
340
  .p-tooltip.dw-tooltip--multiline .p-tooltip-text { white-space: pre-line; }
341
341
  .p-tooltip.dw-tooltip--error .p-tooltip-text { background: var(--dw-status-danger-fg); color: var(--dw-text-inverse); }
342
+ /* Preview (dwTooltipVariant="preview"): a long value shown whole, up to 720 × 340px, then scrolled; line breaks kept,
343
+ long tokens broken anywhere. Pair it with dwTooltipInteractive so the pointer can scroll it. */
344
+ .p-tooltip.dw-tooltip--preview { width: auto; max-width: min(720px, 68vw); }
345
+ .p-tooltip.dw-tooltip--preview .p-tooltip-text {
346
+ display: inline-block;
347
+ box-sizing: border-box;
348
+ width: max-content;
349
+ max-width: min(720px, 68vw);
350
+ max-height: min(42vh, 340px);
351
+ overflow: auto;
352
+ white-space: pre-wrap;
353
+ overflow-wrap: anywhere;
354
+ font-size: var(--dw-font-size-12);
355
+ line-height: 1.45;
356
+ }
342
357
  .p-tooltip.dw-tooltip--error .p-tooltip-arrow { border-top-color: var(--dw-status-danger-fg); }
343
358
  /* Long code / JSON: wide, scrollable, monospace, wrapped. */
344
- .p-tooltip.dw-tooltip--code { width: min(520px, calc(100vw - 48px)); max-width: min(520px, calc(100vw - 48px)); z-index: 5000; }
359
+ .p-tooltip.dw-tooltip--code { width: min(520px, calc(100vw - 48px)); max-width: min(520px, calc(100vw - 48px)); }
345
360
  .p-tooltip.dw-tooltip--code .p-tooltip-text {
346
361
  display: block;
347
362
  width: 100%;
@@ -703,3 +703,39 @@ div.dw-section__header { cursor: default; }
703
703
  .p-splitter.dw-splitter--rail-start > .p-splitter-gutter {
704
704
  background: linear-gradient(to right, var(--dw-surface-rail) 50%, transparent 50%);
705
705
  }
706
+
707
+ /* ── dw-splitter (the component): the same flat panes and hairline gutter, the gutter holding a
708
+ dw-resizer. The pane sizes are flex-grow ratios set by the component; the handle is a zero-size
709
+ anchor at the gutter's centre that the resizer's 10px hit strip straddles. The resizer's own line
710
+ and grip are hidden: the hairline is the gutter's. ── */
711
+ .dw-splitter--horizontal,
712
+ .dw-splitter--vertical { display: flex; height: 100%; min-width: 0; min-height: 0; }
713
+ .dw-splitter--horizontal { flex-direction: row; }
714
+ .dw-splitter--vertical { flex-direction: column; }
715
+ .dw-splitter__pane { display: flex; flex: 1 1 0; min-width: 0; min-height: 0; overflow: hidden; }
716
+ .dw-splitter__pane > * { flex: 1 1 auto; height: 100%; min-width: 0; min-height: 0; }
717
+ .dw-splitter__gutter { position: relative; flex: 0 0 auto; background: transparent; }
718
+ .dw-splitter--horizontal > .dw-splitter__gutter { cursor: col-resize; }
719
+ .dw-splitter--vertical > .dw-splitter__gutter { cursor: row-resize; }
720
+ .dw-splitter__gutter::before {
721
+ content: '';
722
+ position: absolute;
723
+ background: var(--dw-border);
724
+ transition: background var(--dw-dur-micro) var(--dw-ease);
725
+ }
726
+ .dw-splitter--horizontal > .dw-splitter__gutter::before { top: 0; bottom: 0; left: 50%; width: 1px; transform: translateX(-50%); }
727
+ .dw-splitter--vertical > .dw-splitter__gutter::before { top: 50%; right: 0; left: 0; height: 1px; transform: translateY(-50%); }
728
+ .dw-splitter--horizontal > .dw-splitter__gutter:is(:hover, .is-dragging, :has(.dw-resizer:focus-visible))::before { width: 2px; background: var(--dw-accent); }
729
+ .dw-splitter--vertical > .dw-splitter__gutter:is(:hover, .is-dragging, :has(.dw-resizer:focus-visible))::before { height: 2px; background: var(--dw-accent); }
730
+ .dw-splitter__handle { position: absolute; }
731
+ .dw-splitter--horizontal > .dw-splitter__gutter > .dw-splitter__handle { top: 0; bottom: 0; left: 50%; width: 0; }
732
+ .dw-splitter--vertical > .dw-splitter__gutter > .dw-splitter__handle { top: 50%; right: 0; left: 0; height: 0; }
733
+ .dw-splitter__handle > .dw-resizer::before,
734
+ .dw-splitter__handle > .dw-resizer::after { display: none; }
735
+ .dw-splitter.is-resizing { user-select: none; }
736
+ .dw-splitter--rail-start.dw-splitter--horizontal > .dw-splitter__gutter {
737
+ background: linear-gradient(to right, var(--dw-surface-rail) 50%, transparent 50%);
738
+ }
739
+ .dw-splitter--rail-start.dw-splitter--vertical > .dw-splitter__gutter {
740
+ background: linear-gradient(to bottom, var(--dw-surface-rail) 50%, transparent 50%);
741
+ }
@@ -4,6 +4,8 @@
4
4
  *
5
5
  * Rules: colours only in tokens/primitives.css (everything else uses semantic tokens); font-family only in base.css.
6
6
  */
7
+ /* PrimeIcons (pi pi-*): crystal's dependency, so an app never installs or loads it itself. Its font is local. */
8
+ @import './icons.css';
7
9
  @import './tokens/primitives.css';
8
10
  @import './tokens/semantic.css';
9
11
  @import './tokens/semantic-dark.css';
@@ -0,0 +1,6 @@
1
+ /*
2
+ * @dataworkz/crystal — the PrimeIcons font (`pi pi-*`, `<dw-icon pi>`), shipped as @dataworkz/crystal/styles/icons.css.
3
+ * Crystal depends on primeicons, so an app never installs or loads it itself: crystal.css / crystal-no-fonts.css import
4
+ * this file; an app that imports crystal's styles file by file imports it too. The font files are local (no CDN).
5
+ */
6
+ @import 'primeicons/primeicons.css';