@stll/workspace-ui 0.3.3 → 0.4.1

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.
@@ -28,7 +28,7 @@ type CalculationPickerProps = {
28
28
  * Choose what a view calculates: a property, then a reduction. Every step is a
29
29
  * menu item, so the whole choice is reachable from the keyboard.
30
30
  */
31
- declare const CalculationPicker: ({ labels, properties, selections, onChange, children }: CalculationPickerProps) => import("react").JSX.Element;
31
+ declare const CalculationPicker: ({ labels, properties, selections, onChange, children }: CalculationPickerProps) => import("react").JSX.Element | null;
32
32
  type CalculationKindPickerProps = {
33
33
  labels: WorkspaceCalculationLabels;
34
34
  /** Reductions this column's values allow. */
@@ -43,7 +43,7 @@ type CalculationKindPickerProps = {
43
43
  * Choose one column's reduction. The board picks a property first (it shows one
44
44
  * line for the whole board); a table column already is the property.
45
45
  */
46
- declare const CalculationKindPicker: ({ labels, kinds, value, onChange, children }: CalculationKindPickerProps) => import("react").JSX.Element;
46
+ declare const CalculationKindPicker: ({ labels, kinds, value, onChange, children }: CalculationKindPickerProps) => import("react").JSX.Element | null;
47
47
  type CalculationSummaryProps = {
48
48
  calculation: FormattedCalculation;
49
49
  };
@@ -13,6 +13,7 @@ import { jsx, jsxs } from "react/jsx-runtime";
13
13
  * menu item, so the whole choice is reachable from the keyboard.
14
14
  */
15
15
  const CalculationPicker = ({ labels, properties, selections, onChange, children }) => {
16
+ if (properties.length === 0) return null;
16
17
  const select = (propertyId, kind) => {
17
18
  onChange(applyCalculationSelection({
18
19
  selections,
@@ -27,10 +28,7 @@ const CalculationPicker = ({ labels, properties, selections, onChange, children
27
28
  variant: "ghost"
28
29
  }),
29
30
  children: children ?? /* @__PURE__ */ jsx(SigmaIcon, {})
30
- }), /* @__PURE__ */ jsxs(MenuPopup, { children: [properties.length === 0 && /* @__PURE__ */ jsx(MenuItem, {
31
- disabled: true,
32
- children: labels.noProperties
33
- }), properties.map((property) => {
31
+ }), /* @__PURE__ */ jsx(MenuPopup, { children: properties.map((property) => {
34
32
  const selected = selections.find((selection) => selection.propertyId === property.id);
35
33
  return /* @__PURE__ */ jsxs(MenuSub, { children: [/* @__PURE__ */ jsx(MenuSubTrigger, { children: property.name }), /* @__PURE__ */ jsxs(MenuSubPopup, { children: [/* @__PURE__ */ jsxs(MenuItem, {
36
34
  onClick: () => select(property.id, null),
@@ -39,26 +37,29 @@ const CalculationPicker = ({ labels, properties, selections, onChange, children
39
37
  onClick: () => select(property.id, kind),
40
38
  children: [selected?.kind === kind ? /* @__PURE__ */ jsx(CheckIcon, {}) : /* @__PURE__ */ jsx("span", {}), labels.kinds[kind]]
41
39
  }, kind))] })] }, property.id);
42
- })] })] });
40
+ }) })] });
43
41
  };
44
42
  /**
45
43
  * Choose one column's reduction. The board picks a property first (it shows one
46
44
  * line for the whole board); a table column already is the property.
47
45
  */
