ng-hub-ui-board 19.3.6 → 19.3.8
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/.npmignore +1 -2
- package/CHANGELOG.md +76 -0
- package/FUNCTIONALITIES.md +23 -0
- package/karma.conf.js +44 -0
- package/ng-package.json +9 -0
- package/package.json +100 -104
- package/src/lib/board.module.ts +51 -0
- package/src/lib/components/board/board.component.html +88 -0
- package/src/lib/components/board/board.component.ts +167 -0
- package/src/lib/directives/board-column-footer.directive.ts +36 -0
- package/src/lib/directives/board-column-header.directive.ts +34 -0
- package/src/lib/directives/card-template.directive.ts +33 -0
- package/src/lib/models/board-card.ts +48 -0
- package/src/lib/models/board-column.ts +68 -0
- package/src/lib/models/board.ts +39 -0
- package/src/lib/models/reached-end-event.ts +10 -0
- package/src/lib/pipes/invert-color.pipe.ts +62 -0
- package/src/lib/styles/base.scss +232 -0
- package/src/public-api.ts +23 -0
- package/src/test.ts +31 -0
- package/tsconfig.lib.json +15 -0
- package/tsconfig.lib.prod.json +10 -0
- package/tsconfig.spec.json +17 -0
- package/fesm2022/ng-hub-ui-board.mjs +0 -393
- package/fesm2022/ng-hub-ui-board.mjs.map +0 -1
- package/index.d.ts +0 -394
package/.npmignore
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
|
-
|
|
2
|
-
**/package.json
|
|
1
|
+
node_modules
|
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [19.3.8] - 2026-01-16
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- Ensured correct publication of compiled artifacts by refining the release process.
|
|
13
|
+
- Reverted `exports` configuration to maintain consistency with other libraries.
|
|
14
|
+
|
|
15
|
+
## [19.3.7] - 2026-01-16
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
- Removed incorrect `!src/**/*` exclusion from `package.json` that was preventing CSS files from being included in the published package.
|
|
20
|
+
|
|
21
|
+
## [19.3.6] - 2026-01-16
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
|
|
25
|
+
- Added `styles` export configuration in package.json to properly expose SCSS files.
|
|
26
|
+
|
|
27
|
+
## [19.3.5] - 2026-01-15
|
|
28
|
+
|
|
29
|
+
### Changed
|
|
30
|
+
|
|
31
|
+
- Complete refactoring of board styling to use CSS variables for enhanced customization.
|
|
32
|
+
- Documented all available CSS variables in README.
|
|
33
|
+
- Added `StylingBoardExampleComponent` to showcase custom styling capabilities.
|
|
34
|
+
|
|
35
|
+
## [19.3.4] - 2026-01-15
|
|
36
|
+
|
|
37
|
+
### Changed
|
|
38
|
+
|
|
39
|
+
- Complete refactoring of board styling to use CSS variables for enhanced customization.
|
|
40
|
+
- Documented all available CSS variables in README.
|
|
41
|
+
- Added `StylingBoardExampleComponent` to showcase custom styling capabilities.
|
|
42
|
+
|
|
43
|
+
## [19.3.3] - 2026-01-15
|
|
44
|
+
|
|
45
|
+
### Changed
|
|
46
|
+
|
|
47
|
+
- Complete refactoring of board styling to use CSS variables for enhanced customization.
|
|
48
|
+
- Documented all available CSS variables in README.
|
|
49
|
+
- Added `StylingBoardExampleComponent` to showcase custom styling capabilities.
|
|
50
|
+
|
|
51
|
+
## [19.3.2] - 2025-01-15
|
|
52
|
+
|
|
53
|
+
### Changed
|
|
54
|
+
|
|
55
|
+
- Improved `reachedEnd` event documentation in README with correct usage examples showing `event.data` as the complete `BoardColumn` object
|
|
56
|
+
- Updated `reachedEnd` event example to include proper container with fixed height requirement for scroll detection
|
|
57
|
+
- Enhanced license section in README with detailed explanation of CC BY 4.0 permissions, requirements, and attribution example
|
|
58
|
+
|
|
59
|
+
### Fixed
|
|
60
|
+
|
|
61
|
+
- Corrected misleading `reachedEnd` event documentation that incorrectly showed direct access to `event.data.title` instead of extracting the column first
|
|
62
|
+
|
|
63
|
+
## [19.3.1] - 2024-10-05
|
|
64
|
+
|
|
65
|
+
### Added
|
|
66
|
+
|
|
67
|
+
- Comprehensive JSDoc coverage across public models, directives, pipes, and the `HubBoardComponent` for improved API discoverability.
|
|
68
|
+
- New unit test ensuring the `reachedEnd` event is not emitted when column data is unavailable.
|
|
69
|
+
|
|
70
|
+
### Changed
|
|
71
|
+
|
|
72
|
+
- Refined infinite-scroll detection tolerance to ensure `reachedEnd` fires reliably at the bottom of each column.
|
|
73
|
+
- Hardened the document example logic to avoid duplicate lazy-load requests while columns are already loading.
|
|
74
|
+
- Typed the `invertColor` pipe output and improved error handling for invalid HEX values.
|
|
75
|
+
|
|
76
|
+
[19.3.1]: https://github.com/carlos-morcillo/ng-hub-ui-board/compare/19.3.0...19.3.1
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Functionalities of Board Library
|
|
2
|
+
|
|
3
|
+
This table details the functionalities of the `ng-hub-ui-board` library and indicates which ones are covered by interactive examples.
|
|
4
|
+
|
|
5
|
+
## Kanban Board (`hub-ui-board`)
|
|
6
|
+
|
|
7
|
+
| Category | Functionality | Example Covered |
|
|
8
|
+
| :--- | :--- | :---: |
|
|
9
|
+
| **Basic Usage** | Board Display (Columns & Cards) | ✅ |
|
|
10
|
+
| | Responsive Layout | ✅ |
|
|
11
|
+
| **Interactions** | Card Drag & Drop (Same Column) | ✅ |
|
|
12
|
+
| | Card Drag & Drop (Cross Column) | ✅ |
|
|
13
|
+
| | Column Reordering | ✅ |
|
|
14
|
+
| | Card Click Handling | ✅ |
|
|
15
|
+
| | Infinite Scroll (Column Reach End) | ✅ |
|
|
16
|
+
| **Templates** | Custom Card Template (`*cardTpt`) | ✅ |
|
|
17
|
+
| | Custom Column Header Template (`*columnHeaderTpt`) | ✅ |
|
|
18
|
+
| | Custom Column Footer Template (`*columnFooterTpt`) | ✅ |
|
|
19
|
+
| **Configuration** | Disable Column Sorting | ✅ |
|
|
20
|
+
| | Custom Scroll Detection Padding | ✅ |
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
*Note: ✅ indicates an active interactive example is available in the documentation.*
|
package/karma.conf.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// Karma configuration file, see link for more information
|
|
2
|
+
// https://karma-runner.github.io/1.0/config/configuration-file.html
|
|
3
|
+
|
|
4
|
+
module.exports = function (config) {
|
|
5
|
+
config.set({
|
|
6
|
+
basePath: '',
|
|
7
|
+
frameworks: ['jasmine', '@angular-devkit/build-angular'],
|
|
8
|
+
plugins: [
|
|
9
|
+
require('karma-jasmine'),
|
|
10
|
+
require('karma-chrome-launcher'),
|
|
11
|
+
require('karma-jasmine-html-reporter'),
|
|
12
|
+
require('karma-coverage'),
|
|
13
|
+
require('@angular-devkit/build-angular/plugins/karma')
|
|
14
|
+
],
|
|
15
|
+
client: {
|
|
16
|
+
jasmine: {
|
|
17
|
+
// you can add configuration options for Jasmine here
|
|
18
|
+
// the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html
|
|
19
|
+
// for example, you can disable the random execution with `random: false`
|
|
20
|
+
// or set a specific seed with `seed: 4321`
|
|
21
|
+
},
|
|
22
|
+
clearContext: false // leave Jasmine Spec Runner output visible in browser
|
|
23
|
+
},
|
|
24
|
+
jasmineHtmlReporter: {
|
|
25
|
+
suppressAll: true // removes the duplicated traces
|
|
26
|
+
},
|
|
27
|
+
coverageReporter: {
|
|
28
|
+
dir: require('path').join(__dirname, '../../coverage/board'),
|
|
29
|
+
subdir: '.',
|
|
30
|
+
reporters: [
|
|
31
|
+
{ type: 'html' },
|
|
32
|
+
{ type: 'text-summary' }
|
|
33
|
+
]
|
|
34
|
+
},
|
|
35
|
+
reporters: ['progress', 'kjhtml'],
|
|
36
|
+
port: 9876,
|
|
37
|
+
colors: true,
|
|
38
|
+
logLevel: config.LOG_INFO,
|
|
39
|
+
autoWatch: true,
|
|
40
|
+
browsers: ['Chrome'],
|
|
41
|
+
singleRun: false,
|
|
42
|
+
restartOnFileChange: true
|
|
43
|
+
});
|
|
44
|
+
};
|
package/ng-package.json
ADDED
package/package.json
CHANGED
|
@@ -1,105 +1,101 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
"contributors": [
|
|
103
|
-
"Carlos Morcillo Fernández <carlos.morcillo@me.com>"
|
|
104
|
-
]
|
|
105
|
-
}
|
|
2
|
+
"name": "ng-hub-ui-board",
|
|
3
|
+
"version": "19.3.8",
|
|
4
|
+
"description": "An Angular-based Kanban board component with Trello-like drag-and-drop, customizable columns, and straightforward event handling.",
|
|
5
|
+
"main": "bundles/ng-hub-ui-board.umd.js",
|
|
6
|
+
"module": "fesm2022/ng-hub-ui-board.mjs",
|
|
7
|
+
"typings": "index.d.ts",
|
|
8
|
+
"peerDependencies": {
|
|
9
|
+
"@angular/common": ">=18.0.0",
|
|
10
|
+
"@angular/core": ">=18.0.0",
|
|
11
|
+
"@angular/cdk": ">=18.0.0"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"tslib": "^2.3.0"
|
|
15
|
+
},
|
|
16
|
+
"sideEffects": false,
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "ng build board",
|
|
19
|
+
"build:prod": "ng build board --configuration production",
|
|
20
|
+
"test": "ng test board",
|
|
21
|
+
"test:watch": "ng test board --watch",
|
|
22
|
+
"test:coverage": "ng test board --code-coverage",
|
|
23
|
+
"lint": "ng lint board",
|
|
24
|
+
"pack": "npm pack",
|
|
25
|
+
"publish:npm": "npm publish"
|
|
26
|
+
},
|
|
27
|
+
"files": [
|
|
28
|
+
"**/*",
|
|
29
|
+
"!**/*.spec.ts",
|
|
30
|
+
"!**/*.spec.js",
|
|
31
|
+
"!**/*.test.ts",
|
|
32
|
+
"!**/*.test.js"
|
|
33
|
+
],
|
|
34
|
+
"keywords": [
|
|
35
|
+
"angular",
|
|
36
|
+
"kanban",
|
|
37
|
+
"board",
|
|
38
|
+
"trello",
|
|
39
|
+
"drag-drop",
|
|
40
|
+
"dnd",
|
|
41
|
+
"columns",
|
|
42
|
+
"cards",
|
|
43
|
+
"tasks",
|
|
44
|
+
"todo",
|
|
45
|
+
"productivity",
|
|
46
|
+
"collaboration",
|
|
47
|
+
"organization",
|
|
48
|
+
"project-management",
|
|
49
|
+
"workflow",
|
|
50
|
+
"agile",
|
|
51
|
+
"scrum",
|
|
52
|
+
"component",
|
|
53
|
+
"reusable",
|
|
54
|
+
"open-source",
|
|
55
|
+
"typescript",
|
|
56
|
+
"standalone",
|
|
57
|
+
"cdk",
|
|
58
|
+
"ui-library",
|
|
59
|
+
"ng-hub-ui"
|
|
60
|
+
],
|
|
61
|
+
"author": {
|
|
62
|
+
"name": "Carlos Morcillo Fernández",
|
|
63
|
+
"email": "carlos.morcillo@me.com",
|
|
64
|
+
"url": "https://www.carlosmorcillo.com"
|
|
65
|
+
},
|
|
66
|
+
"maintainers": [
|
|
67
|
+
{
|
|
68
|
+
"name": "Carlos Morcillo Fernández",
|
|
69
|
+
"email": "carlos.morcillo@me.com",
|
|
70
|
+
"url": "https://www.carlosmorcillo.com"
|
|
71
|
+
}
|
|
72
|
+
],
|
|
73
|
+
"homepage": "https://github.com/carlos-morcillo/ng-hub-ui-board#readme",
|
|
74
|
+
"repository": {
|
|
75
|
+
"type": "git",
|
|
76
|
+
"url": "git+https://github.com/carlos-morcillo/ng-hub-ui-board.git"
|
|
77
|
+
},
|
|
78
|
+
"bugs": {
|
|
79
|
+
"url": "https://github.com/carlos-morcillo/ng-hub-ui-board/issues",
|
|
80
|
+
"email": "carlos.morcillo@me.com"
|
|
81
|
+
},
|
|
82
|
+
"funding": {
|
|
83
|
+
"type": "buymeacoffee",
|
|
84
|
+
"url": "https://buymeacoffee.com/carlosmorcillo"
|
|
85
|
+
},
|
|
86
|
+
"license": "CC-BY-4.0",
|
|
87
|
+
"engines": {
|
|
88
|
+
"node": ">=18.13.0",
|
|
89
|
+
"npm": ">=9.0.0"
|
|
90
|
+
},
|
|
91
|
+
"publishConfig": {
|
|
92
|
+
"access": "public",
|
|
93
|
+
"registry": "https://registry.npmjs.org/"
|
|
94
|
+
},
|
|
95
|
+
"ng-add": {
|
|
96
|
+
"save": "dependencies"
|
|
97
|
+
},
|
|
98
|
+
"contributors": [
|
|
99
|
+
"Carlos Morcillo Fernández <carlos.morcillo@me.com>"
|
|
100
|
+
]
|
|
101
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { NgModule } from '@angular/core';
|
|
2
|
+
import { HubBoardComponent } from './components/board/board.component';
|
|
3
|
+
import { BoardColumnFooterDirective } from './directives/board-column-footer.directive';
|
|
4
|
+
import { BoardColumnHeaderDirective } from './directives/board-column-header.directive';
|
|
5
|
+
import { CardTemplateDirective } from './directives/card-template.directive';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Angular module that provides board functionality with drag-and-drop support.
|
|
9
|
+
*
|
|
10
|
+
* This module includes all the necessary components and directives for creating
|
|
11
|
+
* Kanban-style boards with customizable columns, cards, and templates.
|
|
12
|
+
*
|
|
13
|
+
* @deprecated Use standalone components instead. Import individual components and directives directly.
|
|
14
|
+
* @publicApi
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```typescript
|
|
18
|
+
* // Legacy module approach (not recommended)
|
|
19
|
+
* import { BoardModule } from 'ng-hub-ui-board';
|
|
20
|
+
*
|
|
21
|
+
* @NgModule({
|
|
22
|
+
* imports: [BoardModule]
|
|
23
|
+
* })
|
|
24
|
+
* export class AppModule {}
|
|
25
|
+
*
|
|
26
|
+
* // Recommended standalone approach
|
|
27
|
+
* import { HubBoardComponent, CardTemplateDirective } from 'ng-hub-ui-board';
|
|
28
|
+
*
|
|
29
|
+
* @Component({
|
|
30
|
+
* standalone: true,
|
|
31
|
+
* imports: [HubBoardComponent, CardTemplateDirective]
|
|
32
|
+
* })
|
|
33
|
+
* export class MyComponent {}
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
@NgModule({
|
|
37
|
+
declarations: [],
|
|
38
|
+
imports: [
|
|
39
|
+
HubBoardComponent,
|
|
40
|
+
CardTemplateDirective,
|
|
41
|
+
BoardColumnHeaderDirective,
|
|
42
|
+
BoardColumnFooterDirective
|
|
43
|
+
],
|
|
44
|
+
exports: [
|
|
45
|
+
HubBoardComponent,
|
|
46
|
+
CardTemplateDirective,
|
|
47
|
+
BoardColumnHeaderDirective,
|
|
48
|
+
BoardColumnFooterDirective
|
|
49
|
+
]
|
|
50
|
+
})
|
|
51
|
+
export class BoardModule {}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
@if (columns().length) {
|
|
2
|
+
<div
|
|
3
|
+
class="hub-board__drop-list"
|
|
4
|
+
cdkDropList
|
|
5
|
+
cdkDropListOrientation="horizontal"
|
|
6
|
+
[cdkDropListData]="columns()"
|
|
7
|
+
(cdkDropListDropped)="dropColumn($event)"
|
|
8
|
+
[cdkDropListSortingDisabled]="columnSortingDisabled()"
|
|
9
|
+
>
|
|
10
|
+
<div cdkDropListGroup class="hub-board__columns">
|
|
11
|
+
@for (column of columns(); let index = $index; track column) {
|
|
12
|
+
<div class="hub-board__column-container" cdkDrag [cdkDragData]="column" [cdkDragDisabled]="column.disabled">
|
|
13
|
+
<div class="hub-board__column" [ngClass]="column.classlist" [ngStyle]="column.style">
|
|
14
|
+
<div class="hub-board__column-header">
|
|
15
|
+
<ng-container
|
|
16
|
+
[ngTemplateOutlet]="columnHeaderTpt() || defaultColumnHeaderTpt"
|
|
17
|
+
[ngTemplateOutletContext]="{ column: column }"
|
|
18
|
+
>
|
|
19
|
+
</ng-container>
|
|
20
|
+
</div>
|
|
21
|
+
<div
|
|
22
|
+
class="hub-board__column-body"
|
|
23
|
+
cdkDropList
|
|
24
|
+
[cdkDropListData]="column"
|
|
25
|
+
(cdkDropListDropped)="dropCard($event)"
|
|
26
|
+
(scroll)="onScroll(index, $event)"
|
|
27
|
+
[cdkDropListEnterPredicate]="column.predicate ?? defaultEnterPredicateFn"
|
|
28
|
+
[cdkDropListSortingDisabled]="column.cardSortingDisabled"
|
|
29
|
+
>
|
|
30
|
+
@for ( card of column.cards; let index = $index; track card ) {
|
|
31
|
+
<div
|
|
32
|
+
class="hub-board__card"
|
|
33
|
+
[class.hub-board__card--disabled]="card.disabled"
|
|
34
|
+
cdkDrag
|
|
35
|
+
[cdkDragData]="card"
|
|
36
|
+
[cdkDragDisabled]="card.disabled"
|
|
37
|
+
(click)="cardClick(card)"
|
|
38
|
+
(mousedown)="card.disabled && $event.stopPropagation()"
|
|
39
|
+
[ngClass]="card.classlist"
|
|
40
|
+
[ngStyle]="card.style"
|
|
41
|
+
>
|
|
42
|
+
<div class="hub-board__card-body">
|
|
43
|
+
<ng-container
|
|
44
|
+
[ngTemplateOutlet]="cardTpt() || defaultCardTpt"
|
|
45
|
+
[ngTemplateOutletContext]="{
|
|
46
|
+
item: card,
|
|
47
|
+
column
|
|
48
|
+
}"
|
|
49
|
+
>
|
|
50
|
+
</ng-container>
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
}
|
|
54
|
+
</div>
|
|
55
|
+
|
|
56
|
+
@if (columnFooterTpt()) {
|
|
57
|
+
<div class="hub-board__column-footer">
|
|
58
|
+
<ng-container
|
|
59
|
+
[ngTemplateOutlet]="columnFooterTpt() ?? null"
|
|
60
|
+
[ngTemplateOutletContext]="{
|
|
61
|
+
column: column
|
|
62
|
+
}"
|
|
63
|
+
>
|
|
64
|
+
</ng-container>
|
|
65
|
+
</div>
|
|
66
|
+
}
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
}
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
<ng-template #defaultCardTpt let-item="item">
|
|
75
|
+
<h6 class="hub-board__card-title">{{ item.title }}</h6>
|
|
76
|
+
<p class="hub-board__card-subtitle">{{ item.description }}</p>
|
|
77
|
+
</ng-template>
|
|
78
|
+
|
|
79
|
+
<ng-template #defaultColumnHeaderTpt let-column="column">
|
|
80
|
+
<div class="d-flex flex-column">
|
|
81
|
+
<h5 class="hub-board__column-header-title">
|
|
82
|
+
{{ column.title }}
|
|
83
|
+
</h5>
|
|
84
|
+
<h6 class="hub-board__column-header-subtitle">
|
|
85
|
+
{{ column.description }}
|
|
86
|
+
</h6>
|
|
87
|
+
</div>
|
|
88
|
+
</ng-template>
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { CdkDragDrop, DragDropModule, moveItemInArray, transferArrayItem } from '@angular/cdk/drag-drop';
|
|
2
|
+
import { NgClass, NgStyle, NgTemplateOutlet } from '@angular/common';
|
|
3
|
+
import { Component, Signal, TemplateRef, computed, contentChild, input, output } from '@angular/core';
|
|
4
|
+
import { BoardColumnFooterDirective } from '../../directives/board-column-footer.directive';
|
|
5
|
+
import { BoardColumnHeaderDirective } from '../../directives/board-column-header.directive';
|
|
6
|
+
import { CardTemplateDirective } from '../../directives/card-template.directive';
|
|
7
|
+
import { Board } from '../../models/board';
|
|
8
|
+
import { BoardCard } from '../../models/board-card';
|
|
9
|
+
import { BoardColumn } from '../../models/board-column';
|
|
10
|
+
import { ReachedEndEvent } from '../../models/reached-end-event';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Standalone Kanban-style board component that provides column-based drag-and-drop,
|
|
14
|
+
* custom templates and infinite-scroll detection.
|
|
15
|
+
*
|
|
16
|
+
* @publicApi
|
|
17
|
+
*/
|
|
18
|
+
@Component({
|
|
19
|
+
selector: 'hub-board, hub-ui-board',
|
|
20
|
+
templateUrl: './board.component.html',
|
|
21
|
+
imports: [NgClass, NgStyle, NgTemplateOutlet, DragDropModule],
|
|
22
|
+
host: {
|
|
23
|
+
class: 'hub-board'
|
|
24
|
+
}
|
|
25
|
+
})
|
|
26
|
+
export class HubBoardComponent {
|
|
27
|
+
/**
|
|
28
|
+
* Reactive input containing the full board definition (columns and cards).
|
|
29
|
+
*/
|
|
30
|
+
readonly board = input<Board>();
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Pixel threshold used when determining whether a column has reached scroll end.
|
|
34
|
+
* Allows for fractional scroll values across different browsers.
|
|
35
|
+
*/
|
|
36
|
+
private readonly scrollDetectionPadding = 1;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Derived list of board columns exposed as a signal to the template.
|
|
40
|
+
*/
|
|
41
|
+
columns: Signal<Array<BoardColumn>> = computed(() => {
|
|
42
|
+
return this.board()?.columns ?? [];
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* When true, column reordering via drag-and-drop is disabled.
|
|
47
|
+
*/
|
|
48
|
+
readonly columnSortingDisabled = input<boolean>(false);
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Custom card template supplied via the `cardTpt` structural directive.
|
|
52
|
+
*/
|
|
53
|
+
readonly cardTpt = contentChild(CardTemplateDirective, {
|
|
54
|
+
read: TemplateRef<unknown>
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Custom column header template supplied via the `columnHeaderTpt` structural directive.
|
|
59
|
+
*/
|
|
60
|
+
readonly columnHeaderTpt = contentChild(BoardColumnHeaderDirective, {
|
|
61
|
+
read: TemplateRef<unknown>
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Custom column footer template supplied via the `columnFooterTpt` structural directive.
|
|
66
|
+
*/
|
|
67
|
+
readonly columnFooterTpt = contentChild(BoardColumnFooterDirective, {
|
|
68
|
+
read: TemplateRef<unknown>
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Emits each time a card is clicked within the board.
|
|
73
|
+
*/
|
|
74
|
+
readonly onCardClick = output<BoardCard>();
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Emits when a card has been repositioned, either within the same column or into another column.
|
|
78
|
+
*/
|
|
79
|
+
readonly onCardMoved = output<CdkDragDrop<BoardColumn, BoardColumn, BoardCard<any>>>();
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Emits when columns are reordered through drag-and-drop.
|
|
83
|
+
*/
|
|
84
|
+
readonly onColumnMoved = output<CdkDragDrop<BoardColumn[]>>();
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Emits when a column body is scrolled to its end, enabling infinite-scroll behaviour.
|
|
88
|
+
*/
|
|
89
|
+
readonly reachedEnd = output<ReachedEndEvent>();
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Default predicate that allows any card to be dropped into any column.
|
|
93
|
+
*
|
|
94
|
+
* @returns Always `true`, indicating that drop operations are permitted.
|
|
95
|
+
*/
|
|
96
|
+
defaultEnterPredicateFn = () => true;
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Emits the clicked card through {@link onCardClick}.
|
|
100
|
+
*
|
|
101
|
+
* @param item - The card that triggered the click event.
|
|
102
|
+
*/
|
|
103
|
+
cardClick(item: BoardCard) {
|
|
104
|
+
this.onCardClick.emit(item);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Updates column order when a drag-and-drop operation completes and emits the resulting event.
|
|
109
|
+
*
|
|
110
|
+
* @param event - Drag-and-drop metadata describing the column movement.
|
|
111
|
+
*/
|
|
112
|
+
dropColumn(event: CdkDragDrop<BoardColumn[]>) {
|
|
113
|
+
moveItemInArray(event.container.data, event.previousIndex, event.currentIndex);
|
|
114
|
+
this.onColumnMoved.emit(event);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Applies card reordering or transfer logic depending on the drag-drop target,
|
|
119
|
+
* then emits the corresponding drag event metadata.
|
|
120
|
+
*
|
|
121
|
+
* @param event - Drag-and-drop metadata describing the card movement.
|
|
122
|
+
*/
|
|
123
|
+
dropCard(event: CdkDragDrop<BoardColumn, BoardColumn, BoardCard<any>>) {
|
|
124
|
+
if (event.previousContainer === event.container) {
|
|
125
|
+
// Reorder the card within the same column
|
|
126
|
+
moveItemInArray(event.container.data.cards, event.previousIndex, event.currentIndex);
|
|
127
|
+
} else {
|
|
128
|
+
// Transfer the card from one column to another
|
|
129
|
+
transferArrayItem(
|
|
130
|
+
event.previousContainer.data.cards,
|
|
131
|
+
event.container.data.cards,
|
|
132
|
+
event.previousIndex,
|
|
133
|
+
event.currentIndex
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
this.onCardMoved.emit(event);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Emits {@link reachedEnd} once a column body is scrolled to its bottom.
|
|
141
|
+
*
|
|
142
|
+
* @param index - Index of the scrolled column within the board.
|
|
143
|
+
* @param event - Browser scroll event originating from the column body element.
|
|
144
|
+
*/
|
|
145
|
+
onScroll(index: number, event: Event) {
|
|
146
|
+
const el = event.target as HTMLElement | null;
|
|
147
|
+
if (!el) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const scrolledToBottom = el.scrollTop + el.clientHeight >= el.scrollHeight - this.scrollDetectionPadding;
|
|
152
|
+
|
|
153
|
+
if (!scrolledToBottom) {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const column = this.board()?.columns?.[index];
|
|
158
|
+
if (!column) {
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
this.reachedEnd.emit({
|
|
163
|
+
index,
|
|
164
|
+
data: column
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
}
|