@cratis/scene.blueprint.default 4.32.0 → 4.33.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.
Files changed (122) hide show
  1. package/ComponentName.ts +12 -0
  2. package/defaultBlueprintComponents.ts +5 -1
  3. package/dist/cjs/ComponentName.js +4 -0
  4. package/dist/cjs/ComponentName.js.map +1 -1
  5. package/dist/cjs/defaultBlueprint.js +1 -1
  6. package/dist/cjs/defaultBlueprintComponentMetadata.js +2 -2
  7. package/dist/cjs/defaultBlueprintComponents.js +10 -2
  8. package/dist/cjs/defaultBlueprintComponents.js.map +1 -1
  9. package/dist/cjs/foundation/ChartWrapper.js +520 -0
  10. package/dist/cjs/foundation/ChartWrapper.js.map +1 -0
  11. package/dist/cjs/foundation/ColumnChooser.js +156 -0
  12. package/dist/cjs/foundation/ColumnChooser.js.map +1 -0
  13. package/dist/cjs/foundation/FilterBuilder.js +307 -0
  14. package/dist/cjs/foundation/FilterBuilder.js.map +1 -0
  15. package/dist/cjs/foundation/SavedViewsMenu.js +199 -0
  16. package/dist/cjs/foundation/SavedViewsMenu.js.map +1 -0
  17. package/dist/cjs/foundation/chartGeometry.js +101 -0
  18. package/dist/cjs/foundation/chartGeometry.js.map +1 -0
  19. package/dist/cjs/foundation/columnModel.js +43 -0
  20. package/dist/cjs/foundation/columnModel.js.map +1 -0
  21. package/dist/cjs/foundation/filterModel.js +123 -0
  22. package/dist/cjs/foundation/filterModel.js.map +1 -0
  23. package/dist/cjs/foundation/foundationComponentMetadata.js +28 -0
  24. package/dist/cjs/foundation/foundationComponentMetadata.js.map +1 -1
  25. package/dist/cjs/foundation/foundationDescriptors.js +153 -0
  26. package/dist/cjs/foundation/foundationDescriptors.js.map +1 -1
  27. package/dist/cjs/foundation/foundationSamples.js +130 -0
  28. package/dist/cjs/foundation/foundationSamples.js.map +1 -1
  29. package/dist/cjs/foundation/foundationStrings.js +25 -0
  30. package/dist/cjs/foundation/foundationStrings.js.map +1 -1
  31. package/dist/cjs/foundation/useElementWidth.js +31 -0
  32. package/dist/cjs/foundation/useElementWidth.js.map +1 -0
  33. package/dist/cjs/foundation/viewModel.js +24 -0
  34. package/dist/cjs/foundation/viewModel.js.map +1 -0
  35. package/dist/cjs/index.js +38 -0
  36. package/dist/cjs/index.js.map +1 -1
  37. package/dist/esm/ComponentName.d.ts +5 -1
  38. package/dist/esm/ComponentName.d.ts.map +1 -1
  39. package/dist/esm/ComponentName.js +4 -0
  40. package/dist/esm/ComponentName.js.map +1 -1
  41. package/dist/esm/defaultBlueprint.js +1 -1
  42. package/dist/esm/defaultBlueprintComponentMetadata.js +2 -2
  43. package/dist/esm/defaultBlueprintComponents.d.ts.map +1 -1
  44. package/dist/esm/defaultBlueprintComponents.js +10 -2
  45. package/dist/esm/defaultBlueprintComponents.js.map +1 -1
  46. package/dist/esm/foundation/ChartWrapper.d.ts +5 -0
  47. package/dist/esm/foundation/ChartWrapper.d.ts.map +1 -0
  48. package/dist/esm/foundation/ChartWrapper.js +517 -0
  49. package/dist/esm/foundation/ChartWrapper.js.map +1 -0
  50. package/dist/esm/foundation/ColumnChooser.d.ts +3 -0
  51. package/dist/esm/foundation/ColumnChooser.d.ts.map +1 -0
  52. package/dist/esm/foundation/ColumnChooser.js +154 -0
  53. package/dist/esm/foundation/ColumnChooser.js.map +1 -0
  54. package/dist/esm/foundation/FilterBuilder.d.ts +3 -0
  55. package/dist/esm/foundation/FilterBuilder.d.ts.map +1 -0
  56. package/dist/esm/foundation/FilterBuilder.js +305 -0
  57. package/dist/esm/foundation/FilterBuilder.js.map +1 -0
  58. package/dist/esm/foundation/Foundation.stories.d.ts +16 -0
  59. package/dist/esm/foundation/Foundation.stories.d.ts.map +1 -1
  60. package/dist/esm/foundation/Foundation.stories.js +16 -0
  61. package/dist/esm/foundation/Foundation.stories.js.map +1 -1
  62. package/dist/esm/foundation/SavedViewsMenu.d.ts +4 -0
  63. package/dist/esm/foundation/SavedViewsMenu.d.ts.map +1 -0
  64. package/dist/esm/foundation/SavedViewsMenu.js +196 -0
  65. package/dist/esm/foundation/SavedViewsMenu.js.map +1 -0
  66. package/dist/esm/foundation/chartGeometry.d.ts +34 -0
  67. package/dist/esm/foundation/chartGeometry.d.ts.map +1 -0
  68. package/dist/esm/foundation/chartGeometry.js +93 -0
  69. package/dist/esm/foundation/chartGeometry.js.map +1 -0
  70. package/dist/esm/foundation/columnModel.d.ts +11 -0
  71. package/dist/esm/foundation/columnModel.d.ts.map +1 -0
  72. package/dist/esm/foundation/columnModel.js +38 -0
  73. package/dist/esm/foundation/columnModel.js.map +1 -0
  74. package/dist/esm/foundation/filterModel.d.ts +33 -0
  75. package/dist/esm/foundation/filterModel.d.ts.map +1 -0
  76. package/dist/esm/foundation/filterModel.js +112 -0
  77. package/dist/esm/foundation/filterModel.js.map +1 -0
  78. package/dist/esm/foundation/foundationComponentMetadata.d.ts.map +1 -1
  79. package/dist/esm/foundation/foundationComponentMetadata.js +28 -0
  80. package/dist/esm/foundation/foundationComponentMetadata.js.map +1 -1
  81. package/dist/esm/foundation/foundationDescriptors.d.ts.map +1 -1
  82. package/dist/esm/foundation/foundationDescriptors.js +154 -1
  83. package/dist/esm/foundation/foundationDescriptors.js.map +1 -1
  84. package/dist/esm/foundation/foundationSamples.d.ts.map +1 -1
  85. package/dist/esm/foundation/foundationSamples.js +130 -0
  86. package/dist/esm/foundation/foundationSamples.js.map +1 -1
  87. package/dist/esm/foundation/foundationStrings.d.ts +25 -0
  88. package/dist/esm/foundation/foundationStrings.d.ts.map +1 -1
  89. package/dist/esm/foundation/foundationStrings.js +25 -0
  90. package/dist/esm/foundation/foundationStrings.js.map +1 -1
  91. package/dist/esm/foundation/index.d.ts +9 -0
  92. package/dist/esm/foundation/index.d.ts.map +1 -1
  93. package/dist/esm/foundation/index.js +9 -0
  94. package/dist/esm/foundation/index.js.map +1 -1
  95. package/dist/esm/foundation/useElementWidth.d.ts +3 -0
  96. package/dist/esm/foundation/useElementWidth.d.ts.map +1 -0
  97. package/dist/esm/foundation/useElementWidth.js +29 -0
  98. package/dist/esm/foundation/useElementWidth.js.map +1 -0
  99. package/dist/esm/foundation/viewModel.d.ts +7 -0
  100. package/dist/esm/foundation/viewModel.d.ts.map +1 -0
  101. package/dist/esm/foundation/viewModel.js +22 -0
  102. package/dist/esm/foundation/viewModel.js.map +1 -0
  103. package/dist/esm/index.js +9 -0
  104. package/dist/esm/index.js.map +1 -1
  105. package/dist/esm/tsconfig.tsbuildinfo +1 -1
  106. package/foundation/ChartWrapper.tsx +252 -0
  107. package/foundation/ColumnChooser.tsx +73 -0
  108. package/foundation/FilterBuilder.tsx +144 -0
  109. package/foundation/Foundation.stories.tsx +16 -0
  110. package/foundation/SavedViewsMenu.tsx +94 -0
  111. package/foundation/chartGeometry.ts +108 -0
  112. package/foundation/columnModel.ts +39 -0
  113. package/foundation/filterModel.ts +111 -0
  114. package/foundation/foundationComponentMetadata.ts +4 -0
  115. package/foundation/foundationDescriptors.ts +86 -1
  116. package/foundation/foundationSamples.ts +19 -0
  117. package/foundation/foundationStrings.ts +25 -0
  118. package/foundation/index.ts +9 -0
  119. package/foundation/useElementWidth.ts +31 -0
  120. package/foundation/viewModel.ts +21 -0
  121. package/package.json +8 -8
  122. package/shell/layout.css +285 -0
