@makigamestudio/ui-core 0.1.5
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/fesm2022/makigamestudio-ui-core.mjs +340 -0
- package/fesm2022/makigamestudio-ui-core.mjs.map +1 -0
- package/package.json +42 -0
- package/theme.css +100 -0
- package/theme.scss +159 -0
- package/types/makigamestudio-ui-core.d.ts +144 -0
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { signal, computed, Injectable, input, output, viewChild, ChangeDetectionStrategy, Component } from '@angular/core';
|
|
3
|
+
import { IonCard, IonCardHeader, IonCardTitle, IonCardSubtitle, IonCardContent, IonButton, IonIcon } from '@ionic/angular/standalone';
|
|
4
|
+
import { addIcons } from 'ionicons';
|
|
5
|
+
import { trashOutline, createOutline } from 'ionicons/icons';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* TestClass - Example class implementing TestInterface.
|
|
9
|
+
* Demonstrates the pattern for creating model classes with validation and factory methods.
|
|
10
|
+
*/
|
|
11
|
+
class TestClass {
|
|
12
|
+
id;
|
|
13
|
+
title;
|
|
14
|
+
description;
|
|
15
|
+
createdAt;
|
|
16
|
+
isActive;
|
|
17
|
+
metadata;
|
|
18
|
+
constructor(data) {
|
|
19
|
+
this.id = data.id;
|
|
20
|
+
this.title = data.title;
|
|
21
|
+
this.description = data.description;
|
|
22
|
+
this.createdAt = data.createdAt instanceof Date ? data.createdAt : new Date(data.createdAt);
|
|
23
|
+
this.isActive = data.isActive;
|
|
24
|
+
this.metadata = data.metadata;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Factory method to create a new TestClass instance with a generated ID.
|
|
28
|
+
*/
|
|
29
|
+
static create(title, description) {
|
|
30
|
+
return new TestClass({
|
|
31
|
+
id: crypto.randomUUID(),
|
|
32
|
+
title,
|
|
33
|
+
description,
|
|
34
|
+
createdAt: new Date(),
|
|
35
|
+
isActive: true,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Creates a new instance with updated properties (immutable pattern).
|
|
40
|
+
*/
|
|
41
|
+
update(changes) {
|
|
42
|
+
return new TestClass({
|
|
43
|
+
...this,
|
|
44
|
+
...changes,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Returns a deactivated copy of this instance.
|
|
49
|
+
*/
|
|
50
|
+
deactivate() {
|
|
51
|
+
return this.update({ isActive: false });
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Converts the instance to a plain JSON object.
|
|
55
|
+
*/
|
|
56
|
+
toJSON() {
|
|
57
|
+
return {
|
|
58
|
+
id: this.id,
|
|
59
|
+
title: this.title,
|
|
60
|
+
description: this.description,
|
|
61
|
+
createdAt: this.createdAt,
|
|
62
|
+
isActive: this.isActive,
|
|
63
|
+
metadata: this.metadata,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* TestService - Example service using Angular Signals for reactive state management.
|
|
70
|
+
* Provided in root, demonstrating singleton service pattern with signal-based state.
|
|
71
|
+
*/
|
|
72
|
+
class TestService {
|
|
73
|
+
// Private writable signal for internal state
|
|
74
|
+
_items = signal([], ...(ngDevMode ? [{ debugName: "_items" }] : []));
|
|
75
|
+
_loading = signal(false, ...(ngDevMode ? [{ debugName: "_loading" }] : []));
|
|
76
|
+
_error = signal(null, ...(ngDevMode ? [{ debugName: "_error" }] : []));
|
|
77
|
+
// Public readonly computed signals for consumers
|
|
78
|
+
/** All items in the store */
|
|
79
|
+
items = this._items.asReadonly();
|
|
80
|
+
/** Loading state indicator */
|
|
81
|
+
loading = this._loading.asReadonly();
|
|
82
|
+
/** Current error message, if any */
|
|
83
|
+
error = this._error.asReadonly();
|
|
84
|
+
/** Count of all items */
|
|
85
|
+
count = computed(() => this._items().length, ...(ngDevMode ? [{ debugName: "count" }] : []));
|
|
86
|
+
/** Only active items */
|
|
87
|
+
activeItems = computed(() => this._items().filter((item) => item.isActive), ...(ngDevMode ? [{ debugName: "activeItems" }] : []));
|
|
88
|
+
/** Count of active items */
|
|
89
|
+
activeCount = computed(() => this.activeItems().length, ...(ngDevMode ? [{ debugName: "activeCount" }] : []));
|
|
90
|
+
/**
|
|
91
|
+
* Adds a new item to the store.
|
|
92
|
+
*/
|
|
93
|
+
addItem(title, description) {
|
|
94
|
+
const newItem = TestClass.create(title, description);
|
|
95
|
+
this._items.update((items) => [...items, newItem]);
|
|
96
|
+
this._error.set(null);
|
|
97
|
+
return newItem;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Removes an item by ID.
|
|
101
|
+
*/
|
|
102
|
+
removeItem(id) {
|
|
103
|
+
const currentItems = this._items();
|
|
104
|
+
const filteredItems = currentItems.filter((item) => item.id !== id);
|
|
105
|
+
if (filteredItems.length === currentItems.length) {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
this._items.set(filteredItems);
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Updates an existing item by ID.
|
|
113
|
+
*/
|
|
114
|
+
updateItem(id, changes) {
|
|
115
|
+
let updatedItem = null;
|
|
116
|
+
this._items.update((items) => items.map((item) => {
|
|
117
|
+
if (item.id === id) {
|
|
118
|
+
updatedItem = item.update(changes);
|
|
119
|
+
return updatedItem;
|
|
120
|
+
}
|
|
121
|
+
return item;
|
|
122
|
+
}));
|
|
123
|
+
return updatedItem;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Deactivates an item by ID.
|
|
127
|
+
*/
|
|
128
|
+
deactivateItem(id) {
|
|
129
|
+
return this.updateItem(id, { isActive: false });
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Gets an item by ID using computed lookup.
|
|
133
|
+
*/
|
|
134
|
+
getItemById(id) {
|
|
135
|
+
return this._items().find((item) => item.id === id);
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Clears all items from the store.
|
|
139
|
+
*/
|
|
140
|
+
clearAll() {
|
|
141
|
+
this._items.set([]);
|
|
142
|
+
this._error.set(null);
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Sets the loading state.
|
|
146
|
+
*/
|
|
147
|
+
setLoading(loading) {
|
|
148
|
+
this._loading.set(loading);
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Sets an error message.
|
|
152
|
+
*/
|
|
153
|
+
setError(error) {
|
|
154
|
+
this._error.set(error);
|
|
155
|
+
}
|
|
156
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TestService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
157
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TestService, providedIn: 'root' });
|
|
158
|
+
}
|
|
159
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TestService, decorators: [{
|
|
160
|
+
type: Injectable,
|
|
161
|
+
args: [{
|
|
162
|
+
providedIn: 'root',
|
|
163
|
+
}]
|
|
164
|
+
}] });
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* TestCardComponent - Standalone Ionic Card component demonstrating
|
|
168
|
+
* Angular 21 best practices: OnPush, Signal inputs/outputs/queries.
|
|
169
|
+
*
|
|
170
|
+
* @example
|
|
171
|
+
* ```html
|
|
172
|
+
* <maki-test-card
|
|
173
|
+
* [item]="myItem"
|
|
174
|
+
* [showActions]="true"
|
|
175
|
+
* (itemClick)="onItemClick($event)"
|
|
176
|
+
* (deleteClick)="onDelete($event)"
|
|
177
|
+
* />
|
|
178
|
+
* ```
|
|
179
|
+
*/
|
|
180
|
+
class TestCardComponent {
|
|
181
|
+
// Signal inputs
|
|
182
|
+
item = input.required(...(ngDevMode ? [{ debugName: "item" }] : []));
|
|
183
|
+
showActions = input(false, ...(ngDevMode ? [{ debugName: "showActions" }] : []));
|
|
184
|
+
// Signal outputs
|
|
185
|
+
itemClick = output();
|
|
186
|
+
editClick = output();
|
|
187
|
+
deleteClick = output();
|
|
188
|
+
// Signal queries
|
|
189
|
+
cardElement = viewChild('cardElement', ...(ngDevMode ? [{ debugName: "cardElement" }] : []));
|
|
190
|
+
// Computed signals
|
|
191
|
+
formattedDate = computed(() => {
|
|
192
|
+
const date = this.item().createdAt;
|
|
193
|
+
if (!date)
|
|
194
|
+
return '';
|
|
195
|
+
const dateObj = date instanceof Date ? date : new Date(date);
|
|
196
|
+
return dateObj.toLocaleDateString(undefined, {
|
|
197
|
+
year: 'numeric',
|
|
198
|
+
month: 'short',
|
|
199
|
+
day: 'numeric',
|
|
200
|
+
});
|
|
201
|
+
}, ...(ngDevMode ? [{ debugName: "formattedDate" }] : []));
|
|
202
|
+
constructor() {
|
|
203
|
+
addIcons({ createOutline, trashOutline });
|
|
204
|
+
}
|
|
205
|
+
onCardClick() {
|
|
206
|
+
this.itemClick.emit(this.item());
|
|
207
|
+
}
|
|
208
|
+
onEditClick(event) {
|
|
209
|
+
event.stopPropagation();
|
|
210
|
+
this.editClick.emit(this.item());
|
|
211
|
+
}
|
|
212
|
+
onDeleteClick(event) {
|
|
213
|
+
event.stopPropagation();
|
|
214
|
+
this.deleteClick.emit(this.item());
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Public method to focus the card element programmatically.
|
|
218
|
+
*/
|
|
219
|
+
focus() {
|
|
220
|
+
this.cardElement()?.nativeElement?.focus();
|
|
221
|
+
}
|
|
222
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TestCardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
223
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: TestCardComponent, isStandalone: true, selector: "maki-test-card", inputs: { item: { classPropertyName: "item", publicName: "item", isSignal: true, isRequired: true, transformFunction: null }, showActions: { classPropertyName: "showActions", publicName: "showActions", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { itemClick: "itemClick", editClick: "editClick", deleteClick: "deleteClick" }, viewQueries: [{ propertyName: "cardElement", first: true, predicate: ["cardElement"], descendants: true, isSignal: true }], ngImport: i0, template: `
|
|
224
|
+
<ion-card
|
|
225
|
+
#cardElement
|
|
226
|
+
[class.inactive]="!item().isActive"
|
|
227
|
+
(click)="onCardClick()"
|
|
228
|
+
>
|
|
229
|
+
<ion-card-header>
|
|
230
|
+
<ion-card-title>{{ item().title }}</ion-card-title>
|
|
231
|
+
@if (formattedDate()) {
|
|
232
|
+
<ion-card-subtitle>
|
|
233
|
+
{{ formattedDate() }}
|
|
234
|
+
@if (!item().isActive) {
|
|
235
|
+
<span class="status-badge inactive">Inactive</span>
|
|
236
|
+
}
|
|
237
|
+
</ion-card-subtitle>
|
|
238
|
+
}
|
|
239
|
+
</ion-card-header>
|
|
240
|
+
|
|
241
|
+
@if (item().description) {
|
|
242
|
+
<ion-card-content>
|
|
243
|
+
<p>{{ item().description }}</p>
|
|
244
|
+
</ion-card-content>
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
@if (showActions()) {
|
|
248
|
+
<ion-card-content class="card-actions">
|
|
249
|
+
<ion-button
|
|
250
|
+
fill="outline"
|
|
251
|
+
size="small"
|
|
252
|
+
(click)="onEditClick($event)"
|
|
253
|
+
>
|
|
254
|
+
<ion-icon slot="start" name="create-outline"></ion-icon>
|
|
255
|
+
Edit
|
|
256
|
+
</ion-button>
|
|
257
|
+
<ion-button
|
|
258
|
+
fill="outline"
|
|
259
|
+
size="small"
|
|
260
|
+
color="danger"
|
|
261
|
+
(click)="onDeleteClick($event)"
|
|
262
|
+
>
|
|
263
|
+
<ion-icon slot="start" name="trash-outline"></ion-icon>
|
|
264
|
+
Delete
|
|
265
|
+
</ion-button>
|
|
266
|
+
</ion-card-content>
|
|
267
|
+
}
|
|
268
|
+
</ion-card>
|
|
269
|
+
`, isInline: true, styles: [":host{display:block}ion-card{--background: var(--maki-card-background, var(--ion-card-background, #fff));--color: var(--maki-card-color, var(--ion-text-color));margin:var(--maki-card-margin, 16px);border-radius:var(--maki-card-border-radius, 12px);transition:opacity .2s ease,transform .2s ease;cursor:pointer}ion-card:hover{transform:translateY(-2px)}ion-card.inactive{opacity:.6}ion-card-title{color:var(--maki-primary, var(--ion-color-primary));font-weight:600}ion-card-subtitle{display:flex;align-items:center;gap:8px}.status-badge{font-size:.75rem;padding:2px 8px;border-radius:4px;font-weight:500}.status-badge.inactive{background-color:var(--ion-color-medium-tint);color:var(--ion-color-medium-contrast)}.card-actions{display:flex;gap:8px;padding-top:0}.card-actions ion-button{--border-color: var(--maki-primary, var(--ion-color-primary));--color: var(--maki-primary, var(--ion-color-primary))}\n"], dependencies: [{ kind: "component", type: IonCard, selector: "ion-card", inputs: ["button", "color", "disabled", "download", "href", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] }, { kind: "component", type: IonCardHeader, selector: "ion-card-header", inputs: ["color", "mode", "translucent"] }, { kind: "component", type: IonCardTitle, selector: "ion-card-title", inputs: ["color", "mode"] }, { kind: "component", type: IonCardSubtitle, selector: "ion-card-subtitle", inputs: ["color", "mode"] }, { kind: "component", type: IonCardContent, selector: "ion-card-content", inputs: ["mode"] }, { kind: "component", type: IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
270
|
+
}
|
|
271
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TestCardComponent, decorators: [{
|
|
272
|
+
type: Component,
|
|
273
|
+
args: [{ selector: 'maki-test-card', standalone: true, imports: [
|
|
274
|
+
IonCard,
|
|
275
|
+
IonCardHeader,
|
|
276
|
+
IonCardTitle,
|
|
277
|
+
IonCardSubtitle,
|
|
278
|
+
IonCardContent,
|
|
279
|
+
IonButton,
|
|
280
|
+
IonIcon,
|
|
281
|
+
], changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
282
|
+
<ion-card
|
|
283
|
+
#cardElement
|
|
284
|
+
[class.inactive]="!item().isActive"
|
|
285
|
+
(click)="onCardClick()"
|
|
286
|
+
>
|
|
287
|
+
<ion-card-header>
|
|
288
|
+
<ion-card-title>{{ item().title }}</ion-card-title>
|
|
289
|
+
@if (formattedDate()) {
|
|
290
|
+
<ion-card-subtitle>
|
|
291
|
+
{{ formattedDate() }}
|
|
292
|
+
@if (!item().isActive) {
|
|
293
|
+
<span class="status-badge inactive">Inactive</span>
|
|
294
|
+
}
|
|
295
|
+
</ion-card-subtitle>
|
|
296
|
+
}
|
|
297
|
+
</ion-card-header>
|
|
298
|
+
|
|
299
|
+
@if (item().description) {
|
|
300
|
+
<ion-card-content>
|
|
301
|
+
<p>{{ item().description }}</p>
|
|
302
|
+
</ion-card-content>
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
@if (showActions()) {
|
|
306
|
+
<ion-card-content class="card-actions">
|
|
307
|
+
<ion-button
|
|
308
|
+
fill="outline"
|
|
309
|
+
size="small"
|
|
310
|
+
(click)="onEditClick($event)"
|
|
311
|
+
>
|
|
312
|
+
<ion-icon slot="start" name="create-outline"></ion-icon>
|
|
313
|
+
Edit
|
|
314
|
+
</ion-button>
|
|
315
|
+
<ion-button
|
|
316
|
+
fill="outline"
|
|
317
|
+
size="small"
|
|
318
|
+
color="danger"
|
|
319
|
+
(click)="onDeleteClick($event)"
|
|
320
|
+
>
|
|
321
|
+
<ion-icon slot="start" name="trash-outline"></ion-icon>
|
|
322
|
+
Delete
|
|
323
|
+
</ion-button>
|
|
324
|
+
</ion-card-content>
|
|
325
|
+
}
|
|
326
|
+
</ion-card>
|
|
327
|
+
`, styles: [":host{display:block}ion-card{--background: var(--maki-card-background, var(--ion-card-background, #fff));--color: var(--maki-card-color, var(--ion-text-color));margin:var(--maki-card-margin, 16px);border-radius:var(--maki-card-border-radius, 12px);transition:opacity .2s ease,transform .2s ease;cursor:pointer}ion-card:hover{transform:translateY(-2px)}ion-card.inactive{opacity:.6}ion-card-title{color:var(--maki-primary, var(--ion-color-primary));font-weight:600}ion-card-subtitle{display:flex;align-items:center;gap:8px}.status-badge{font-size:.75rem;padding:2px 8px;border-radius:4px;font-weight:500}.status-badge.inactive{background-color:var(--ion-color-medium-tint);color:var(--ion-color-medium-contrast)}.card-actions{display:flex;gap:8px;padding-top:0}.card-actions ion-button{--border-color: var(--maki-primary, var(--ion-color-primary));--color: var(--maki-primary, var(--ion-color-primary))}\n"] }]
|
|
328
|
+
}], ctorParameters: () => [], propDecorators: { item: [{ type: i0.Input, args: [{ isSignal: true, alias: "item", required: true }] }], showActions: [{ type: i0.Input, args: [{ isSignal: true, alias: "showActions", required: false }] }], itemClick: [{ type: i0.Output, args: ["itemClick"] }], editClick: [{ type: i0.Output, args: ["editClick"] }], deleteClick: [{ type: i0.Output, args: ["deleteClick"] }], cardElement: [{ type: i0.ViewChild, args: ['cardElement', { isSignal: true }] }] } });
|
|
329
|
+
|
|
330
|
+
/*
|
|
331
|
+
* Public API Surface of @makigamestudio/ui-core
|
|
332
|
+
*/
|
|
333
|
+
// Models
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Generated bundle index. Do not edit.
|
|
337
|
+
*/
|
|
338
|
+
|
|
339
|
+
export { TestCardComponent, TestClass, TestService };
|
|
340
|
+
//# sourceMappingURL=makigamestudio-ui-core.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"makigamestudio-ui-core.mjs","sources":["../../../projects/ui-core/src/lib/models/test.class.ts","../../../projects/ui-core/src/lib/services/test.service.ts","../../../projects/ui-core/src/lib/components/test-card/test-card.component.ts","../../../projects/ui-core/src/public-api.ts","../../../projects/ui-core/src/makigamestudio-ui-core.ts"],"sourcesContent":["import { TestInterface } from './test.interface';\n\n/**\n * TestClass - Example class implementing TestInterface.\n * Demonstrates the pattern for creating model classes with validation and factory methods.\n */\nexport class TestClass implements TestInterface {\n readonly id: string;\n readonly title: string;\n readonly description?: string;\n readonly createdAt: Date;\n readonly isActive: boolean;\n readonly metadata?: Record<string, unknown>;\n\n constructor(data: TestInterface) {\n this.id = data.id;\n this.title = data.title;\n this.description = data.description;\n this.createdAt = data.createdAt instanceof Date ? data.createdAt : new Date(data.createdAt);\n this.isActive = data.isActive;\n this.metadata = data.metadata;\n }\n\n /**\n * Factory method to create a new TestClass instance with a generated ID.\n */\n static create(title: string, description?: string): TestClass {\n return new TestClass({\n id: crypto.randomUUID(),\n title,\n description,\n createdAt: new Date(),\n isActive: true,\n });\n }\n\n /**\n * Creates a new instance with updated properties (immutable pattern).\n */\n update(changes: Partial<Omit<TestInterface, 'id' | 'createdAt'>>): TestClass {\n return new TestClass({\n ...this,\n ...changes,\n });\n }\n\n /**\n * Returns a deactivated copy of this instance.\n */\n deactivate(): TestClass {\n return this.update({ isActive: false });\n }\n\n /**\n * Converts the instance to a plain JSON object.\n */\n toJSON(): TestInterface {\n return {\n id: this.id,\n title: this.title,\n description: this.description,\n createdAt: this.createdAt,\n isActive: this.isActive,\n metadata: this.metadata,\n };\n }\n}\n","import { Injectable, computed, signal } from '@angular/core';\nimport { TestClass } from '../models/test.class';\nimport { TestInterface } from '../models/test.interface';\n\n/**\n * TestService - Example service using Angular Signals for reactive state management.\n * Provided in root, demonstrating singleton service pattern with signal-based state.\n */\n@Injectable({\n providedIn: 'root',\n})\nexport class TestService {\n // Private writable signal for internal state\n private readonly _items = signal<readonly TestClass[]>([]);\n private readonly _loading = signal<boolean>(false);\n private readonly _error = signal<string | null>(null);\n\n // Public readonly computed signals for consumers\n /** All items in the store */\n readonly items = this._items.asReadonly();\n\n /** Loading state indicator */\n readonly loading = this._loading.asReadonly();\n\n /** Current error message, if any */\n readonly error = this._error.asReadonly();\n\n /** Count of all items */\n readonly count = computed(() => this._items().length);\n\n /** Only active items */\n readonly activeItems = computed(() => this._items().filter((item) => item.isActive));\n\n /** Count of active items */\n readonly activeCount = computed(() => this.activeItems().length);\n\n /**\n * Adds a new item to the store.\n */\n addItem(title: string, description?: string): TestClass {\n const newItem = TestClass.create(title, description);\n this._items.update((items) => [...items, newItem]);\n this._error.set(null);\n return newItem;\n }\n\n /**\n * Removes an item by ID.\n */\n removeItem(id: string): boolean {\n const currentItems = this._items();\n const filteredItems = currentItems.filter((item) => item.id !== id);\n\n if (filteredItems.length === currentItems.length) {\n return false;\n }\n\n this._items.set(filteredItems);\n return true;\n }\n\n /**\n * Updates an existing item by ID.\n */\n updateItem(id: string, changes: Partial<Omit<TestInterface, 'id' | 'createdAt'>>): TestClass | null {\n let updatedItem: TestClass | null = null;\n\n this._items.update((items) =>\n items.map((item) => {\n if (item.id === id) {\n updatedItem = item.update(changes);\n return updatedItem;\n }\n return item;\n })\n );\n\n return updatedItem;\n }\n\n /**\n * Deactivates an item by ID.\n */\n deactivateItem(id: string): TestClass | null {\n return this.updateItem(id, { isActive: false });\n }\n\n /**\n * Gets an item by ID using computed lookup.\n */\n getItemById(id: string): TestClass | undefined {\n return this._items().find((item) => item.id === id);\n }\n\n /**\n * Clears all items from the store.\n */\n clearAll(): void {\n this._items.set([]);\n this._error.set(null);\n }\n\n /**\n * Sets the loading state.\n */\n setLoading(loading: boolean): void {\n this._loading.set(loading);\n }\n\n /**\n * Sets an error message.\n */\n setError(error: string | null): void {\n this._error.set(error);\n }\n}\n","import {\n ChangeDetectionStrategy,\n Component,\n computed,\n ElementRef,\n input,\n output,\n viewChild,\n} from '@angular/core';\nimport { IonButton, IonCard, IonCardContent, IonCardHeader, IonCardSubtitle, IonCardTitle, IonIcon } from '@ionic/angular/standalone';\nimport { addIcons } from 'ionicons';\nimport { createOutline, trashOutline } from 'ionicons/icons';\nimport { TestInterface } from '../../models/test.interface';\n\n/**\n * TestCardComponent - Standalone Ionic Card component demonstrating\n * Angular 21 best practices: OnPush, Signal inputs/outputs/queries.\n *\n * @example\n * ```html\n * <maki-test-card\n * [item]=\"myItem\"\n * [showActions]=\"true\"\n * (itemClick)=\"onItemClick($event)\"\n * (deleteClick)=\"onDelete($event)\"\n * />\n * ```\n */\n@Component({\n selector: 'maki-test-card',\n standalone: true,\n imports: [\n IonCard,\n IonCardHeader,\n IonCardTitle,\n IonCardSubtitle,\n IonCardContent,\n IonButton,\n IonIcon,\n ],\n changeDetection: ChangeDetectionStrategy.OnPush,\n template: `\n <ion-card\n #cardElement\n [class.inactive]=\"!item().isActive\"\n (click)=\"onCardClick()\"\n >\n <ion-card-header>\n <ion-card-title>{{ item().title }}</ion-card-title>\n @if (formattedDate()) {\n <ion-card-subtitle>\n {{ formattedDate() }}\n @if (!item().isActive) {\n <span class=\"status-badge inactive\">Inactive</span>\n }\n </ion-card-subtitle>\n }\n </ion-card-header>\n\n @if (item().description) {\n <ion-card-content>\n <p>{{ item().description }}</p>\n </ion-card-content>\n }\n\n @if (showActions()) {\n <ion-card-content class=\"card-actions\">\n <ion-button\n fill=\"outline\"\n size=\"small\"\n (click)=\"onEditClick($event)\"\n >\n <ion-icon slot=\"start\" name=\"create-outline\"></ion-icon>\n Edit\n </ion-button>\n <ion-button\n fill=\"outline\"\n size=\"small\"\n color=\"danger\"\n (click)=\"onDeleteClick($event)\"\n >\n <ion-icon slot=\"start\" name=\"trash-outline\"></ion-icon>\n Delete\n </ion-button>\n </ion-card-content>\n }\n </ion-card>\n `,\n styles: [`\n :host {\n display: block;\n }\n\n ion-card {\n --background: var(--maki-card-background, var(--ion-card-background, #fff));\n --color: var(--maki-card-color, var(--ion-text-color));\n margin: var(--maki-card-margin, 16px);\n border-radius: var(--maki-card-border-radius, 12px);\n transition: opacity 0.2s ease, transform 0.2s ease;\n cursor: pointer;\n\n &:hover {\n transform: translateY(-2px);\n }\n\n &.inactive {\n opacity: 0.6;\n }\n }\n\n ion-card-title {\n color: var(--maki-primary, var(--ion-color-primary));\n font-weight: 600;\n }\n\n ion-card-subtitle {\n display: flex;\n align-items: center;\n gap: 8px;\n }\n\n .status-badge {\n font-size: 0.75rem;\n padding: 2px 8px;\n border-radius: 4px;\n font-weight: 500;\n\n &.inactive {\n background-color: var(--ion-color-medium-tint);\n color: var(--ion-color-medium-contrast);\n }\n }\n\n .card-actions {\n display: flex;\n gap: 8px;\n padding-top: 0;\n\n ion-button {\n --border-color: var(--maki-primary, var(--ion-color-primary));\n --color: var(--maki-primary, var(--ion-color-primary));\n }\n }\n `],\n})\nexport class TestCardComponent {\n // Signal inputs\n readonly item = input.required<TestInterface>();\n readonly showActions = input<boolean>(false);\n\n // Signal outputs\n readonly itemClick = output<TestInterface>();\n readonly editClick = output<TestInterface>();\n readonly deleteClick = output<TestInterface>();\n\n // Signal queries\n readonly cardElement = viewChild<ElementRef<HTMLIonCardElement>>('cardElement');\n\n // Computed signals\n readonly formattedDate = computed(() => {\n const date = this.item().createdAt;\n if (!date) return '';\n\n const dateObj = date instanceof Date ? date : new Date(date);\n return dateObj.toLocaleDateString(undefined, {\n year: 'numeric',\n month: 'short',\n day: 'numeric',\n });\n });\n\n constructor() {\n addIcons({ createOutline, trashOutline });\n }\n\n protected onCardClick(): void {\n this.itemClick.emit(this.item());\n }\n\n protected onEditClick(event: Event): void {\n event.stopPropagation();\n this.editClick.emit(this.item());\n }\n\n protected onDeleteClick(event: Event): void {\n event.stopPropagation();\n this.deleteClick.emit(this.item());\n }\n\n /**\n * Public method to focus the card element programmatically.\n */\n focus(): void {\n this.cardElement()?.nativeElement?.focus();\n }\n}\n","/*\n * Public API Surface of @makigamestudio/ui-core\n */\n\n// Models\nexport { TestClass } from './lib/models/test.class';\nexport type { TestInterface } from './lib/models/test.interface';\n\n// Services\nexport { TestService } from './lib/services/test.service';\n\n// Components\nexport { TestCardComponent } from './lib/components/test-card/test-card.component';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@makigamestudio/ui-core",
|
|
3
|
+
"version": "0.1.5",
|
|
4
|
+
"description": "Angular 21 + Ionic 8 component library with zoneless architecture and signal-based state management",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"angular",
|
|
7
|
+
"ionic",
|
|
8
|
+
"zoneless",
|
|
9
|
+
"signals",
|
|
10
|
+
"standalone",
|
|
11
|
+
"components",
|
|
12
|
+
"ui-core"
|
|
13
|
+
],
|
|
14
|
+
"author": "MakiGameStudio",
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git+https://github.com/gdor/ng-ion-core.git"
|
|
19
|
+
},
|
|
20
|
+
"peerDependencies": {
|
|
21
|
+
"@angular/common": "^21.0.0",
|
|
22
|
+
"@angular/core": "^21.0.0",
|
|
23
|
+
"@ionic/angular": "^8.0.0"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"tslib": "^2.6.0"
|
|
27
|
+
},
|
|
28
|
+
"exports": {
|
|
29
|
+
"./theme.scss": "./theme.scss",
|
|
30
|
+
"./theme.css": "./theme.css",
|
|
31
|
+
"./package.json": {
|
|
32
|
+
"default": "./package.json"
|
|
33
|
+
},
|
|
34
|
+
".": {
|
|
35
|
+
"types": "./types/makigamestudio-ui-core.d.ts",
|
|
36
|
+
"default": "./fesm2022/makigamestudio-ui-core.mjs"
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"module": "fesm2022/makigamestudio-ui-core.mjs",
|
|
40
|
+
"typings": "types/makigamestudio-ui-core.d.ts",
|
|
41
|
+
"sideEffects": false
|
|
42
|
+
}
|
package/theme.css
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @makigamestudio/maki-core Theme Variables (Pre-compiled CSS)
|
|
3
|
+
*
|
|
4
|
+
* Import this file in your global styles if you don't use SCSS.
|
|
5
|
+
* Example: @import '@makigamestudio/maki-core/theme.css';
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
:root {
|
|
9
|
+
/* Brand Colors */
|
|
10
|
+
--maki-primary: #3880ff;
|
|
11
|
+
--maki-primary-rgb: 56, 128, 255;
|
|
12
|
+
--maki-primary-contrast: #ffffff;
|
|
13
|
+
--maki-primary-shade: #3171e0;
|
|
14
|
+
--maki-primary-tint: #4c8dff;
|
|
15
|
+
|
|
16
|
+
--maki-secondary: #3dc2ff;
|
|
17
|
+
--maki-secondary-rgb: 61, 194, 255;
|
|
18
|
+
--maki-secondary-contrast: #000000;
|
|
19
|
+
--maki-secondary-shade: #36abe0;
|
|
20
|
+
--maki-secondary-tint: #50c8ff;
|
|
21
|
+
|
|
22
|
+
--maki-accent: #5260ff;
|
|
23
|
+
--maki-accent-rgb: 82, 96, 255;
|
|
24
|
+
--maki-accent-contrast: #ffffff;
|
|
25
|
+
--maki-accent-shade: #4854e0;
|
|
26
|
+
--maki-accent-tint: #6370ff;
|
|
27
|
+
|
|
28
|
+
/* Semantic Colors */
|
|
29
|
+
--maki-success: #2dd36f;
|
|
30
|
+
--maki-warning: #ffc409;
|
|
31
|
+
--maki-danger: #eb445a;
|
|
32
|
+
--maki-info: #3dc2ff;
|
|
33
|
+
|
|
34
|
+
/* Background & Surface */
|
|
35
|
+
--maki-background: #f4f5f8;
|
|
36
|
+
--maki-background-rgb: 244, 245, 248;
|
|
37
|
+
--maki-surface: #ffffff;
|
|
38
|
+
--maki-surface-rgb: 255, 255, 255;
|
|
39
|
+
|
|
40
|
+
/* Text Colors */
|
|
41
|
+
--maki-text-primary: #1a1a1a;
|
|
42
|
+
--maki-text-secondary: #666666;
|
|
43
|
+
--maki-text-muted: #999999;
|
|
44
|
+
--maki-text-inverse: #ffffff;
|
|
45
|
+
|
|
46
|
+
/* Card Component */
|
|
47
|
+
--maki-card-background: var(--maki-surface);
|
|
48
|
+
--maki-card-color: var(--maki-text-primary);
|
|
49
|
+
--maki-card-margin: 16px;
|
|
50
|
+
--maki-card-border-radius: 12px;
|
|
51
|
+
--maki-card-box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
52
|
+
|
|
53
|
+
/* Spacing Scale */
|
|
54
|
+
--maki-spacing-xs: 4px;
|
|
55
|
+
--maki-spacing-sm: 8px;
|
|
56
|
+
--maki-spacing-md: 16px;
|
|
57
|
+
--maki-spacing-lg: 24px;
|
|
58
|
+
--maki-spacing-xl: 32px;
|
|
59
|
+
--maki-spacing-xxl: 48px;
|
|
60
|
+
|
|
61
|
+
/* Border Radius Scale */
|
|
62
|
+
--maki-radius-sm: 4px;
|
|
63
|
+
--maki-radius-md: 8px;
|
|
64
|
+
--maki-radius-lg: 12px;
|
|
65
|
+
--maki-radius-xl: 16px;
|
|
66
|
+
--maki-radius-full: 9999px;
|
|
67
|
+
|
|
68
|
+
/* Typography */
|
|
69
|
+
--maki-font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
|
|
70
|
+
--maki-font-size-xs: 0.75rem;
|
|
71
|
+
--maki-font-size-sm: 0.875rem;
|
|
72
|
+
--maki-font-size-md: 1rem;
|
|
73
|
+
--maki-font-size-lg: 1.125rem;
|
|
74
|
+
--maki-font-size-xl: 1.25rem;
|
|
75
|
+
--maki-font-size-xxl: 1.5rem;
|
|
76
|
+
|
|
77
|
+
/* Transitions */
|
|
78
|
+
--maki-transition-fast: 150ms ease;
|
|
79
|
+
--maki-transition-normal: 250ms ease;
|
|
80
|
+
--maki-transition-slow: 350ms ease;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/* Dark Mode Overrides */
|
|
84
|
+
@media (prefers-color-scheme: dark) {
|
|
85
|
+
:root {
|
|
86
|
+
--maki-background: #1a1a1a;
|
|
87
|
+
--maki-background-rgb: 26, 26, 26;
|
|
88
|
+
--maki-surface: #2d2d2d;
|
|
89
|
+
--maki-surface-rgb: 45, 45, 45;
|
|
90
|
+
|
|
91
|
+
--maki-text-primary: #ffffff;
|
|
92
|
+
--maki-text-secondary: #b3b3b3;
|
|
93
|
+
--maki-text-muted: #808080;
|
|
94
|
+
--maki-text-inverse: #1a1a1a;
|
|
95
|
+
|
|
96
|
+
--maki-card-background: var(--maki-surface);
|
|
97
|
+
--maki-card-color: var(--maki-text-primary);
|
|
98
|
+
--maki-card-box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
|
|
99
|
+
}
|
|
100
|
+
}
|
package/theme.scss
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @makigamestudio/maki-core Theme Variables
|
|
3
|
+
*
|
|
4
|
+
* Import this file in your global styles to customize the library components.
|
|
5
|
+
* Example: @use '@makigamestudio/maki-core/theme.scss';
|
|
6
|
+
*
|
|
7
|
+
* Override these variables in your own SCSS to customize the appearance.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
:root {
|
|
11
|
+
// ==============================================
|
|
12
|
+
// Brand Colors
|
|
13
|
+
// ==============================================
|
|
14
|
+
--maki-primary: #3880ff;
|
|
15
|
+
--maki-primary-rgb: 56, 128, 255;
|
|
16
|
+
--maki-primary-contrast: #ffffff;
|
|
17
|
+
--maki-primary-shade: #3171e0;
|
|
18
|
+
--maki-primary-tint: #4c8dff;
|
|
19
|
+
|
|
20
|
+
--maki-secondary: #3dc2ff;
|
|
21
|
+
--maki-secondary-rgb: 61, 194, 255;
|
|
22
|
+
--maki-secondary-contrast: #000000;
|
|
23
|
+
--maki-secondary-shade: #36abe0;
|
|
24
|
+
--maki-secondary-tint: #50c8ff;
|
|
25
|
+
|
|
26
|
+
--maki-accent: #5260ff;
|
|
27
|
+
--maki-accent-rgb: 82, 96, 255;
|
|
28
|
+
--maki-accent-contrast: #ffffff;
|
|
29
|
+
--maki-accent-shade: #4854e0;
|
|
30
|
+
--maki-accent-tint: #6370ff;
|
|
31
|
+
|
|
32
|
+
// ==============================================
|
|
33
|
+
// Semantic Colors
|
|
34
|
+
// ==============================================
|
|
35
|
+
--maki-success: #2dd36f;
|
|
36
|
+
--maki-warning: #ffc409;
|
|
37
|
+
--maki-danger: #eb445a;
|
|
38
|
+
--maki-info: #3dc2ff;
|
|
39
|
+
|
|
40
|
+
// ==============================================
|
|
41
|
+
// Background & Surface
|
|
42
|
+
// ==============================================
|
|
43
|
+
--maki-background: #f4f5f8;
|
|
44
|
+
--maki-background-rgb: 244, 245, 248;
|
|
45
|
+
--maki-surface: #ffffff;
|
|
46
|
+
--maki-surface-rgb: 255, 255, 255;
|
|
47
|
+
|
|
48
|
+
// ==============================================
|
|
49
|
+
// Text Colors
|
|
50
|
+
// ==============================================
|
|
51
|
+
--maki-text-primary: #1a1a1a;
|
|
52
|
+
--maki-text-secondary: #666666;
|
|
53
|
+
--maki-text-muted: #999999;
|
|
54
|
+
--maki-text-inverse: #ffffff;
|
|
55
|
+
|
|
56
|
+
// ==============================================
|
|
57
|
+
// Card Component
|
|
58
|
+
// ==============================================
|
|
59
|
+
--maki-card-background: var(--maki-surface);
|
|
60
|
+
--maki-card-color: var(--maki-text-primary);
|
|
61
|
+
--maki-card-margin: 16px;
|
|
62
|
+
--maki-card-border-radius: 12px;
|
|
63
|
+
--maki-card-box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
64
|
+
|
|
65
|
+
// ==============================================
|
|
66
|
+
// Spacing Scale
|
|
67
|
+
// ==============================================
|
|
68
|
+
--maki-spacing-xs: 4px;
|
|
69
|
+
--maki-spacing-sm: 8px;
|
|
70
|
+
--maki-spacing-md: 16px;
|
|
71
|
+
--maki-spacing-lg: 24px;
|
|
72
|
+
--maki-spacing-xl: 32px;
|
|
73
|
+
--maki-spacing-xxl: 48px;
|
|
74
|
+
|
|
75
|
+
// ==============================================
|
|
76
|
+
// Border Radius Scale
|
|
77
|
+
// ==============================================
|
|
78
|
+
--maki-radius-sm: 4px;
|
|
79
|
+
--maki-radius-md: 8px;
|
|
80
|
+
--maki-radius-lg: 12px;
|
|
81
|
+
--maki-radius-xl: 16px;
|
|
82
|
+
--maki-radius-full: 9999px;
|
|
83
|
+
|
|
84
|
+
// ==============================================
|
|
85
|
+
// Typography
|
|
86
|
+
// ==============================================
|
|
87
|
+
--maki-font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
|
|
88
|
+
--maki-font-size-xs: 0.75rem;
|
|
89
|
+
--maki-font-size-sm: 0.875rem;
|
|
90
|
+
--maki-font-size-md: 1rem;
|
|
91
|
+
--maki-font-size-lg: 1.125rem;
|
|
92
|
+
--maki-font-size-xl: 1.25rem;
|
|
93
|
+
--maki-font-size-xxl: 1.5rem;
|
|
94
|
+
|
|
95
|
+
// ==============================================
|
|
96
|
+
// Transitions
|
|
97
|
+
// ==============================================
|
|
98
|
+
--maki-transition-fast: 150ms ease;
|
|
99
|
+
--maki-transition-normal: 250ms ease;
|
|
100
|
+
--maki-transition-slow: 350ms ease;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ==============================================
|
|
104
|
+
// Dark Mode Overrides
|
|
105
|
+
// ==============================================
|
|
106
|
+
@media (prefers-color-scheme: dark) {
|
|
107
|
+
:root {
|
|
108
|
+
--maki-background: #1a1a1a;
|
|
109
|
+
--maki-background-rgb: 26, 26, 26;
|
|
110
|
+
--maki-surface: #2d2d2d;
|
|
111
|
+
--maki-surface-rgb: 45, 45, 45;
|
|
112
|
+
|
|
113
|
+
--maki-text-primary: #ffffff;
|
|
114
|
+
--maki-text-secondary: #b3b3b3;
|
|
115
|
+
--maki-text-muted: #808080;
|
|
116
|
+
--maki-text-inverse: #1a1a1a;
|
|
117
|
+
|
|
118
|
+
--maki-card-background: var(--maki-surface);
|
|
119
|
+
--maki-card-color: var(--maki-text-primary);
|
|
120
|
+
--maki-card-box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// ==============================================
|
|
125
|
+
// Utility Mixins (for SCSS consumers)
|
|
126
|
+
// ==============================================
|
|
127
|
+
@mixin maki-flex-center {
|
|
128
|
+
display: flex;
|
|
129
|
+
align-items: center;
|
|
130
|
+
justify-content: center;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
@mixin maki-flex-between {
|
|
134
|
+
display: flex;
|
|
135
|
+
align-items: center;
|
|
136
|
+
justify-content: space-between;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
@mixin maki-card-hover {
|
|
140
|
+
transition: transform var(--maki-transition-fast), box-shadow var(--maki-transition-fast);
|
|
141
|
+
|
|
142
|
+
&:hover {
|
|
143
|
+
transform: translateY(-2px);
|
|
144
|
+
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
@mixin maki-truncate($lines: 1) {
|
|
149
|
+
@if $lines == 1 {
|
|
150
|
+
white-space: nowrap;
|
|
151
|
+
overflow: hidden;
|
|
152
|
+
text-overflow: ellipsis;
|
|
153
|
+
} @else {
|
|
154
|
+
display: -webkit-box;
|
|
155
|
+
-webkit-line-clamp: $lines;
|
|
156
|
+
-webkit-box-orient: vertical;
|
|
157
|
+
overflow: hidden;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import * as _angular_core from '@angular/core';
|
|
2
|
+
import { ElementRef } from '@angular/core';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* TestInterface - Example interface exported from ng-ion-core library.
|
|
6
|
+
* Demonstrates the pattern for defining data contracts.
|
|
7
|
+
*/
|
|
8
|
+
interface TestInterface {
|
|
9
|
+
/** Unique identifier */
|
|
10
|
+
readonly id: string;
|
|
11
|
+
/** Display title */
|
|
12
|
+
readonly title: string;
|
|
13
|
+
/** Optional description text */
|
|
14
|
+
readonly description?: string;
|
|
15
|
+
/** Timestamp of creation */
|
|
16
|
+
readonly createdAt: Date;
|
|
17
|
+
/** Whether the item is currently active */
|
|
18
|
+
readonly isActive: boolean;
|
|
19
|
+
/** Arbitrary metadata as key-value pairs */
|
|
20
|
+
readonly metadata?: Record<string, unknown>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* TestClass - Example class implementing TestInterface.
|
|
25
|
+
* Demonstrates the pattern for creating model classes with validation and factory methods.
|
|
26
|
+
*/
|
|
27
|
+
declare class TestClass implements TestInterface {
|
|
28
|
+
readonly id: string;
|
|
29
|
+
readonly title: string;
|
|
30
|
+
readonly description?: string;
|
|
31
|
+
readonly createdAt: Date;
|
|
32
|
+
readonly isActive: boolean;
|
|
33
|
+
readonly metadata?: Record<string, unknown>;
|
|
34
|
+
constructor(data: TestInterface);
|
|
35
|
+
/**
|
|
36
|
+
* Factory method to create a new TestClass instance with a generated ID.
|
|
37
|
+
*/
|
|
38
|
+
static create(title: string, description?: string): TestClass;
|
|
39
|
+
/**
|
|
40
|
+
* Creates a new instance with updated properties (immutable pattern).
|
|
41
|
+
*/
|
|
42
|
+
update(changes: Partial<Omit<TestInterface, 'id' | 'createdAt'>>): TestClass;
|
|
43
|
+
/**
|
|
44
|
+
* Returns a deactivated copy of this instance.
|
|
45
|
+
*/
|
|
46
|
+
deactivate(): TestClass;
|
|
47
|
+
/**
|
|
48
|
+
* Converts the instance to a plain JSON object.
|
|
49
|
+
*/
|
|
50
|
+
toJSON(): TestInterface;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* TestService - Example service using Angular Signals for reactive state management.
|
|
55
|
+
* Provided in root, demonstrating singleton service pattern with signal-based state.
|
|
56
|
+
*/
|
|
57
|
+
declare class TestService {
|
|
58
|
+
private readonly _items;
|
|
59
|
+
private readonly _loading;
|
|
60
|
+
private readonly _error;
|
|
61
|
+
/** All items in the store */
|
|
62
|
+
readonly items: _angular_core.Signal<readonly TestClass[]>;
|
|
63
|
+
/** Loading state indicator */
|
|
64
|
+
readonly loading: _angular_core.Signal<boolean>;
|
|
65
|
+
/** Current error message, if any */
|
|
66
|
+
readonly error: _angular_core.Signal<string | null>;
|
|
67
|
+
/** Count of all items */
|
|
68
|
+
readonly count: _angular_core.Signal<number>;
|
|
69
|
+
/** Only active items */
|
|
70
|
+
readonly activeItems: _angular_core.Signal<TestClass[]>;
|
|
71
|
+
/** Count of active items */
|
|
72
|
+
readonly activeCount: _angular_core.Signal<number>;
|
|
73
|
+
/**
|
|
74
|
+
* Adds a new item to the store.
|
|
75
|
+
*/
|
|
76
|
+
addItem(title: string, description?: string): TestClass;
|
|
77
|
+
/**
|
|
78
|
+
* Removes an item by ID.
|
|
79
|
+
*/
|
|
80
|
+
removeItem(id: string): boolean;
|
|
81
|
+
/**
|
|
82
|
+
* Updates an existing item by ID.
|
|
83
|
+
*/
|
|
84
|
+
updateItem(id: string, changes: Partial<Omit<TestInterface, 'id' | 'createdAt'>>): TestClass | null;
|
|
85
|
+
/**
|
|
86
|
+
* Deactivates an item by ID.
|
|
87
|
+
*/
|
|
88
|
+
deactivateItem(id: string): TestClass | null;
|
|
89
|
+
/**
|
|
90
|
+
* Gets an item by ID using computed lookup.
|
|
91
|
+
*/
|
|
92
|
+
getItemById(id: string): TestClass | undefined;
|
|
93
|
+
/**
|
|
94
|
+
* Clears all items from the store.
|
|
95
|
+
*/
|
|
96
|
+
clearAll(): void;
|
|
97
|
+
/**
|
|
98
|
+
* Sets the loading state.
|
|
99
|
+
*/
|
|
100
|
+
setLoading(loading: boolean): void;
|
|
101
|
+
/**
|
|
102
|
+
* Sets an error message.
|
|
103
|
+
*/
|
|
104
|
+
setError(error: string | null): void;
|
|
105
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<TestService, never>;
|
|
106
|
+
static ɵprov: _angular_core.ɵɵInjectableDeclaration<TestService>;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* TestCardComponent - Standalone Ionic Card component demonstrating
|
|
111
|
+
* Angular 21 best practices: OnPush, Signal inputs/outputs/queries.
|
|
112
|
+
*
|
|
113
|
+
* @example
|
|
114
|
+
* ```html
|
|
115
|
+
* <maki-test-card
|
|
116
|
+
* [item]="myItem"
|
|
117
|
+
* [showActions]="true"
|
|
118
|
+
* (itemClick)="onItemClick($event)"
|
|
119
|
+
* (deleteClick)="onDelete($event)"
|
|
120
|
+
* />
|
|
121
|
+
* ```
|
|
122
|
+
*/
|
|
123
|
+
declare class TestCardComponent {
|
|
124
|
+
readonly item: _angular_core.InputSignal<TestInterface>;
|
|
125
|
+
readonly showActions: _angular_core.InputSignal<boolean>;
|
|
126
|
+
readonly itemClick: _angular_core.OutputEmitterRef<TestInterface>;
|
|
127
|
+
readonly editClick: _angular_core.OutputEmitterRef<TestInterface>;
|
|
128
|
+
readonly deleteClick: _angular_core.OutputEmitterRef<TestInterface>;
|
|
129
|
+
readonly cardElement: _angular_core.Signal<ElementRef<HTMLIonCardElement> | undefined>;
|
|
130
|
+
readonly formattedDate: _angular_core.Signal<string>;
|
|
131
|
+
constructor();
|
|
132
|
+
protected onCardClick(): void;
|
|
133
|
+
protected onEditClick(event: Event): void;
|
|
134
|
+
protected onDeleteClick(event: Event): void;
|
|
135
|
+
/**
|
|
136
|
+
* Public method to focus the card element programmatically.
|
|
137
|
+
*/
|
|
138
|
+
focus(): void;
|
|
139
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<TestCardComponent, never>;
|
|
140
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<TestCardComponent, "maki-test-card", never, { "item": { "alias": "item"; "required": true; "isSignal": true; }; "showActions": { "alias": "showActions"; "required": false; "isSignal": true; }; }, { "itemClick": "itemClick"; "editClick": "editClick"; "deleteClick": "deleteClick"; }, never, never, true, never>;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export { TestCardComponent, TestClass, TestService };
|
|
144
|
+
export type { TestInterface };
|