@marianmeres/stuic 3.187.0 → 3.189.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.
@@ -0,0 +1,2 @@
1
+ export { default as Gantt, type Props as GanttProps, type GanttRow, type GanttBar, type GanttBarLabels, type GanttSelectDetail, } from "./Gantt.svelte";
2
+ export { buildGanttAxis, placeRange, placePoint, dayToFraction, boundsOf, isoWeekNumber, GANTT_MAX_COLUMNS, type GanttAxis, type GanttAxisOptions, type GanttColumn, type GanttGroup, type GanttPlacement, type GanttDateInput, type GanttUnit, } from "./gantt-geometry.js";
@@ -0,0 +1,2 @@
1
+ export { default as Gantt, } from "./Gantt.svelte";
2
+ export { buildGanttAxis, placeRange, placePoint, dayToFraction, boundsOf, isoWeekNumber, GANTT_MAX_COLUMNS, } from "./gantt-geometry.js";
@@ -0,0 +1,201 @@
1
+ <script lang="ts" module>
2
+ import type { Snippet } from "svelte";
3
+ import type { ValidateOptions } from "../../actions/validate.svelte.js";
4
+ import type { TranslateFn } from "../../types.js";
5
+ import type { ColorPickerCustom } from "../ColorPicker/ColorPicker.svelte";
6
+ import type { ColorPickerSwatch } from "../ColorPicker/palettes.js";
7
+ import type { THC } from "../Thc/Thc.svelte";
8
+ import type { InputWrapClassProps } from "./types.js";
9
+
10
+ type SnippetWithId = Snippet<[{ id: string }]>;
11
+
12
+ export interface Props extends InputWrapClassProps, Record<string, any> {
13
+ /** Current color (bindable). Any CSS color string; `""` = no color */
14
+ value?: string;
15
+ label?: SnippetWithId | THC;
16
+ description?: SnippetWithId | THC;
17
+ class?: string;
18
+ id?: string;
19
+ renderSize?: "sm" | "md" | "lg" | string;
20
+ required?: boolean;
21
+ disabled?: boolean;
22
+ validate?: boolean | Omit<ValidateOptions, "setValidationResult">;
23
+ labelAfter?: SnippetWithId | THC;
24
+ inputBefore?: SnippetWithId | THC;
25
+ inputAfter?: SnippetWithId | THC;
26
+ inputBelow?: SnippetWithId | THC;
27
+ below?: SnippetWithId | THC;
28
+ labelLeft?: boolean;
29
+ labelLeftWidth?: "normal" | "wide";
30
+ labelLeftBreakpoint?: number;
31
+ /** Classes for the underlying <ColorPicker> root */
32
+ classInput?: string;
33
+ style?: string;
34
+ //
35
+ // Below: forwarded as-is to the underlying <ColorPicker>.
36
+ //
37
+ /** The swatches: color strings or `{ value, label }` objects */
38
+ palette?: ColorPickerSwatch[];
39
+ /** Cap the palette at this many swatches per row */
40
+ columns?: number;
41
+ /** Which custom-color controls to show (default `"both"`) */
42
+ custom?: ColorPickerCustom;
43
+ /** Offer clearing (the "no color" swatch, Delete / Backspace) */
44
+ allowClear?: boolean;
45
+ /** Form field name (hidden input) */
46
+ name?: string;
47
+ /** i18n translate function (see `createColorPickerT`) */
48
+ t?: TranslateFn;
49
+ /** Fires when the user commits a color (not while dragging the native picker) */
50
+ onchange?: (value: string) => void;
51
+ /** Class for every swatch button */
52
+ classSwatch?: string;
53
+ }
54
+ </script>
55
+
56
+ <script lang="ts">
57
+ import type { ValidationResult } from "../../actions/validate.svelte.js";
58
+ import { getId } from "../../utils/get-id.js";
59
+ import { twMerge } from "../../utils/tw-merge.js";
60
+ import ColorPicker from "../ColorPicker/ColorPicker.svelte";
61
+ import InputWrap from "./_internal/InputWrap.svelte";
62
+
63
+ let {
64
+ value = $bindable(""),
65
+ label = "",
66
+ id = getId(),
67
+ description,
68
+ class: classProp,
69
+ renderSize = "md",
70
+ //
71
+ required = false,
72
+ disabled = false,
73
+ //
74
+ // Renamed local binding to avoid collision with `export function validate()` below.
75
+ validate: validateProp,
76
+ //
77
+ labelAfter,
78
+ inputBefore,
79
+ inputAfter,
80
+ inputBelow,
81
+ below,
82
+ //
83
+ labelLeft = false,
84
+ labelLeftWidth = "normal",
85
+ labelLeftBreakpoint = 480,
86
+ //
87
+ classInput,
88
+ //
89
+ palette,
90
+ columns,
91
+ custom,
92
+ allowClear,
93
+ name,
94
+ t,
95
+ onchange,
96
+ classSwatch,
97
+ //
98
+ classLabel,
99
+ classLabelBox,
100
+ classInputBox,
101
+ classInputBoxWrap,
102
+ classInputBoxWrapInvalid,
103
+ classDescBox,
104
+ classDescBoxToggle,
105
+ classBelowBox,
106
+ classValidationBox,
107
+ style = "",
108
+ //
109
+ ...rest
110
+ }: Props = $props();
111
+
112
+ //
113
+ let validation: ValidationResult | undefined = $state();
114
+ const setValidationResult = (res: ValidationResult) => (validation = res);
115
+
116
+ // Delegate the imperative API to the inner ColorPicker.
117
+ let pickerRef: ColorPicker | undefined = $state();
118
+
119
+ /** Trigger validation now. Renders the inline message if invalid. */
120
+ export function validate(): ValidationResult | undefined {
121
+ pickerRef?.validate();
122
+ return validation;
123
+ }
124
+
125
+ /** Clear the inline validation message. */
126
+ export function clearValidation(): void {
127
+ pickerRef?.clearValidation?.();
128
+ validation = undefined;
129
+ }
130
+
131
+ /** Current validation state. */
132
+ export function getValidation(): ValidationResult | undefined {
133
+ return validation;
134
+ }
135
+
136
+ /** Focus the swatch group's tab stop (or the first custom-color control). */
137
+ export function focus(): void {
138
+ pickerRef?.focus?.();
139
+ }
140
+
141
+ /** Scroll the field into view. Defaults to smooth + center. */
142
+ export function scrollIntoView(opts?: ScrollIntoViewOptions): void {
143
+ pickerRef?.scrollIntoView?.(opts);
144
+ }
145
+ </script>
146
+
147
+ <InputWrap
148
+ {description}
149
+ class={classProp}
150
+ size={renderSize}
151
+ {id}
152
+ {label}
153
+ {labelAfter}
154
+ {inputBefore}
155
+ {inputAfter}
156
+ {inputBelow}
157
+ {below}
158
+ {required}
159
+ {disabled}
160
+ {labelLeft}
161
+ {labelLeftWidth}
162
+ {labelLeftBreakpoint}
163
+ {classLabel}
164
+ {classLabelBox}
165
+ {classInputBox}
166
+ {classInputBoxWrapInvalid}
167
+ {classDescBox}
168
+ {classDescBoxToggle}
169
+ {classBelowBox}
170
+ {classValidationBox}
171
+ {validation}
172
+ classInputBoxWrap={twMerge("input-wrap-transparent", classInputBoxWrap)}
173
+ {style}
174
+ >
175
+ <!--
176
+ `labelledby` (not the InputWrap's `for={id}`) is what names the swatch group: a
177
+ radiogroup is a <div>, which a `for` cannot label. Matches InputWrap's own
178
+ `{id}-label`, and only when there is a label to point at — otherwise the picker
179
+ keeps its own `t("color")` name. The picker's string `label` prop is deliberately
180
+ not forwarded: here `label` is the visible one.
181
+ -->
182
+ <ColorPicker
183
+ bind:this={pickerRef}
184
+ bind:value
185
+ {palette}
186
+ {columns}
187
+ {custom}
188
+ {allowClear}
189
+ {name}
190
+ {t}
191
+ {onchange}
192
+ {classSwatch}
193
+ {required}
194
+ {disabled}
195
+ class={classInput}
196
+ labelledby={label ? `${id}-label` : undefined}
197
+ validate={validateProp}
198
+ {setValidationResult}
199
+ {...rest}
200
+ />
201
+ </InputWrap>
@@ -0,0 +1,59 @@
1
+ import type { Snippet } from "svelte";
2
+ import type { ValidateOptions } from "../../actions/validate.svelte.js";
3
+ import type { TranslateFn } from "../../types.js";
4
+ import type { ColorPickerCustom } from "../ColorPicker/ColorPicker.svelte";
5
+ import type { ColorPickerSwatch } from "../ColorPicker/palettes.js";
6
+ import type { THC } from "../Thc/Thc.svelte";
7
+ import type { InputWrapClassProps } from "./types.js";
8
+ type SnippetWithId = Snippet<[{
9
+ id: string;
10
+ }]>;
11
+ export interface Props extends InputWrapClassProps, Record<string, any> {
12
+ /** Current color (bindable). Any CSS color string; `""` = no color */
13
+ value?: string;
14
+ label?: SnippetWithId | THC;
15
+ description?: SnippetWithId | THC;
16
+ class?: string;
17
+ id?: string;
18
+ renderSize?: "sm" | "md" | "lg" | string;
19
+ required?: boolean;
20
+ disabled?: boolean;
21
+ validate?: boolean | Omit<ValidateOptions, "setValidationResult">;
22
+ labelAfter?: SnippetWithId | THC;
23
+ inputBefore?: SnippetWithId | THC;
24
+ inputAfter?: SnippetWithId | THC;
25
+ inputBelow?: SnippetWithId | THC;
26
+ below?: SnippetWithId | THC;
27
+ labelLeft?: boolean;
28
+ labelLeftWidth?: "normal" | "wide";
29
+ labelLeftBreakpoint?: number;
30
+ /** Classes for the underlying <ColorPicker> root */
31
+ classInput?: string;
32
+ style?: string;
33
+ /** The swatches: color strings or `{ value, label }` objects */
34
+ palette?: ColorPickerSwatch[];
35
+ /** Cap the palette at this many swatches per row */
36
+ columns?: number;
37
+ /** Which custom-color controls to show (default `"both"`) */
38
+ custom?: ColorPickerCustom;
39
+ /** Offer clearing (the "no color" swatch, Delete / Backspace) */
40
+ allowClear?: boolean;
41
+ /** Form field name (hidden input) */
42
+ name?: string;
43
+ /** i18n translate function (see `createColorPickerT`) */
44
+ t?: TranslateFn;
45
+ /** Fires when the user commits a color (not while dragging the native picker) */
46
+ onchange?: (value: string) => void;
47
+ /** Class for every swatch button */
48
+ classSwatch?: string;
49
+ }
50
+ import type { ValidationResult } from "../../actions/validate.svelte.js";
51
+ declare const FieldColorPicker: import("svelte").Component<Props, {
52
+ validate: () => ValidationResult | undefined;
53
+ clearValidation: () => void;
54
+ getValidation: () => ValidationResult | undefined;
55
+ focus: () => void;
56
+ scrollIntoView: (opts?: ScrollIntoViewOptions) => void;
57
+ }, "value">;
58
+ type FieldColorPicker = ReturnType<typeof FieldColorPicker>;
59
+ export default FieldColorPicker;
@@ -15,6 +15,7 @@ A comprehensive form input system with multiple field components, validation sup
15
15
  | `FieldCheckbox` | Single checkbox with label |
16
16
  | `FieldRadios` | Radio button group |
17
17
  | `FieldSwitch` | Toggle switch field |
18
+ | `FieldColorPicker` | Color picker (swatches + custom color) field — see below |
18
19
  | `FieldFile` | File upload input |
19
20
  | `FieldAssets` | Asset/image upload with preview |
20
21
  | `FieldSingleAsset` | One asset (avatar, logo, cover, one document) — see below |
@@ -184,6 +185,50 @@ The visible label names the control via `aria-labelledby` (the switch is announc
184
185
  `switch, on/off` with that name). Clicking the label text does not toggle — the switch's
185
186
  own root is a `<label>`, so an HTML `for` association is not possible; click the switch.
186
187
 
188
+ ### Color Picker
189
+
190
+ `FieldColorPicker` wraps a [`ColorPicker`](../ColorPicker/README.md) in the standard field
191
+ scaffolding — visible label, description, validation box, `labelLeft*`, every
192
+ `InputWrapClassProps` member — the way `FieldSwitch` wraps `Switch`.
193
+
194
+ ```svelte
195
+ <script lang="ts">
196
+ import { FieldColorPicker } from "stuic";
197
+
198
+ let accent = $state("");
199
+ </script>
200
+
201
+ <FieldColorPicker
202
+ label="Page colour"
203
+ description="Used for buttons and links on the public page."
204
+ name="accent"
205
+ required
206
+ bind:value={accent}
207
+ />
208
+ ```
209
+
210
+ | Prop | Goes to |
211
+ | ----------------------------------------------------------------------------------------- | ------------------------------------------------------------- |
212
+ | `value` (bindable), `palette`, `columns`, `custom`, `allowClear`, `name`, `t`, `onchange` | the inner `<ColorPicker>` (same semantics, same defaults) |
213
+ | `classSwatch` | every swatch button |
214
+ | `classInput` | the inner `<ColorPicker>`'s root class |
215
+ | `required`, `disabled`, `validate` | both: the shell (asterisk, dimming, message) and the picker |
216
+ | `renderSize` | the surrounding field shell only — swatch size is a CSS token |
217
+ | everything else unrecognized (`...rest`) | the inner `<ColorPicker>` root |
218
+
219
+ - **Naming.** The visible label names the swatch radiogroup via `aria-labelledby` →
220
+ `{id}-label`, with no competing `aria-label`. Without a `label` the group keeps the
221
+ picker's own `t("color")` name. Clicking the label text focuses nothing (a radiogroup is
222
+ not labelable by `for`) — same as `FieldSwitch`. With `palette={[]}` and
223
+ `allowClear={false}` there is no radiogroup, so the label names nothing; the hex field
224
+ keeps its own "Hex value" name.
225
+ - **Validation.** The picker's hidden input runs the `validate` action; the result renders
226
+ in the field's validation box. `validate()`, `clearValidation()`, `getValidation()`,
227
+ `focus()` (the checked swatch — the group's tab stop) and `scrollIntoView()` are
228
+ available via `bind:this`, so `validateAllFields` / `scrollToFirstInvalidField` work.
229
+ - **Layout.** The input wrap is transparent (no border / focus ring — the picker draws its
230
+ own swatches and hex field). A disabled field dims once (the shell), not twice.
231
+
187
232
  ### Input with Addons
188
233
 
189
234
  ```svelte
@@ -144,12 +144,18 @@
144
144
  /* ============================================================================
145
145
  BASE INPUT ELEMENT STYLES
146
146
  Replaces @tailwindcss/forms plugin for input elements
147
+
148
+ `:where(:not(.stuic-color-picker *))` (here, in the size variants and in the
149
+ iOS zoom guard) exempts a composite control that styles its own raw inputs:
150
+ FieldColorPicker's hex field and native picker would otherwise lose their
151
+ border, font and size to these rules. Inside `:where()`, so the specificity
152
+ every existing override was written against is unchanged.
147
153
  ============================================================================ */
148
154
 
149
155
  .stuic-input
150
156
  input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not(
151
157
  [type="file"]
152
- ),
158
+ ):where(:not(.stuic-color-picker *)),
153
159
  .stuic-input textarea,
154
160
  .stuic-input select {
155
161
  /* Reset browser defaults */
@@ -202,7 +208,7 @@
202
208
  .stuic-input[data-size="sm"]
203
209
  input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not(
204
210
  [type="file"]
205
- ),
211
+ ):where(:not(.stuic-color-picker *)),
206
212
  .stuic-input[data-size="sm"] textarea,
207
213
  .stuic-input[data-size="sm"] select {
208
214
  padding: var(--stuic-input-padding-y-sm) var(--stuic-input-padding-x-sm);
@@ -217,13 +223,13 @@
217
223
  .stuic-input[data-size="md"]
218
224
  input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not(
219
225
  [type="file"]
220
- ),
226
+ ):where(:not(.stuic-color-picker *)),
221
227
  .stuic-input[data-size="md"] textarea,
222
228
  .stuic-input[data-size="md"] select,
223
229
  .stuic-input:not([data-size])
224
230
  input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not(
225
231
  [type="file"]
226
- ),
232
+ ):where(:not(.stuic-color-picker *)),
227
233
  .stuic-input:not([data-size]) textarea,
228
234
  .stuic-input:not([data-size]) select {
229
235
  padding: var(--stuic-input-padding-y-md) var(--stuic-input-padding-x-md);
@@ -239,7 +245,7 @@
239
245
  .stuic-input[data-size="lg"]
240
246
  input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not(
241
247
  [type="file"]
242
- ),
248
+ ):where(:not(.stuic-color-picker *)),
243
249
  .stuic-input[data-size="lg"] textarea,
244
250
  .stuic-input[data-size="lg"] select {
245
251
  padding: var(--stuic-input-padding-y-lg) var(--stuic-input-padding-x-lg);
@@ -329,6 +335,12 @@
329
335
  box-shadow: none;
330
336
  }
331
337
 
338
+ /* FieldColorPicker: a disabled shell already dims the wrap — dimming the picker
339
+ again would compound to 0.25 */
340
+ .stuic-input.disabled .stuic-color-picker[data-disabled] {
341
+ opacity: 1;
342
+ }
343
+
332
344
  /* Size-specific input-wrap padding */
333
345
  .stuic-input[data-size="lg"] .input-wrap {
334
346
  padding: calc(var(--spacing) * 1);
@@ -841,7 +853,7 @@
841
853
  .stuic-input[data-size="sm"]
842
854
  input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not(
843
855
  [type="file"]
844
- ),
856
+ ):where(:not(.stuic-color-picker *)),
845
857
  .stuic-input[data-size="sm"] textarea,
846
858
  .stuic-input[data-size="sm"] select {
847
859
  font-size: max(
@@ -853,13 +865,13 @@
853
865
  .stuic-input[data-size="md"]
854
866
  input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not(
855
867
  [type="file"]
856
- ),
868
+ ):where(:not(.stuic-color-picker *)),
857
869
  .stuic-input[data-size="md"] textarea,
858
870
  .stuic-input[data-size="md"] select,
859
871
  .stuic-input:not([data-size])
860
872
  input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not(
861
873
  [type="file"]
862
- ),
874
+ ):where(:not(.stuic-color-picker *)),
863
875
  .stuic-input:not([data-size]) textarea,
864
876
  .stuic-input:not([data-size]) select {
865
877
  font-size: max(
@@ -871,7 +883,7 @@
871
883
  .stuic-input[data-size="lg"]
872
884
  input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not(
873
885
  [type="file"]
874
- ),
886
+ ):where(:not(.stuic-color-picker *)),
875
887
  .stuic-input[data-size="lg"] textarea,
876
888
  .stuic-input[data-size="lg"] select {
877
889
  font-size: max(
@@ -4,6 +4,7 @@ export { default as FieldSingleAsset, type Props as FieldSingleAssetProps, type
4
4
  export { createFieldSingleAssetT, FIELD_SINGLE_ASSET_MESSAGES_EN, type FieldSingleAssetMessageKey, type FieldSingleAssetMessages, } from "./field-single-asset-i18n.js";
5
5
  export { FIELD_SINGLE_ASSET_MESSAGES_SK } from "./field-single-asset-i18n-sk.js";
6
6
  export { default as FieldCheckbox, type Props as FieldCheckboxProps, } from "./FieldCheckbox.svelte";
7
+ export { default as FieldColorPicker, type Props as FieldColorPickerProps, } from "./FieldColorPicker.svelte";
7
8
  export { default as Honeypot, type Props as HoneypotProps } from "./Honeypot.svelte";
8
9
  export { default as TimeTrap, type Props as TimeTrapProps, type TimeTrapSnapshot, } from "./TimeTrap.svelte";
9
10
  export { default as FieldFile, type Props as FieldFileProps } from "./FieldFile.svelte";
@@ -4,6 +4,7 @@ export { default as FieldSingleAsset, } from "./FieldSingleAsset.svelte";
4
4
  export { createFieldSingleAssetT, FIELD_SINGLE_ASSET_MESSAGES_EN, } from "./field-single-asset-i18n.js";
5
5
  export { FIELD_SINGLE_ASSET_MESSAGES_SK } from "./field-single-asset-i18n-sk.js";
6
6
  export { default as FieldCheckbox, } from "./FieldCheckbox.svelte";
7
+ export { default as FieldColorPicker, } from "./FieldColorPicker.svelte";
7
8
  export { default as Honeypot } from "./Honeypot.svelte";
8
9
  export { default as TimeTrap, } from "./TimeTrap.svelte";
9
10
  export { default as FieldFile } from "./FieldFile.svelte";
package/dist/index.css CHANGED
@@ -103,6 +103,7 @@ In practice:
103
103
  @import "./components/EmptyState/index.css";
104
104
  @import "./components/FieldsBuilder/index.css";
105
105
  @import "./components/Float/index.css";
106
+ @import "./components/Gantt/index.css";
106
107
  @import "./components/H/index.css";
107
108
  @import "./components/Header/index.css";
108
109
  @import "./components/ImageCycler/index.css";
package/dist/index.d.ts CHANGED
@@ -55,6 +55,7 @@ export * from "./components/EmailVerifyForm/index.js";
55
55
  export * from "./components/EmptyState/index.js";
56
56
  export * from "./components/FieldsBuilder/index.js";
57
57
  export * from "./components/Float/index.js";
58
+ export * from "./components/Gantt/index.js";
58
59
  export * from "./components/H/index.js";
59
60
  export * from "./components/Header/index.js";
60
61
  export * from "./components/ImageCycler/index.js";
package/dist/index.js CHANGED
@@ -61,6 +61,7 @@ export * from "./components/EmailVerifyForm/index.js";
61
61
  export * from "./components/EmptyState/index.js";
62
62
  export * from "./components/FieldsBuilder/index.js";
63
63
  export * from "./components/Float/index.js";
64
+ export * from "./components/Gantt/index.js";
64
65
  export * from "./components/H/index.js";
65
66
  export * from "./components/Header/index.js";
66
67
  export * from "./components/ImageCycler/index.js";
@@ -7,7 +7,7 @@ import type { ValidationResult } from "../actions/validate.svelte.js";
7
7
  * Every STUIC `Field*` component (FieldInput, FieldPhoneNumber, FieldCountry,
8
8
  * FieldSelect, FieldCheckbox, FieldTextarea, FieldFile, FieldObject,
9
9
  * FieldAssets, FieldInputLocalized, FieldKeyValues, FieldLikeButton,
10
- * FieldRadios, FieldSwitch) satisfies this interface via `export function`.
10
+ * FieldRadios, FieldSwitch, FieldColorPicker) satisfies this interface via `export function`.
11
11
  */
12
12
  export interface ValidatableField {
13
13
  /** Run the validator now. Renders the inline error if invalid. */
@@ -73,6 +73,7 @@
73
73
  | CronInput | Cron expression editor with presets and validation |
74
74
  | Fieldset | Field grouping with legend |
75
75
  | FieldKeyValues | Key-value pair editor |
76
+ | FieldColorPicker | ColorPicker in the field shell: visible label names the swatch group, description, validation box |
76
77
  | FieldTable | Rows × typed columns editor (text/number/select/checkbox/date/url cells, container layout) |
77
78
  | FieldsBuilder | Field-definition list editor ("what properties does a thing have?") |
78
79
  | FieldAssets | File/asset management |
@@ -110,6 +111,7 @@
110
111
  | Stat | KPI/stat card: label + value + delta with trend arrow and semantic coloring |
111
112
  | DescriptionList | Term/value list (`<dl>`): stacked or two-column by container query, hairlines, truncation, totals |
112
113
  | Timeline | Vertical event list on a rail: dot/icon/custom markers, inline or opposite time, alternate layout |
114
+ | Gantt | Horizontal schedule chart: day/week/month axis, bars, milestones, progress, today line |
113
115
  | TrendChart | Svelte wrapper for `@marianmeres/trend-chart` (subpath-only: `@marianmeres/stuic/trend-chart`) |
114
116
  | Tree | Hierarchical tree view with keyboard nav and drag-and-drop |
115
117
  | X | Styled close/multiply SVG icon |
@@ -159,7 +161,7 @@ Use `validate={false}` to bypass stuic's validation entirely.
159
161
 
160
162
  > **Why default-on?** Hidden-input field components (`FieldPhoneNumber`,
161
163
  > `FieldCountry`, `FieldObject`, `FieldAssets`, `FieldSingleAsset`, `FieldInputLocalized`,
162
- > `FieldKeyValues`, `FieldTable`, `FieldLikeButton`, `FieldDate`, `FieldDateRange`, `Rating`) _must_ be default-on because hidden
164
+ > `FieldKeyValues`, `FieldTable`, `FieldLikeButton`, `FieldDate`, `FieldDateRange`, `ColorPicker`, `FieldColorPicker`, `Rating`) _must_ be default-on because hidden
163
165
  > inputs are excluded from native browser constraint validation — without the
164
166
  > stuic action enforcing `required` in a `customValidator`, the attribute is a
165
167
  > silent no-op. Plain-input field components were harmonized to the same
@@ -170,8 +172,8 @@ Use `validate={false}` to bypass stuic's validation entirely.
170
172
  Available on `FieldInput`, `FieldMoney`, `FieldTextarea`, `FieldCheckbox`,
171
173
  `FieldSelect`, `FieldFile`, `FieldObject`, `FieldAssets`, `FieldSingleAsset`, `FieldInputLocalized`,
172
174
  `FieldKeyValues`, `FieldTable`, `FieldPhoneNumber`, `FieldCountry`, `FieldLikeButton`,
173
- `FieldRadios`, `FieldSwitch`, `FieldOptions`, `FieldDate`, `FieldDateRange`, and
174
- `Switch`:
175
+ `FieldRadios`, `FieldSwitch`, `FieldColorPicker`, `FieldOptions`, `FieldDate`, `FieldDateRange`,
176
+ `Switch`, and `ColorPicker`:
175
177
 
176
178
  | Method | Returns | Purpose |
177
179
  | ----------------------- | ------------------------------- | ------------------------------------------------------------- |
@@ -928,7 +930,9 @@ Swatch values are **never parsed** — they go to CSS as `--stuic-color-picker-s
928
930
  | `onchange` | `(value: string) => void` | — | User **commits** only (see below) |
929
931
  | `t` | `TranslateFn` | English | Group label, swatch names, "no color", custom-color labels, required message |
930
932
 
931
- Class slots: `class`, `classSwatch`.
933
+ Class slots: `class`, `classSwatch`. `labelledby` (an element id) names the swatch group via `aria-labelledby` instead of `label`'s `aria-label`.
934
+
935
+ **In a form layout** use `FieldColorPicker` (exported from `Input`): the same `InputWrap` shell as every `Field*` (visible label, description, validation box, `labelLeft*`, `InputWrapClassProps`), with the picker props forwarded. The visible label names the radiogroup via `labelledby`; `focus()` / `scrollIntoView()` join the imperative API so `scrollToFirstInvalidField` works. The Input stylesheet exempts inputs inside `.stuic-color-picker` from its field-input rules (`:where(:not(.stuic-color-picker *))`, zero specificity) — otherwise the hex field and native picker would lose their border, font and size inside the shell.
932
936
 
933
937
  ### Preview vs commit
934
938
 
@@ -1352,6 +1356,62 @@ Prefix: `--stuic-stepper-*`
1352
1356
 
1353
1357
  ---
1354
1358
 
1359
+ ## Gantt
1360
+
1361
+ Horizontal schedule chart — the classic project plan (task per row, progress, milestones) and the resource lane view (many bars per row) in one component. Whole-day, inclusive-at-both-ends ranges throughout (`YYYY-MM-DD`, or a `Date` read as its local calendar date), so no timezone can shift a bar by a day. Columns are days, weeks or months; `week`/`month` snap the window outwards to whole units, and bars are positioned _through_ the columns, so an equal-width February column still holds 28 days. Read-only by design: no drag-to-reschedule, no dependency arrows (use `Timeline` for a vertical event list).
1362
+
1363
+ Layout is one scroll container holding a sticky two-tier header (groups over units), a sticky left label column and, for the body, a **single** absolutely-positioned grid layer (gridlines + weekend shading + today line) rather than a cell per row per column. The frame always fills its parent and `unitWidth` is a _minimum_: a narrow axis shares the spare width between its columns (no dead gutter, and the grid layer keeps matching the track because both grow by the same rule), a wide one scrolls. `unitWidth="fit"` drops the minimum so the chart never scrolls sideways. The track is `overflow: clip`, so a label positioned past its bar cannot put a scrollbar on a chart that fits.
1364
+
1365
+ ### Exports
1366
+
1367
+ | Export | Kind | Description |
1368
+ | ------------------- | --------- | ------------------------------------------------------------------------------------------------------------- |
1369
+ | `Gantt` | component | Main component |
1370
+ | `GanttProps` | type | Props type |
1371
+ | `GanttRow` | type | `{ id?, label?, description?, bars, href?, data? }` |
1372
+ | `GanttBar` | type | `{ from, to?, label?, intent?, progress?, milestone?, inset?, href?, title?, disabled?, id?, class?, data? }` |
1373
+ | `GanttBarLabels` | type | `"inside" \| "after" \| "none"` |
1374
+ | `GanttSelectDetail` | type | `{ bar, row, rowIndex, barIndex, placement, point }` — `onSelect` / `renderBar` |
1375
+ | `GanttUnit` | type | `"day" \| "week" \| "month"` |
1376
+ | `GanttAxis` | type | `{ unit, from, to, start, end, columns, groups, totalDays }` |
1377
+ | `GanttColumn` | type | `{ date, days, offset, index, label, subLabel?, groupIndex, isWeekend, isToday }` |
1378
+ | `GanttGroup` | type | `{ key, label, span, from }` — a header group cell |
1379
+ | `GanttPlacement` | type | `{ start, end, clippedStart, clippedEnd, days }` — 0..1 track fractions |
1380
+ | `GanttDateInput` | type | `IsoDate \| Date` |
1381
+ | `GanttAxisOptions` | type | `{ unit?, weekStartsOn?, locale?, today? }` |
1382
+ | `buildGanttAxis` | function | `(from, to, options) => GanttAxis` — throws on a bad or too-wide window |
1383
+ | `placeRange` | function | `(from, to, axis) => GanttPlacement \| null` (`null` = misses the window) |
1384
+ | `placePoint` | function | `(date, axis) => number \| null` — the middle of that day, 0..1 |
1385
+ | `dayToFraction` | function | `(dayOffset, axis) => number` — column-aware, so month widths stay equal |
1386
+ | `boundsOf` | function | `(ranges) => { from, to } \| null` — the default window |
1387
+ | `isoWeekNumber` | function | `(iso) => number` — ISO-8601 week, used for the week header |
1388
+ | `GANTT_MAX_COLUMNS` | const | `2000` — over this `buildGanttAxis` throws instead of building the nodes |
1389
+
1390
+ ### Key Props
1391
+
1392
+ | Prop | Type | Default | Description |
1393
+ | ------------- | ----------------------------- | ---------- | ----------------------------------------------------------- |
1394
+ | `rows` | `GanttRow[]` | required | The lanes, in display order |
1395
+ | `from` / `to` | `IsoDate \| Date` | bar extent | The window, inclusive at both ends |
1396
+ | `unit` | `GanttUnit` | `"day"` | Column granularity |
1397
+ | `unitWidth` | `number \| "fit"` | token | **Minimum** column px (the zoom); `"fit"` drops the minimum |
1398
+ | `today` | `IsoDate \| Date \| null` | today | The marker line's day; `null` removes it |
1399
+ | `labels` | `boolean` | auto | Left label column — on when any row has a `label` |
1400
+ | `barLabels` | `GanttBarLabels` | `"inside"` | Where a bar's own label is drawn |
1401
+ | `onSelect` | `(GanttSelectDetail) => void` | — | Bar click; also what turns bars into focusable `<button>`s |
1402
+
1403
+ Also `weekStartsOn`, `locale`, `formatColumn`, `formatGroup`, `formatBarAria`. Snippets: `renderBar`, `renderRowLabel`, `renderCorner`, `empty`. Class slots: `class`, `classHeader`, `classRow`, `classRowLabel`, `classTrack`, `classBar`.
1404
+
1405
+ A bar entirely outside the window is dropped (a hairline at the edge reads as "starts today"); one that overhangs is clipped and flagged `data-clipped-start` / `data-clipped-end`. `data-*` attributes survive `unstyled` — they describe the data, not the styling.
1406
+
1407
+ ### CSS Tokens
1408
+
1409
+ Prefix: `--stuic-gantt-*`
1410
+
1411
+ `unit-width`, `label-width`, `row-height`, `max-height`, `bg`, `border-color`, `font-size`, `header-{bg,text,font-size,padding,sub-text}`, `grid-line-color`, `weekend-bg`, `today-color`, `today-width`, `row-bg-hover`, `label-{bg,padding,font-size,text}`, `description-{font-size,text}`, `bar-{height,height-inset,bg,text,font-size,padding-inline,progress-bg,opacity-disabled,radius}`, `milestone-{size,radius}`, `empty-padding`, `radius`, `border-width`, `transition`
1412
+
1413
+ ---
1414
+
1355
1415
  ## Timeline
1356
1416
 
1357
1417
  Vertical event list with markers on a rail — activity feeds, audit logs, order tracking, history pages. A real `<ol>` (explicit `role="list"`); every item has a marker (small dot, icon bubble when `item.icon` is set, or anything via `renderMarker`), an optional time label, title, description and footer. Data-driven (`items`), static by design — no "current" step (use `Stepper` vertical for progress flows). The list is a CSS grid with `subgrid` items, so the rail and the optional time column align across items without fixed widths; the rail line runs behind the markers and a background-colored ring separates them.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marianmeres/stuic",
3
- "version": "3.187.0",
3
+ "version": "3.189.0",
4
4
  "packageManager": "pnpm@11.5.0",
5
5
  "scripts": {
6
6
  "dev": "vite dev",