@@ -0,0 +1,196 @@
1
+ import { jsx, jsxs } from 'react/jsx-runtime';
2
+ import { useMemo, useState, useId, useRef, useEffect } from 'react';
3
+ import { useValueOutput } from '@cratis/scene.react';
4
+ import { readComponentState, ComponentState } from './ComponentState.js';
5
+ import { FoundationIcon } from './FoundationIcon.js';
6
+ import { readFoundationStrings } from './foundationStrings.js';
7
+ import { readPermissionRule, PermissionRule } from './PermissionRule.js';
8
+ import { FoundationFrame } from './StateFrame.js';
9
+ import { readOptionalString } from '../shell/elementProperties.js';
10
+ import { readViews } from './viewModel.js';
11
+
12
+ /** The output a menu action is published under: `save` or `manage`, which the host acts on. */ const savedViewActionOutput = 'action';
13
+ /**
14
+ * The `savedViewsMenu` component: a list's saved views as a menu button, with the current view as its label.
15
+ *
16
+ * `views` are the choices (id, label, description) and `activeView` the one in force. Choosing a view publishes its id as the `value`
17
+ * output and runs the behaviors attached to the element, so a screen binds its query to the view. The menu follows the ARIA menu
18
+ * pattern: arrows, Home and End move, Enter chooses, Escape closes and returns focus to the button. "Save current view" and
19
+ * "Manage views" publish `save` or `manage` on the `action` output, and each honours its own permission rule (`savePermission`,
20
+ * `managePermission`): hidden, or disabled with the reason.
21
+ */ function SavedViewsMenu({ element, interactions, bindingOutputs }) {
22
+ const views = useMemo(()=>readViews(element.properties.views), [
23
+ element.properties.views
24
+ ]);
25
+ const suppliedActive = readOptionalString(element, 'activeView') ?? views[0]?.id;
26
+ const [active, setActive] = useState(suppliedActive);
27
+ const [open, setOpen] = useState(false);
28
+ const strings = readFoundationStrings(element);
29
+ const state = readComponentState(element, views.length > 0);
30
+ const menuId = useId();
31
+ const button = useRef(null);
32
+ const menu = useRef(null);
33
+ const rules = {
34
+ save: readPermissionRule(element, 'savePermission'),
35
+ manage: readPermissionRule(element, 'managePermission')
36
+ };
37
+ useEffect(()=>setActive(suppliedActive), [
38
+ suppliedActive
39
+ ]);
40
+ useValueOutput(bindingOutputs, active);
41
+ useEffect(()=>{
42
+ if (open) menu.current?.querySelector('[role^="menuitem"]:not([aria-disabled="true"])')?.focus();
43
+ }, [
44
+ open
45
+ ]);
46
+ const items = ()=>Array.from(menu.current?.querySelectorAll('[role^="menuitem"]:not([aria-disabled="true"])') ?? []);
47
+ const move = (event)=>{
48
+ const entries = items();
49
+ const index = entries.indexOf(document.activeElement);
50
+ const target = event.key === 'ArrowDown' ? (index + 1) % entries.length : event.key === 'ArrowUp' ? (index - 1 + entries.length) % entries.length : event.key === 'Home' ? 0 : event.key === 'End' ? entries.length - 1 : -1;
51
+ if (target >= 0) {
52
+ event.preventDefault();
53
+ entries[target]?.focus();
54
+ }
55
+ if (event.key === 'Escape') {
56
+ setOpen(false);
57
+ button.current?.focus();
58
+ }
59
+ if (event.key === 'Tab') setOpen(false);
60
+ };
61
+ const choose = (id)=>{
62
+ setActive(id);
63
+ setOpen(false);
64
+ button.current?.focus();
65
+ interactions?.onSelect?.();
66
+ };
67
+ const run = (action)=>{
68
+ bindingOutputs?.setOutput(savedViewActionOutput, action);
69
+ setOpen(false);
70
+ button.current?.focus();
71
+ interactions?.onClick?.({
72
+ stopPropagation: ()=>undefined
73
+ });
74
+ };
75
+ const label = views.find((view)=>view.id === active)?.label ?? strings.viewsDefault;
76
+ return /*#__PURE__*/ jsx(FoundationFrame, {
77
+ element: element,
78
+ interactions: interactions,
79
+ name: "saved-views",
80
+ state: state,
81
+ skeleton: [
82
+ 45
83
+ ],
84
+ children: state !== ComponentState.Loading && /*#__PURE__*/ jsxs("div", {
85
+ className: "foundation-popover-host",
86
+ children: [
87
+ /*#__PURE__*/ jsxs("button", {
88
+ ref: button,
89
+ type: "button",
90
+ className: "foundation-button",
91
+ "data-emphasis": "secondary",
92
+ "aria-haspopup": "menu",
93
+ "aria-expanded": open,
94
+ "aria-controls": menuId,
95
+ onClick: ()=>setOpen(!open),
96
+ onKeyDown: (event)=>{
97
+ if (event.key === 'ArrowDown' && !open) {
98
+ event.preventDefault();
99
+ setOpen(true);
100
+ }
101
+ },
102
+ children: [
103
+ /*#__PURE__*/ jsx("span", {
104
+ className: "foundation-visually-hidden",
105
+ children: `${strings.viewsLabel}: `
106
+ }),
107
+ /*#__PURE__*/ jsx("span", {
108
+ children: label
109
+ }),
110
+ /*#__PURE__*/ jsx(FoundationIcon, {
111
+ name: "chevron-down"
112
+ })
113
+ ]
114
+ }),
115
+ /*#__PURE__*/ jsxs("div", {
116
+ id: menuId,
117
+ ref: menu,
118
+ className: "foundation-popover",
119
+ role: "menu",
120
+ "aria-label": strings.viewsLabel,
121
+ hidden: !open,
122
+ onKeyDown: move,
123
+ children: [
124
+ views.length === 0 && /*#__PURE__*/ jsx("span", {
125
+ className: "foundation-count",
126
+ children: strings.viewsNone
127
+ }),
128
+ views.map((view)=>/*#__PURE__*/ jsxs("button", {
129
+ type: "button",
130
+ role: "menuitemradio",
131
+ "aria-checked": view.id === active,
132
+ className: "foundation-menu-item",
133
+ tabIndex: -1,
134
+ onClick: ()=>choose(view.id),
135
+ children: [
136
+ /*#__PURE__*/ jsx("span", {
137
+ className: "foundation-menu-check",
138
+ children: view.id === active && /*#__PURE__*/ jsx(FoundationIcon, {
139
+ name: "check"
140
+ })
141
+ }),
142
+ /*#__PURE__*/ jsx("span", {
143
+ children: view.label
144
+ }),
145
+ view.description && /*#__PURE__*/ jsx("span", {
146
+ className: "foundation-count",
147
+ children: view.description
148
+ })
149
+ ]
150
+ }, view.id)),
151
+ (rules.save !== PermissionRule.Hidden || rules.manage !== PermissionRule.Hidden) && /*#__PURE__*/ jsx("hr", {
152
+ className: "foundation-separator"
153
+ }),
154
+ rules.save !== PermissionRule.Hidden && /*#__PURE__*/ jsxs("button", {
155
+ type: "button",
156
+ role: "menuitem",
157
+ className: "foundation-menu-item",
158
+ tabIndex: -1,
159
+ "aria-disabled": rules.save === PermissionRule.Disabled || undefined,
160
+ title: rules.save === PermissionRule.Disabled ? strings.disabledNoPermission : undefined,
161
+ onClick: rules.save === PermissionRule.Enabled ? ()=>run('save') : undefined,
162
+ children: [
163
+ /*#__PURE__*/ jsx("span", {
164
+ className: "foundation-menu-check"
165
+ }),
166
+ /*#__PURE__*/ jsx("span", {
167
+ children: strings.viewsSave
168
+ })
169
+ ]
170
+ }),
171
+ rules.manage !== PermissionRule.Hidden && /*#__PURE__*/ jsxs("button", {
172
+ type: "button",
173
+ role: "menuitem",
174
+ className: "foundation-menu-item",
175
+ tabIndex: -1,
176
+ "aria-disabled": rules.manage === PermissionRule.Disabled || undefined,
177
+ title: rules.manage === PermissionRule.Disabled ? strings.disabledNoPermission : undefined,
178
+ onClick: rules.manage === PermissionRule.Enabled ? ()=>run('manage') : undefined,
179
+ children: [
180
+ /*#__PURE__*/ jsx("span", {
181
+ className: "foundation-menu-check"
182
+ }),
183
+ /*#__PURE__*/ jsx("span", {
184
+ children: strings.viewsManage
185
+ })
186
+ ]
187
+ })
188
+ ]
189
+ })
190
+ ]
191
+ })
192
+ });
193
+ }
194
+
195
+ export { SavedViewsMenu, savedViewActionOutput };
196
+ //# sourceMappingURL=SavedViewsMenu.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SavedViewsMenu.js","sources":["../../../foundation/SavedViewsMenu.tsx"],"sourcesContent":["// Copyright (c) Cratis. All rights reserved.\n// Licensed under the MIT license. See LICENSE file in the project root for full license information.\n\nimport { KeyboardEvent, useEffect, useId, useMemo, useRef, useState } from 'react';\nimport { RegisteredComponentProps, useValueOutput } from '@cratis/scene.react';\nimport { ComponentState, readComponentState } from './ComponentState';\nimport { FoundationIcon } from './FoundationIcon';\nimport { readFoundationStrings } from './foundationStrings';\nimport { PermissionRule, readPermissionRule } from './PermissionRule';\nimport { FoundationFrame } from './StateFrame';\nimport { readOptionalString } from '../shell/elementProperties';\nimport { readViews } from './viewModel';\n\n/** The output a menu action is published under: `save` or `manage`, which the host acts on. */\nexport const savedViewActionOutput = 'action';\n\n/**\n * The `savedViewsMenu` component: a list's saved views as a menu button, with the current view as its label.\n *\n * `views` are the choices (id, label, description) and `activeView` the one in force. Choosing a view publishes its id as the `value`\n * output and runs the behaviors attached to the element, so a screen binds its query to the view. The menu follows the ARIA menu\n * pattern: arrows, Home and End move, Enter chooses, Escape closes and returns focus to the button. \"Save current view\" and\n * \"Manage views\" publish `save` or `manage` on the `action` output, and each honours its own permission rule (`savePermission`,\n * `managePermission`): hidden, or disabled with the reason.\n */\nexport function SavedViewsMenu({ element, interactions, bindingOutputs }: RegisteredComponentProps) {\n const views = useMemo(() => readViews(element.properties.views), [element.properties.views]);\n const suppliedActive = readOptionalString(element, 'activeView') ?? views[0]?.id;\n const [active, setActive] = useState<string | undefined>(suppliedActive);\n const [open, setOpen] = useState(false);\n const strings = readFoundationStrings(element);\n const state = readComponentState(element, views.length > 0);\n const menuId = useId();\n const button = useRef<HTMLButtonElement>(null);\n const menu = useRef<HTMLDivElement>(null);\n const rules = { save: readPermissionRule(element, 'savePermission'), manage: readPermissionRule(element, 'managePermission') };\n\n useEffect(() => setActive(suppliedActive), [suppliedActive]);\n useValueOutput(bindingOutputs, active);\n useEffect(() => { if (open) menu.current?.querySelector<HTMLElement>('[role^=\"menuitem\"]:not([aria-disabled=\"true\"])')?.focus(); }, [open]);\n\n const items = () => Array.from(menu.current?.querySelectorAll<HTMLElement>('[role^=\"menuitem\"]:not([aria-disabled=\"true\"])') ?? []);\n const move = (event: KeyboardEvent) => {\n const entries = items();\n const index = entries.indexOf(document.activeElement as HTMLElement);\n const target = event.key === 'ArrowDown' ? (index + 1) % entries.length : event.key === 'ArrowUp' ? (index - 1 + entries.length) % entries.length\n : event.key === 'Home' ? 0 : event.key === 'End' ? entries.length - 1 : -1;\n\n if (target >= 0) { event.preventDefault(); entries[target]?.focus(); }\n if (event.key === 'Escape') { setOpen(false); button.current?.focus(); }\n if (event.key === 'Tab') setOpen(false);\n };\n const choose = (id: string) => { setActive(id); setOpen(false); button.current?.focus(); interactions?.onSelect?.(); };\n const run = (action: 'save' | 'manage') => { bindingOutputs?.setOutput(savedViewActionOutput, action); setOpen(false); button.current?.focus(); interactions?.onClick?.({ stopPropagation: () => undefined }); };\n const label = views.find(view => view.id === active)?.label ?? strings.viewsDefault;\n\n return (\n <FoundationFrame element={element} interactions={interactions} name='saved-views' state={state} skeleton={[45]}>\n {state !== ComponentState.Loading && (\n <div className='foundation-popover-host'>\n <button ref={button} type='button' className='foundation-button' data-emphasis='secondary' aria-haspopup='menu' aria-expanded={open} aria-controls={menuId}\n onClick={() => setOpen(!open)} onKeyDown={event => { if (event.key === 'ArrowDown' && !open) { event.preventDefault(); setOpen(true); } }}>\n <span className='foundation-visually-hidden'>{`${strings.viewsLabel}: `}</span>\n <span>{label}</span>\n <FoundationIcon name='chevron-down' />\n </button>\n <div id={menuId} ref={menu} className='foundation-popover' role='menu' aria-label={strings.viewsLabel} hidden={!open} onKeyDown={move}>\n {views.length === 0 && <span className='foundation-count'>{strings.viewsNone}</span>}\n {views.map(view => (\n <button key={view.id} type='button' role='menuitemradio' aria-checked={view.id === active} className='foundation-menu-item' tabIndex={-1} onClick={() => choose(view.id)}>\n <span className='foundation-menu-check'>{view.id === active && <FoundationIcon name='check' />}</span>\n <span>{view.label}</span>\n {view.description && <span className='foundation-count'>{view.description}</span>}\n </button>\n ))}\n {(rules.save !== PermissionRule.Hidden || rules.manage !== PermissionRule.Hidden) && <hr className='foundation-separator' />}\n {rules.save !== PermissionRule.Hidden && (\n <button type='button' role='menuitem' className='foundation-menu-item' tabIndex={-1} aria-disabled={rules.save === PermissionRule.Disabled || undefined}\n title={rules.save === PermissionRule.Disabled ? strings.disabledNoPermission : undefined} onClick={rules.save === PermissionRule.Enabled ? () => run('save') : undefined}>\n <span className='foundation-menu-check' /><span>{strings.viewsSave}</span>\n </button>\n )}\n {rules.manage !== PermissionRule.Hidden && (\n <button type='button' role='menuitem' className='foundation-menu-item' tabIndex={-1} aria-disabled={rules.manage === PermissionRule.Disabled || undefined}\n title={rules.manage === PermissionRule.Disabled ? strings.disabledNoPermission : undefined} onClick={rules.manage === PermissionRule.Enabled ? () => run('manage') : undefined}>\n <span className='foundation-menu-check' /><span>{strings.viewsManage}</span>\n </button>\n )}\n </div>\n </div>\n )}\n </FoundationFrame>\n );\n}\n"],"names":["savedViewActionOutput","SavedViewsMenu","element","interactions","bindingOutputs","views","useMemo","readViews","properties","suppliedActive","readOptionalString","id","active","setActive","useState","open","setOpen","strings","readFoundationStrings","state","readComponentState","length","menuId","useId","button","useRef","menu","rules","save","readPermissionRule","manage","useEffect","useValueOutput","current","querySelector","focus","items","Array","from","querySelectorAll","move","event","entries","index","indexOf","document","activeElement","target","key","preventDefault","choose","onSelect","run","action","setOutput","onClick","stopPropagation","undefined","label","find","view","viewsDefault","_jsx","FoundationFrame","name","skeleton","ComponentState","Loading","_jsxs","div","className","ref","type","data-emphasis","aria-haspopup","aria-expanded","aria-controls","onKeyDown","span","viewsLabel","FoundationIcon","role","aria-label","hidden","viewsNone","map","aria-checked","tabIndex","description","PermissionRule","Hidden","hr","aria-disabled","Disabled","title","disabledNoPermission","Enabled","viewsSave","viewsManage"],"mappings":"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"}
@@ -0,0 +1,34 @@
1
+ export declare enum ChartKind {
2
+ Line = "line",
3
+ Bar = "bar",
4
+ Stacked = "stacked",
5
+ Donut = "donut",
6
+ Funnel = "funnel"
7
+ }
8
+ export interface ChartSeries {
9
+ name: string;
10
+ values: number[];
11
+ }
12
+ export interface ChartScale {
13
+ maximum: number;
14
+ ticks: number[];
15
+ }
16
+ export declare function niceScale(maximum: number, target?: number): ChartScale;
17
+ export declare function readSeries(value: unknown): ChartSeries[];
18
+ export declare function dataMaximum(kind: ChartKind, series: ChartSeries[], categoryCount: number): number;
19
+ export interface DonutSlice {
20
+ index: number;
21
+ value: number;
22
+ share: number;
23
+ start: number;
24
+ end: number;
25
+ }
26
+ export declare function donutSlices(values: number[]): DonutSlice[];
27
+ export declare function ringPath(centerX: number, centerY: number, outer: number, inner: number, start: number, end: number): string;
28
+ export interface FunnelStage {
29
+ index: number;
30
+ value: number;
31
+ widthRatio: number;
32
+ }
33
+ export declare function funnelStages(values: number[]): FunnelStage[];
34
+ //# sourceMappingURL=chartGeometry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chartGeometry.d.ts","sourceRoot":"","sources":["../../../foundation/chartGeometry.ts"],"names":[],"mappings":"AAIA,oBAAY,SAAS;IACjB,IAAI,SAAS;IACb,GAAG,QAAQ;IACX,OAAO,YAAY;IACnB,KAAK,UAAU;IACf,MAAM,WAAW;CACpB;AAGD,MAAM,WAAW,WAAW;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,EAAE,CAAC;CACpB;AAGD,MAAM,WAAW,UAAU;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,EAAE,CAAC;CACnB;AAGD,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,SAAI,GAAG,UAAU,CAWjE;AAGD,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,WAAW,EAAE,CASxD;AAGD,wBAAgB,WAAW,CAAC,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,EAAE,aAAa,EAAE,MAAM,GAAG,MAAM,CAMjG;AAGD,MAAM,WAAW,UAAU;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;CACf;AAGD,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,UAAU,EAAE,CAa1D;AAGD,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAS3H;AAGD,MAAM,WAAW,WAAW;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;CACtB;AAGD,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,WAAW,EAAE,CAG5D"}
@@ -0,0 +1,93 @@
1
+ // Copyright (c) Cratis. All rights reserved.
2
+ // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
+ /** The kinds of chart the wrapper draws (spec Part B 2.3: line, bar, stacked, donut, funnel). */ var ChartKind = /*#__PURE__*/ function(ChartKind) {
4
+ ChartKind["Line"] = "line";
5
+ ChartKind["Bar"] = "bar";
6
+ ChartKind["Stacked"] = "stacked";
7
+ ChartKind["Donut"] = "donut";
8
+ ChartKind["Funnel"] = "funnel";
9
+ return ChartKind;
10
+ }({});
11
+ /** Builds a scale from zero to a rounded-up maximum of the values, in about `target` steps. */ function niceScale(maximum, target = 4) {
12
+ if (!Number.isFinite(maximum) || maximum <= 0) return {
13
+ maximum: 1,
14
+ ticks: [
15
+ 0,
16
+ 1
17
+ ]
18
+ };
19
+ const rough = maximum / target;
20
+ const magnitude = 10 ** Math.floor(Math.log10(rough));
21
+ const residual = rough / magnitude;
22
+ const step = (residual <= 1 ? 1 : residual <= 2 ? 2 : residual <= 5 ? 5 : 10) * magnitude;
23
+ const top = Math.ceil(maximum / step) * step;
24
+ const ticks = [];
25
+ for(let tick = 0; tick <= top + step / 2; tick += step)ticks.push(Number(tick.toFixed(10)));
26
+ return {
27
+ maximum: top,
28
+ ticks
29
+ };
30
+ }
31
+ /** Reads series out of a property value, dropping anything that is not a named list of numbers, so a half-bound chart draws what it has. */ function readSeries(value) {
32
+ if (!Array.isArray(value)) return [];
33
+ return value.flatMap((entry)=>{
34
+ if (typeof entry !== 'object' || entry === null || Array.isArray(entry)) return [];
35
+ const { name, values } = entry;
36
+ if (typeof name !== 'string' || !Array.isArray(values)) return [];
37
+ return [
38
+ {
39
+ name,
40
+ values: values.map((item)=>typeof item === 'number' && Number.isFinite(item) ? item : 0)
41
+ }
42
+ ];
43
+ });
44
+ }
45
+ /** The extent of the values a chart of this kind has to fit: stacked series add up, the others are compared one by one. */ function dataMaximum(kind, series, categoryCount) {
46
+ if (kind === "stacked") {
47
+ return Math.max(0, ...Array.from({
48
+ length: categoryCount
49
+ }, (_, index)=>series.reduce((sum, item)=>sum + Math.max(0, item.values[index] ?? 0), 0)));
50
+ }
51
+ return Math.max(0, ...series.flatMap((item)=>item.values));
52
+ }
53
+ /** Divides the positive values into slices that together make one turn. */ function donutSlices(values) {
54
+ const positive = values.map((value)=>Math.max(0, value));
55
+ const total = positive.reduce((sum, value)=>sum + value, 0);
56
+ if (total === 0) return [];
57
+ let angle = 0;
58
+ return positive.flatMap((value, index)=>{
59
+ if (value === 0) return [];
60
+ const share = value / total;
61
+ const slice = {
62
+ index,
63
+ value,
64
+ share,
65
+ start: angle,
66
+ end: angle + share * Math.PI * 2
67
+ };
68
+ angle = slice.end;
69
+ return [
70
+ slice
71
+ ];
72
+ });
73
+ }
74
+ /** The SVG path of a ring segment between two radii; a full turn is drawn as two halves, because one arc cannot start and end on the same point. */ function ringPath(centerX, centerY, outer, inner, start, end) {
75
+ const point = (radius, angle)=>`${(centerX + radius * Math.sin(angle)).toFixed(2)} ${(centerY - radius * Math.cos(angle)).toFixed(2)}`;
76
+ if (end - start >= Math.PI * 2 - 1e-6) {
77
+ const middle = start + Math.PI;
78
+ return `${ringPath(centerX, centerY, outer, inner, start, middle)} ${ringPath(centerX, centerY, outer, inner, middle, end)}`;
79
+ }
80
+ const large = end - start > Math.PI ? 1 : 0;
81
+ return `M ${point(outer, start)} A ${outer} ${outer} 0 ${large} 1 ${point(outer, end)} L ${point(inner, end)} A ${inner} ${inner} 0 ${large} 0 ${point(inner, start)} Z`;
82
+ }
83
+ /** Sizes the stages of a funnel against the largest value. */ function funnelStages(values) {
84
+ const maximum = Math.max(0, ...values);
85
+ return values.map((value, index)=>({
86
+ index,
87
+ value,
88
+ widthRatio: maximum === 0 ? 0 : Math.max(0, value) / maximum
89
+ }));
90
+ }
91
+
92
+ export { ChartKind, dataMaximum, donutSlices, funnelStages, niceScale, readSeries, ringPath };
93
+ //# sourceMappingURL=chartGeometry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chartGeometry.js","sources":["../../../foundation/chartGeometry.ts"],"sourcesContent":["// Copyright (c) Cratis. All rights reserved.\n// Licensed under the MIT license. See LICENSE file in the project root for full license information.\n\n/** The kinds of chart the wrapper draws (spec Part B 2.3: line, bar, stacked, donut, funnel). */\nexport enum ChartKind {\n Line = 'line',\n Bar = 'bar',\n Stacked = 'stacked',\n Donut = 'donut',\n Funnel = 'funnel',\n}\n\n/** One named series of values, one per category. */\nexport interface ChartSeries {\n name: string;\n values: number[];\n}\n\n/** A rounded-up axis maximum and the tick values below it, so an axis reads 0, 25, 50, 75, 100 and not 0, 23.4, 46.8. */\nexport interface ChartScale {\n maximum: number;\n ticks: number[];\n}\n\n/** Builds a scale from zero to a rounded-up maximum of the values, in about `target` steps. */\nexport function niceScale(maximum: number, target = 4): ChartScale {\n if (!Number.isFinite(maximum) || maximum <= 0) return { maximum: 1, ticks: [0, 1] };\n\n const rough = maximum / target;\n const magnitude = 10 ** Math.floor(Math.log10(rough));\n const residual = rough / magnitude;\n const step = (residual <= 1 ? 1 : residual <= 2 ? 2 : residual <= 5 ? 5 : 10) * magnitude;\n const top = Math.ceil(maximum / step) * step;\n const ticks: number[] = [];\n for (let tick = 0; tick <= top + step / 2; tick += step) ticks.push(Number(tick.toFixed(10)));\n return { maximum: top, ticks };\n}\n\n/** Reads series out of a property value, dropping anything that is not a named list of numbers, so a half-bound chart draws what it has. */\nexport function readSeries(value: unknown): ChartSeries[] {\n if (!Array.isArray(value)) return [];\n\n return value.flatMap((entry): ChartSeries[] => {\n if (typeof entry !== 'object' || entry === null || Array.isArray(entry)) return [];\n const { name, values } = entry as Record<string, unknown>;\n if (typeof name !== 'string' || !Array.isArray(values)) return [];\n return [{ name, values: values.map(item => (typeof item === 'number' && Number.isFinite(item) ? item : 0)) }];\n });\n}\n\n/** The extent of the values a chart of this kind has to fit: stacked series add up, the others are compared one by one. */\nexport function dataMaximum(kind: ChartKind, series: ChartSeries[], categoryCount: number): number {\n if (kind === ChartKind.Stacked) {\n return Math.max(0, ...Array.from({ length: categoryCount }, (_, index) => series.reduce((sum, item) => sum + Math.max(0, item.values[index] ?? 0), 0)));\n }\n\n return Math.max(0, ...series.flatMap(item => item.values));\n}\n\n/** A slice of a donut, in radians clockwise from the top. */\nexport interface DonutSlice {\n index: number;\n value: number;\n share: number;\n start: number;\n end: number;\n}\n\n/** Divides the positive values into slices that together make one turn. */\nexport function donutSlices(values: number[]): DonutSlice[] {\n const positive = values.map(value => Math.max(0, value));\n const total = positive.reduce((sum, value) => sum + value, 0);\n if (total === 0) return [];\n\n let angle = 0;\n return positive.flatMap((value, index) => {\n if (value === 0) return [];\n const share = value / total;\n const slice = { index, value, share, start: angle, end: angle + share * Math.PI * 2 };\n angle = slice.end;\n return [slice];\n });\n}\n\n/** The SVG path of a ring segment between two radii; a full turn is drawn as two halves, because one arc cannot start and end on the same point. */\nexport function ringPath(centerX: number, centerY: number, outer: number, inner: number, start: number, end: number): string {\n const point = (radius: number, angle: number) => `${(centerX + radius * Math.sin(angle)).toFixed(2)} ${(centerY - radius * Math.cos(angle)).toFixed(2)}`;\n if (end - start >= Math.PI * 2 - 1e-6) {\n const middle = start + Math.PI;\n return `${ringPath(centerX, centerY, outer, inner, start, middle)} ${ringPath(centerX, centerY, outer, inner, middle, end)}`;\n }\n\n const large = end - start > Math.PI ? 1 : 0;\n return `M ${point(outer, start)} A ${outer} ${outer} 0 ${large} 1 ${point(outer, end)} L ${point(inner, end)} A ${inner} ${inner} 0 ${large} 0 ${point(inner, start)} Z`;\n}\n\n/** One stage of a funnel: a centered bar whose width is its share of the widest stage. */\nexport interface FunnelStage {\n index: number;\n value: number;\n widthRatio: number;\n}\n\n/** Sizes the stages of a funnel against the largest value. */\nexport function funnelStages(values: number[]): FunnelStage[] {\n const maximum = Math.max(0, ...values);\n return values.map((value, index) => ({ index, value, widthRatio: maximum === 0 ? 0 : Math.max(0, value) / maximum }));\n}\n"],"names":["ChartKind","niceScale","maximum","target","Number","isFinite","ticks","rough","magnitude","Math","floor","log10","residual","step","top","ceil","tick","push","toFixed","readSeries","value","Array","isArray","flatMap","entry","name","values","map","item","dataMaximum","kind","series","categoryCount","max","from","length","_","index","reduce","sum","donutSlices","positive","total","angle","share","slice","start","end","PI","ringPath","centerX","centerY","outer","inner","point","radius","sin","cos","middle","large","funnelStages","widthRatio"],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ export interface ChooserColumn {
2
+ id: string;
3
+ label: string;
4
+ visible: boolean;
5
+ locked: boolean;
6
+ }
7
+ export declare function readColumns(value: unknown): ChooserColumn[];
8
+ export declare const visibleColumnIds: (columns: ChooserColumn[]) => string[];
9
+ export declare function moveColumn(columns: ChooserColumn[], index: number, direction: -1 | 1): ChooserColumn[];
10
+ export declare const toggleColumn: (columns: ChooserColumn[], index: number) => ChooserColumn[];
11
+ //# sourceMappingURL=columnModel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"columnModel.d.ts","sourceRoot":"","sources":["../../../foundation/columnModel.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,aAAa;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,OAAO,CAAC;CACnB;AAGD,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,aAAa,EAAE,CAS3D;AAGD,eAAO,MAAM,gBAAgB,YAAa,aAAa,EAAE,KAAG,MAAM,EAAuE,CAAC;AAG1I,wBAAgB,UAAU,CAAC,OAAO,EAAE,aAAa,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG,aAAa,EAAE,CAOtG;AAGD,eAAO,MAAM,YAAY,YAAa,aAAa,EAAE,SAAS,MAAM,KAAG,aAAa,EAC4C,CAAC"}
@@ -0,0 +1,38 @@
1
+ // Copyright (c) Cratis. All rights reserved.
2
+ // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
+ /** One column a table could show. */ /** Reads columns out of a property value; a column is visible unless it says otherwise, and a locked column is always visible. */ function readColumns(value) {
4
+ if (!Array.isArray(value)) return [];
5
+ return value.flatMap((entry)=>{
6
+ if (typeof entry !== 'object' || entry === null || Array.isArray(entry)) return [];
7
+ const { id, label, visible, locked } = entry;
8
+ if (typeof id !== 'string' || id.length === 0) return [];
9
+ return [
10
+ {
11
+ id,
12
+ label: typeof label === 'string' && label.length > 0 ? label : id,
13
+ visible: locked === true || visible !== false,
14
+ locked: locked === true
15
+ }
16
+ ];
17
+ });
18
+ }
19
+ /** The ids of the visible columns, in order: what the chooser publishes and a table binds its columns to. */ const visibleColumnIds = (columns)=>columns.filter((column)=>column.visible).map((column)=>column.id);
20
+ /** Moves a column one place up or down, staying where it is at either end. */ function moveColumn(columns, index, direction) {
21
+ const target = index + direction;
22
+ if (target < 0 || target >= columns.length) return columns;
23
+ const result = [
24
+ ...columns
25
+ ];
26
+ [result[index], result[target]] = [
27
+ result[target],
28
+ result[index]
29
+ ];
30
+ return result;
31
+ }
32
+ /** Shows or hides a column; a locked column cannot be hidden. */ const toggleColumn = (columns, index)=>columns.map((column, position)=>position === index && !column.locked ? {
33
+ ...column,
34
+ visible: !column.visible
35
+ } : column);
36
+
37
+ export { moveColumn, readColumns, toggleColumn, visibleColumnIds };
38
+ //# sourceMappingURL=columnModel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"columnModel.js","sources":["../../../foundation/columnModel.ts"],"sourcesContent":["// Copyright (c) Cratis. All rights reserved.\n// Licensed under the MIT license. See LICENSE file in the project root for full license information.\n\n/** One column a table could show. */\nexport interface ChooserColumn {\n id: string;\n label: string;\n visible: boolean;\n locked: boolean;\n}\n\n/** Reads columns out of a property value; a column is visible unless it says otherwise, and a locked column is always visible. */\nexport function readColumns(value: unknown): ChooserColumn[] {\n if (!Array.isArray(value)) return [];\n\n return value.flatMap((entry): ChooserColumn[] => {\n if (typeof entry !== 'object' || entry === null || Array.isArray(entry)) return [];\n const { id, label, visible, locked } = entry as Record<string, unknown>;\n if (typeof id !== 'string' || id.length === 0) return [];\n return [{ id, label: typeof label === 'string' && label.length > 0 ? label : id, visible: locked === true || visible !== false, locked: locked === true }];\n });\n}\n\n/** The ids of the visible columns, in order: what the chooser publishes and a table binds its columns to. */\nexport const visibleColumnIds = (columns: ChooserColumn[]): string[] => columns.filter(column => column.visible).map(column => column.id);\n\n/** Moves a column one place up or down, staying where it is at either end. */\nexport function moveColumn(columns: ChooserColumn[], index: number, direction: -1 | 1): ChooserColumn[] {\n const target = index + direction;\n if (target < 0 || target >= columns.length) return columns;\n\n const result = [...columns];\n [result[index], result[target]] = [result[target], result[index]];\n return result;\n}\n\n/** Shows or hides a column; a locked column cannot be hidden. */\nexport const toggleColumn = (columns: ChooserColumn[], index: number): ChooserColumn[] =>\n columns.map((column, position) => (position === index && !column.locked ? { ...column, visible: !column.visible } : column));\n"],"names":["readColumns","value","Array","isArray","flatMap","entry","id","label","visible","locked","length","visibleColumnIds","columns","filter","column","map","moveColumn","index","direction","target","result","toggleColumn","position"],"mappings":"AAAA;AACA;AAEA,yKASO,SAASA,YAAYC,KAAc,EAAA;AACtC,IAAA,IAAI,CAACC,KAAAA,CAAMC,OAAO,CAACF,KAAAA,CAAAA,EAAQ,OAAO,EAAE;IAEpC,OAAOA,KAAAA,CAAMG,OAAO,CAAC,CAACC,KAAAA,GAAAA;QAClB,IAAI,OAAOA,KAAAA,KAAU,QAAA,IAAYA,KAAAA,KAAU,IAAA,IAAQH,MAAMC,OAAO,CAACE,KAAAA,CAAAA,EAAQ,OAAO,EAAE;QAClF,MAAM,EAAEC,EAAE,EAAEC,KAAK,EAAEC,OAAO,EAAEC,MAAM,EAAE,GAAGJ,KAAAA;QACvC,IAAI,OAAOC,OAAO,QAAA,IAAYA,EAAAA,CAAGI,MAAM,KAAK,CAAA,EAAG,OAAO,EAAE;QACxD,OAAO;AAAC,YAAA;AAAEJ,gBAAAA,EAAAA;AAAIC,gBAAAA,KAAAA,EAAO,OAAOA,KAAAA,KAAU,QAAA,IAAYA,MAAMG,MAAM,GAAG,IAAIH,KAAAA,GAAQD,EAAAA;gBAAIE,OAAAA,EAASC,MAAAA,KAAW,QAAQD,OAAAA,KAAY,KAAA;AAAOC,gBAAAA,MAAAA,EAAQA,MAAAA,KAAW;AAAK;AAAE,SAAA;AAC9J,IAAA,CAAA,CAAA;AACJ;AAEA,8GACO,MAAME,gBAAAA,GAAmB,CAACC,OAAAA,GAAuCA,OAAAA,CAAQC,MAAM,CAACC,CAAAA,SAAUA,MAAAA,CAAON,OAAO,EAAEO,GAAG,CAACD,CAAAA,MAAAA,GAAUA,MAAAA,CAAOR,EAAE;AAExI,+EACO,SAASU,UAAAA,CAAWJ,OAAwB,EAAEK,KAAa,EAAEC,SAAiB,EAAA;AACjF,IAAA,MAAMC,SAASF,KAAAA,GAAQC,SAAAA;AACvB,IAAA,IAAIC,SAAS,CAAA,IAAKA,MAAAA,IAAUP,OAAAA,CAAQF,MAAM,EAAE,OAAOE,OAAAA;AAEnD,IAAA,MAAMQ,MAAAA,GAAS;AAAIR,QAAAA,GAAAA;AAAQ,KAAA;IAC3B,CAACQ,MAAM,CAACH,KAAAA,CAAM,EAAEG,MAAM,CAACD,MAAAA,CAAO,CAAC,GAAG;AAACC,QAAAA,MAAM,CAACD,MAAAA,CAAO;AAAEC,QAAAA,MAAM,CAACH,KAAAA;AAAO,KAAA;IACjE,OAAOG,MAAAA;AACX;AAEA,kEACO,MAAMC,eAAe,CAACT,OAAAA,EAA0BK,QACnDL,OAAAA,CAAQG,GAAG,CAAC,CAACD,QAAQQ,QAAAA,GAAcA,QAAAA,KAAaL,SAAS,CAACH,MAAAA,CAAOL,MAAM,GAAG;AAAE,YAAA,GAAGK,MAAM;YAAEN,OAAAA,EAAS,CAACM,OAAON;AAAQ,SAAA,GAAIM,MAAAA;;"}
@@ -0,0 +1,33 @@
1
+ export declare enum FilterFieldType {
2
+ Text = "text",
3
+ Number = "number",
4
+ Date = "date",
5
+ Select = "select",
6
+ Boolean = "boolean"
7
+ }
8
+ export interface FilterField {
9
+ name: string;
10
+ label: string;
11
+ type: FilterFieldType;
12
+ options: string[];
13
+ }
14
+ export interface FilterCondition {
15
+ field: string;
16
+ operator: string;
17
+ value: string;
18
+ }
19
+ export interface FilterGroup {
20
+ operator: 'and' | 'or';
21
+ conditions: FilterCondition[];
22
+ groups: FilterGroup[];
23
+ }
24
+ export declare const maximumFilterDepth = 3;
25
+ export declare const emptyFilter: () => FilterGroup;
26
+ export declare const filterOperators: Record<FilterFieldType, string[]>;
27
+ export declare const valuelessOperators: string[];
28
+ export declare function readFilterFields(value: unknown): FilterField[];
29
+ export declare function normalizeFilter(value: unknown, fields: FilterField[], depth?: number): FilterGroup;
30
+ export declare function countActiveConditions(group: FilterGroup): number;
31
+ export declare function updateGroupAt(root: FilterGroup, path: number[], change: (group: FilterGroup) => FilterGroup): FilterGroup;
32
+ export declare function newCondition(fields: FilterField[]): FilterCondition;
33
+ //# sourceMappingURL=filterModel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"filterModel.d.ts","sourceRoot":"","sources":["../../../foundation/filterModel.ts"],"names":[],"mappings":"AAIA,oBAAY,eAAe;IACvB,IAAI,SAAS;IACb,MAAM,WAAW;IACjB,IAAI,SAAS;IACb,MAAM,WAAW;IACjB,OAAO,YAAY;CACtB;AAGD,MAAM,WAAW,WAAW;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,eAAe,CAAC;IACtB,OAAO,EAAE,MAAM,EAAE,CAAC;CACrB;AAGD,MAAM,WAAW,eAAe;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;CACjB;AAGD,MAAM,WAAW,WAAW;IACxB,QAAQ,EAAE,KAAK,GAAG,IAAI,CAAC;IACvB,UAAU,EAAE,eAAe,EAAE,CAAC;IAC9B,MAAM,EAAE,WAAW,EAAE,CAAC;CACzB;AAGD,eAAO,MAAM,kBAAkB,IAAI,CAAC;AAGpC,eAAO,MAAM,WAAW,QAAO,WAAgE,CAAC;AAGhG,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,eAAe,EAAE,MAAM,EAAE,CAM7D,CAAC;AAGF,eAAO,MAAM,kBAAkB,UAA+B,CAAC;AAG/D,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,WAAW,EAAE,CAc9D;AAGD,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,EAAE,KAAK,SAAI,GAAG,WAAW,CAoB7F;AAGD,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,WAAW,GAAG,MAAM,CAGhE;AAGD,wBAAgB,aAAa,CAAC,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,WAAW,GAAG,WAAW,CAKzH;AAGD,wBAAgB,YAAY,CAAC,MAAM,EAAE,WAAW,EAAE,GAAG,eAAe,CAGnE"}
@@ -0,0 +1,112 @@
1
+ // Copyright (c) Cratis. All rights reserved.
2
+ // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
+ /** What a filterable field holds, which decides the operators offered for it. */ var FilterFieldType = /*#__PURE__*/ function(FilterFieldType) {
4
+ FilterFieldType["Text"] = "text";
5
+ FilterFieldType["Number"] = "number";
6
+ FilterFieldType["Date"] = "date";
7
+ FilterFieldType["Select"] = "select";
8
+ FilterFieldType["Boolean"] = "boolean";
9
+ return FilterFieldType;
10
+ }({});
11
+ /** The deepest a group may nest; deeper filters are unreadable, and a runaway value is cut rather than rendered forever. */ const maximumFilterDepth = 3;
12
+ /** An empty group. */ const emptyFilter = ()=>({
13
+ operator: 'and',
14
+ conditions: [],
15
+ groups: []
16
+ });
17
+ /** The operators of each field type, in the order they are offered; the first is the default. */ const filterOperators = {
18
+ ["text"]: [
19
+ 'contains',
20
+ 'equals',
21
+ 'notEquals',
22
+ 'startsWith',
23
+ 'isEmpty'
24
+ ],
25
+ ["number"]: [
26
+ 'equals',
27
+ 'notEquals',
28
+ 'greater',
29
+ 'greaterOrEqual',
30
+ 'less',
31
+ 'lessOrEqual'
32
+ ],
33
+ ["date"]: [
34
+ 'on',
35
+ 'before',
36
+ 'after'
37
+ ],
38
+ ["select"]: [
39
+ 'is',
40
+ 'isNot'
41
+ ],
42
+ ["boolean"]: [
43
+ 'true',
44
+ 'false'
45
+ ]
46
+ };
47
+ /** The operators that need no value to compare with. */ const valuelessOperators = [
48
+ 'isEmpty',
49
+ 'true',
50
+ 'false'
51
+ ];
52
+ /** Reads fields out of a property value, dropping entries without a name, and defaulting what is unknown to text. */ function readFilterFields(value) {
53
+ if (!Array.isArray(value)) return [];
54
+ return value.flatMap((entry)=>{
55
+ if (typeof entry !== 'object' || entry === null || Array.isArray(entry)) return [];
56
+ const { name, label, type, options } = entry;
57
+ if (typeof name !== 'string' || name.length === 0) return [];
58
+ return [
59
+ {
60
+ name,
61
+ label: typeof label === 'string' && label.length > 0 ? label : name,
62
+ type: Object.values(FilterFieldType).includes(type) ? type : "text",
63
+ options: Array.isArray(options) ? options.filter((option)=>typeof option === 'string') : []
64
+ }
65
+ ];
66
+ });
67
+ }
68
+ /** Reads a filter out of a property value, keeping the conditions on known fields with operators their type offers. */ function normalizeFilter(value, fields, depth = 1) {
69
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) return emptyFilter();
70
+ const { operator, conditions, groups } = value;
71
+ return {
72
+ operator: operator === 'or' ? 'or' : 'and',
73
+ conditions: Array.isArray(conditions) ? conditions.flatMap((entry)=>{
74
+ if (typeof entry !== 'object' || entry === null) return [];
75
+ const condition = entry;
76
+ const field = fields.find((candidate)=>candidate.name === condition.field);
77
+ if (!field) return [];
78
+ const operators = filterOperators[field.type];
79
+ return [
80
+ {
81
+ field: field.name,
82
+ operator: typeof condition.operator === 'string' && operators.includes(condition.operator) ? condition.operator : operators[0],
83
+ value: typeof condition.value === 'string' ? condition.value : typeof condition.value === 'number' ? String(condition.value) : ''
84
+ }
85
+ ];
86
+ }) : [],
87
+ groups: depth < maximumFilterDepth && Array.isArray(groups) ? groups.map((group)=>normalizeFilter(group, fields, depth + 1)) : []
88
+ };
89
+ }
90
+ /** How many conditions are in force: a condition that needs a value and has none is not filtering anything. */ function countActiveConditions(group) {
91
+ const own = group.conditions.filter((condition)=>valuelessOperators.includes(condition.operator) || condition.value.trim().length > 0).length;
92
+ return own + group.groups.reduce((sum, nested)=>sum + countActiveConditions(nested), 0);
93
+ }
94
+ /** Applies a change to the group at a path of nested group indexes, leaving the rest as it was. */ function updateGroupAt(root, path, change) {
95
+ if (path.length === 0) return change(root);
96
+ const [index, ...rest] = path;
97
+ return {
98
+ ...root,
99
+ groups: root.groups.map((group, position)=>position === index ? updateGroupAt(group, rest, change) : group)
100
+ };
101
+ }
102
+ /** A new condition on the first field, with that field's default operator. */ function newCondition(fields) {
103
+ const field = fields[0];
104
+ return {
105
+ field: field?.name ?? '',
106
+ operator: field ? filterOperators[field.type][0] : 'contains',
107
+ value: ''
108
+ };
109
+ }
110
+
111
+ export { FilterFieldType, countActiveConditions, emptyFilter, filterOperators, maximumFilterDepth, newCondition, normalizeFilter, readFilterFields, updateGroupAt, valuelessOperators };
112
+ //# sourceMappingURL=filterModel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"filterModel.js","sources":["../../../foundation/filterModel.ts"],"sourcesContent":["// Copyright (c) Cratis. All rights reserved.\n// Licensed under the MIT license. See LICENSE file in the project root for full license information.\n\n/** What a filterable field holds, which decides the operators offered for it. */\nexport enum FilterFieldType {\n Text = 'text',\n Number = 'number',\n Date = 'date',\n Select = 'select',\n Boolean = 'boolean',\n}\n\n/** A field a filter can be built on. */\nexport interface FilterField {\n name: string;\n label: string;\n type: FilterFieldType;\n options: string[];\n}\n\n/** One test of a field. */\nexport interface FilterCondition {\n field: string;\n operator: string;\n value: string;\n}\n\n/** Conditions and nested groups joined by AND or OR. */\nexport interface FilterGroup {\n operator: 'and' | 'or';\n conditions: FilterCondition[];\n groups: FilterGroup[];\n}\n\n/** The deepest a group may nest; deeper filters are unreadable, and a runaway value is cut rather than rendered forever. */\nexport const maximumFilterDepth = 3;\n\n/** An empty group. */\nexport const emptyFilter = (): FilterGroup => ({ operator: 'and', conditions: [], groups: [] });\n\n/** The operators of each field type, in the order they are offered; the first is the default. */\nexport const filterOperators: Record<FilterFieldType, string[]> = {\n [FilterFieldType.Text]: ['contains', 'equals', 'notEquals', 'startsWith', 'isEmpty'],\n [FilterFieldType.Number]: ['equals', 'notEquals', 'greater', 'greaterOrEqual', 'less', 'lessOrEqual'],\n [FilterFieldType.Date]: ['on', 'before', 'after'],\n [FilterFieldType.Select]: ['is', 'isNot'],\n [FilterFieldType.Boolean]: ['true', 'false'],\n};\n\n/** The operators that need no value to compare with. */\nexport const valuelessOperators = ['isEmpty', 'true', 'false'];\n\n/** Reads fields out of a property value, dropping entries without a name, and defaulting what is unknown to text. */\nexport function readFilterFields(value: unknown): FilterField[] {\n if (!Array.isArray(value)) return [];\n\n return value.flatMap((entry): FilterField[] => {\n if (typeof entry !== 'object' || entry === null || Array.isArray(entry)) return [];\n const { name, label, type, options } = entry as Record<string, unknown>;\n if (typeof name !== 'string' || name.length === 0) return [];\n return [{\n name,\n label: typeof label === 'string' && label.length > 0 ? label : name,\n type: (Object.values(FilterFieldType) as string[]).includes(type as string) ? (type as FilterFieldType) : FilterFieldType.Text,\n options: Array.isArray(options) ? options.filter((option): option is string => typeof option === 'string') : [],\n }];\n });\n}\n\n/** Reads a filter out of a property value, keeping the conditions on known fields with operators their type offers. */\nexport function normalizeFilter(value: unknown, fields: FilterField[], depth = 1): FilterGroup {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) return emptyFilter();\n\n const { operator, conditions, groups } = value as Record<string, unknown>;\n return {\n operator: operator === 'or' ? 'or' : 'and',\n conditions: Array.isArray(conditions) ? conditions.flatMap((entry): FilterCondition[] => {\n if (typeof entry !== 'object' || entry === null) return [];\n const condition = entry as Record<string, unknown>;\n const field = fields.find(candidate => candidate.name === condition.field);\n if (!field) return [];\n const operators = filterOperators[field.type];\n return [{\n field: field.name,\n operator: typeof condition.operator === 'string' && operators.includes(condition.operator) ? condition.operator : operators[0],\n value: typeof condition.value === 'string' ? condition.value : typeof condition.value === 'number' ? String(condition.value) : '',\n }];\n }) : [],\n groups: depth < maximumFilterDepth && Array.isArray(groups) ? groups.map(group => normalizeFilter(group, fields, depth + 1)) : [],\n };\n}\n\n/** How many conditions are in force: a condition that needs a value and has none is not filtering anything. */\nexport function countActiveConditions(group: FilterGroup): number {\n const own = group.conditions.filter(condition => valuelessOperators.includes(condition.operator) || condition.value.trim().length > 0).length;\n return own + group.groups.reduce((sum, nested) => sum + countActiveConditions(nested), 0);\n}\n\n/** Applies a change to the group at a path of nested group indexes, leaving the rest as it was. */\nexport function updateGroupAt(root: FilterGroup, path: number[], change: (group: FilterGroup) => FilterGroup): FilterGroup {\n if (path.length === 0) return change(root);\n\n const [index, ...rest] = path;\n return { ...root, groups: root.groups.map((group, position) => (position === index ? updateGroupAt(group, rest, change) : group)) };\n}\n\n/** A new condition on the first field, with that field's default operator. */\nexport function newCondition(fields: FilterField[]): FilterCondition {\n const field = fields[0];\n return { field: field?.name ?? '', operator: field ? filterOperators[field.type][0] : 'contains', value: '' };\n}\n"],"names":["FilterFieldType","maximumFilterDepth","emptyFilter","operator","conditions","groups","filterOperators","valuelessOperators","readFilterFields","value","Array","isArray","flatMap","entry","name","label","type","options","length","Object","values","includes","filter","option","normalizeFilter","fields","depth","condition","field","find","candidate","operators","String","map","group","countActiveConditions","own","trim","reduce","sum","nested","updateGroupAt","root","path","change","index","rest","position","newCondition"],"mappings":"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"}