@stll/workspace-ui 0.3.2 → 0.4.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.
@@ -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>({ allowNone, excludedGroupBy, getBuiltInGroupLabel, labels, onValueChange, schema, value }: WorkspaceKanbanGroupingPickerProps<TRow, TProperty>) => import("react").JSX.Element;
8
+ //#endregion
9
+ export { WorkspaceKanbanGroupingPicker };
@@ -0,0 +1,39 @@
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
+ /**
7
+ * Accessible picker shared by the board's Group and Sub-group controls. The
8
+ * caller labels built-ins because only its domain owns their human names.
9
+ */
10
+ const WorkspaceKanbanGroupingPicker = ({ allowNone, excludedGroupBy, getBuiltInGroupLabel, labels, onValueChange, schema, value }) => {
11
+ const choices = getWorkspaceKanbanGroupingChoices({
12
+ getBuiltInGroupLabel,
13
+ schema
14
+ }).filter(({ id }) => id !== excludedGroupBy);
15
+ const display = choices.find((choice) => choice.id === value)?.label ?? (allowNone ? labels.none : labels.placeholder);
16
+ return /* @__PURE__ */ jsxs(Select, {
17
+ onValueChange: (nextValue) => {
18
+ if (nextValue === null) return;
19
+ onValueChange(nextValue);
20
+ },
21
+ value,
22
+ children: [/* @__PURE__ */ jsx(SelectTrigger, {
23
+ "aria-label": labels.control,
24
+ size: "sm",
25
+ children: /* @__PURE__ */ jsx(SelectValue, {
26
+ placeholder: labels.placeholder,
27
+ children: display
28
+ })
29
+ }), /* @__PURE__ */ jsxs(SelectPopup, { children: [allowNone ? /* @__PURE__ */ jsx(SelectItem, {
30
+ value: NO_GROUPING_VALUE,
31
+ children: labels.none
32
+ }) : null, choices.map((choice) => /* @__PURE__ */ jsx(SelectItem, {
33
+ value: choice.id,
34
+ children: choice.label
35
+ }, choice.id))] })]
36
+ });
37
+ };
38
+ //#endregion
39
+ export { WorkspaceKanbanGroupingPicker };
@@ -0,0 +1,105 @@
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 = {
17
+ emptyGroups: KanbanEmptyGroupVisibility;
18
+ groupBy: string;
19
+ hiddenGroups: readonly KanbanSavedGroupReference[];
20
+ orderedGroups: readonly KanbanSavedGroupReference[];
21
+ };
22
+ type KanbanSavedSubgroupState = KanbanSavedAxisState & {
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 = {
30
+ group: KanbanSavedAxisState;
31
+ subgroup: KanbanSavedSubgroupState | null;
32
+ version: 1;
33
+ };
34
+ type WorkspaceKanbanProperty = GenericProperty & {
35
+ id: string;
36
+ name: string;
37
+ };
38
+ type CreateWorkspaceKanbanSchemaParams<TRow, TProperty extends WorkspaceKanbanProperty> = {
39
+ builtInGroups: readonly KanbanBuiltInGroup<TRow>[];
40
+ properties: readonly TProperty[];
41
+ };
42
+ /**
43
+ * Turns the authoritative workspace property model into board declarations.
44
+ * Select options remain the sole source of property-derived columns and lanes.
45
+ */
46
+ declare const createWorkspaceKanbanSchema: <TRow, TProperty extends WorkspaceKanbanProperty>({ builtInGroups, properties }: CreateWorkspaceKanbanSchemaParams<TRow, TProperty>) => KanbanSchema<TRow, TProperty>;
47
+ type WorkspaceKanbanGroupingChoice = {
48
+ id: string;
49
+ label: string;
50
+ type: "built-in" | "property";
51
+ };
52
+ type WorkspaceKanbanBuiltInGroupLabel<TRow> = (group: KanbanBuiltInGroup<TRow>) => string;
53
+ /** Property-aware data for a Group or Sub-group picker. */
54
+ declare const getWorkspaceKanbanGroupingChoices: <TRow, TProperty extends WorkspaceKanbanProperty>({ getBuiltInGroupLabel, schema }: {
55
+ getBuiltInGroupLabel: WorkspaceKanbanBuiltInGroupLabel<TRow>;
56
+ schema: KanbanSchema<TRow, TProperty>;
57
+ }) => WorkspaceKanbanGroupingChoice[];
58
+ type WorkspaceKanbanGroupingPickerLabels = {
59
+ control: string;
60
+ none: string;
61
+ placeholder: string;
62
+ };
63
+ type WorkspaceKanbanGroupingPickerProps<TRow, TProperty extends WorkspaceKanbanProperty> = {
64
+ allowNone: boolean;
65
+ excludedGroupBy?: string | undefined;
66
+ getBuiltInGroupLabel: WorkspaceKanbanBuiltInGroupLabel<TRow>;
67
+ labels: WorkspaceKanbanGroupingPickerLabels;
68
+ onValueChange: (groupBy: string) => void;
69
+ schema: KanbanSchema<TRow, TProperty>;
70
+ value: string;
71
+ };
72
+ type ResolveWorkspaceKanbanViewParams<TRow, TProperty> = {
73
+ schema: KanbanSchema<TRow, TProperty>;
74
+ state: KanbanSavedViewState;
75
+ };
76
+ type ResolvedWorkspaceKanbanView<TRow, TProperty> = {
77
+ group: KanbanGrouping<TRow, TProperty>;
78
+ subgroup: KanbanGrouping<TRow, TProperty>;
79
+ };
80
+ /**
81
+ * A property cannot control both axes. Rejecting that configuration here means
82
+ * a later diagonal drag never has two contradictory assignments for one field.
83
+ */
84
+ declare const resolveWorkspaceKanbanView: <TRow, TProperty>({ schema, state }: ResolveWorkspaceKanbanViewParams<TRow, TProperty>) => ResolvedWorkspaceKanbanView<TRow, TProperty>;
85
+ type KanbanPresentedLane<TRow> = {
86
+ cells: KanbanBoardCell<TRow>[];
87
+ collapsed: boolean;
88
+ lane: KanbanBoardLane;
89
+ };
90
+ type KanbanBoardPresentation<TRow> = {
91
+ columns: KanbanGroup[];
92
+ lanes: KanbanPresentedLane<TRow>[];
93
+ matrix: KanbanBoardMatrix<TRow>;
94
+ };
95
+ /**
96
+ * Apply saved presentation state without rebuilding the matrix. This preserves
97
+ * the canonical placement invariant while letting a view hide empty groups,
98
+ * individually hide groups, reorder them, and collapse swimlanes.
99
+ */
100
+ declare const presentKanbanBoard: <TRow>({ matrix, state }: {
101
+ matrix: KanbanBoardMatrix<TRow>;
102
+ state: KanbanSavedViewState;
103
+ }) => KanbanBoardPresentation<TRow>;
104
+ //#endregion
105
+ 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.2",
3
+ "version": "0.4.0",
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.7.0",
114
+ "@stll/ui": "^0.9.0",
107
115
  "better-result": "3.0.0",
108
116
  "lucide-react": "1.30.0",
109
117
  "use-intl": "4.13.5"