48
- const CalculationKindPicker = ({ labels, kinds, value, onChange, children }) => /* @__PURE__ */ jsxs(Menu, { children: [/* @__PURE__ */ jsx(MenuTrigger, {
49
- "aria-label": labels.choose,
50
- render: /* @__PURE__ */ jsx(Button, {
51
- size: "icon-xs",
52
- variant: "ghost"
53
- }),
54
- children: children ?? /* @__PURE__ */ jsx(SigmaIcon, {})
55
- }), /* @__PURE__ */ jsxs(MenuPopup, { children: [/* @__PURE__ */ jsxs(MenuItem, {
56
- onClick: () => onChange(null),
57
- children: [value === null ? /* @__PURE__ */ jsx(CheckIcon, {}) : /* @__PURE__ */ jsx("span", {}), labels.none]
58
- }), kinds.map((kind) => /* @__PURE__ */ jsxs(MenuItem, {
59
- onClick: () => onChange(kind),
60
- children: [value === kind ? /* @__PURE__ */ jsx(CheckIcon, {}) : /* @__PURE__ */ jsx("span", {}), labels.kinds[kind]]
61
- }, kind))] })] });
46
+ const CalculationKindPicker = ({ labels, kinds, value, onChange, children }) => {
47
+ if (kinds.length === 0) return null;
48
+ return /* @__PURE__ */ jsxs(Menu, { children: [/* @__PURE__ */ jsx(MenuTrigger, {
49
+ "aria-label": labels.choose,
50
+ render: /* @__PURE__ */ jsx(Button, {
51
+ size: "icon-xs",
52
+ variant: "ghost"
53
+ }),
54
+ children: children ?? /* @__PURE__ */ jsx(SigmaIcon, {})
55
+ }), /* @__PURE__ */ jsxs(MenuPopup, { children: [/* @__PURE__ */ jsxs(MenuItem, {
56
+ onClick: () => onChange(null),
57
+ children: [value === null ? /* @__PURE__ */ jsx(CheckIcon, {}) : /* @__PURE__ */ jsx("span", {}), labels.none]
58
+ }), kinds.map((kind) => /* @__PURE__ */ jsxs(MenuItem, {
59
+ onClick: () => onChange(kind),
60
+ children: [value === kind ? /* @__PURE__ */ jsx(CheckIcon, {}) : /* @__PURE__ */ jsx("span", {}), labels.kinds[kind]]
61
+ }, kind))] })] });
62
+ };
62
63
  /**
63
64
  * The calculation as one compact line. When it reduces to several currencies
64
65
  * the line stays short and the full set sits behind a tooltip.
package/dist/index.d.ts CHANGED
@@ -10,6 +10,8 @@ import { getClipFieldValueLabel } from "./field-value-logic.js";
10
10
  import { FieldValue, IntFieldValue, MoneyFieldValue, PersonFieldValue } from "./field-value.js";
11
11
  import { SortChips, SortChipsLabels, SortChipsProps, SortDescriptor, SortableProperty, sortDirectionHint } from "./sorts.js";
12
12
  import { TableSkeletonRows } from "./table-skeleton-rows.js";
13
+ import { CreateWorkspaceKanbanSchemaParams, KANBAN_EMPTY_GROUP_VISIBILITIES, KanbanBoardPresentation, KanbanEmptyGroupVisibility, KanbanPresentedLane, KanbanSavedAxisState, KanbanSavedGroupReference, KanbanSavedSubgroupState, KanbanSavedViewState, ResolveWorkspaceKanbanViewParams, ResolvedWorkspaceKanbanView, WorkspaceKanbanBuiltInGroupLabel, WorkspaceKanbanGroupingChoice, WorkspaceKanbanGroupingPickerLabels, WorkspaceKanbanGroupingPickerProps, WorkspaceKanbanProperty, createWorkspaceKanbanSchema, getWorkspaceKanbanGroupingChoices, presentKanbanBoard, resolveWorkspaceKanbanView } from "./kanban-view.js";
14
+ import { WorkspaceKanbanGroupingPicker } from "./kanban-grouping-picker.js";
13
15
  import { WorkspaceViewDirection, WorkspaceViewDropPosition, reorderWorkspaceViewIds, toWorkspaceViewDropPosition } from "./view-switcher.logic.js";
14
16
  import { WorkspaceViewSwitcher, WorkspaceViewSwitcherEditing, WorkspaceViewSwitcherItem, WorkspaceViewSwitcherProps, WorkspaceViewSwitcherReorder } from "./view-switcher.js";
15
- export { type ApplyCalculationSelectionParams, CONDITION_OPERATORS, type CalculationFormatters, CalculationKindPicker, type CalculationKindPickerProps, type CalculationLabels, CalculationPicker, type CalculationPickerProps, type CalculationProperty, type CalculationResult, type CalculationSelection, CalculationSummary, type CalculationSummaryProps, type ColorVariants, ColumnCalculation, ConditionBuilder, type ConditionBuilderLabels, type ConditionCapabilities, type ConditionOperator, FIELD_CONTENT_TYPES, type FieldContent, type FieldOption, type FieldOptionChoice, FieldValue, type FieldValueType, type FormatCalculationParams, type FormatMoneyCentsParams, type FormattedCalculation, type FormulaCellRenderCtx, type GenericProperty, IntFieldValue, MoneyFieldValue, type OptionColor, PersonFieldValue, PropertyIcon, type PropertyIconType, SortChips, type SortChipsLabels, type SortChipsProps, type SortDescriptor, type SortableProperty, TableSkeletonRows, type UseCalculationParams, type ValueEditorKind, type ValueEditorRenderCtx, type WorkspaceCalculationLabels, type WorkspaceFieldContent, type WorkspaceViewDirection, type WorkspaceViewDropPosition, WorkspaceViewSwitcher, type WorkspaceViewSwitcherEditing, type WorkspaceViewSwitcherItem, type WorkspaceViewSwitcherProps, type WorkspaceViewSwitcherReorder, appendChild, applyCalculationSelection, asGroup, buildLeaf, currencyMinorUnitDigits, emptyColor, fieldForNode, formatCalculationResult, formatMoneyCents, getClipFieldValueLabel, isConditionOperator, isMultiValue, leafFromField, leafOperand, leafOperator, leafValueList, leafValueString, operandsEqual, operatorsFor, optionColors, removeChild, reorderWorkspaceViewIds, replaceChild, resolveOptionColor, sortDirectionHint, toWorkspaceViewDropPosition, useCalculation, valueEditorFor };
17
+ export { type ApplyCalculationSelectionParams, CONDITION_OPERATORS, type CalculationFormatters, CalculationKindPicker, type CalculationKindPickerProps, type CalculationLabels, CalculationPicker, type CalculationPickerProps, type CalculationProperty, type CalculationResult, type CalculationSelection, CalculationSummary, type CalculationSummaryProps, type ColorVariants, ColumnCalculation, ConditionBuilder, type ConditionBuilderLabels, type ConditionCapabilities, type ConditionOperator, type CreateWorkspaceKanbanSchemaParams, FIELD_CONTENT_TYPES, type FieldContent, type FieldOption, type FieldOptionChoice, FieldValue, type FieldValueType, type FormatCalculationParams, type FormatMoneyCentsParams, type FormattedCalculation, type FormulaCellRenderCtx, type GenericProperty, IntFieldValue, KANBAN_EMPTY_GROUP_VISIBILITIES, type KanbanBoardPresentation, type KanbanEmptyGroupVisibility, type KanbanPresentedLane, type KanbanSavedAxisState, type KanbanSavedGroupReference, type KanbanSavedSubgroupState, type KanbanSavedViewState, MoneyFieldValue, type OptionColor, PersonFieldValue, PropertyIcon, type PropertyIconType, type ResolveWorkspaceKanbanViewParams, type ResolvedWorkspaceKanbanView, SortChips, type SortChipsLabels, type SortChipsProps, type SortDescriptor, type SortableProperty, TableSkeletonRows, type UseCalculationParams, type ValueEditorKind, type ValueEditorRenderCtx, type WorkspaceCalculationLabels, type WorkspaceFieldContent, type WorkspaceKanbanBuiltInGroupLabel, type WorkspaceKanbanGroupingChoice, WorkspaceKanbanGroupingPicker, type WorkspaceKanbanGroupingPickerLabels, type WorkspaceKanbanGroupingPickerProps, type WorkspaceKanbanProperty, type WorkspaceViewDirection, type WorkspaceViewDropPosition, WorkspaceViewSwitcher, type WorkspaceViewSwitcherEditing, type WorkspaceViewSwitcherItem, type WorkspaceViewSwitcherProps, type WorkspaceViewSwitcherReorder, appendChild, applyCalculationSelection, asGroup, buildLeaf, createWorkspaceKanbanSchema, currencyMinorUnitDigits, emptyColor, fieldForNode, formatCalculationResult, formatMoneyCents, getClipFieldValueLabel, getWorkspaceKanbanGroupingChoices, isConditionOperator, isMultiValue, leafFromField, leafOperand, leafOperator, leafValueList, leafValueString, operandsEqual, operatorsFor, optionColors, presentKanbanBoard, removeChild, reorderWorkspaceViewIds, replaceChild, resolveOptionColor, resolveWorkspaceKanbanView, sortDirectionHint, toWorkspaceViewDropPosition, useCalculation, valueEditorFor };
package/dist/index.js CHANGED
@@ -10,6 +10,8 @@ import { PropertyIcon } from "./property-icon.js";
10
10
  import { SortChips, sortDirectionHint } from "./sorts.js";
11
11
  import { TableSkeletonRows } from "./table-skeleton-rows.js";
12
12
  import { FIELD_CONTENT_TYPES } from "./types.js";
13
+ import { KANBAN_EMPTY_GROUP_VISIBILITIES, createWorkspaceKanbanSchema, getWorkspaceKanbanGroupingChoices, presentKanbanBoard, resolveWorkspaceKanbanView } from "./kanban-view.js";
14
+ import { WorkspaceKanbanGroupingPicker } from "./kanban-grouping-picker.js";
13
15
  import { reorderWorkspaceViewIds, toWorkspaceViewDropPosition } from "./view-switcher.logic.js";
14
16
  import { WorkspaceViewSwitcher } from "./view-switcher.js";
15
- export { CONDITION_OPERATORS, CalculationKindPicker, CalculationPicker, CalculationSummary, ColumnCalculation, ConditionBuilder, FIELD_CONTENT_TYPES, FieldValue, IntFieldValue, MoneyFieldValue, PersonFieldValue, PropertyIcon, SortChips, TableSkeletonRows, WorkspaceViewSwitcher, appendChild, applyCalculationSelection, asGroup, buildLeaf, currencyMinorUnitDigits, emptyColor, fieldForNode, formatCalculationResult, formatMoneyCents, getClipFieldValueLabel, isConditionOperator, isMultiValue, leafFromField, leafOperand, leafOperator, leafValueList, leafValueString, operandsEqual, operatorsFor, optionColors, removeChild, reorderWorkspaceViewIds, replaceChild, resolveOptionColor, sortDirectionHint, toWorkspaceViewDropPosition, useCalculation, valueEditorFor };
17
+ export { CONDITION_OPERATORS, CalculationKindPicker, CalculationPicker, CalculationSummary, ColumnCalculation, ConditionBuilder, FIELD_CONTENT_TYPES, FieldValue, IntFieldValue, KANBAN_EMPTY_GROUP_VISIBILITIES, MoneyFieldValue, PersonFieldValue, PropertyIcon, SortChips, TableSkeletonRows, WorkspaceKanbanGroupingPicker, WorkspaceViewSwitcher, appendChild, applyCalculationSelection, asGroup, buildLeaf, createWorkspaceKanbanSchema, currencyMinorUnitDigits, emptyColor, fieldForNode, formatCalculationResult, formatMoneyCents, getClipFieldValueLabel, getWorkspaceKanbanGroupingChoices, isConditionOperator, isMultiValue, leafFromField, leafOperand, leafOperator, leafValueList, leafValueString, operandsEqual, operatorsFor, optionColors, presentKanbanBoard, removeChild, reorderWorkspaceViewIds, replaceChild, resolveOptionColor, resolveWorkspaceKanbanView, sortDirectionHint, toWorkspaceViewDropPosition, useCalculation, valueEditorFor };
@@ -0,0 +1,9 @@
1
+ import { WorkspaceKanbanGroupingPickerProps, WorkspaceKanbanProperty } from "./kanban-view.js";
2
+ //#region src/kanban-grouping-picker.d.ts
3
+ /**
4
+ * Accessible picker shared by the board's Group and Sub-group controls. The
5
+ * caller labels built-ins because only its domain owns their human names.
6
+ */
7
+ declare const WorkspaceKanbanGroupingPicker: <TRow, TProperty extends WorkspaceKanbanProperty<TGroupId>, TGroupId extends string = string>({ allowNone, excludedGroupBy, getBuiltInGroupLabel, labels, onValueChange, schema, value }: WorkspaceKanbanGroupingPickerProps<TRow, TProperty, TGroupId>) => import("react").JSX.Element;
8
+ //#endregion
9
+ export { WorkspaceKanbanGroupingPicker };
@@ -0,0 +1,46 @@
1
+ import { getWorkspaceKanbanGroupingChoices } from "./kanban-view.js";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { Select, SelectItem, SelectPopup, SelectTrigger, SelectValue } from "@stll/ui/select";
4
+ //#region src/kanban-grouping-picker.tsx
5
+ const NO_GROUPING_VALUE = "";
6
+ const toSelectTransportValue = (value) => value;
7
+ /**
8
+ * Accessible picker shared by the board's Group and Sub-group controls. The
9
+ * caller labels built-ins because only its domain owns their human names.
10
+ */
11
+ const WorkspaceKanbanGroupingPicker = ({ allowNone, excludedGroupBy, getBuiltInGroupLabel, labels, onValueChange, schema, value }) => {
12
+ const choices = getWorkspaceKanbanGroupingChoices({
13
+ getBuiltInGroupLabel,
14
+ schema
15
+ }).filter(({ id }) => id !== excludedGroupBy);
16
+ const display = choices.find((choice) => choice.id === value)?.label ?? (allowNone ? labels.none : labels.placeholder);
17
+ const selectedValue = toSelectTransportValue(value);
18
+ return /* @__PURE__ */ jsxs(Select, {
19
+ onValueChange: (nextValue) => {
20
+ if (nextValue === null) return;
21
+ if (nextValue === NO_GROUPING_VALUE) {
22
+ onValueChange(NO_GROUPING_VALUE);
23
+ return;
24
+ }
25
+ const nextChoice = choices.find(({ id }) => id === nextValue);
26
+ if (nextChoice !== void 0) onValueChange(nextChoice.id);
27
+ },
28
+ value: selectedValue,
29
+ children: [/* @__PURE__ */ jsx(SelectTrigger, {
30
+ "aria-label": labels.control,
31
+ size: "sm",
32
+ children: /* @__PURE__ */ jsx(SelectValue, {
33
+ placeholder: labels.placeholder,
34
+ children: display
35
+ })
36
+ }), /* @__PURE__ */ jsxs(SelectPopup, { children: [allowNone ? /* @__PURE__ */ jsx(SelectItem, {
37
+ value: NO_GROUPING_VALUE,
38
+ children: labels.none
39
+ }) : null, choices.map((choice) => /* @__PURE__ */ jsx(SelectItem, {
40
+ value: toSelectTransportValue(choice.id),
41
+ children: choice.label
42
+ }, choice.id))] })]
43
+ });
44
+ };
45
+ //#endregion
46
+ export { WorkspaceKanbanGroupingPicker };
@@ -0,0 +1,106 @@
1
+ import { GenericProperty } from "./types.js";
2
+ import { KanbanBoardCell, KanbanBoardLane, KanbanBoardMatrix, KanbanBuiltInGroup, KanbanGroup, KanbanGrouping, KanbanSchema } from "@stll/ui/kanban";
3
+ //#region src/kanban-view.d.ts
4
+ declare const KANBAN_EMPTY_GROUP_VISIBILITIES: {
5
+ readonly hide: "hide";
6
+ readonly show: "show";
7
+ };
8
+ type KanbanEmptyGroupVisibility = (typeof KANBAN_EMPTY_GROUP_VISIBILITIES)[keyof typeof KANBAN_EMPTY_GROUP_VISIBILITIES];
9
+ /** A persisted reference keeps a real option distinct from the No value lane. */
10
+ type KanbanSavedGroupReference = {
11
+ type: "option";
12
+ value: string;
13
+ } | {
14
+ type: "uncategorized";
15
+ };
16
+ type KanbanSavedAxisState<TGroupId extends string = string> = {
17
+ emptyGroups: KanbanEmptyGroupVisibility;
18
+ groupBy: TGroupId;
19
+ hiddenGroups: readonly KanbanSavedGroupReference[];
20
+ orderedGroups: readonly KanbanSavedGroupReference[];
21
+ };
22
+ type KanbanSavedSubgroupState<TGroupId extends string = string> = KanbanSavedAxisState<TGroupId> & {
23
+ collapsedGroups: readonly KanbanSavedGroupReference[];
24
+ };
25
+ /**
26
+ * View state is deliberately separate from the board's domain data. It may
27
+ * change visibility, order, and collapse, but cannot change a card's cell.
28
+ */
29
+ type KanbanSavedViewState<TGroupId extends string = string> = {
30
+ group: KanbanSavedAxisState<TGroupId>;
31
+ subgroup: KanbanSavedSubgroupState<TGroupId> | null;
32
+ version: 1;
33
+ };
34
+ type WorkspaceKanbanProperty<TGroupId extends string = string> = GenericProperty & {
35
+ id: TGroupId;
36
+ name: string;
37
+ };
38
+ type CreateWorkspaceKanbanSchemaParams<TRow, TBuiltInGroupId extends string, TProperty extends WorkspaceKanbanProperty> = {
39
+ builtInGroups: readonly KanbanBuiltInGroup<TRow, TBuiltInGroupId>[];
40
+ properties: readonly TProperty[];
41
+ };
42
+ type WorkspaceKanbanSchemaGroupId<TBuiltInGroupId extends string, TProperty extends WorkspaceKanbanProperty> = TBuiltInGroupId | TProperty["id"];
43
+ /**
44
+ * Turns the authoritative workspace property model into board declarations.
45
+ * Select options remain the sole source of property-derived columns and lanes.
46
+ */
47
+ declare const createWorkspaceKanbanSchema: <TRow, TBuiltInGroupId extends string, TProperty extends WorkspaceKanbanProperty>({ builtInGroups, properties }: CreateWorkspaceKanbanSchemaParams<TRow, TBuiltInGroupId, TProperty>) => KanbanSchema<TRow, TProperty, WorkspaceKanbanSchemaGroupId<TBuiltInGroupId, TProperty>>;
48
+ type WorkspaceKanbanGroupingChoice<TGroupId extends string = string> = {
49
+ id: TGroupId;
50
+ label: string;
51
+ type: "built-in" | "property";
52
+ };
53
+ type WorkspaceKanbanBuiltInGroupLabel<TRow, TGroupId extends string = string> = (group: KanbanBuiltInGroup<TRow, TGroupId>) => string;
54
+ /** Property-aware data for a Group or Sub-group picker. */
55
+ declare const getWorkspaceKanbanGroupingChoices: <TRow, TProperty extends WorkspaceKanbanProperty<TGroupId>, TGroupId extends string = string>({ getBuiltInGroupLabel, schema }: {
56
+ getBuiltInGroupLabel: WorkspaceKanbanBuiltInGroupLabel<TRow, TGroupId>;
57
+ schema: KanbanSchema<TRow, TProperty, TGroupId>;
58
+ }) => WorkspaceKanbanGroupingChoice<TGroupId>[];
59
+ type WorkspaceKanbanGroupingPickerLabels = {
60
+ control: string;
61
+ none: string;
62
+ placeholder: string;
63
+ };
64
+ type WorkspaceKanbanGroupingPickerProps<TRow, TProperty extends WorkspaceKanbanProperty<TGroupId>, TGroupId extends string = string> = {
65
+ allowNone: boolean;
66
+ excludedGroupBy?: TGroupId | undefined;
67
+ getBuiltInGroupLabel: WorkspaceKanbanBuiltInGroupLabel<TRow, TGroupId>;
68
+ labels: WorkspaceKanbanGroupingPickerLabels;
69
+ onValueChange: (groupBy: TGroupId | "") => void;
70
+ schema: KanbanSchema<TRow, TProperty, TGroupId>;
71
+ value: TGroupId | "";
72
+ };
73
+ type ResolveWorkspaceKanbanViewParams<TRow, TProperty, TGroupId extends string = string> = {
74
+ schema: KanbanSchema<TRow, TProperty, TGroupId>;
75
+ state: KanbanSavedViewState<TGroupId>;
76
+ };
77
+ type ResolvedWorkspaceKanbanView<TRow, TProperty, TGroupId extends string = string> = {
78
+ group: KanbanGrouping<TRow, TProperty, TGroupId>;
79
+ subgroup: KanbanGrouping<TRow, TProperty, TGroupId>;
80
+ };
81
+ /**
82
+ * A property cannot control both axes. Rejecting that configuration here means
83
+ * a later diagonal drag never has two contradictory assignments for one field.
84
+ */
85
+ declare const resolveWorkspaceKanbanView: <TRow, TProperty, TGroupId extends string = string>({ schema, state }: ResolveWorkspaceKanbanViewParams<TRow, TProperty, TGroupId>) => ResolvedWorkspaceKanbanView<TRow, TProperty, TGroupId>;
86
+ type KanbanPresentedLane<TRow> = {
87
+ cells: KanbanBoardCell<TRow>[];
88
+ collapsed: boolean;
89
+ lane: KanbanBoardLane;
90
+ };
91
+ type KanbanBoardPresentation<TRow> = {
92
+ columns: KanbanGroup[];
93
+ lanes: KanbanPresentedLane<TRow>[];
94
+ matrix: KanbanBoardMatrix<TRow>;
95
+ };
96
+ /**
97
+ * Apply saved presentation state without rebuilding the matrix. This preserves
98
+ * the canonical placement invariant while letting a view hide empty groups,
99
+ * individually hide groups, reorder them, and collapse swimlanes.
100
+ */
101
+ declare const presentKanbanBoard: <TRow>({ matrix, state }: {
102
+ matrix: KanbanBoardMatrix<TRow>;
103
+ state: KanbanSavedViewState;
104
+ }) => KanbanBoardPresentation<TRow>;
105
+ //#endregion
106
+ export { CreateWorkspaceKanbanSchemaParams, KANBAN_EMPTY_GROUP_VISIBILITIES, KanbanBoardPresentation, KanbanEmptyGroupVisibility, KanbanPresentedLane, KanbanSavedAxisState, KanbanSavedGroupReference, KanbanSavedSubgroupState, KanbanSavedViewState, ResolveWorkspaceKanbanViewParams, ResolvedWorkspaceKanbanView, WorkspaceKanbanBuiltInGroupLabel, WorkspaceKanbanGroupingChoice, WorkspaceKanbanGroupingPickerLabels, WorkspaceKanbanGroupingPickerProps, WorkspaceKanbanProperty, createWorkspaceKanbanSchema, getWorkspaceKanbanGroupingChoices, presentKanbanBoard, resolveWorkspaceKanbanView };
@@ -0,0 +1,109 @@
1
+ import { panic } from "better-result";
2
+ import { orderKanbanCellsByColumns, resolveKanbanGrouping } from "@stll/ui/kanban";
3
+ //#region src/kanban-view.ts
4
+ const KANBAN_EMPTY_GROUP_VISIBILITIES = {
5
+ hide: "hide",
6
+ show: "show"
7
+ };
8
+ /**
9
+ * Turns the authoritative workspace property model into board declarations.
10
+ * Select options remain the sole source of property-derived columns and lanes.
11
+ */
12
+ const createWorkspaceKanbanSchema = ({ builtInGroups, properties }) => ({
13
+ builtInGroups,
14
+ getPropertyId: (property) => property.id,
15
+ getPropertyOptions: (property) => {
16
+ if (property.content.type !== "single-select") return null;
17
+ return property.content.options.map((option) => ({
18
+ optionColor: option.color,
19
+ value: option.value,
20
+ label: option.value
21
+ }));
22
+ },
23
+ properties
24
+ });
25
+ /** Property-aware data for a Group or Sub-group picker. */
26
+ const getWorkspaceKanbanGroupingChoices = ({ getBuiltInGroupLabel, schema }) => {
27
+ const choices = [];
28
+ for (const group of schema.builtInGroups) choices.push({
29
+ id: group.id,
30
+ label: getBuiltInGroupLabel(group),
31
+ type: "built-in"
32
+ });
33
+ for (const property of schema.properties) {
34
+ if (schema.getPropertyOptions(property) === null) continue;
35
+ choices.push({
36
+ id: property.id,
37
+ label: property.name,
38
+ type: "property"
39
+ });
40
+ }
41
+ return choices;
42
+ };
43
+ /**
44
+ * A property cannot control both axes. Rejecting that configuration here means
45
+ * a later diagonal drag never has two contradictory assignments for one field.
46
+ */
47
+ const resolveWorkspaceKanbanView = ({ schema, state }) => {
48
+ return {
49
+ group: resolveKanbanGrouping({
50
+ groupBy: state.group.groupBy,
51
+ schema
52
+ }),
53
+ subgroup: state.subgroup === null || state.subgroup.groupBy === state.group.groupBy ? resolveKanbanGrouping({
54
+ groupBy: "",
55
+ schema
56
+ }) : resolveKanbanGrouping({
57
+ groupBy: state.subgroup.groupBy,
58
+ schema
59
+ })
60
+ };
61
+ };
62
+ const sameGroupReference = (reference, group) => reference.type === "uncategorized" ? group.value === null : group.value === reference.value;
63
+ const refersToGroup = (references, group) => references.some((reference) => sameGroupReference(reference, group));
64
+ const containsGroup = (groups, group) => groups.some((candidate) => candidate.value === group.value);
65
+ const orderGroups = (groups, orderedGroups) => {
66
+ const ordered = [];
67
+ for (const reference of orderedGroups) {
68
+ const group = groups.find((candidate) => sameGroupReference(reference, candidate));
69
+ if (group !== void 0 && !containsGroup(ordered, group)) ordered.push(group);
70
+ }
71
+ for (const group of groups) if (!refersToGroup(orderedGroups, group)) ordered.push(group);
72
+ return ordered;
73
+ };
74
+ const isGroupEmpty = (group, matrix) => matrix.cells.every((cell) => cell.coordinate.column.value !== group.value || cell.rows.length === 0);
75
+ const isLaneEmpty = (lane, matrix) => matrix.cells.every((cell) => cell.coordinate.lane.type !== lane.type || lane.type === "group" && cell.coordinate.lane.type === "group" && cell.coordinate.lane.group.value !== lane.group.value || cell.rows.length === 0);
76
+ /**
77
+ * Apply saved presentation state without rebuilding the matrix. This preserves
78
+ * the canonical placement invariant while letting a view hide empty groups,
79
+ * individually hide groups, reorder them, and collapse swimlanes.
80
+ */
81
+ const presentKanbanBoard = ({ matrix, state }) => {
82
+ const columns = orderGroups(matrix.columns, state.group.orderedGroups).filter((group) => !refersToGroup(state.group.hiddenGroups, group) && (state.group.emptyGroups === "show" || !isGroupEmpty(group, matrix)));
83
+ const subgroupState = state.subgroup;
84
+ return {
85
+ columns,
86
+ lanes: (subgroupState === null || matrix.lanes.some((lane) => lane.type === "none") ? matrix.lanes : orderGroups(matrix.lanes.filter((lane) => lane.type === "group").map((lane) => lane.group), subgroupState.orderedGroups).map((group) => ({
87
+ group,
88
+ type: "group"
89
+ }))).filter((lane) => {
90
+ if (lane.type === "none" || subgroupState === null) return true;
91
+ return !refersToGroup(subgroupState.hiddenGroups, lane.group) && (subgroupState.emptyGroups === "show" || !isLaneEmpty(lane, matrix));
92
+ }).map((lane) => {
93
+ const collapsed = lane.type === "group" && subgroupState !== null && refersToGroup(subgroupState.collapsedGroups, lane.group);
94
+ const cells = orderKanbanCellsByColumns({
95
+ cells: matrix.cells.filter((cell) => lane.type === "none" ? cell.coordinate.lane.type === "none" : cell.coordinate.lane.type === "group" && cell.coordinate.lane.group.value === lane.group.value),
96
+ columns
97
+ });
98
+ if (cells.length !== columns.length) return panic("Kanban presentation lane is missing a declared cell");
99
+ return {
100
+ cells,
101
+ collapsed,
102
+ lane
103
+ };
104
+ }),
105
+ matrix
106
+ };
107
+ };
108
+ //#endregion
109
+ export { KANBAN_EMPTY_GROUP_VISIBILITIES, createWorkspaceKanbanSchema, getWorkspaceKanbanGroupingChoices, presentKanbanBoard, resolveWorkspaceKanbanView };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stll/workspace-ui",
3
- "version": "0.3.3",
3
+ "version": "0.4.1",
4
4
  "description": "Reusable workspace presentation components and view helpers for Stella.",
5
5
  "keywords": [
6
6
  "calculations",
@@ -59,6 +59,14 @@
59
59
  "types": "./dist/field-value-logic.d.ts",
60
60
  "import": "./dist/field-value-logic.js"
61
61
  },
62
+ "./kanban-grouping-picker": {
63
+ "types": "./dist/kanban-grouping-picker.d.ts",
64
+ "import": "./dist/kanban-grouping-picker.js"
65
+ },
66
+ "./kanban-view": {
67
+ "types": "./dist/kanban-view.d.ts",
68
+ "import": "./dist/kanban-view.js"
69
+ },
62
70
  "./property-icon": {
63
71
  "types": "./dist/property-icon.d.ts",
64
72
  "import": "./dist/property-icon.js"
@@ -103,7 +111,7 @@
103
111
  "@stll/calculations": "^0.1.0",
104
112
  "@stll/conditions": "^0.2.0",
105
113
  "@stll/money": "^0.1.0",
106
- "@stll/ui": "^0.8.0",
114
+ "@stll/ui": "^0.9.1",
107
115
  "better-result": "3.0.0",
108
116
  "lucide-react": "1.30.0",
109
117
  "use-intl": "4.13.5"