@gravionlabs/helix-ui 22.2.13 → 22.2.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +61 -61
- package/core.css +7 -5
- package/fesm2022/gravionlabs-helix-ui-chart.mjs +14 -14
- package/fesm2022/gravionlabs-helix-ui-chart.mjs.map +1 -1
- package/fesm2022/gravionlabs-helix-ui-validators.mjs.map +1 -1
- package/fesm2022/gravionlabs-helix-ui.mjs +1298 -1301
- package/fesm2022/gravionlabs-helix-ui.mjs.map +1 -1
- package/package.json +1 -1
- package/styles.css +7 -5
- package/types/gravionlabs-helix-ui.d.ts +0 -180
package/README.md
CHANGED
|
@@ -38,67 +38,67 @@ import { HxChart } from '@gravionlabs/helix-ui/chart';
|
|
|
38
38
|
|
|
39
39
|
## Components
|
|
40
40
|
|
|
41
|
-
| Component
|
|
42
|
-
|
|
|
43
|
-
| `HxButton`
|
|
44
|
-
| `HxInput`
|
|
45
|
-
| `HxCheckbox`
|
|
46
|
-
| `HxRadio`
|
|
47
|
-
| `HxSelect`
|
|
48
|
-
| `HxDivider`
|
|
49
|
-
| `HxTooltip`
|
|
50
|
-
| `HxAvatar`, `HxAvatarGroup`
|
|
51
|
-
| `HxBadge`, `HxOverlayBadge`
|
|
52
|
-
| `HxBreadcrumb`
|
|
53
|
-
| `HxDataTable`, `HxColumn`, `HxCell`, `HxTableHeader`, `HxTableBody`, `HxTableFooter`, `HxTableCaption`, `HxTableEmpty`, `HxRowExpansion` | `hx-data-table` …
|
|
54
|
-
| `HxCarousel`, `HxCarouselItem`
|
|
55
|
-
| `HxGalleria`, `HxGalleriaItemTemplate`, `HxGalleriaThumbnail`, `HxGalleriaCaption`
|
|
56
|
-
| `HxImage`, `HxImageIndicator`
|
|
57
|
-
| `HxPaginator`
|
|
58
|
-
| `HxIconField`, `HxInputIcon`
|
|
59
|
-
| `HxFloatLabel`
|
|
60
|
-
| `HxInputGroup`, `HxInputGroupAddon`
|
|
61
|
-
| `HxInputNumber`
|
|
62
|
-
| `HxPassword`
|
|
63
|
-
| `HxCard`
|
|
64
|
-
| `HxProgressBar`, `HxProgressSpinner`
|
|
65
|
-
| `HxTree`
|
|
66
|
-
| `HxTimeline`
|
|
67
|
-
| `HxSkeleton`
|
|
68
|
-
| `HxSplitButton`
|
|
69
|
-
| `HxStepper`, `HxStepList`, `HxStep`, `HxStepPanels`, `HxStepPanel`
|
|
70
|
-
| `HxButtonGroup`
|
|
71
|
-
| `HxChart`
|
|
72
|
-
| `HxDialog`, `HxDialogService`, `HxDialogRef`
|
|
73
|
-
| `HxDrawer`
|
|
74
|
-
| `HxPopover`
|
|
75
|
-
| `HxConfirmationService`, `HxConfirmDialog`, `HxConfirmPopup`
|
|
76
|
-
| `HxTable`
|
|
77
|
-
| `HxChip`
|
|
78
|
-
| `HxTag`
|
|
79
|
-
| `HxTabs`, `HxTabList`, `HxTab`, `HxTabPanels`, `HxTabPanel`
|
|
80
|
-
| `HxToast`, `HxMessageService`
|
|
81
|
-
| `HxMenubar`
|
|
82
|
-
| `HxMenu`
|
|
83
|
-
| `HxMessage`
|
|
84
|
-
| `HxToolbar`
|
|
85
|
-
| `HxAccordion`, `HxAccordionPanel`, `HxAccordionHeader`, `HxAccordionContent`
|
|
86
|
-
| `HxFieldset`
|
|
87
|
-
| `HxPanel`
|
|
88
|
-
| `HxFileUpload`
|
|
89
|
-
| `HxDateTimeRangePicker`
|
|
90
|
-
| `HxDateRangeQuickSelect`
|
|
91
|
-
| `HxDateTimePicker`
|
|
92
|
-
| `HxTimePicker`
|
|
93
|
-
| `HxDatePicker`
|
|
94
|
-
| `HxAutoComplete`
|
|
95
|
-
| `HxMultiSelect`
|
|
96
|
-
| `HxListbox`
|
|
97
|
-
| `HxRating`
|
|
98
|
-
| `HxSlider`
|
|
99
|
-
| `HxToggleButton`
|
|
100
|
-
| `HxSelectButton`
|
|
101
|
-
| `HxSwitch`
|
|
41
|
+
| Component | Selector | |
|
|
42
|
+
| ---------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
43
|
+
| `HxButton` | `button[hx-button]`, `a[hx-button]` | variants, severities, sizes, loading, icon-only |
|
|
44
|
+
| `HxInput` | `input[hx-input]`, `textarea[hx-input]` | outlined / filled, sizes, invalid, textarea `autoResize` |
|
|
45
|
+
| `HxCheckbox` | `input[hx-checkbox]` | native checkbox, indeterminate |
|
|
46
|
+
| `HxRadio` | `input[hx-radio]` | native radio |
|
|
47
|
+
| `HxSelect` | `hx-select` | CDK listbox in an overlay, all three forms APIs |
|
|
48
|
+
| `HxDivider` | `hx-divider` | horizontal/vertical, labelled |
|
|
49
|
+
| `HxTooltip` | `[hx-tooltip]` | CDK overlay, hover and focus |
|
|
50
|
+
| `HxAvatar`, `HxAvatarGroup` | `hx-avatar`, `hx-avatar-group` | picture, icon or initials; sizes, shapes; the group overlaps its avatars |
|
|
51
|
+
| `HxBadge`, `HxOverlayBadge` | `hx-badge`, `hx-overlay-badge` | count or dot marker, severities and sizes; the overlay variant sits on a corner of the wrapped element |
|
|
52
|
+
| `HxBreadcrumb` | `hx-breadcrumb` | router links, home item |
|
|
53
|
+
| `HxDataTable`, `HxColumn`, `HxCell`, `HxTableHeader`, `HxTableBody`, `HxTableFooter`, `HxTableCaption`, `HxTableEmpty`, `HxRowExpansion` | `hx-data-table` … | column model, cell and row templates, sorting (single or multiple), paging, lazy loading, selection (checkbox, single, meta key), global and column filters, row expansion, caption, empty and loading state, scrolling body with a sticky header |
|
|
54
|
+
| `HxCarousel`, `HxCarouselItem` | `hx-carousel` | slides over items: numVisible/numScroll, responsive counts, circular, autoplay, indicators, keyboard |
|
|
55
|
+
| `HxGalleria`, `HxGalleriaItemTemplate`, `HxGalleriaThumbnail`, `HxGalleriaCaption` | `hx-galleria` | image gallery: stage, thumbnail strip, captions, indicators, autoplay, full screen in a modal dialog |
|
|
56
|
+
| `HxImage`, `HxImageIndicator` | `hx-image` | picture with a modal preview: rotate, zoom, close; labelled toolbar |
|
|
57
|
+
| `HxPaginator` | `hx-paginator` | first/previous/page links/next/last, rows per page, current page report; `first` and `rows` models |
|
|
58
|
+
| `HxIconField`, `HxInputIcon` | `hx-icon-field`, `[hx-input-icon]` | icon on the start or end edge of a text field |
|
|
59
|
+
| `HxFloatLabel` | `hx-float-label` | a label that sits in the field and floats up on focus or a value (`over`, `in`, `on`) |
|
|
60
|
+
| `HxInputGroup`, `HxInputGroupAddon` | `hx-input-group`, `hx-input-group-addon` | joins fields, selects, buttons and addons |
|
|
61
|
+
| `HxInputNumber` | `hx-input-number` | locale formatting, step buttons, all three forms APIs |
|
|
62
|
+
| `HxPassword` | `hx-password` | show/hide toggle, all three forms APIs |
|
|
63
|
+
| `HxCard` | `hx-card` | surface with header, title (a heading), subtitle, content and footer slots |
|
|
64
|
+
| `HxProgressBar`, `HxProgressSpinner` | `hx-progress-bar`, `hx-progress-spinner` | determinate and indeterminate bar, spinner; `role=progressbar`, reduced motion |
|
|
65
|
+
| `HxTree` | `hx-tree` | hierarchical data: selection (single, multiple, checkbox), filter, lazy children, node template, WAI-ARIA tree keyboard |
|
|
66
|
+
| `HxTimeline` | `hx-timeline` | events on a line: vertical or horizontal, left/right/alternate, content, opposite and marker templates |
|
|
67
|
+
| `HxSkeleton` | `hx-skeleton` | loading placeholder: rectangle or circle, any size, wave (off under reduced motion), `aria-hidden` |
|
|
68
|
+
| `HxSplitButton` | `hx-split-button` | default action plus an `hx-menu` of related actions; severity, variant, size, rounded, raised |
|
|
69
|
+
| `HxStepper`, `HxStepList`, `HxStep`, `HxStepPanels`, `HxStepPanel` | `hx-stepper` … | steps with panels: completed/active state, linear mode, `activateCallback`, lazy panels |
|
|
70
|
+
| `HxButtonGroup` | `hx-button-group` | joins `hx-button`s: square inner corners, shared borders, `role=group` |
|
|
71
|
+
| `HxChart` | `hx-chart` | from `@gravionlabs/helix-ui/chart`: chart.js with colours, grid and font from the tokens; optional peer dependency loaded on demand, redraws on theme change, canvas fallback slot |
|
|
72
|
+
| `HxDialog`, `HxDialogService`, `HxDialogRef` | `hx-dialog` | modal or non-modal dialog on the CDK dialog; declarative with `[(visible)]` or opened as a component from a service with data and a result |
|
|
73
|
+
| `HxDrawer` | `hx-drawer` | panel that slides in from an edge, same dialog semantics as `hx-dialog`; modal, full screen, reduced motion |
|
|
74
|
+
| `HxPopover` | `hx-popover` | anchored panel with arrow: `toggle(event, target)`, outside click and Escape close, non-modal dialog with focus in and back |
|
|
75
|
+
| `HxConfirmationService`, `HxConfirmDialog`, `HxConfirmPopup` | `hx-confirm-dialog`, `hx-confirm-popup` | ask the user to confirm: centred `alertdialog` or a popup anchored to a target; `confirm()` with callbacks or `confirmAsync()` |
|
|
76
|
+
| `HxTable` | `table[hx-table]` | native table styles: caption, header, body, footer, striped, gridlines, hover, sizes, sticky header; no behaviour (data grids use AG Grid) |
|
|
77
|
+
| `HxChip` | `hx-chip` | entity pill with picture or icon, removable by button, Backspace or Delete |
|
|
78
|
+
| `HxTag` | `hx-tag` | label for a category or status: severities, rounded, decorative icon |
|
|
79
|
+
| `HxTabs`, `HxTabList`, `HxTab`, `HxTabPanels`, `HxTabPanel` | `hx-tabs` … | WAI-ARIA tabs: roving tabindex, automatic activation, lazy panels, scrollable list |
|
|
80
|
+
| `HxToast`, `HxMessageService` | `hx-toast` | short messages from a service, live regions, pause on hover and focus, keys and positions |
|
|
81
|
+
| `HxMenubar` | `hx-menubar` | horizontal menu with dropdown submenus on the CDK menubar, collapses into a button below a breakpoint |
|
|
82
|
+
| `HxMenu` | `hx-menu` | menu of `HxMenuItem`s, inline or popup, group headings and nested submenus on the CDK menu |
|
|
83
|
+
| `HxMessage` | `hx-message` | inline message: severity, filled/outlined/simple, icon, closable, life |
|
|
84
|
+
| `HxToolbar` | `hx-toolbar` | start, center and end areas that wrap on small screens; no toolbar role |
|
|
85
|
+
| `HxAccordion`, `HxAccordionPanel`, `HxAccordionHeader`, `HxAccordionContent` | `hx-accordion` … | WAI-ARIA accordion on the CDK, single or multiple |
|
|
86
|
+
| `HxFieldset` | `hx-fieldset` | native fieldset and legend, optional collapse with a legend button |
|
|
87
|
+
| `HxPanel` | `hx-panel` | titled container, optional collapse with a toggle button, header and footer slots |
|
|
88
|
+
| `HxFileUpload` | `hx-file-upload` | choose and drop files, list, validation messages, progress; no HTTP, the app uploads |
|
|
89
|
+
| `HxDateTimeRangePicker` | `hx-date-time-range-picker` | date-time range in one field with one popup calendar and a time row per end (`{ from, to }`), same time options as the time picker, all three forms APIs |
|
|
90
|
+
| `HxDateRangeQuickSelect` | `hx-date-range-quick-select` | Kibana-style quick select for a date range: Last/Next N unit, commonly and recently used ranges, a start and an end editor (absolute, relative, now), date-math `expression` model, all three forms APIs |
|
|
91
|
+
| `HxDateTimePicker` | `hx-date-time-picker` | date and time in one field: a calendar popup (or inline) with a time row under it (`Date`), 24 h or 12 h, hour only, steps, `minDate`/`maxDate` with the time, typing, all three forms APIs |
|
|
92
|
+
| `HxTimePicker` | `hx-time-picker` | time field with a time row popup or inline (`Date` today at that time), 24 h or 12 h, hour only, steps, `minTime`/`maxTime`, typing, all three forms APIs |
|
|
93
|
+
| `HxDatePicker` | `hx-date-picker` | date field with a calendar popup or inline: single, range or multiple days, typing, month and year views, all three forms APIs |
|
|
94
|
+
| `HxAutoComplete` | `hx-auto-complete` | text field with suggestions from `(complete)`, chips, dropdown, all three forms APIs |
|
|
95
|
+
| `HxMultiSelect` | `hx-multi-select` | select for several values: comma or chips, filter, select all, all three forms APIs |
|
|
96
|
+
| `HxListbox` | `hx-listbox` | inline single or multiple list on the CDK listbox, filter, checkmark, all three forms APIs |
|
|
97
|
+
| `HxRating` | `hx-rating` | star rating as a radio group, read only as an image, all three forms APIs |
|
|
98
|
+
| `HxSlider` | `hx-slider` | one value or a range on native range inputs, horizontal or vertical, all three forms APIs |
|
|
99
|
+
| `HxToggleButton` | `hx-toggle-button` | on/off button with labels and icons, all three forms APIs |
|
|
100
|
+
| `HxSelectButton` | `hx-select-button` | single or multiple, all three forms APIs |
|
|
101
|
+
| `HxSwitch` | `input[hx-switch]` | native checkbox with `role="switch"` |
|
|
102
102
|
|
|
103
103
|
Selectors, inputs and classes use the prefix `hx` (`hx-button`); the custom properties keep the prefix `--h-`.
|
|
104
104
|
|
package/core.css
CHANGED
|
@@ -927,11 +927,15 @@
|
|
|
927
927
|
-webkit-mask: var(--hx-check) center/contain no-repeat;
|
|
928
928
|
transform: scale(0);
|
|
929
929
|
transition: transform var(--h-checkbox-transition-duration);
|
|
930
|
-
--hx-check: url(
|
|
930
|
+
--hx-check: url(
|
|
931
|
+
"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 14' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m3 7.5 2.8 2.8L11 4.5'/%3E%3C/svg%3E"
|
|
932
|
+
);
|
|
931
933
|
}
|
|
932
934
|
|
|
933
935
|
.hx-checkbox:indeterminate::before {
|
|
934
|
-
--hx-check: url(
|
|
936
|
+
--hx-check: url(
|
|
937
|
+
"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 14' fill='none' stroke='black' stroke-width='2' stroke-linecap='round'%3E%3Cpath d='M3 7h8'/%3E%3C/svg%3E"
|
|
938
|
+
);
|
|
935
939
|
}
|
|
936
940
|
|
|
937
941
|
.hx-checkbox:checked,
|
|
@@ -1681,9 +1685,7 @@
|
|
|
1681
1685
|
}
|
|
1682
1686
|
|
|
1683
1687
|
/* invalid */
|
|
1684
|
-
.hx-float-label:has(> [aria-invalid=true],
|
|
1685
|
-
> .ng-invalid.ng-touched,
|
|
1686
|
-
> .hx-select-invalid) > label {
|
|
1688
|
+
.hx-float-label:has(> [aria-invalid=true], > .ng-invalid.ng-touched, > .hx-select-invalid) > label {
|
|
1687
1689
|
color: var(--h-floatlabel-invalid-color);
|
|
1688
1690
|
}
|
|
1689
1691
|
|
|
@@ -29,21 +29,21 @@ var HxChart = class HxChart {
|
|
|
29
29
|
#zone = inject(NgZone);
|
|
30
30
|
#loader = inject(HX_CHART_LOADER);
|
|
31
31
|
#platform = inject(PLATFORM_ID);
|
|
32
|
-
canvas = viewChild.required("canvas", ...ngDevMode ? [{ debugName: "canvas" }] :
|
|
33
|
-
type = input("bar", ...ngDevMode ? [{ debugName: "type" }] :
|
|
34
|
-
data = input({}, ...ngDevMode ? [{ debugName: "data" }] :
|
|
35
|
-
options = input({}, ...ngDevMode ? [{ debugName: "options" }] :
|
|
36
|
-
plugins = input([], ...ngDevMode ? [{ debugName: "plugins" }] :
|
|
37
|
-
width = input(...ngDevMode ? [void 0, { debugName: "width" }] :
|
|
38
|
-
height = input(...ngDevMode ? [void 0, { debugName: "height" }] :
|
|
32
|
+
canvas = viewChild.required("canvas", ...ngDevMode ? [{ debugName: "canvas" }] : /* istanbul ignore next */ []);
|
|
33
|
+
type = input("bar", ...ngDevMode ? [{ debugName: "type" }] : /* istanbul ignore next */ []);
|
|
34
|
+
data = input({}, ...ngDevMode ? [{ debugName: "data" }] : /* istanbul ignore next */ []);
|
|
35
|
+
options = input({}, ...ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []);
|
|
36
|
+
plugins = input([], ...ngDevMode ? [{ debugName: "plugins" }] : /* istanbul ignore next */ []);
|
|
37
|
+
width = input(...ngDevMode ? [void 0, { debugName: "width" }] : /* istanbul ignore next */ []);
|
|
38
|
+
height = input(...ngDevMode ? [void 0, { debugName: "height" }] : /* istanbul ignore next */ []);
|
|
39
39
|
responsive = input(true, {
|
|
40
|
-
...ngDevMode ? { debugName: "responsive" } :
|
|
40
|
+
...ngDevMode ? { debugName: "responsive" } : /* istanbul ignore next */ {},
|
|
41
41
|
transform: (v) => v !== false && v !== "false"
|
|
42
42
|
});
|
|
43
|
-
ariaLabel = input(...ngDevMode ? [void 0, { debugName: "ariaLabel" }] :
|
|
44
|
-
ariaLabelledBy = input(...ngDevMode ? [void 0, { debugName: "ariaLabelledBy" }] :
|
|
43
|
+
ariaLabel = input(...ngDevMode ? [void 0, { debugName: "ariaLabel" }] : /* istanbul ignore next */ []);
|
|
44
|
+
ariaLabelledBy = input(...ngDevMode ? [void 0, { debugName: "ariaLabelledBy" }] : /* istanbul ignore next */ []);
|
|
45
45
|
themeDelay = input(50, {
|
|
46
|
-
...ngDevMode ? { debugName: "themeDelay" } :
|
|
46
|
+
...ngDevMode ? { debugName: "themeDelay" } : /* istanbul ignore next */ {},
|
|
47
47
|
transform: numberAttribute
|
|
48
48
|
});
|
|
49
49
|
dataSelect = output();
|
|
@@ -214,7 +214,7 @@ var HxChart = class HxChart {
|
|
|
214
214
|
}
|
|
215
215
|
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
216
216
|
minVersion: "12.0.0",
|
|
217
|
-
version: "22.2.
|
|
217
|
+
version: "22.2.3",
|
|
218
218
|
ngImport: i0,
|
|
219
219
|
type: HxChart,
|
|
220
220
|
deps: [],
|
|
@@ -222,7 +222,7 @@ var HxChart = class HxChart {
|
|
|
222
222
|
});
|
|
223
223
|
static ɵcmp = i0.ɵɵngDeclareComponent({
|
|
224
224
|
minVersion: "17.2.0",
|
|
225
|
-
version: "22.2.
|
|
225
|
+
version: "22.2.3",
|
|
226
226
|
type: HxChart,
|
|
227
227
|
isStandalone: true,
|
|
228
228
|
selector: "hx-chart",
|
|
@@ -326,7 +326,7 @@ var HxChart = class HxChart {
|
|
|
326
326
|
};
|
|
327
327
|
i0.ɵɵngDeclareClassMetadata({
|
|
328
328
|
minVersion: "12.0.0",
|
|
329
|
-
version: "22.2.
|
|
329
|
+
version: "22.2.3",
|
|
330
330
|
ngImport: i0,
|
|
331
331
|
type: HxChart,
|
|
332
332
|
decorators: [{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gravionlabs-helix-ui-chart.mjs","names":[],"sources":["../../../projects/ui/chart/chart.ts"],"sourcesContent":["import { isPlatformBrowser } from '@angular/common';\nimport {\n afterNextRender,\n ChangeDetectionStrategy,\n Component,\n DestroyRef,\n ElementRef,\n effect,\n InjectionToken,\n inject,\n input,\n NgZone,\n numberAttribute,\n output,\n PLATFORM_ID,\n untracked,\n viewChild,\n} from '@angular/core';\n\nexport type HxChartType =\n | 'bar'\n | 'line'\n | 'scatter'\n | 'bubble'\n | 'pie'\n | 'doughnut'\n | 'polarArea'\n | 'radar';\n\n/** What `(dataSelect)` carries: the click and what chart.js found under it. */\nexport interface HxChartSelectEvent {\n originalEvent: Event;\n /** The nearest element under the pointer (chart.js `{ datasetIndex, index, element }`). */\n element: unknown;\n /** The elements of that dataset. */\n dataset: unknown[];\n}\n\n/** The part of chart.js this component uses; the real module is `chart.js/auto`. */\nexport interface HxChartModule {\n default: new (canvas: HTMLCanvasElement, config: unknown) => HxChartInstance;\n}\n\nexport interface HxChartInstance {\n data: unknown;\n options: unknown;\n update(mode?: string): void;\n destroy(): void;\n getElementsAtEventForMode(\n event: Event,\n mode: string,\n options: object,\n useFinalPosition: boolean,\n ): unknown[];\n}\n\n/**\n * Loads chart.js. The default is a dynamic `import('chart.js/auto')`, so chart.js is only fetched by apps that show\n * a chart; provide another function to register a smaller set of chart.js parts or to replace it in tests.\n */\nexport const HX_CHART_LOADER = new InjectionToken<() => Promise<HxChartModule>>('HX_CHART_LOADER', {\n providedIn: 'root',\n factory: () => () => import('chart.js/auto') as unknown as Promise<HxChartModule>,\n});\n\n/** Colours of the datasets that do not set their own, as tokens in the order they are used. */\nconst PALETTE = [\n '--h-primary-color',\n '--h-orange-500',\n '--h-green-500',\n '--h-purple-500',\n '--h-sky-500',\n '--h-red-500',\n '--h-yellow-500',\n '--h-teal-500',\n];\n\ntype Json = Record<string, unknown>;\n\n/** The parts of chart.js data and datasets that are read here; everything else is passed on untouched. */\ninterface ChartDataLike extends Json {\n datasets?: unknown;\n}\ninterface DatasetLike extends Json {\n data?: unknown;\n backgroundColor?: unknown;\n borderColor?: unknown;\n}\n\nconst isObject = (value: unknown): value is Json =>\n typeof value === 'object' && value !== null && !Array.isArray(value);\n\n/** `defaults` underneath, `overrides` on top; objects merge, everything else is replaced. */\nfunction merge(defaults: Json, overrides: Json): Json {\n const out: Json = { ...defaults };\n for (const [key, value] of Object.entries(overrides)) {\n const base = out[key];\n out[key] = isObject(base) && isObject(value) ? merge(base, value) : value;\n }\n return out;\n}\n\n/**\n * A chart.js chart whose colours, grid and font come from the Helix tokens.\n *\n * ```html\n * <hx-chart type=\"bar\" [data]=\"data\" [options]=\"options\" height=\"20rem\" ariaLabel=\"Sales per month\">\n * <table hxChartFallback>…the same numbers as text…</table>\n * </hx-chart>\n * ```\n *\n * chart.js is an optional peer dependency, loaded with a dynamic import (see `HX_CHART_LOADER`) the first time a\n * chart is shown. Datasets without colours get the Helix palette; text, grid and legend colours come from\n * `--h-text-muted-color` and `--h-content-border-color` of the element. Your `options` win over these defaults.\n * The chart is redrawn when `class` or `style` of `<html>` change (dark mode, the primary colour).\n *\n * The canvas is an `img` named by `ariaLabel` / `ariaLabelledBy`; content marked `hxChartFallback` becomes the\n * canvas fallback content, which assistive technology reads: put the numbers there as text or as a table.\n */\n@Component({\n selector: 'hx-chart',\n changeDetection: ChangeDetectionStrategy.OnPush,\n host: { class: 'hx-chart' },\n template: `\n <div class=\"hx-chart-frame\" [style.width]=\"width() ?? null\" [style.height]=\"height() ?? null\">\n <canvas\n #canvas\n class=\"hx-chart-canvas\"\n role=\"img\"\n [attr.aria-label]=\"ariaLabel() || null\"\n [attr.aria-labelledby]=\"ariaLabelledBy() || null\"\n (click)=\"onClick($event)\"\n ><ng-content select=\"[hxChartFallback]\" /></canvas>\n </div>\n `,\n})\nexport class HxChart {\n readonly #host = inject<ElementRef<HTMLElement>>(ElementRef).nativeElement;\n readonly #zone = inject(NgZone);\n readonly #loader = inject(HX_CHART_LOADER);\n readonly #platform = inject(PLATFORM_ID);\n private readonly canvas = viewChild.required<ElementRef<HTMLCanvasElement>>('canvas');\n\n readonly type = input<HxChartType>('bar');\n /** chart.js data: `{ labels, datasets }`. Pass a new object to update the chart. */\n readonly data = input<ChartDataLike>({});\n /** chart.js options, on top of the token defaults. Pass a new object to update the chart. */\n readonly options = input<Json>({});\n /** Extra chart.js plugins for this chart. */\n readonly plugins = input<readonly unknown[]>([]);\n /** CSS width of the frame, e.g. `30rem`; the chart fills its container by default. */\n readonly width = input<string>();\n /** CSS height of the frame; without it chart.js keeps its aspect ratio. */\n readonly height = input<string>();\n /** Resize with the container. */\n readonly responsive = input(true, { transform: (v: unknown) => v !== false && v !== 'false' });\n readonly ariaLabel = input<string>();\n readonly ariaLabelledBy = input<string>();\n /** Delay of a redraw after the theme changed, in ms; the style change is usually followed by more. */\n readonly themeDelay = input(50, { transform: numberAttribute });\n\n /** A data point or dataset was clicked. */\n readonly dataSelect = output<HxChartSelectEvent>();\n\n #chart: HxChartInstance | undefined;\n #chartType: HxChartType | undefined;\n #ready = false;\n #loading = false;\n #observer: MutationObserver | undefined;\n #timer: ReturnType<typeof setTimeout> | undefined;\n\n constructor() {\n afterNextRender(() => {\n this.#ready = true;\n void this.#render();\n this.#watchTheme();\n });\n effect(() => {\n this.type();\n this.data();\n this.options();\n this.plugins();\n this.responsive();\n this.width();\n this.height();\n untracked(() => {\n if (this.#ready) void this.#render();\n });\n });\n inject(DestroyRef).onDestroy(() => {\n clearTimeout(this.#timer);\n this.#observer?.disconnect();\n this.#chart?.destroy();\n this.#chart = undefined;\n this.#ready = false;\n });\n }\n\n /** The chart.js instance, once it exists; `undefined` before chart.js has loaded. */\n get chart(): HxChartInstance | undefined {\n return this.#chart;\n }\n\n /** Redraws the chart, e.g. after the theme changed in a way the component cannot see. */\n refresh(): void {\n if (this.#chart) {\n this.#apply(this.#chart);\n this.#chart.update();\n }\n }\n\n protected onClick(event: MouseEvent): void {\n const chart = this.#chart;\n if (!chart) return;\n const element = chart.getElementsAtEventForMode(event, 'nearest', { intersect: true }, false);\n const dataset = chart.getElementsAtEventForMode(event, 'dataset', { intersect: true }, false);\n if (element?.[0] && dataset)\n this.dataSelect.emit({ originalEvent: event, element: element[0], dataset });\n }\n\n async #render(): Promise<void> {\n if (!isPlatformBrowser(this.#platform)) return;\n const type = this.type();\n if (this.#chart && this.#chartType === type) {\n this.#apply(this.#chart);\n this.#chart.update();\n return;\n }\n if (this.#loading) return;\n this.#loading = true;\n try {\n const module = await this.#loader();\n if (!this.#ready) return; // destroyed while chart.js loaded\n this.#chart?.destroy();\n const Chart = module.default;\n this.#zone.runOutsideAngular(() => {\n this.#chart = new Chart(this.canvas().nativeElement, {\n type: this.type(),\n data: this.#themedData(),\n options: this.#themedOptions(),\n plugins: [...this.plugins()],\n });\n });\n this.#chartType = this.type();\n } catch (error) {\n console.error(\n 'hx-chart: chart.js could not be loaded. Is the peer dependency \"chart.js\" installed?',\n error,\n );\n return;\n } finally {\n this.#loading = false;\n }\n // inputs may have changed while chart.js loaded\n if (this.#chart && this.#chartType !== this.type()) await this.#render();\n }\n\n #apply(chart: HxChartInstance): void {\n chart.data = this.#themedData();\n chart.options = this.#themedOptions();\n }\n\n #token(name: string): string {\n return getComputedStyle(this.#host).getPropertyValue(name).trim();\n }\n\n /** The datasets with palette colours where they set none. The input is not changed. */\n #themedData(): Json {\n const data = this.data();\n const datasets = data.datasets;\n if (!Array.isArray(datasets)) return data;\n const palette = PALETTE.map((name) => this.#token(name)).filter(Boolean);\n if (palette.length === 0) return data;\n const perPoint = ['pie', 'doughnut', 'polarArea'].includes(this.type());\n return {\n ...data,\n datasets: datasets.map((dataset: DatasetLike, i: number) => {\n if (!isObject(dataset)) return dataset;\n const color = palette[i % palette.length];\n const count = Array.isArray(dataset.data) ? dataset.data.length : 0;\n const points = Array.from({ length: count }, (_, n) => palette[n % palette.length]);\n const filled = this.type() === 'line' || this.type() === 'radar';\n return {\n ...dataset,\n backgroundColor:\n dataset.backgroundColor ??\n (perPoint ? points : filled ? `color-mix(in srgb, ${color}, transparent 80%)` : color),\n borderColor:\n dataset.borderColor ?? (perPoint ? this.#token('--h-content-background') : color),\n };\n }),\n };\n }\n\n #themedOptions(): Json {\n const text = this.#token('--h-text-color');\n const muted = this.#token('--h-text-muted-color');\n const grid = this.#token('--h-content-border-color');\n const axis = { ticks: { color: muted }, grid: { color: grid }, border: { color: grid } };\n const radial = {\n ticks: { color: muted, backdropColor: 'transparent' },\n grid: { color: grid },\n angleLines: { color: grid },\n pointLabels: { color: text },\n };\n const type = this.type();\n const scales =\n type === 'radar' || type === 'polarArea'\n ? { r: radial }\n : type === 'pie' || type === 'doughnut'\n ? {}\n : { x: axis, y: axis };\n const defaults: Json = {\n responsive: this.responsive(),\n maintainAspectRatio: !(this.height() || this.width()),\n color: muted,\n borderColor: grid,\n plugins: { legend: { labels: { color: text } } },\n scales,\n };\n return merge(defaults, this.options());\n }\n\n #watchTheme(): void {\n if (typeof MutationObserver === 'undefined') return;\n this.#observer = new MutationObserver(() => {\n clearTimeout(this.#timer);\n this.#timer = setTimeout(() => this.refresh(), this.themeDelay());\n });\n this.#observer.observe(document.documentElement, {\n attributes: true,\n attributeFilter: ['class', 'style', 'data-theme'],\n });\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"gravionlabs-helix-ui-chart.mjs","names":[],"sources":["../../../projects/ui/chart/chart.ts"],"sourcesContent":["import { isPlatformBrowser } from '@angular/common';\nimport {\n afterNextRender,\n ChangeDetectionStrategy,\n Component,\n DestroyRef,\n ElementRef,\n effect,\n InjectionToken,\n inject,\n input,\n NgZone,\n numberAttribute,\n output,\n PLATFORM_ID,\n untracked,\n viewChild,\n} from '@angular/core';\n\nexport type HxChartType =\n | 'bar'\n | 'line'\n | 'scatter'\n | 'bubble'\n | 'pie'\n | 'doughnut'\n | 'polarArea'\n | 'radar';\n\n/** What `(dataSelect)` carries: the click and what chart.js found under it. */\nexport interface HxChartSelectEvent {\n originalEvent: Event;\n /** The nearest element under the pointer (chart.js `{ datasetIndex, index, element }`). */\n element: unknown;\n /** The elements of that dataset. */\n dataset: unknown[];\n}\n\n/** The part of chart.js this component uses; the real module is `chart.js/auto`. */\nexport interface HxChartModule {\n default: new (canvas: HTMLCanvasElement, config: unknown) => HxChartInstance;\n}\n\nexport interface HxChartInstance {\n data: unknown;\n options: unknown;\n update(mode?: string): void;\n destroy(): void;\n getElementsAtEventForMode(\n event: Event,\n mode: string,\n options: object,\n useFinalPosition: boolean,\n ): unknown[];\n}\n\n/**\n * Loads chart.js. The default is a dynamic `import('chart.js/auto')`, so chart.js is only fetched by apps that show\n * a chart; provide another function to register a smaller set of chart.js parts or to replace it in tests.\n */\nexport const HX_CHART_LOADER = new InjectionToken<() => Promise<HxChartModule>>('HX_CHART_LOADER', {\n providedIn: 'root',\n factory: () => () => import('chart.js/auto') as unknown as Promise<HxChartModule>,\n});\n\n/** Colours of the datasets that do not set their own, as tokens in the order they are used. */\nconst PALETTE = [\n '--h-primary-color',\n '--h-orange-500',\n '--h-green-500',\n '--h-purple-500',\n '--h-sky-500',\n '--h-red-500',\n '--h-yellow-500',\n '--h-teal-500',\n];\n\ntype Json = Record<string, unknown>;\n\n/** The parts of chart.js data and datasets that are read here; everything else is passed on untouched. */\ninterface ChartDataLike extends Json {\n datasets?: unknown;\n}\ninterface DatasetLike extends Json {\n data?: unknown;\n backgroundColor?: unknown;\n borderColor?: unknown;\n}\n\nconst isObject = (value: unknown): value is Json =>\n typeof value === 'object' && value !== null && !Array.isArray(value);\n\n/** `defaults` underneath, `overrides` on top; objects merge, everything else is replaced. */\nfunction merge(defaults: Json, overrides: Json): Json {\n const out: Json = { ...defaults };\n for (const [key, value] of Object.entries(overrides)) {\n const base = out[key];\n out[key] = isObject(base) && isObject(value) ? merge(base, value) : value;\n }\n return out;\n}\n\n/**\n * A chart.js chart whose colours, grid and font come from the Helix tokens.\n *\n * ```html\n * <hx-chart type=\"bar\" [data]=\"data\" [options]=\"options\" height=\"20rem\" ariaLabel=\"Sales per month\">\n * <table hxChartFallback>…the same numbers as text…</table>\n * </hx-chart>\n * ```\n *\n * chart.js is an optional peer dependency, loaded with a dynamic import (see `HX_CHART_LOADER`) the first time a\n * chart is shown. Datasets without colours get the Helix palette; text, grid and legend colours come from\n * `--h-text-muted-color` and `--h-content-border-color` of the element. Your `options` win over these defaults.\n * The chart is redrawn when `class` or `style` of `<html>` change (dark mode, the primary colour).\n *\n * The canvas is an `img` named by `ariaLabel` / `ariaLabelledBy`; content marked `hxChartFallback` becomes the\n * canvas fallback content, which assistive technology reads: put the numbers there as text or as a table.\n */\n@Component({\n selector: 'hx-chart',\n changeDetection: ChangeDetectionStrategy.OnPush,\n host: { class: 'hx-chart' },\n template: `\n <div class=\"hx-chart-frame\" [style.width]=\"width() ?? null\" [style.height]=\"height() ?? null\">\n <canvas\n #canvas\n class=\"hx-chart-canvas\"\n role=\"img\"\n [attr.aria-label]=\"ariaLabel() || null\"\n [attr.aria-labelledby]=\"ariaLabelledBy() || null\"\n (click)=\"onClick($event)\"\n ><ng-content select=\"[hxChartFallback]\" /></canvas>\n </div>\n `,\n})\nexport class HxChart {\n readonly #host = inject<ElementRef<HTMLElement>>(ElementRef).nativeElement;\n readonly #zone = inject(NgZone);\n readonly #loader = inject(HX_CHART_LOADER);\n readonly #platform = inject(PLATFORM_ID);\n private readonly canvas = viewChild.required<ElementRef<HTMLCanvasElement>>('canvas');\n\n readonly type = input<HxChartType>('bar');\n /** chart.js data: `{ labels, datasets }`. Pass a new object to update the chart. */\n readonly data = input<ChartDataLike>({});\n /** chart.js options, on top of the token defaults. Pass a new object to update the chart. */\n readonly options = input<Json>({});\n /** Extra chart.js plugins for this chart. */\n readonly plugins = input<readonly unknown[]>([]);\n /** CSS width of the frame, e.g. `30rem`; the chart fills its container by default. */\n readonly width = input<string>();\n /** CSS height of the frame; without it chart.js keeps its aspect ratio. */\n readonly height = input<string>();\n /** Resize with the container. */\n readonly responsive = input(true, { transform: (v: unknown) => v !== false && v !== 'false' });\n readonly ariaLabel = input<string>();\n readonly ariaLabelledBy = input<string>();\n /** Delay of a redraw after the theme changed, in ms; the style change is usually followed by more. */\n readonly themeDelay = input(50, { transform: numberAttribute });\n\n /** A data point or dataset was clicked. */\n readonly dataSelect = output<HxChartSelectEvent>();\n\n #chart: HxChartInstance | undefined;\n #chartType: HxChartType | undefined;\n #ready = false;\n #loading = false;\n #observer: MutationObserver | undefined;\n #timer: ReturnType<typeof setTimeout> | undefined;\n\n constructor() {\n afterNextRender(() => {\n this.#ready = true;\n void this.#render();\n this.#watchTheme();\n });\n effect(() => {\n this.type();\n this.data();\n this.options();\n this.plugins();\n this.responsive();\n this.width();\n this.height();\n untracked(() => {\n if (this.#ready) void this.#render();\n });\n });\n inject(DestroyRef).onDestroy(() => {\n clearTimeout(this.#timer);\n this.#observer?.disconnect();\n this.#chart?.destroy();\n this.#chart = undefined;\n this.#ready = false;\n });\n }\n\n /** The chart.js instance, once it exists; `undefined` before chart.js has loaded. */\n get chart(): HxChartInstance | undefined {\n return this.#chart;\n }\n\n /** Redraws the chart, e.g. after the theme changed in a way the component cannot see. */\n refresh(): void {\n if (this.#chart) {\n this.#apply(this.#chart);\n this.#chart.update();\n }\n }\n\n protected onClick(event: MouseEvent): void {\n const chart = this.#chart;\n if (!chart) return;\n const element = chart.getElementsAtEventForMode(event, 'nearest', { intersect: true }, false);\n const dataset = chart.getElementsAtEventForMode(event, 'dataset', { intersect: true }, false);\n if (element?.[0] && dataset)\n this.dataSelect.emit({ originalEvent: event, element: element[0], dataset });\n }\n\n async #render(): Promise<void> {\n if (!isPlatformBrowser(this.#platform)) return;\n const type = this.type();\n if (this.#chart && this.#chartType === type) {\n this.#apply(this.#chart);\n this.#chart.update();\n return;\n }\n if (this.#loading) return;\n this.#loading = true;\n try {\n const module = await this.#loader();\n if (!this.#ready) return; // destroyed while chart.js loaded\n this.#chart?.destroy();\n const Chart = module.default;\n this.#zone.runOutsideAngular(() => {\n this.#chart = new Chart(this.canvas().nativeElement, {\n type: this.type(),\n data: this.#themedData(),\n options: this.#themedOptions(),\n plugins: [...this.plugins()],\n });\n });\n this.#chartType = this.type();\n } catch (error) {\n console.error(\n 'hx-chart: chart.js could not be loaded. Is the peer dependency \"chart.js\" installed?',\n error,\n );\n return;\n } finally {\n this.#loading = false;\n }\n // inputs may have changed while chart.js loaded\n if (this.#chart && this.#chartType !== this.type()) await this.#render();\n }\n\n #apply(chart: HxChartInstance): void {\n chart.data = this.#themedData();\n chart.options = this.#themedOptions();\n }\n\n #token(name: string): string {\n return getComputedStyle(this.#host).getPropertyValue(name).trim();\n }\n\n /** The datasets with palette colours where they set none. The input is not changed. */\n #themedData(): Json {\n const data = this.data();\n const datasets = data.datasets;\n if (!Array.isArray(datasets)) return data;\n const palette = PALETTE.map((name) => this.#token(name)).filter(Boolean);\n if (palette.length === 0) return data;\n const perPoint = ['pie', 'doughnut', 'polarArea'].includes(this.type());\n return {\n ...data,\n datasets: datasets.map((dataset: DatasetLike, i: number) => {\n if (!isObject(dataset)) return dataset;\n const color = palette[i % palette.length];\n const count = Array.isArray(dataset.data) ? dataset.data.length : 0;\n const points = Array.from({ length: count }, (_, n) => palette[n % palette.length]);\n const filled = this.type() === 'line' || this.type() === 'radar';\n return {\n ...dataset,\n backgroundColor:\n dataset.backgroundColor ??\n (perPoint ? points : filled ? `color-mix(in srgb, ${color}, transparent 80%)` : color),\n borderColor:\n dataset.borderColor ?? (perPoint ? this.#token('--h-content-background') : color),\n };\n }),\n };\n }\n\n #themedOptions(): Json {\n const text = this.#token('--h-text-color');\n const muted = this.#token('--h-text-muted-color');\n const grid = this.#token('--h-content-border-color');\n const axis = { ticks: { color: muted }, grid: { color: grid }, border: { color: grid } };\n const radial = {\n ticks: { color: muted, backdropColor: 'transparent' },\n grid: { color: grid },\n angleLines: { color: grid },\n pointLabels: { color: text },\n };\n const type = this.type();\n const scales =\n type === 'radar' || type === 'polarArea'\n ? { r: radial }\n : type === 'pie' || type === 'doughnut'\n ? {}\n : { x: axis, y: axis };\n const defaults: Json = {\n responsive: this.responsive(),\n maintainAspectRatio: !(this.height() || this.width()),\n color: muted,\n borderColor: grid,\n plugins: { legend: { labels: { color: text } } },\n scales,\n };\n return merge(defaults, this.options());\n }\n\n #watchTheme(): void {\n if (typeof MutationObserver === 'undefined') return;\n this.#observer = new MutationObserver(() => {\n clearTimeout(this.#timer);\n this.#timer = setTimeout(() => this.refresh(), this.themeDelay());\n });\n this.#observer.observe(document.documentElement, {\n attributes: true,\n attributeFilter: ['class', 'style', 'data-theme'],\n });\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gravionlabs-helix-ui-validators.mjs","names":[],"sources":["../../../projects/ui/validators/validator-key.enum.ts","../../../projects/ui/validators/validators.ts"],"sourcesContent":["export enum ValidatorKey {\n Required = 'Required',\n Email = 'Email',\n Number = 'Number',\n Integer = 'Integer',\n Min = 'Min',\n Max = 'Max',\n MinLength = 'MinLength',\n MaxLength = 'MaxLength',\n Pattern = 'Pattern',\n Date = 'Date',\n OneOf = 'OneOf',\n AllOf = 'AllOf',\n}\n","import type { AbstractControl, ValidationErrors, ValidatorFn } from '@angular/forms';\nimport { FormControl, Validators as NgValidators } from '@angular/forms';\nimport { ValidatorKey } from './validator-key.enum';\n\nexport type ValidatorMessage = (value: any) => string;\n\nconst EMPTY_VALUES = ['', null, undefined];\n\nconst isEmptyValue = (v: unknown): boolean => EMPTY_VALUES.includes(v as string);\n\nconst checkEmpty =\n (allowEmpty: boolean, check: (value: unknown) => boolean) =>\n (ctrl: AbstractControl): boolean =>\n allowEmpty\n ? isEmptyValue(ctrl.value) || check(ctrl.value)\n : !isEmptyValue(ctrl.value) && check(ctrl.value);\n\nconst buildValidator =\n (\n isValid: (ctrl: AbstractControl) => boolean,\n key: ValidatorKey,\n msg: string | ValidatorMessage,\n ): ValidatorFn =>\n (ctrl: AbstractControl): ValidationErrors | null =>\n isValid(ctrl) ? null : { [key]: typeof msg === 'function' ? msg(ctrl.value) : msg };\n\nexport { buildValidator, checkEmpty, EMPTY_VALUES, isEmptyValue };\n\nexport const Validators = {\n required: (msg: string | ValidatorMessage): ValidatorFn => {\n const predicate = (ctrl: AbstractControl): boolean => !isEmptyValue(ctrl.value);\n return buildValidator(predicate, ValidatorKey.Required, msg);\n },\n\n email: (msg: string | ValidatorMessage, allowEmpty = true): ValidatorFn => {\n const check = (value: unknown): boolean => {\n const ctrl = new FormControl(value);\n return NgValidators.email(ctrl) === null;\n };\n return buildValidator(checkEmpty(allowEmpty, check), ValidatorKey.Email, msg);\n },\n\n pattern: (msg: string | ValidatorMessage, regex: RegExp, allowEmpty = true): ValidatorFn => {\n const check = (value: unknown): boolean => regex.test(value as string);\n return buildValidator(checkEmpty(allowEmpty, check), ValidatorKey.Pattern, msg);\n },\n\n date: (msg: string | ValidatorMessage, allowEmpty = true): ValidatorFn => {\n const check = (value: unknown): boolean => {\n const d = new Date(value as string | number | Date);\n return !Number.isNaN(d.getTime());\n };\n return buildValidator(checkEmpty(allowEmpty, check), ValidatorKey.Date, msg);\n },\n\n number: (msg: string | ValidatorMessage, allowEmpty = true): ValidatorFn => {\n const check = (value: unknown): boolean =>\n typeof value !== 'boolean' && !Number.isNaN(Number(value));\n return buildValidator(checkEmpty(allowEmpty, check), ValidatorKey.Number, msg);\n },\n\n integer: (msg: string | ValidatorMessage, allowEmpty = true): ValidatorFn => {\n const check = (value: unknown): boolean => {\n if (typeof value === 'boolean') return false;\n const num = Number(value);\n return !Number.isNaN(num) && num % 1 === 0;\n };\n return buildValidator(checkEmpty(allowEmpty, check), ValidatorKey.Integer, msg);\n },\n\n min: (msg: string | ValidatorMessage, minimum: number, allowEmpty = true): ValidatorFn => {\n const check = (value: unknown): boolean => Number(value) >= minimum;\n return buildValidator(checkEmpty(allowEmpty, check), ValidatorKey.Min, msg);\n },\n\n max: (msg: string | ValidatorMessage, maximum: number, allowEmpty = true): ValidatorFn => {\n const check = (value: unknown): boolean => Number(value) <= maximum;\n return buildValidator(checkEmpty(allowEmpty, check), ValidatorKey.Max, msg);\n },\n\n minLength: (msg: string | ValidatorMessage, min: number, allowEmpty = true): ValidatorFn => {\n const check = (value: unknown): boolean => (value as { length: number }).length >= min;\n return buildValidator(checkEmpty(allowEmpty, check), ValidatorKey.MinLength, msg);\n },\n\n maxLength: (msg: string | ValidatorMessage, max: number, allowEmpty = true): ValidatorFn => {\n const check = (value: unknown): boolean => (value as { length: number }).length <= max;\n return buildValidator(checkEmpty(allowEmpty, check), ValidatorKey.MaxLength, msg);\n },\n\n oneOf: (msg: string | ValidatorMessage, options: unknown[]): ValidatorFn => {\n const anyEmptyMember = EMPTY_VALUES.some((v) => options.includes(v));\n return (ctrl: AbstractControl): ValidationErrors | null => {\n const value = ctrl.value;\n if (anyEmptyMember && isEmptyValue(value)) return null;\n if (options.includes(value)) return null;\n return { [ValidatorKey.OneOf]: typeof msg === 'function' ? msg(value) : msg };\n };\n },\n\n allOf: (msg: string | ValidatorMessage, options: unknown[], allowEmpty = true): ValidatorFn => {\n const check = (value: unknown): boolean =>\n Array.isArray(value) && value.every((v) => options.includes(v));\n return buildValidator(checkEmpty(allowEmpty, check), ValidatorKey.AllOf, msg);\n },\n};\n"],"mappings":";AAAA,IAAY;CAAZ,SAAY,cAAY;CACtB,aAAA,cAAA;CACA,aAAA,WAAA;CACA,aAAA,YAAA;CACA,aAAA,aAAA;CACA,aAAA,SAAA;CACA,aAAA,SAAA;CACA,aAAA,eAAA;CACA,aAAA,eAAA;CACA,aAAA,aAAA;CACA,aAAA,UAAA;CACA,aAAA,WAAA;CACA,aAAA,WAAA;AACF,EAAA,CAbY,iBAAA,eAAY,CAAA,EAAA;ACMxB,MAAM,eAAe;CAAC;CAAI;CAAM;AAAS;AAEzC,MAAM,gBAAgB,MAAwB,aAAa,SAAS,CAAW;AAE/E,MAAM,cACH,YAAqB,WACrB,SACC,aACI,aAAa,KAAK,KAAK,KAAK,MAAM,KAAK,KAAK,IAC5C,CAAC,aAAa,KAAK,KAAK,KAAK,MAAM,KAAK,KAAK;AAErD,MAAM,kBAEF,SACA,KACA,SAED,SACC,QAAQ,IAAI,IAAI,OAAO,GAAG,MAAM,OAAO,QAAQ,aAAa,IAAI,KAAK,KAAK,IAAI,IAAG;AAIrF,MAAa,aAAa;CACxB,WAAW,QAA+C;EACxD,MAAM,aAAa,SAAmC,CAAC,aAAa,KAAK,KAAK;EAC9E,OAAO,eAAe,WAAW,aAAa,UAAU,GAAG;CAC7D;CAEA,QAAQ,KAAgC,aAAa,SAAqB;EACxE,MAAM,SAAS,UAA2B;GACxC,MAAM,OAAO,IAAI,YAAY,KAAK;GAClC,
|
|
1
|
+
{"version":3,"file":"gravionlabs-helix-ui-validators.mjs","names":["NgValidators"],"sources":["../../../projects/ui/validators/validator-key.enum.ts","../../../projects/ui/validators/validators.ts"],"sourcesContent":["export enum ValidatorKey {\n Required = 'Required',\n Email = 'Email',\n Number = 'Number',\n Integer = 'Integer',\n Min = 'Min',\n Max = 'Max',\n MinLength = 'MinLength',\n MaxLength = 'MaxLength',\n Pattern = 'Pattern',\n Date = 'Date',\n OneOf = 'OneOf',\n AllOf = 'AllOf',\n}\n","import type { AbstractControl, ValidationErrors, ValidatorFn } from '@angular/forms';\nimport { FormControl, Validators as NgValidators } from '@angular/forms';\nimport { ValidatorKey } from './validator-key.enum';\n\nexport type ValidatorMessage = (value: any) => string;\n\nconst EMPTY_VALUES = ['', null, undefined];\n\nconst isEmptyValue = (v: unknown): boolean => EMPTY_VALUES.includes(v as string);\n\nconst checkEmpty =\n (allowEmpty: boolean, check: (value: unknown) => boolean) =>\n (ctrl: AbstractControl): boolean =>\n allowEmpty\n ? isEmptyValue(ctrl.value) || check(ctrl.value)\n : !isEmptyValue(ctrl.value) && check(ctrl.value);\n\nconst buildValidator =\n (\n isValid: (ctrl: AbstractControl) => boolean,\n key: ValidatorKey,\n msg: string | ValidatorMessage,\n ): ValidatorFn =>\n (ctrl: AbstractControl): ValidationErrors | null =>\n isValid(ctrl) ? null : { [key]: typeof msg === 'function' ? msg(ctrl.value) : msg };\n\nexport { buildValidator, checkEmpty, EMPTY_VALUES, isEmptyValue };\n\nexport const Validators = {\n required: (msg: string | ValidatorMessage): ValidatorFn => {\n const predicate = (ctrl: AbstractControl): boolean => !isEmptyValue(ctrl.value);\n return buildValidator(predicate, ValidatorKey.Required, msg);\n },\n\n email: (msg: string | ValidatorMessage, allowEmpty = true): ValidatorFn => {\n const check = (value: unknown): boolean => {\n const ctrl = new FormControl(value);\n return NgValidators.email(ctrl) === null;\n };\n return buildValidator(checkEmpty(allowEmpty, check), ValidatorKey.Email, msg);\n },\n\n pattern: (msg: string | ValidatorMessage, regex: RegExp, allowEmpty = true): ValidatorFn => {\n const check = (value: unknown): boolean => regex.test(value as string);\n return buildValidator(checkEmpty(allowEmpty, check), ValidatorKey.Pattern, msg);\n },\n\n date: (msg: string | ValidatorMessage, allowEmpty = true): ValidatorFn => {\n const check = (value: unknown): boolean => {\n const d = new Date(value as string | number | Date);\n return !Number.isNaN(d.getTime());\n };\n return buildValidator(checkEmpty(allowEmpty, check), ValidatorKey.Date, msg);\n },\n\n number: (msg: string | ValidatorMessage, allowEmpty = true): ValidatorFn => {\n const check = (value: unknown): boolean =>\n typeof value !== 'boolean' && !Number.isNaN(Number(value));\n return buildValidator(checkEmpty(allowEmpty, check), ValidatorKey.Number, msg);\n },\n\n integer: (msg: string | ValidatorMessage, allowEmpty = true): ValidatorFn => {\n const check = (value: unknown): boolean => {\n if (typeof value === 'boolean') return false;\n const num = Number(value);\n return !Number.isNaN(num) && num % 1 === 0;\n };\n return buildValidator(checkEmpty(allowEmpty, check), ValidatorKey.Integer, msg);\n },\n\n min: (msg: string | ValidatorMessage, minimum: number, allowEmpty = true): ValidatorFn => {\n const check = (value: unknown): boolean => Number(value) >= minimum;\n return buildValidator(checkEmpty(allowEmpty, check), ValidatorKey.Min, msg);\n },\n\n max: (msg: string | ValidatorMessage, maximum: number, allowEmpty = true): ValidatorFn => {\n const check = (value: unknown): boolean => Number(value) <= maximum;\n return buildValidator(checkEmpty(allowEmpty, check), ValidatorKey.Max, msg);\n },\n\n minLength: (msg: string | ValidatorMessage, min: number, allowEmpty = true): ValidatorFn => {\n const check = (value: unknown): boolean => (value as { length: number }).length >= min;\n return buildValidator(checkEmpty(allowEmpty, check), ValidatorKey.MinLength, msg);\n },\n\n maxLength: (msg: string | ValidatorMessage, max: number, allowEmpty = true): ValidatorFn => {\n const check = (value: unknown): boolean => (value as { length: number }).length <= max;\n return buildValidator(checkEmpty(allowEmpty, check), ValidatorKey.MaxLength, msg);\n },\n\n oneOf: (msg: string | ValidatorMessage, options: unknown[]): ValidatorFn => {\n const anyEmptyMember = EMPTY_VALUES.some((v) => options.includes(v));\n return (ctrl: AbstractControl): ValidationErrors | null => {\n const value = ctrl.value;\n if (anyEmptyMember && isEmptyValue(value)) return null;\n if (options.includes(value)) return null;\n return { [ValidatorKey.OneOf]: typeof msg === 'function' ? msg(value) : msg };\n };\n },\n\n allOf: (msg: string | ValidatorMessage, options: unknown[], allowEmpty = true): ValidatorFn => {\n const check = (value: unknown): boolean =>\n Array.isArray(value) && value.every((v) => options.includes(v));\n return buildValidator(checkEmpty(allowEmpty, check), ValidatorKey.AllOf, msg);\n },\n};\n"],"mappings":";AAAA,IAAY;CAAZ,SAAY,cAAY;CACtB,aAAA,cAAA;CACA,aAAA,WAAA;CACA,aAAA,YAAA;CACA,aAAA,aAAA;CACA,aAAA,SAAA;CACA,aAAA,SAAA;CACA,aAAA,eAAA;CACA,aAAA,eAAA;CACA,aAAA,aAAA;CACA,aAAA,UAAA;CACA,aAAA,WAAA;CACA,aAAA,WAAA;AACF,EAAA,CAbY,iBAAA,eAAY,CAAA,EAAA;ACMxB,MAAM,eAAe;CAAC;CAAI;CAAM;AAAS;AAEzC,MAAM,gBAAgB,MAAwB,aAAa,SAAS,CAAW;AAE/E,MAAM,cACH,YAAqB,WACrB,SACC,aACI,aAAa,KAAK,KAAK,KAAK,MAAM,KAAK,KAAK,IAC5C,CAAC,aAAa,KAAK,KAAK,KAAK,MAAM,KAAK,KAAK;AAErD,MAAM,kBAEF,SACA,KACA,SAED,SACC,QAAQ,IAAI,IAAI,OAAO,GAAG,MAAM,OAAO,QAAQ,aAAa,IAAI,KAAK,KAAK,IAAI,IAAG;AAIrF,MAAa,aAAa;CACxB,WAAW,QAA+C;EACxD,MAAM,aAAa,SAAmC,CAAC,aAAa,KAAK,KAAK;EAC9E,OAAO,eAAe,WAAW,aAAa,UAAU,GAAG;CAC7D;CAEA,QAAQ,KAAgC,aAAa,SAAqB;EACxE,MAAM,SAAS,UAA2B;GACxC,MAAM,OAAO,IAAI,YAAY,KAAK;GAClC,OAAOA,aAAa,MAAM,IAAI,MAAM;EACtC;EACA,OAAO,eAAe,WAAW,YAAY,KAAK,GAAG,aAAa,OAAO,GAAG;CAC9E;CAEA,UAAU,KAAgC,OAAe,aAAa,SAAqB;EACzF,MAAM,SAAS,UAA4B,MAAM,KAAK,KAAe;EACrE,OAAO,eAAe,WAAW,YAAY,KAAK,GAAG,aAAa,SAAS,GAAG;CAChF;CAEA,OAAO,KAAgC,aAAa,SAAqB;EACvE,MAAM,SAAS,UAA2B;GACxC,MAAM,IAAI,IAAI,KAAK,KAA+B;GAClD,OAAO,CAAC,OAAO,MAAM,EAAE,QAAO,CAAE;EAClC;EACA,OAAO,eAAe,WAAW,YAAY,KAAK,GAAG,aAAa,MAAM,GAAG;CAC7E;CAEA,SAAS,KAAgC,aAAa,SAAqB;EACzE,MAAM,SAAS,UACb,OAAO,UAAU,aAAa,CAAC,OAAO,MAAM,OAAO,KAAK,CAAC;EAC3D,OAAO,eAAe,WAAW,YAAY,KAAK,GAAG,aAAa,QAAQ,GAAG;CAC/E;CAEA,UAAU,KAAgC,aAAa,SAAqB;EAC1E,MAAM,SAAS,UAA2B;GACxC,IAAI,OAAO,UAAU,WAAW,OAAO;GACvC,MAAM,MAAM,OAAO,KAAK;GACxB,OAAO,CAAC,OAAO,MAAM,GAAG,KAAK,MAAM,MAAM;EAC3C;EACA,OAAO,eAAe,WAAW,YAAY,KAAK,GAAG,aAAa,SAAS,GAAG;CAChF;CAEA,MAAM,KAAgC,SAAiB,aAAa,SAAqB;EACvF,MAAM,SAAS,UAA4B,OAAO,KAAK,KAAK;EAC5D,OAAO,eAAe,WAAW,YAAY,KAAK,GAAG,aAAa,KAAK,GAAG;CAC5E;CAEA,MAAM,KAAgC,SAAiB,aAAa,SAAqB;EACvF,MAAM,SAAS,UAA4B,OAAO,KAAK,KAAK;EAC5D,OAAO,eAAe,WAAW,YAAY,KAAK,GAAG,aAAa,KAAK,GAAG;CAC5E;CAEA,YAAY,KAAgC,KAAa,aAAa,SAAqB;EACzF,MAAM,SAAS,UAA6B,MAA6B,UAAU;EACnF,OAAO,eAAe,WAAW,YAAY,KAAK,GAAG,aAAa,WAAW,GAAG;CAClF;CAEA,YAAY,KAAgC,KAAa,aAAa,SAAqB;EACzF,MAAM,SAAS,UAA6B,MAA6B,UAAU;EACnF,OAAO,eAAe,WAAW,YAAY,KAAK,GAAG,aAAa,WAAW,GAAG;CAClF;CAEA,QAAQ,KAAgC,YAAmC;EACzE,MAAM,iBAAiB,aAAa,MAAM,MAAM,QAAQ,SAAS,CAAC,CAAC;EACnE,QAAQ,SAAkD;GACxD,MAAM,QAAQ,KAAK;GACnB,IAAI,kBAAkB,aAAa,KAAK,GAAG,OAAO;GAClD,IAAI,QAAQ,SAAS,KAAK,GAAG,OAAO;GACpC,OAAO,GAAG,aAAa,QAAQ,OAAO,QAAQ,aAAa,IAAI,KAAK,IAAI,IAAG;EAC7E;CACF;CAEA,QAAQ,KAAgC,SAAoB,aAAa,SAAqB;EAC5F,MAAM,SAAS,UACb,MAAM,QAAQ,KAAK,KAAK,MAAM,OAAO,MAAM,QAAQ,SAAS,CAAC,CAAC;EAChE,OAAO,eAAe,WAAW,YAAY,KAAK,GAAG,aAAa,OAAO,GAAG;CAC9E;AACD"}
|