@mk-kit/ui 0.34.0 → 0.35.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.
package/README.md CHANGED
@@ -1,12 +1,17 @@
1
1
  # @mk-kit/ui
2
2
 
3
- **Themable, accessible Angular 22 component library for admin dashboards & UIs.**
3
+ **Themable, accessible Angular component library for admin dashboards, back-offices and internal tools.**
4
4
 
5
- Signals-first. WCAG 2.1 AA. Every pixel controlled by CSS variables. Light &
6
- dark mode out of the box. Zero runtime dependencies beyond Angular.
5
+ 165+ standalone components, directives and services data tables, charts,
6
+ date & time pickers, editors, kanban, overlays, an app shell — written for
7
+ Angular 22 with signals and `OnPush` from day one. Every visual value is a
8
+ `--mk-*` CSS custom property; light and dark ship out of the box; WCAG 2.1 AA
9
+ is the target, not the marketing. MIT licensed. Zero runtime dependencies
10
+ beyond Angular.
7
11
 
8
- > Like Angular Material but leaner, admin-oriented, and re-themable by editing
9
- > a handful of CSS custom properties.
12
+ - **Docs & live demos:** <https://mk-kit.dev>
13
+ - **Changelog:** [CHANGELOG.md](https://github.com/mk-kit/mk-kit/blob/main/CHANGELOG.md)
14
+ - **Issues:** <https://github.com/mk-kit/mk-kit/issues>
10
15
 
11
16
  ## Install
12
17
 
@@ -14,32 +19,33 @@ dark mode out of the box. Zero runtime dependencies beyond Angular.
14
19
  ng add @mk-kit/ui
15
20
  ```
16
21
 
17
- `ng add` installs the package from npm and wires the theme stylesheet into
18
- your `angular.json`. To do it by hand instead:
22
+ `ng add` installs the package and wires the theme stylesheet into
23
+ `angular.json`. By hand instead:
19
24
 
20
25
  ```bash
21
26
  npm install @mk-kit/ui
22
27
  ```
23
28
 
24
- Peer dependencies: `@angular/core`, `@angular/common`, `@angular/forms` (v22+).
29
+ Peer dependencies: `@angular/core`, `@angular/common`, `@angular/forms`,
30
+ `@angular/platform-browser` (^22) and `rxjs` (^7.8).
25
31
 
26
32
  ## Setup
27
33
 
28
- **1. Import the theme stylesheet** once (e.g. in `angular.json` `styles` or your
29
- global `styles.css`):
34
+ **1. Import the theme stylesheet** once (in `angular.json` `styles` or your
35
+ global stylesheet):
30
36
 
31
37
  ```css
32
38
  @import '@mk-kit/ui/styles.css';
33
39
  ```
34
40
 
35
- **2. Add the `mk-app` class** to your `<body>` (or a top-level wrapper) so the
36
- background, text color, fonts and themed scrollbars apply:
41
+ **2. Add the `mk-app` class** to `<body>` (or a top-level wrapper) so
42
+ background, text colour, fonts and themed scrollbars apply:
37
43
 
38
44
  ```html
39
45
  <body class="mk-app">
40
46
  ```
41
47
 
42
- **3. Use components** — everything is standalone, just import what you need:
48
+ **3. Use components** — everything is standalone; import what you need:
43
49
 
44
50
  ```ts
45
51
  import { Component, inject } from '@angular/core';
@@ -61,25 +67,64 @@ export class AppRoot {
61
67
  }
62
68
  ```
63
69
 
70
+ ## What's inside
71
+
72
+ | Group | Highlights |
73
+ |---|---|
74
+ | **Forms & inputs** (49) | text, number, password (strength meter), OTP, phone (country prefix), postal code, currency, card number, IBAN, tax id, masked input, tag input, select, autocomplete, multi-select, tree-select, transfer list, checkbox, radio, switch, slider, range slider, rating, colour picker, file upload (dropzone), signature pad, numeric keypad & on-screen keyboard, repeater (`FormArray`), form field with automatic validation messages, form error summary |
75
+ | **Date & time** | calendar, date picker, date-range picker, time picker, month/year picker, week picker, inline mini date, event calendar with editable week/day grid (drag to move / resize) |
76
+ | **Tables & grids** (6) | data table — sort, multi-select, expandable rows, grouping, sticky header, column resize / reorder / pin, inline cell edit, responsive stacking — plus `MkTableDataSource` for server-side sort/page/filter |
77
+ | **Charts** (12) | line/area, bar (stacked, horizontal, label fitting), donut, gauge, progress ring, scatter/bubble, radar, funnel, treemap, heatmap, calendar heatmap, sparkline — SVG, themed, accessible |
78
+ | **Data display** (25) | cards, lists, stat cards, badges, tags, chips, avatars & groups, timeline, description list, tree, empty state, countdown, QR code, diff view, JSON viewer, code block, virtual scroll, carousel, kanban |
79
+ | **Navigation & layout** (18) | app shell (responsive sidebar), nav list & groups, tabs, stepper, breadcrumb, pagination, menu, context menu, command palette (⌘K), page header, toolbar, splitter, drawer, scroll area, FAB, back-to-top |
80
+ | **Feedback & overlays** (20) | dialogs (+ `confirm()` / `alert()` / `prompt()`), bottom sheet, drawer, popover, popconfirm, hovercard, tooltip, toast, snackbar, alert, banner, result page, notification center, product tour, progress bar, loading bar, spinner, skeletons |
81
+ | **Editors & interactions** (22) | block editor (Notion-style, HTML round-trip), rich text, markdown renderer, code editor, log viewer, drag & drop, sortable list, @mentions, hotkeys, undo/redo history, permissions (`*mkCan`), clipboard, intersect, infinite scroll, ripple, scrollspy |
82
+ | **Media** (6) | image with states, gallery, lightbox, cropper, media manager |
83
+ | **Core services** | theme (light/dark/system + density), overlay & anchored panels, focus trap, live announcer, icon registry, i18n |
84
+
85
+ The full, searchable index lives in the docs (`/components-index`).
86
+
87
+ ## Entry points
88
+
89
+ Import from the umbrella `@mk-kit/ui`, or from a group to keep bundles lean:
90
+
91
+ ```ts
92
+ import { MkTable } from '@mk-kit/ui/table';
93
+ import { MkLineChart } from '@mk-kit/ui/data';
94
+ import { MkDatePicker } from '@mk-kit/ui/datetime';
95
+ ```
96
+
97
+ Available: `core`, `forms`, `datetime`, `table`, `data`, `navigation`,
98
+ `feedback`, `directives`, `dnd`, `media`, `icon`, `button`, `checkbox`, `chip`,
99
+ `context-menu`, `rich-text`, `block-editor`. `sideEffects: false` throughout.
100
+
64
101
  ## Theming
65
102
 
66
- The entire look is driven by `--mk-*` custom properties defined on `:root`.
67
- Override any of them — globally or scoped to a subtree — to re-brand instantly:
103
+ The entire look is driven by `--mk-*` custom properties on `:root`. Override
104
+ any of them — globally, or scoped to a subtree — to re-brand at runtime, no
105
+ rebuild:
68
106
 
69
107
  ```css
70
108
  :root {
71
- --mk-primary: #7c3aed;
72
- --mk-primary-hover: #6d28d9;
109
+ --mk-primary: #0f766e;
110
+ --mk-primary-contrast: #ffffff;
73
111
  --mk-radius-md: 4px;
74
112
  --mk-font-sans: 'Inter', system-ui, sans-serif;
75
113
  }
114
+
115
+ [data-mk-theme='dark'] {
116
+ --mk-primary: #2dd4bf;
117
+ --mk-primary-contrast: #042f2e;
118
+ }
76
119
  ```
77
120
 
121
+ The docs include a theme builder that exports a ready-to-paste `:root` block.
122
+
78
123
  ### Dark mode
79
124
 
80
- Dark mode works with no JavaScript — it follows the OS `prefers-color-scheme`.
81
- To let users choose explicitly, set `data-mk-theme` on `<html>` (`"light"` |
82
- `"dark"`), or use the built-in `MkThemeService`:
125
+ Dark mode follows the OS `prefers-color-scheme` with no JavaScript. To let
126
+ users choose, set `data-mk-theme` on `<html>` (`"light"` | `"dark"`) or use
127
+ `MkThemeService`:
83
128
 
84
129
  ```ts
85
130
  const theme = inject(MkThemeService);
@@ -89,26 +134,65 @@ theme.toggle(); // flip light/dark
89
134
  theme.resolvedTheme(); // signal: 'light' | 'dark'
90
135
  ```
91
136
 
92
- The service persists the choice to `localStorage` and is fully SSR-safe.
137
+ The choice is persisted to `localStorage`; the service is SSR-safe.
93
138
 
94
- ## What's inside
139
+ ### Density & touch
95
140
 
96
- - **Forms**Button, FormField, Input, Select, Checkbox, Radio, Switch, Slider
97
- - **Data** — Table (sortable/sticky), Card, Badge, Tag, Chip, Avatar, List,
98
- StatCard, ProgressBar, Spinner, Skeleton, Divider
99
- - **Feedback** — Alert, Toast, Dialog (+confirm), Tooltip
100
- - **Navigation & layout** — Tabs, Accordion, Breadcrumb, Pagination, Menu,
101
- AppShell, NavList
141
+ Three densities`comfortable`, `compact`, `touch` via `data-mk-density`
142
+ or `MkThemeService.setDensity()`. Touch density enlarges every hit target;
143
+ inputs use 16px text on coarse pointers, drag starts on long-press, overlays
144
+ respect safe-area insets.
102
145
 
103
- All components: `OnPush`, signal inputs/outputs, keyboard-operable, screen-reader
104
- labelled, `:focus-visible` rings, and `prefers-reduced-motion` aware.
146
+ ## Internationalisation
147
+
148
+ Every string the library renders comes from one provider:
149
+
150
+ ```ts
151
+ import { provideMkI18n } from '@mk-kit/ui/core';
152
+
153
+ bootstrapApplication(AppRoot, {
154
+ providers: [provideMkI18n({ close: 'Zamknij', noData: 'Brak danych' })],
155
+ });
156
+ ```
157
+
158
+ Overrides can be scoped to a component subtree for mixed-locale screens.
159
+ Layout and arrow keys flip under `dir="rtl"`.
105
160
 
106
161
  ## Accessibility
107
162
 
108
- mk-kit targets **WCAG 2.1 AA**: semantic roles, complete `aria-*` wiring, focus
109
- trapping for overlays, roving tabindex for composite widgets, live-region status
110
- announcements, and color contrast that holds in both themes. Information is never
111
- conveyed by color alone.
163
+ Target: **WCAG 2.1 AA**. Semantic roles and complete `aria-*` wiring, roving
164
+ tabindex for composite widgets, focus trapping and background `inert` for
165
+ modals, an Escape that closes only the topmost overlay, live-region
166
+ announcements for async state, contrast-checked tokens in both themes,
167
+ `prefers-reduced-motion` and `forced-colors` support. Axe runs over rendered
168
+ fixtures in the test suite; information is never conveyed by colour alone.
169
+
170
+ ## SSR & zoneless
171
+
172
+ Every component guards non-browser platforms (an SSR smoke suite renders the
173
+ whole library on the server), and nothing depends on Zone.js.
174
+
175
+ ## Coming from PrimeNG?
176
+
177
+ The admin surface maps closely — `p-table` → `mk-table`, `p-select` →
178
+ `mk-select`, `p-datePicker` → `mk-date-picker`, `DialogService` →
179
+ `MkDialogService`, `MessageService` → `MkToastService` — and the parts
180
+ PrimeUI sells separately (charts, text editor, scheduler, task board) are
181
+ here under MIT. The docs landing page carries a mapping table.
182
+
183
+ ## Versioning & support
184
+
185
+ mk-kit tracks the current Angular major. A matching release follows each new
186
+ Angular major within weeks; the previous major keeps receiving fixes for six
187
+ months. Releases are published from CI with npm provenance
188
+ (`npm audit signatures`).
189
+
190
+ ## Contributing
191
+
192
+ See [CONTRIBUTING.md](https://github.com/mk-kit/mk-kit/blob/main/CONTRIBUTING.md)
193
+ (DCO sign-off), [SECURITY.md](https://github.com/mk-kit/mk-kit/blob/main/SECURITY.md)
194
+ for vulnerability reports, and [TRADEMARK.md](https://github.com/mk-kit/mk-kit/blob/main/TRADEMARK.md)
195
+ for use of the name and logo.
112
196
 
113
197
  ## License
114
198
 
@@ -678,7 +678,7 @@ function sideOf(placement) {
678
678
  return 'top';
679
679
  if (placement.startsWith('bottom'))
680
680
  return 'bottom';
681
- return placement;
681
+ return placement.startsWith('left') ? 'left' : 'right';
682
682
  }
683
683
  function alignOf(placement) {
684
684
  if (placement.endsWith('-start'))
@@ -740,8 +740,14 @@ function mkComputeAnchoredPosition(anchor, panel, viewport, opts) {
740
740
  left = anchor.left + anchor.width / 2 - w / 2;
741
741
  }
742
742
  else {
743
- // Left/right: centre on the cross (vertical) axis.
744
- top = anchor.top + anchor.height / 2 - h / 2;
743
+ // Left/right: the cross axis is vertical. `-start` tops the panel with the
744
+ // anchor (a submenu beside its item), `-end` bottoms it, else centre.
745
+ if (align === 'start')
746
+ top = anchor.top;
747
+ else if (align === 'end')
748
+ top = anchor.bottom - h;
749
+ else
750
+ top = anchor.top + anchor.height / 2 - h / 2;
745
751
  }
746
752
  if (clamp) {
747
753
  left = Math.max(gap, Math.min(left, vw - w - gap));
@@ -793,6 +799,13 @@ class MkAnchoredPanel {
793
799
  /** Clamp the panel inside the viewport. */
794
800
  clamp = input(true, /* @ts-ignore */
795
801
  ...(ngDevMode ? [{ debugName: "clamp" }] : /* istanbul ignore next */ []));
802
+ /**
803
+ * Predicate consulted before an outside pointerdown dismisses the panel.
804
+ * Return `true` for targets that must keep it open — e.g. a nested panel
805
+ * (a submenu) that lives in the top layer outside this element.
806
+ */
807
+ keepOpenWhen = input(null, /* @ts-ignore */
808
+ ...(ngDevMode ? [{ debugName: "keepOpenWhen" }] : /* istanbul ignore next */ []));
796
809
  /** Emitted on an outside pointerdown or when the window loses focus. */
797
810
  dismiss = output();
798
811
  popover = false;
@@ -828,6 +841,8 @@ class MkAnchoredPanel {
828
841
  const anchorEl = this.resolveAnchorEl();
829
842
  if (anchorEl?.contains(target))
830
843
  return;
844
+ if (this.keepOpenWhen()?.(target))
845
+ return;
831
846
  this.dismiss.emit();
832
847
  };
833
848
  onWindowBlur = () => this.dismiss.emit();
@@ -1022,7 +1037,7 @@ class MkAnchoredPanel {
1022
1037
  el.remove();
1023
1038
  }
1024
1039
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkAnchoredPanel, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1025
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.7", type: MkAnchoredPanel, isStandalone: true, selector: "[mkAnchoredPanel]", inputs: { anchor: { classPropertyName: "anchor", publicName: "mkAnchoredPanelFor", isSignal: true, isRequired: false, transformFunction: null }, anchorRect: { classPropertyName: "anchorRect", publicName: "anchorRect", isSignal: true, isRequired: false, transformFunction: null }, placement: { classPropertyName: "placement", publicName: "placement", isSignal: true, isRequired: false, transformFunction: null }, gap: { classPropertyName: "gap", publicName: "gap", isSignal: true, isRequired: false, transformFunction: null }, matchWidth: { classPropertyName: "matchWidth", publicName: "matchWidth", isSignal: true, isRequired: false, transformFunction: null }, flip: { classPropertyName: "flip", publicName: "flip", isSignal: true, isRequired: false, transformFunction: null }, clamp: { classPropertyName: "clamp", publicName: "clamp", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { dismiss: "dismiss" }, exportAs: ["mkAnchoredPanel"], ngImport: i0 });
1040
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.7", type: MkAnchoredPanel, isStandalone: true, selector: "[mkAnchoredPanel]", inputs: { anchor: { classPropertyName: "anchor", publicName: "mkAnchoredPanelFor", isSignal: true, isRequired: false, transformFunction: null }, anchorRect: { classPropertyName: "anchorRect", publicName: "anchorRect", isSignal: true, isRequired: false, transformFunction: null }, placement: { classPropertyName: "placement", publicName: "placement", isSignal: true, isRequired: false, transformFunction: null }, gap: { classPropertyName: "gap", publicName: "gap", isSignal: true, isRequired: false, transformFunction: null }, matchWidth: { classPropertyName: "matchWidth", publicName: "matchWidth", isSignal: true, isRequired: false, transformFunction: null }, flip: { classPropertyName: "flip", publicName: "flip", isSignal: true, isRequired: false, transformFunction: null }, clamp: { classPropertyName: "clamp", publicName: "clamp", isSignal: true, isRequired: false, transformFunction: null }, keepOpenWhen: { classPropertyName: "keepOpenWhen", publicName: "keepOpenWhen", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { dismiss: "dismiss" }, exportAs: ["mkAnchoredPanel"], ngImport: i0 });
1026
1041
  }
1027
1042
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkAnchoredPanel, decorators: [{
1028
1043
  type: Directive,
@@ -1030,7 +1045,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
1030
1045
  selector: '[mkAnchoredPanel]',
1031
1046
  exportAs: 'mkAnchoredPanel',
1032
1047
  }]
1033
- }], propDecorators: { anchor: [{ type: i0.Input, args: [{ isSignal: true, alias: "mkAnchoredPanelFor", required: false }] }], anchorRect: [{ type: i0.Input, args: [{ isSignal: true, alias: "anchorRect", required: false }] }], placement: [{ type: i0.Input, args: [{ isSignal: true, alias: "placement", required: false }] }], gap: [{ type: i0.Input, args: [{ isSignal: true, alias: "gap", required: false }] }], matchWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "matchWidth", required: false }] }], flip: [{ type: i0.Input, args: [{ isSignal: true, alias: "flip", required: false }] }], clamp: [{ type: i0.Input, args: [{ isSignal: true, alias: "clamp", required: false }] }], dismiss: [{ type: i0.Output, args: ["dismiss"] }] } });
1048
+ }], propDecorators: { anchor: [{ type: i0.Input, args: [{ isSignal: true, alias: "mkAnchoredPanelFor", required: false }] }], anchorRect: [{ type: i0.Input, args: [{ isSignal: true, alias: "anchorRect", required: false }] }], placement: [{ type: i0.Input, args: [{ isSignal: true, alias: "placement", required: false }] }], gap: [{ type: i0.Input, args: [{ isSignal: true, alias: "gap", required: false }] }], matchWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "matchWidth", required: false }] }], flip: [{ type: i0.Input, args: [{ isSignal: true, alias: "flip", required: false }] }], clamp: [{ type: i0.Input, args: [{ isSignal: true, alias: "clamp", required: false }] }], keepOpenWhen: [{ type: i0.Input, args: [{ isSignal: true, alias: "keepOpenWhen", required: false }] }], dismiss: [{ type: i0.Output, args: ["dismiss"] }] } });
1034
1049
 
1035
1050
  /** The built-in English validation messages. */
1036
1051
  const MK_DEFAULT_VALIDATION = {
@@ -1132,6 +1147,11 @@ const MK_DEFAULT_I18N = {
1132
1147
  chooseWeek: 'Choose week',
1133
1148
  openCalendar: 'Open calendar',
1134
1149
  openTimeList: 'Open time list',
1150
+ moreActions: 'More actions',
1151
+ selectDateTime: 'Select date and time…',
1152
+ chooseDateTime: 'Choose date and time',
1153
+ chooseTime: 'Choose time',
1154
+ openDateTimePicker: 'Open date and time picker',
1135
1155
  daySegment: 'Day',
1136
1156
  monthSegment: 'Month',
1137
1157
  yearSegment: 'Year',