@masterteam/components 0.0.89 → 0.0.91
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/assets/common.css +2 -2
- package/fesm2022/masterteam-components-client-page.mjs +16 -5
- package/fesm2022/masterteam-components-client-page.mjs.map +1 -1
- package/fesm2022/masterteam-components-entities.mjs +470 -0
- package/fesm2022/masterteam-components-entities.mjs.map +1 -0
- package/fesm2022/masterteam-components-multi-select-field.mjs +95 -4
- package/fesm2022/masterteam-components-multi-select-field.mjs.map +1 -1
- package/fesm2022/masterteam-components-property-filter-builder.mjs +383 -0
- package/fesm2022/masterteam-components-property-filter-builder.mjs.map +1 -0
- package/fesm2022/masterteam-components-select-field.mjs +135 -4
- package/fesm2022/masterteam-components-select-field.mjs.map +1 -1
- package/fesm2022/masterteam-components-sidebar.mjs +2 -2
- package/fesm2022/masterteam-components-sidebar.mjs.map +1 -1
- package/fesm2022/masterteam-components-table.mjs +1 -1
- package/fesm2022/masterteam-components-table.mjs.map +1 -1
- package/fesm2022/masterteam-components-topbar.mjs +2 -2
- package/fesm2022/masterteam-components-topbar.mjs.map +1 -1
- package/package.json +9 -1
- package/types/masterteam-components-client-page.d.ts +3 -0
- package/types/masterteam-components-entities.d.ts +253 -0
- package/types/masterteam-components-multi-select-field.d.ts +25 -1
- package/types/masterteam-components-property-filter-builder.d.ts +90 -0
- package/types/masterteam-components-select-field.d.ts +31 -1
|
@@ -0,0 +1,470 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { input, computed, Component, inject, ElementRef, NgZone, output, signal, Directive, model } from '@angular/core';
|
|
3
|
+
import { Avatar } from '@masterteam/components/avatar';
|
|
4
|
+
import { Button } from '@masterteam/components/button';
|
|
5
|
+
import { SecureImagePipe } from '@masterteam/components/upload-field';
|
|
6
|
+
import { Progress } from '@masterteam/components/progress';
|
|
7
|
+
import { CdkDrag, CdkDropList, CdkDragPlaceholder } from '@angular/cdk/drag-drop';
|
|
8
|
+
import { DOCUMENT } from '@angular/common';
|
|
9
|
+
|
|
10
|
+
const ENTITY_EMPTY_VALUE_PLACEHOLDER = '_';
|
|
11
|
+
function isValueMissing(value) {
|
|
12
|
+
if (value === null || value === undefined) {
|
|
13
|
+
return true;
|
|
14
|
+
}
|
|
15
|
+
if (typeof value === 'string') {
|
|
16
|
+
return value.trim().length === 0;
|
|
17
|
+
}
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
function displayOrPlaceholder(value) {
|
|
21
|
+
if (isValueMissing(value)) {
|
|
22
|
+
return ENTITY_EMPTY_VALUE_PLACEHOLDER;
|
|
23
|
+
}
|
|
24
|
+
return String(value);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
class EntityText {
|
|
28
|
+
/** Full entity data object */
|
|
29
|
+
data = input(...(ngDevMode ? [undefined, { debugName: "data" }] : []));
|
|
30
|
+
/** Individual inputs (used when data is not provided) */
|
|
31
|
+
name = input(...(ngDevMode ? [undefined, { debugName: "name" }] : []));
|
|
32
|
+
value = input(...(ngDevMode ? [undefined, { debugName: "value" }] : []));
|
|
33
|
+
displayName = computed(() => this.data()?.name ?? this.name() ?? '', ...(ngDevMode ? [{ debugName: "displayName" }] : []));
|
|
34
|
+
displayValue = computed(() => {
|
|
35
|
+
const d = this.data();
|
|
36
|
+
if (d) {
|
|
37
|
+
return displayOrPlaceholder(typeof d.value === 'string' ? d.value : null);
|
|
38
|
+
}
|
|
39
|
+
return displayOrPlaceholder(this.value());
|
|
40
|
+
}, ...(ngDevMode ? [{ debugName: "displayValue" }] : []));
|
|
41
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: EntityText, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
42
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.0.3", type: EntityText, isStandalone: true, selector: "mt-entity-text", inputs: { data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<div class=\"flex flex-col gap-0.5\">\r\n <span class=\"text-sm font-semibold\">{{ displayValue() }}</span>\r\n <span class=\"text-sm text-gray-500\">{{ displayName() }}</span>\r\n</div>\r\n" });
|
|
43
|
+
}
|
|
44
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: EntityText, decorators: [{
|
|
45
|
+
type: Component,
|
|
46
|
+
args: [{ selector: 'mt-entity-text', standalone: true, template: "<div class=\"flex flex-col gap-0.5\">\r\n <span class=\"text-sm font-semibold\">{{ displayValue() }}</span>\r\n <span class=\"text-sm text-gray-500\">{{ displayName() }}</span>\r\n</div>\r\n" }]
|
|
47
|
+
}], propDecorators: { data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }] } });
|
|
48
|
+
|
|
49
|
+
class EntityDate {
|
|
50
|
+
/** Full entity data object */
|
|
51
|
+
data = input(...(ngDevMode ? [undefined, { debugName: "data" }] : []));
|
|
52
|
+
/** Individual inputs (used when data is not provided) */
|
|
53
|
+
name = input(...(ngDevMode ? [undefined, { debugName: "name" }] : []));
|
|
54
|
+
value = input(...(ngDevMode ? [undefined, { debugName: "value" }] : []));
|
|
55
|
+
displayName = computed(() => this.data()?.name ?? this.name() ?? '', ...(ngDevMode ? [{ debugName: "displayName" }] : []));
|
|
56
|
+
displayValue = computed(() => {
|
|
57
|
+
const d = this.data();
|
|
58
|
+
if (d) {
|
|
59
|
+
return displayOrPlaceholder(typeof d.value === 'string' ? d.value : null);
|
|
60
|
+
}
|
|
61
|
+
return displayOrPlaceholder(this.value());
|
|
62
|
+
}, ...(ngDevMode ? [{ debugName: "displayValue" }] : []));
|
|
63
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: EntityDate, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
64
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.0.3", type: EntityDate, isStandalone: true, selector: "mt-entity-date", inputs: { data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<div class=\"flex flex-col gap-0.5\">\r\n <span class=\"text-sm font-semibold\">{{ displayValue() }}</span>\r\n <span class=\"text-sm text-gray-500\">{{ displayName() }}</span>\r\n</div>\r\n" });
|
|
65
|
+
}
|
|
66
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: EntityDate, decorators: [{
|
|
67
|
+
type: Component,
|
|
68
|
+
args: [{ selector: 'mt-entity-date', standalone: true, template: "<div class=\"flex flex-col gap-0.5\">\r\n <span class=\"text-sm font-semibold\">{{ displayValue() }}</span>\r\n <span class=\"text-sm text-gray-500\">{{ displayName() }}</span>\r\n</div>\r\n" }]
|
|
69
|
+
}], propDecorators: { data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }] } });
|
|
70
|
+
|
|
71
|
+
class EntityStatus {
|
|
72
|
+
/** Full entity data object */
|
|
73
|
+
data = input(...(ngDevMode ? [undefined, { debugName: "data" }] : []));
|
|
74
|
+
/** Individual inputs (used when data is not provided) */
|
|
75
|
+
name = input(...(ngDevMode ? [undefined, { debugName: "name" }] : []));
|
|
76
|
+
value = input(...(ngDevMode ? [undefined, { debugName: "value" }] : []));
|
|
77
|
+
displayName = computed(() => this.data()?.name ?? this.name() ?? '', ...(ngDevMode ? [{ debugName: "displayName" }] : []));
|
|
78
|
+
statusValue = computed(() => {
|
|
79
|
+
const d = this.data();
|
|
80
|
+
if (d && typeof d.value === 'object' && d.value !== null) {
|
|
81
|
+
return d.value;
|
|
82
|
+
}
|
|
83
|
+
return this.value() ?? null;
|
|
84
|
+
}, ...(ngDevMode ? [{ debugName: "statusValue" }] : []));
|
|
85
|
+
badgeStyle = computed(() => {
|
|
86
|
+
const status = this.statusValue();
|
|
87
|
+
if (!status?.color)
|
|
88
|
+
return {};
|
|
89
|
+
return {
|
|
90
|
+
color: status.color,
|
|
91
|
+
backgroundColor: this.hexToRgba(status.color, 0.12),
|
|
92
|
+
};
|
|
93
|
+
}, ...(ngDevMode ? [{ debugName: "badgeStyle" }] : []));
|
|
94
|
+
emptyLabel = ENTITY_EMPTY_VALUE_PLACEHOLDER;
|
|
95
|
+
statusLabel = computed(() => displayOrPlaceholder(this.statusValue()?.display), ...(ngDevMode ? [{ debugName: "statusLabel" }] : []));
|
|
96
|
+
hexToRgba(hex, alpha) {
|
|
97
|
+
const r = parseInt(hex.slice(1, 3), 16);
|
|
98
|
+
const g = parseInt(hex.slice(3, 5), 16);
|
|
99
|
+
const b = parseInt(hex.slice(5, 7), 16);
|
|
100
|
+
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
|
|
101
|
+
}
|
|
102
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: EntityStatus, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
103
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.3", type: EntityStatus, isStandalone: true, selector: "mt-entity-status", inputs: { data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<div class=\"flex flex-col gap-1\">\r\n @if (statusValue(); as status) {\r\n <span\r\n class=\"inline-flex items-center px-3 py-2 rounded-md text-xs font-semibold w-fit\"\r\n [style]=\"badgeStyle()\"\r\n >\r\n {{ statusLabel() }}\r\n </span>\r\n } @else {\r\n <span\r\n class=\"inline-flex items-center px-3 py-2 rounded-md text-xs font-semibold w-fit text-gray-500 bg-gray-100\"\r\n >\r\n {{ emptyLabel }}\r\n </span>\r\n }\r\n <!-- <span class=\"text-xs text-gray-500\">{{ displayName() }}</span> -->\r\n</div>\r\n" });
|
|
104
|
+
}
|
|
105
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: EntityStatus, decorators: [{
|
|
106
|
+
type: Component,
|
|
107
|
+
args: [{ selector: 'mt-entity-status', standalone: true, template: "<div class=\"flex flex-col gap-1\">\r\n @if (statusValue(); as status) {\r\n <span\r\n class=\"inline-flex items-center px-3 py-2 rounded-md text-xs font-semibold w-fit\"\r\n [style]=\"badgeStyle()\"\r\n >\r\n {{ statusLabel() }}\r\n </span>\r\n } @else {\r\n <span\r\n class=\"inline-flex items-center px-3 py-2 rounded-md text-xs font-semibold w-fit text-gray-500 bg-gray-100\"\r\n >\r\n {{ emptyLabel }}\r\n </span>\r\n }\r\n <!-- <span class=\"text-xs text-gray-500\">{{ displayName() }}</span> -->\r\n</div>\r\n" }]
|
|
108
|
+
}], propDecorators: { data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }] } });
|
|
109
|
+
|
|
110
|
+
class EntityUser {
|
|
111
|
+
/** Full entity data object */
|
|
112
|
+
data = input(...(ngDevMode ? [undefined, { debugName: "data" }] : []));
|
|
113
|
+
/** Individual inputs (used when data is not provided) */
|
|
114
|
+
displayName = input(...(ngDevMode ? [undefined, { debugName: "displayName" }] : []));
|
|
115
|
+
photoUrl = input(...(ngDevMode ? [undefined, { debugName: "photoUrl" }] : []));
|
|
116
|
+
rawUserName = computed(() => this.data()?.displayName ?? this.displayName() ?? null, ...(ngDevMode ? [{ debugName: "rawUserName" }] : []));
|
|
117
|
+
userName = computed(() => displayOrPlaceholder(this.rawUserName()), ...(ngDevMode ? [{ debugName: "userName" }] : []));
|
|
118
|
+
userPhoto = computed(() => this.data()?.photoUrl ?? this.photoUrl() ?? '', ...(ngDevMode ? [{ debugName: "userPhoto" }] : []));
|
|
119
|
+
labelText = computed(() => '', ...(ngDevMode ? [{ debugName: "labelText" }] : []));
|
|
120
|
+
initials = computed(() => {
|
|
121
|
+
const rawName = this.rawUserName();
|
|
122
|
+
if (isValueMissing(rawName))
|
|
123
|
+
return '';
|
|
124
|
+
const name = String(rawName).trim();
|
|
125
|
+
const parts = name.split(/\s+/);
|
|
126
|
+
if (parts.length >= 2) {
|
|
127
|
+
return (parts[0][0] + parts[1][0]).toUpperCase();
|
|
128
|
+
}
|
|
129
|
+
return name.substring(0, 2).toUpperCase();
|
|
130
|
+
}, ...(ngDevMode ? [{ debugName: "initials" }] : []));
|
|
131
|
+
// ── Configuration-driven visibility ──
|
|
132
|
+
config = computed(() => this.data()?.configuration ?? {}, ...(ngDevMode ? [{ debugName: "config" }] : []));
|
|
133
|
+
showDisplayName = computed(() => this.config().showDisplayName ?? true, ...(ngDevMode ? [{ debugName: "showDisplayName" }] : []));
|
|
134
|
+
showPhoneNumber = computed(() => this.config().showPhoneNumber ?? false, ...(ngDevMode ? [{ debugName: "showPhoneNumber" }] : []));
|
|
135
|
+
showEmail = computed(() => this.config().showEmail ?? false, ...(ngDevMode ? [{ debugName: "showEmail" }] : []));
|
|
136
|
+
phoneNumber = computed(() => this.data()?.phoneNumber ?? '', ...(ngDevMode ? [{ debugName: "phoneNumber" }] : []));
|
|
137
|
+
email = computed(() => this.data()?.email ?? '', ...(ngDevMode ? [{ debugName: "email" }] : []));
|
|
138
|
+
hasContactInfo = computed(() => (this.showPhoneNumber() && !!this.phoneNumber()) ||
|
|
139
|
+
(this.showEmail() && !!this.email()), ...(ngDevMode ? [{ debugName: "hasContactInfo" }] : []));
|
|
140
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: EntityUser, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
141
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.3", type: EntityUser, isStandalone: true, selector: "mt-entity-user", inputs: { data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null }, displayName: { classPropertyName: "displayName", publicName: "displayName", isSignal: true, isRequired: false, transformFunction: null }, photoUrl: { classPropertyName: "photoUrl", publicName: "photoUrl", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<div class=\"flex items-center gap-2\">\r\n <mt-avatar\r\n [image]=\"\r\n userPhoto()\r\n ? (userPhoto() | secureImage: true : undefined : 'avatar/')\r\n : ''\r\n \"\r\n [label]=\"initials()\"\r\n styleClass=\"w-11! h-11! text-lg! text-white! bg-primary-500!\"\r\n ></mt-avatar>\r\n\r\n @if (showDisplayName()) {\r\n <div class=\"flex flex-col min-w-0 flex-1\">\r\n <span class=\"text-sm font-semibold truncate\">{{ userName() }}</span>\r\n @if (labelText()) {\r\n <span class=\"text-xs text-gray-500 truncate\">{{ labelText() }}</span>\r\n }\r\n </div>\r\n }\r\n\r\n @if (hasContactInfo()) {\r\n <div class=\"flex items-center gap-1 ms-auto shrink-0\">\r\n @if (showPhoneNumber() && phoneNumber()) {\r\n <a [href]=\"'tel:' + phoneNumber()\" [title]=\"phoneNumber()\">\r\n <mt-button\r\n icon=\"communication.phone\"\r\n [rounded]=\"true\"\r\n [text]=\"true\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n [tooltip]=\"phoneNumber()\"\r\n />\r\n </a>\r\n }\r\n @if (showEmail() && email()) {\r\n <a [href]=\"'mailto:' + email()\" [title]=\"email()\">\r\n <mt-button\r\n icon=\"communication.mail-01\"\r\n [rounded]=\"true\"\r\n [text]=\"true\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n [tooltip]=\"email()\"\r\n />\r\n </a>\r\n }\r\n </div>\r\n }\r\n</div>\r\n", dependencies: [{ kind: "component", type: Avatar, selector: "mt-avatar", inputs: ["label", "icon", "image", "styleClass", "size", "shape", "badge", "badgeSize", "badgeSeverity"], outputs: ["onImageError"] }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "pipe", type: SecureImagePipe, name: "secureImage" }] });
|
|
142
|
+
}
|
|
143
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: EntityUser, decorators: [{
|
|
144
|
+
type: Component,
|
|
145
|
+
args: [{ selector: 'mt-entity-user', standalone: true, imports: [Avatar, Button, SecureImagePipe], template: "<div class=\"flex items-center gap-2\">\r\n <mt-avatar\r\n [image]=\"\r\n userPhoto()\r\n ? (userPhoto() | secureImage: true : undefined : 'avatar/')\r\n : ''\r\n \"\r\n [label]=\"initials()\"\r\n styleClass=\"w-11! h-11! text-lg! text-white! bg-primary-500!\"\r\n ></mt-avatar>\r\n\r\n @if (showDisplayName()) {\r\n <div class=\"flex flex-col min-w-0 flex-1\">\r\n <span class=\"text-sm font-semibold truncate\">{{ userName() }}</span>\r\n @if (labelText()) {\r\n <span class=\"text-xs text-gray-500 truncate\">{{ labelText() }}</span>\r\n }\r\n </div>\r\n }\r\n\r\n @if (hasContactInfo()) {\r\n <div class=\"flex items-center gap-1 ms-auto shrink-0\">\r\n @if (showPhoneNumber() && phoneNumber()) {\r\n <a [href]=\"'tel:' + phoneNumber()\" [title]=\"phoneNumber()\">\r\n <mt-button\r\n icon=\"communication.phone\"\r\n [rounded]=\"true\"\r\n [text]=\"true\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n [tooltip]=\"phoneNumber()\"\r\n />\r\n </a>\r\n }\r\n @if (showEmail() && email()) {\r\n <a [href]=\"'mailto:' + email()\" [title]=\"email()\">\r\n <mt-button\r\n icon=\"communication.mail-01\"\r\n [rounded]=\"true\"\r\n [text]=\"true\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n [tooltip]=\"email()\"\r\n />\r\n </a>\r\n }\r\n </div>\r\n }\r\n</div>\r\n" }]
|
|
146
|
+
}], propDecorators: { data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }], displayName: [{ type: i0.Input, args: [{ isSignal: true, alias: "displayName", required: false }] }], photoUrl: [{ type: i0.Input, args: [{ isSignal: true, alias: "photoUrl", required: false }] }] } });
|
|
147
|
+
|
|
148
|
+
class EntityPercentage {
|
|
149
|
+
/** Full entity data object */
|
|
150
|
+
data = input(...(ngDevMode ? [undefined, { debugName: "data" }] : []));
|
|
151
|
+
/** Individual inputs (used when data is not provided) */
|
|
152
|
+
name = input(...(ngDevMode ? [undefined, { debugName: "name" }] : []));
|
|
153
|
+
value = input(...(ngDevMode ? [undefined, { debugName: "value" }] : []));
|
|
154
|
+
rawValue = input(...(ngDevMode ? [undefined, { debugName: "rawValue" }] : []));
|
|
155
|
+
displayName = computed(() => this.data()?.name ?? this.name() ?? '', ...(ngDevMode ? [{ debugName: "displayName" }] : []));
|
|
156
|
+
displayValue = computed(() => {
|
|
157
|
+
const d = this.data();
|
|
158
|
+
if (d) {
|
|
159
|
+
return displayOrPlaceholder(typeof d.value === 'string' ? d.value : null);
|
|
160
|
+
}
|
|
161
|
+
return displayOrPlaceholder(this.value());
|
|
162
|
+
}, ...(ngDevMode ? [{ debugName: "displayValue" }] : []));
|
|
163
|
+
rawNumericValue = computed(() => this.data()?.rawValue ?? this.rawValue() ?? this.value(), ...(ngDevMode ? [{ debugName: "rawNumericValue" }] : []));
|
|
164
|
+
hasNumericValue = computed(() => {
|
|
165
|
+
const raw = this.rawNumericValue();
|
|
166
|
+
if (isValueMissing(raw)) {
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
return !Number.isNaN(parseFloat(String(raw)));
|
|
170
|
+
}, ...(ngDevMode ? [{ debugName: "hasNumericValue" }] : []));
|
|
171
|
+
numericValue = computed(() => {
|
|
172
|
+
const raw = this.rawNumericValue();
|
|
173
|
+
const num = parseFloat(raw ?? '0');
|
|
174
|
+
return isNaN(num) ? 0 : Math.min(num, 100);
|
|
175
|
+
}, ...(ngDevMode ? [{ debugName: "numericValue" }] : []));
|
|
176
|
+
maxValue = computed(() => {
|
|
177
|
+
const raw = this.rawNumericValue();
|
|
178
|
+
const num = parseFloat(raw ?? '0');
|
|
179
|
+
if (isNaN(num))
|
|
180
|
+
return 100;
|
|
181
|
+
return num > 100 ? Math.ceil(num) : 100;
|
|
182
|
+
}, ...(ngDevMode ? [{ debugName: "maxValue" }] : []));
|
|
183
|
+
// ── Configuration-driven visibility ──
|
|
184
|
+
config = computed(() => this.data()?.configuration ?? {}, ...(ngDevMode ? [{ debugName: "config" }] : []));
|
|
185
|
+
showName = computed(() => this.config().showName ?? true, ...(ngDevMode ? [{ debugName: "showName" }] : []));
|
|
186
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: EntityPercentage, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
187
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.3", type: EntityPercentage, isStandalone: true, selector: "mt-entity-percentage", inputs: { data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, rawValue: { classPropertyName: "rawValue", publicName: "rawValue", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<div class=\"flex flex-col gap-1\">\r\n <div class=\"flex items-center justify-between\">\r\n @if (showName()) {\r\n <span class=\"text-xs font-semibold\">{{ displayName() }}</span>\r\n }\r\n <span class=\"text-xs font-bold\" [class.ms-auto]=\"!showName()\">{{\r\n displayValue()\r\n }}</span>\r\n </div>\r\n @if (hasNumericValue()) {\r\n <mt-progress\r\n [value]=\"numericValue()\"\r\n [showLabel]=\"false\"\r\n [height]=\"9\"\r\n [maxValue]=\"maxValue()\"\r\n >\r\n </mt-progress>\r\n } @else {\r\n <div class=\"h-[9px] rounded-full bg-gray-200\"></div>\r\n }\r\n</div>\r\n", dependencies: [{ kind: "component", type: Progress, selector: "mt-progress", inputs: ["value", "mode", "showLabel", "unit", "color", "minValue", "maxValue", "height", "circleSize", "strokeWidth", "customClass"] }] });
|
|
188
|
+
}
|
|
189
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: EntityPercentage, decorators: [{
|
|
190
|
+
type: Component,
|
|
191
|
+
args: [{ selector: 'mt-entity-percentage', standalone: true, imports: [Progress], template: "<div class=\"flex flex-col gap-1\">\r\n <div class=\"flex items-center justify-between\">\r\n @if (showName()) {\r\n <span class=\"text-xs font-semibold\">{{ displayName() }}</span>\r\n }\r\n <span class=\"text-xs font-bold\" [class.ms-auto]=\"!showName()\">{{\r\n displayValue()\r\n }}</span>\r\n </div>\r\n @if (hasNumericValue()) {\r\n <mt-progress\r\n [value]=\"numericValue()\"\r\n [showLabel]=\"false\"\r\n [height]=\"9\"\r\n [maxValue]=\"maxValue()\"\r\n >\r\n </mt-progress>\r\n } @else {\r\n <div class=\"h-[9px] rounded-full bg-gray-200\"></div>\r\n }\r\n</div>\r\n" }]
|
|
192
|
+
}], propDecorators: { data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], rawValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "rawValue", required: false }] }] } });
|
|
193
|
+
|
|
194
|
+
class EntityCurrency {
|
|
195
|
+
/** Full entity data object */
|
|
196
|
+
data = input(...(ngDevMode ? [undefined, { debugName: "data" }] : []));
|
|
197
|
+
/** Individual inputs (used when data is not provided) */
|
|
198
|
+
name = input(...(ngDevMode ? [undefined, { debugName: "name" }] : []));
|
|
199
|
+
value = input(...(ngDevMode ? [undefined, { debugName: "value" }] : []));
|
|
200
|
+
displayName = computed(() => this.data()?.name ?? this.name() ?? '', ...(ngDevMode ? [{ debugName: "displayName" }] : []));
|
|
201
|
+
displayValue = computed(() => {
|
|
202
|
+
const d = this.data();
|
|
203
|
+
if (d) {
|
|
204
|
+
return displayOrPlaceholder(typeof d.value === 'string' ? d.value : null);
|
|
205
|
+
}
|
|
206
|
+
return displayOrPlaceholder(this.value());
|
|
207
|
+
}, ...(ngDevMode ? [{ debugName: "displayValue" }] : []));
|
|
208
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: EntityCurrency, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
209
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.0.3", type: EntityCurrency, isStandalone: true, selector: "mt-entity-currency", inputs: { data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<div class=\"flex flex-col gap-0.5\">\r\n <span class=\"text-sm font-semibold\">{{ displayValue() }}</span>\r\n <span class=\"text-sm text-gray-500\">{{ displayName() }}</span>\r\n</div>\r\n" });
|
|
210
|
+
}
|
|
211
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: EntityCurrency, decorators: [{
|
|
212
|
+
type: Component,
|
|
213
|
+
args: [{ selector: 'mt-entity-currency', standalone: true, template: "<div class=\"flex flex-col gap-0.5\">\r\n <span class=\"text-sm font-semibold\">{{ displayValue() }}</span>\r\n <span class=\"text-sm text-gray-500\">{{ displayName() }}</span>\r\n</div>\r\n" }]
|
|
214
|
+
}], propDecorators: { data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }] } });
|
|
215
|
+
|
|
216
|
+
class EntityCheckbox {
|
|
217
|
+
/** Full entity data object */
|
|
218
|
+
data = input(...(ngDevMode ? [undefined, { debugName: "data" }] : []));
|
|
219
|
+
/** Individual inputs (used when data is not provided) */
|
|
220
|
+
name = input(...(ngDevMode ? [undefined, { debugName: "name" }] : []));
|
|
221
|
+
value = input(...(ngDevMode ? [undefined, { debugName: "value" }] : []));
|
|
222
|
+
rawValue = input(...(ngDevMode ? [undefined, { debugName: "rawValue" }] : []));
|
|
223
|
+
displayName = computed(() => this.data()?.name ?? this.name() ?? '', ...(ngDevMode ? [{ debugName: "displayName" }] : []));
|
|
224
|
+
emptyLabel = ENTITY_EMPTY_VALUE_PLACEHOLDER;
|
|
225
|
+
checkboxState = computed(() => {
|
|
226
|
+
const raw = this.data()?.rawValue ?? this.rawValue();
|
|
227
|
+
if (raw !== undefined && raw !== null) {
|
|
228
|
+
return this.parseBoolean(raw);
|
|
229
|
+
}
|
|
230
|
+
const val = this.data()?.value ?? this.value();
|
|
231
|
+
if (typeof val === 'string') {
|
|
232
|
+
return this.parseBoolean(val);
|
|
233
|
+
}
|
|
234
|
+
return null;
|
|
235
|
+
}, ...(ngDevMode ? [{ debugName: "checkboxState" }] : []));
|
|
236
|
+
parseBoolean(value) {
|
|
237
|
+
const normalized = value.trim().toLowerCase();
|
|
238
|
+
if (!normalized.length) {
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
if (normalized === 'true') {
|
|
242
|
+
return true;
|
|
243
|
+
}
|
|
244
|
+
if (normalized === 'false') {
|
|
245
|
+
return false;
|
|
246
|
+
}
|
|
247
|
+
return null;
|
|
248
|
+
}
|
|
249
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: EntityCheckbox, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
250
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.3", type: EntityCheckbox, isStandalone: true, selector: "mt-entity-checkbox", inputs: { data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, rawValue: { classPropertyName: "rawValue", publicName: "rawValue", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<div class=\"flex items-center gap-2\">\r\n @if (checkboxState() === true) {\r\n <svg class=\"w-5 h-5 text-green-500\" viewBox=\"0 0 20 20\" fill=\"currentColor\">\r\n <path\r\n fill-rule=\"evenodd\"\r\n d=\"M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z\"\r\n clip-rule=\"evenodd\"\r\n />\r\n </svg>\r\n } @else if (checkboxState() === false) {\r\n <svg class=\"w-5 h-5 text-gray-300\" viewBox=\"0 0 20 20\" fill=\"currentColor\">\r\n <path\r\n fill-rule=\"evenodd\"\r\n d=\"M10 18a8 8 0 100-16 8 8 0 000 16zm-1-5.414l-2.293-2.293a1 1 0 011.414-1.414L10 11.172l3.879-3.879a1 1 0 111.414 1.414L10 13.414l-.707-.707-.293-.293z\"\r\n clip-rule=\"evenodd\"\r\n />\r\n </svg>\r\n } @else {\r\n <span\r\n class=\"inline-flex h-5 min-w-5 items-center justify-center rounded-full bg-gray-100 px-1 text-[10px] font-semibold text-gray-500\"\r\n >\r\n {{ emptyLabel }}\r\n </span>\r\n }\r\n <span class=\"text-sm font-semibold\">{{ displayName() }}</span>\r\n</div>\r\n" });
|
|
251
|
+
}
|
|
252
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: EntityCheckbox, decorators: [{
|
|
253
|
+
type: Component,
|
|
254
|
+
args: [{ selector: 'mt-entity-checkbox', standalone: true, template: "<div class=\"flex items-center gap-2\">\r\n @if (checkboxState() === true) {\r\n <svg class=\"w-5 h-5 text-green-500\" viewBox=\"0 0 20 20\" fill=\"currentColor\">\r\n <path\r\n fill-rule=\"evenodd\"\r\n d=\"M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z\"\r\n clip-rule=\"evenodd\"\r\n />\r\n </svg>\r\n } @else if (checkboxState() === false) {\r\n <svg class=\"w-5 h-5 text-gray-300\" viewBox=\"0 0 20 20\" fill=\"currentColor\">\r\n <path\r\n fill-rule=\"evenodd\"\r\n d=\"M10 18a8 8 0 100-16 8 8 0 000 16zm-1-5.414l-2.293-2.293a1 1 0 011.414-1.414L10 11.172l3.879-3.879a1 1 0 111.414 1.414L10 13.414l-.707-.707-.293-.293z\"\r\n clip-rule=\"evenodd\"\r\n />\r\n </svg>\r\n } @else {\r\n <span\r\n class=\"inline-flex h-5 min-w-5 items-center justify-center rounded-full bg-gray-100 px-1 text-[10px] font-semibold text-gray-500\"\r\n >\r\n {{ emptyLabel }}\r\n </span>\r\n }\r\n <span class=\"text-sm font-semibold\">{{ displayName() }}</span>\r\n</div>\r\n" }]
|
|
255
|
+
}], propDecorators: { data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], rawValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "rawValue", required: false }] }] } });
|
|
256
|
+
|
|
257
|
+
class EntityPreview {
|
|
258
|
+
/** Single entity data to display */
|
|
259
|
+
data = input.required(...(ngDevMode ? [{ debugName: "data" }] : []));
|
|
260
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: EntityPreview, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
261
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.3", type: EntityPreview, isStandalone: true, selector: "mt-entity-preview", inputs: { data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: true, transformFunction: null } }, host: { classAttribute: "w-full" }, ngImport: i0, template: "@switch (data().viewType) {\r\n @case (\"Text\") {\r\n <mt-entity-text [data]=\"data()\" />\r\n }\r\n @case (\"Date\") {\r\n <mt-entity-date [data]=\"data()\" />\r\n }\r\n @case (\"DateTime\") {\r\n <mt-entity-date [data]=\"data()\" />\r\n }\r\n @case (\"Status\") {\r\n <mt-entity-status [data]=\"data()\" />\r\n }\r\n @case (\"User\") {\r\n <mt-entity-user [data]=\"data()\" />\r\n }\r\n @case (\"Percentage\") {\r\n <mt-entity-percentage [data]=\"data()\" />\r\n }\r\n @case (\"Currency\") {\r\n <mt-entity-currency [data]=\"data()\" />\r\n }\r\n @case (\"Checkbox\") {\r\n <mt-entity-checkbox [data]=\"data()\" />\r\n }\r\n @default {\r\n <mt-entity-text [data]=\"data()\" />\r\n }\r\n}\r\n", dependencies: [{ kind: "component", type: EntityText, selector: "mt-entity-text", inputs: ["data", "name", "value"] }, { kind: "component", type: EntityDate, selector: "mt-entity-date", inputs: ["data", "name", "value"] }, { kind: "component", type: EntityStatus, selector: "mt-entity-status", inputs: ["data", "name", "value"] }, { kind: "component", type: EntityUser, selector: "mt-entity-user", inputs: ["data", "displayName", "photoUrl"] }, { kind: "component", type: EntityPercentage, selector: "mt-entity-percentage", inputs: ["data", "name", "value", "rawValue"] }, { kind: "component", type: EntityCurrency, selector: "mt-entity-currency", inputs: ["data", "name", "value"] }, { kind: "component", type: EntityCheckbox, selector: "mt-entity-checkbox", inputs: ["data", "name", "value", "rawValue"] }] });
|
|
262
|
+
}
|
|
263
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: EntityPreview, decorators: [{
|
|
264
|
+
type: Component,
|
|
265
|
+
args: [{ selector: 'mt-entity-preview', standalone: true, imports: [
|
|
266
|
+
EntityText,
|
|
267
|
+
EntityDate,
|
|
268
|
+
EntityStatus,
|
|
269
|
+
EntityUser,
|
|
270
|
+
EntityPercentage,
|
|
271
|
+
EntityCurrency,
|
|
272
|
+
EntityCheckbox,
|
|
273
|
+
], host: {
|
|
274
|
+
class: 'w-full',
|
|
275
|
+
}, template: "@switch (data().viewType) {\r\n @case (\"Text\") {\r\n <mt-entity-text [data]=\"data()\" />\r\n }\r\n @case (\"Date\") {\r\n <mt-entity-date [data]=\"data()\" />\r\n }\r\n @case (\"DateTime\") {\r\n <mt-entity-date [data]=\"data()\" />\r\n }\r\n @case (\"Status\") {\r\n <mt-entity-status [data]=\"data()\" />\r\n }\r\n @case (\"User\") {\r\n <mt-entity-user [data]=\"data()\" />\r\n }\r\n @case (\"Percentage\") {\r\n <mt-entity-percentage [data]=\"data()\" />\r\n }\r\n @case (\"Currency\") {\r\n <mt-entity-currency [data]=\"data()\" />\r\n }\r\n @case (\"Checkbox\") {\r\n <mt-entity-checkbox [data]=\"data()\" />\r\n }\r\n @default {\r\n <mt-entity-text [data]=\"data()\" />\r\n }\r\n}\r\n" }]
|
|
276
|
+
}], propDecorators: { data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: true }] }] } });
|
|
277
|
+
|
|
278
|
+
class EntitiesPreview {
|
|
279
|
+
/** Array of entity data to display */
|
|
280
|
+
entities = input.required(...(ngDevMode ? [{ debugName: "entities" }] : []));
|
|
281
|
+
/** Entities sorted by order */
|
|
282
|
+
sortedEntities = computed(() => [...this.entities()].sort((a, b) => (a.order ?? 0) - (b.order ?? 0)), ...(ngDevMode ? [{ debugName: "sortedEntities" }] : []));
|
|
283
|
+
/** Returns the grid-column span for a given entity size (1-12) */
|
|
284
|
+
getColSpan(entity) {
|
|
285
|
+
const size = entity.configuration?.size ?? 4;
|
|
286
|
+
return `span ${size}`;
|
|
287
|
+
}
|
|
288
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: EntitiesPreview, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
289
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.3", type: EntitiesPreview, isStandalone: true, selector: "mt-entities-preview", inputs: { entities: { classPropertyName: "entities", publicName: "entities", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<div class=\"grid grid-cols-12 gap-x-4 gap-y-10\">\r\n @for (entity of sortedEntities(); track $index) {\r\n <div\r\n class=\"min-w-0 flex items-center p-3\"\r\n [class.border]=\"entity.configuration?.showBorder\"\r\n [class.border-dashed]=\"entity.configuration?.showBorder\"\r\n [class.border-gray-200]=\"entity.configuration?.showBorder\"\r\n [class.rounded-lg]=\"entity.configuration?.showBorder\"\r\n [style.grid-column]=\"getColSpan(entity)\"\r\n >\r\n <mt-entity-preview [data]=\"entity\" />\r\n </div>\r\n }\r\n</div>\r\n", dependencies: [{ kind: "component", type: EntityPreview, selector: "mt-entity-preview", inputs: ["data"] }] });
|
|
290
|
+
}
|
|
291
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: EntitiesPreview, decorators: [{
|
|
292
|
+
type: Component,
|
|
293
|
+
args: [{ selector: 'mt-entities-preview', standalone: true, imports: [EntityPreview], template: "<div class=\"grid grid-cols-12 gap-x-4 gap-y-10\">\r\n @for (entity of sortedEntities(); track $index) {\r\n <div\r\n class=\"min-w-0 flex items-center p-3\"\r\n [class.border]=\"entity.configuration?.showBorder\"\r\n [class.border-dashed]=\"entity.configuration?.showBorder\"\r\n [class.border-gray-200]=\"entity.configuration?.showBorder\"\r\n [class.rounded-lg]=\"entity.configuration?.showBorder\"\r\n [style.grid-column]=\"getColSpan(entity)\"\r\n >\r\n <mt-entity-preview [data]=\"entity\" />\r\n </div>\r\n }\r\n</div>\r\n" }]
|
|
294
|
+
}], propDecorators: { entities: [{ type: i0.Input, args: [{ isSignal: true, alias: "entities", required: true }] }] } });
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Base class that encapsulates all entity resize-via-drag logic.
|
|
298
|
+
*
|
|
299
|
+
* Extend this directive in any component that needs column-resize behaviour
|
|
300
|
+
* on a 12-column CSS grid. The subclass must:
|
|
301
|
+
* - Provide a `model.required<EntityData[]>()` (or equivalent) for the
|
|
302
|
+
* entity list so `updateEntitySize()` can be called after resize.
|
|
303
|
+
* - Contain a `.grid` element (or override `getGridElement()`).
|
|
304
|
+
*
|
|
305
|
+
* Resize flow:
|
|
306
|
+
* 1. `onResizeStart(event, entity)` — called from a mousedown on the handle
|
|
307
|
+
* 2. mousemove → snaps to nearest column, updates `resizePreviewSize`
|
|
308
|
+
* 3. mouseup → emits `entityResized` with previous & new size
|
|
309
|
+
*/
|
|
310
|
+
class EntitiesResizeBase {
|
|
311
|
+
elRef = inject(ElementRef);
|
|
312
|
+
zone = inject(NgZone);
|
|
313
|
+
doc = inject(DOCUMENT);
|
|
314
|
+
/** Emits when an entity is resized via the drag handle */
|
|
315
|
+
entityResized = output();
|
|
316
|
+
// ── Resize state ──
|
|
317
|
+
/** The entity currently being resized (null when idle) */
|
|
318
|
+
resizingEntity = signal(null, ...(ngDevMode ? [{ debugName: "resizingEntity" }] : []));
|
|
319
|
+
/** Live preview size while dragging */
|
|
320
|
+
resizePreviewSize = signal(null, ...(ngDevMode ? [{ debugName: "resizePreviewSize" }] : []));
|
|
321
|
+
/** Bound listeners kept for cleanup */
|
|
322
|
+
_onMouseMove = null;
|
|
323
|
+
_onMouseUp = null;
|
|
324
|
+
// ── Public API ──
|
|
325
|
+
/**
|
|
326
|
+
* Returns the grid element used to compute column widths.
|
|
327
|
+
* Override if the grid selector differs.
|
|
328
|
+
*/
|
|
329
|
+
getGridElement() {
|
|
330
|
+
return this.elRef.nativeElement.querySelector('.grid');
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Returns the grid-column span string for a given entity.
|
|
334
|
+
* While resizing the active entity, returns the preview size.
|
|
335
|
+
*/
|
|
336
|
+
getResizeColSpan(entity) {
|
|
337
|
+
if (this.resizingEntity() === entity && this.resizePreviewSize() !== null) {
|
|
338
|
+
return `span ${this.resizePreviewSize()}`;
|
|
339
|
+
}
|
|
340
|
+
const size = entity.configuration?.size ?? 4;
|
|
341
|
+
return `span ${size}`;
|
|
342
|
+
}
|
|
343
|
+
/** Starts a resize operation from the right-edge handle */
|
|
344
|
+
onResizeStart(event, entity) {
|
|
345
|
+
// Prevent the drag-drop from starting
|
|
346
|
+
event.preventDefault();
|
|
347
|
+
event.stopPropagation();
|
|
348
|
+
const gridEl = this.getGridElement();
|
|
349
|
+
if (!gridEl)
|
|
350
|
+
return;
|
|
351
|
+
const gridRect = gridEl.getBoundingClientRect();
|
|
352
|
+
const gridWidth = gridRect.width;
|
|
353
|
+
const columnWidth = gridWidth / 12;
|
|
354
|
+
const previousSize = entity.configuration?.size ?? 4;
|
|
355
|
+
// Left edge of the entity cell
|
|
356
|
+
const cellEl = event.target.closest('.entity-cell');
|
|
357
|
+
if (!cellEl)
|
|
358
|
+
return;
|
|
359
|
+
const cellLeft = cellEl.getBoundingClientRect().left;
|
|
360
|
+
this.resizingEntity.set(entity);
|
|
361
|
+
this.resizePreviewSize.set(previousSize);
|
|
362
|
+
// Run mouse listeners outside Angular zone for performance
|
|
363
|
+
this.zone.runOutsideAngular(() => {
|
|
364
|
+
this._onMouseMove = (e) => {
|
|
365
|
+
const rawWidth = e.clientX - cellLeft;
|
|
366
|
+
// Snap to nearest full column (min 1, max 12)
|
|
367
|
+
let cols = Math.round(rawWidth / columnWidth);
|
|
368
|
+
cols = Math.max(1, Math.min(12, cols));
|
|
369
|
+
const current = this.resizePreviewSize();
|
|
370
|
+
if (current !== cols) {
|
|
371
|
+
this.zone.run(() => this.resizePreviewSize.set(cols));
|
|
372
|
+
}
|
|
373
|
+
};
|
|
374
|
+
this._onMouseUp = () => {
|
|
375
|
+
this._cleanupListeners();
|
|
376
|
+
const newSize = this.resizePreviewSize();
|
|
377
|
+
this.resizingEntity.set(null);
|
|
378
|
+
this.resizePreviewSize.set(null);
|
|
379
|
+
if (newSize !== previousSize) {
|
|
380
|
+
this.zone.run(() => {
|
|
381
|
+
this.onResizeComplete(entity, previousSize, newSize);
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
};
|
|
385
|
+
this.doc.addEventListener('mousemove', this._onMouseMove);
|
|
386
|
+
this.doc.addEventListener('mouseup', this._onMouseUp, { once: true });
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
/** Removes global mouse listeners */
|
|
390
|
+
_cleanupListeners() {
|
|
391
|
+
if (this._onMouseMove) {
|
|
392
|
+
this.doc.removeEventListener('mousemove', this._onMouseMove);
|
|
393
|
+
this._onMouseMove = null;
|
|
394
|
+
}
|
|
395
|
+
if (this._onMouseUp) {
|
|
396
|
+
this.doc.removeEventListener('mouseup', this._onMouseUp);
|
|
397
|
+
this._onMouseUp = null;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: EntitiesResizeBase, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
401
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.0.3", type: EntitiesResizeBase, isStandalone: true, outputs: { entityResized: "entityResized" }, ngImport: i0 });
|
|
402
|
+
}
|
|
403
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: EntitiesResizeBase, decorators: [{
|
|
404
|
+
type: Directive
|
|
405
|
+
}], propDecorators: { entityResized: [{ type: i0.Output, args: ["entityResized"] }] } });
|
|
406
|
+
|
|
407
|
+
class EntitiesManage extends EntitiesResizeBase {
|
|
408
|
+
/** Array of entity data – supports two-way binding to keep order in sync */
|
|
409
|
+
entities = model.required(...(ngDevMode ? [{ debugName: "entities" }] : []));
|
|
410
|
+
/** Emits the reordered entities array after each drag-drop */
|
|
411
|
+
entitiesReordered = output();
|
|
412
|
+
/** Entities sorted by their order field */
|
|
413
|
+
sortedEntities = computed(() => [...this.entities()].sort((a, b) => (a.order ?? 0) - (b.order ?? 0)), ...(ngDevMode ? [{ debugName: "sortedEntities" }] : []));
|
|
414
|
+
/** Returns the grid-column span for a given entity size (1-12) */
|
|
415
|
+
getColSpan(entity) {
|
|
416
|
+
return this.getResizeColSpan(entity);
|
|
417
|
+
}
|
|
418
|
+
/** Handle drag-drop reorder */
|
|
419
|
+
onDrop(event) {
|
|
420
|
+
if (event.previousIndex === event.currentIndex)
|
|
421
|
+
return;
|
|
422
|
+
const items = [...this.sortedEntities()];
|
|
423
|
+
const [moved] = items.splice(event.previousIndex, 1);
|
|
424
|
+
items.splice(event.currentIndex, 0, moved);
|
|
425
|
+
// Reassign order based on new positions
|
|
426
|
+
const reordered = items.map((item, index) => ({
|
|
427
|
+
...item,
|
|
428
|
+
order: index + 1,
|
|
429
|
+
}));
|
|
430
|
+
// Update the model (two-way binding)
|
|
431
|
+
this.entities.set(reordered);
|
|
432
|
+
// Emit the reordered list
|
|
433
|
+
this.entitiesReordered.emit(reordered);
|
|
434
|
+
}
|
|
435
|
+
// ── Resize completion (from EntitiesResizeBase) ──
|
|
436
|
+
onResizeComplete(entity, previousSize, newSize) {
|
|
437
|
+
// Update entity in the list
|
|
438
|
+
const updated = this.entities().map((e) => e === entity
|
|
439
|
+
? {
|
|
440
|
+
...e,
|
|
441
|
+
configuration: {
|
|
442
|
+
...e.configuration,
|
|
443
|
+
size: newSize,
|
|
444
|
+
},
|
|
445
|
+
}
|
|
446
|
+
: e);
|
|
447
|
+
this.entities.set(updated);
|
|
448
|
+
this.entityResized.emit({
|
|
449
|
+
entity: {
|
|
450
|
+
...entity,
|
|
451
|
+
configuration: { ...entity.configuration, size: newSize },
|
|
452
|
+
},
|
|
453
|
+
previousSize,
|
|
454
|
+
newSize,
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: EntitiesManage, deps: null, target: i0.ɵɵFactoryTarget.Component });
|
|
458
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.3", type: EntitiesManage, isStandalone: true, selector: "mt-entities-manage", inputs: { entities: { classPropertyName: "entities", publicName: "entities", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { entities: "entitiesChange", entitiesReordered: "entitiesReordered" }, usesInheritance: true, ngImport: i0, template: "<div\r\n cdkDropList\r\n cdkDropListOrientation=\"mixed\"\r\n [cdkDropListData]=\"sortedEntities()\"\r\n (cdkDropListDropped)=\"onDrop($event)\"\r\n class=\"grid grid-cols-12 gap-x-4 gap-y-10\"\r\n>\r\n @for (entity of sortedEntities(); track entity.order) {\r\n <div\r\n cdkDrag\r\n [cdkDragData]=\"entity\"\r\n class=\"entity-cell group relative min-w-0 flex items-center p-3 cursor-grab active:cursor-grabbing\"\r\n [class.border]=\"entity.configuration?.showBorder\"\r\n [class.border-dashed]=\"entity.configuration?.showBorder\"\r\n [class.border-gray-200]=\"entity.configuration?.showBorder\"\r\n [class.rounded-lg]=\"entity.configuration?.showBorder\"\r\n [class.resizing]=\"resizingEntity() === entity\"\r\n [style.grid-column]=\"getColSpan(entity)\"\r\n >\r\n <!-- Drag placeholder -->\r\n <div\r\n *cdkDragPlaceholder\r\n class=\"h-full min-h-12 bg-black/10 rounded-xl\"\r\n [style.grid-column]=\"getColSpan(entity)\"\r\n ></div>\r\n\r\n <mt-entity-preview [data]=\"entity\" class=\"flex-1 min-w-0\" />\r\n\r\n <!-- Resize handle (right edge) -->\r\n <div class=\"resize-handle\" (mousedown)=\"onResizeStart($event, entity)\">\r\n <div class=\"resize-handle-bar\"></div>\r\n </div>\r\n </div>\r\n }\r\n</div>\r\n", styles: [".cdk-drag{cursor:grab}.cdk-drag:active,.cdk-drag-preview{cursor:grabbing}.cdk-drag-placeholder{opacity:.5}.cdk-drop-list-dragging .cdk-drag{cursor:grabbing}.entity-cell{position:relative}.resize-handle{position:absolute;top:0;right:-4px;width:8px;height:100%;cursor:col-resize;z-index:10;display:flex;align-items:center;justify-content:center;opacity:0;transition:opacity .15s ease}.entity-cell:hover .resize-handle,.entity-cell.resizing .resize-handle{opacity:1}.resize-handle-bar{width:3px;height:24px;border-radius:2px;background-color:var(--p-primary-color, #6366f1);transition:height .15s ease,background-color .15s ease}.resize-handle:hover .resize-handle-bar{height:36px;background-color:var(--p-primary-color, #4f46e5)}.entity-cell.resizing{outline:2px dashed var(--p-primary-color, #6366f1);outline-offset:-1px;border-radius:8px;-webkit-user-select:none;user-select:none}\n"], dependencies: [{ kind: "component", type: EntityPreview, selector: "mt-entity-preview", inputs: ["data"] }, { kind: "directive", type: CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: CdkDragPlaceholder, selector: "ng-template[cdkDragPlaceholder]", inputs: ["data"] }] });
|
|
459
|
+
}
|
|
460
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: EntitiesManage, decorators: [{
|
|
461
|
+
type: Component,
|
|
462
|
+
args: [{ selector: 'mt-entities-manage', standalone: true, imports: [EntityPreview, CdkDrag, CdkDropList, CdkDragPlaceholder], template: "<div\r\n cdkDropList\r\n cdkDropListOrientation=\"mixed\"\r\n [cdkDropListData]=\"sortedEntities()\"\r\n (cdkDropListDropped)=\"onDrop($event)\"\r\n class=\"grid grid-cols-12 gap-x-4 gap-y-10\"\r\n>\r\n @for (entity of sortedEntities(); track entity.order) {\r\n <div\r\n cdkDrag\r\n [cdkDragData]=\"entity\"\r\n class=\"entity-cell group relative min-w-0 flex items-center p-3 cursor-grab active:cursor-grabbing\"\r\n [class.border]=\"entity.configuration?.showBorder\"\r\n [class.border-dashed]=\"entity.configuration?.showBorder\"\r\n [class.border-gray-200]=\"entity.configuration?.showBorder\"\r\n [class.rounded-lg]=\"entity.configuration?.showBorder\"\r\n [class.resizing]=\"resizingEntity() === entity\"\r\n [style.grid-column]=\"getColSpan(entity)\"\r\n >\r\n <!-- Drag placeholder -->\r\n <div\r\n *cdkDragPlaceholder\r\n class=\"h-full min-h-12 bg-black/10 rounded-xl\"\r\n [style.grid-column]=\"getColSpan(entity)\"\r\n ></div>\r\n\r\n <mt-entity-preview [data]=\"entity\" class=\"flex-1 min-w-0\" />\r\n\r\n <!-- Resize handle (right edge) -->\r\n <div class=\"resize-handle\" (mousedown)=\"onResizeStart($event, entity)\">\r\n <div class=\"resize-handle-bar\"></div>\r\n </div>\r\n </div>\r\n }\r\n</div>\r\n", styles: [".cdk-drag{cursor:grab}.cdk-drag:active,.cdk-drag-preview{cursor:grabbing}.cdk-drag-placeholder{opacity:.5}.cdk-drop-list-dragging .cdk-drag{cursor:grabbing}.entity-cell{position:relative}.resize-handle{position:absolute;top:0;right:-4px;width:8px;height:100%;cursor:col-resize;z-index:10;display:flex;align-items:center;justify-content:center;opacity:0;transition:opacity .15s ease}.entity-cell:hover .resize-handle,.entity-cell.resizing .resize-handle{opacity:1}.resize-handle-bar{width:3px;height:24px;border-radius:2px;background-color:var(--p-primary-color, #6366f1);transition:height .15s ease,background-color .15s ease}.resize-handle:hover .resize-handle-bar{height:36px;background-color:var(--p-primary-color, #4f46e5)}.entity-cell.resizing{outline:2px dashed var(--p-primary-color, #6366f1);outline-offset:-1px;border-radius:8px;-webkit-user-select:none;user-select:none}\n"] }]
|
|
463
|
+
}], propDecorators: { entities: [{ type: i0.Input, args: [{ isSignal: true, alias: "entities", required: true }] }, { type: i0.Output, args: ["entitiesChange"] }], entitiesReordered: [{ type: i0.Output, args: ["entitiesReordered"] }] } });
|
|
464
|
+
|
|
465
|
+
/**
|
|
466
|
+
* Generated bundle index. Do not edit.
|
|
467
|
+
*/
|
|
468
|
+
|
|
469
|
+
export { EntitiesManage, EntitiesPreview, EntitiesResizeBase, EntityCheckbox, EntityCurrency, EntityDate, EntityPercentage, EntityPreview, EntityStatus, EntityText, EntityUser };
|
|
470
|
+
//# sourceMappingURL=masterteam-components-entities.mjs.map
|