@startinblox/boilerplate 5.0.0 → 6.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.gitlab-ci.yml +1 -1
- package/AGENTS.md +518 -0
- package/README.md +16 -19
- package/biome.json +1 -1
- package/cypress/component/sample-object.cy.ts +816 -0
- package/cypress/component/solid-boilerplate.cy.ts +894 -5
- package/cypress/e2e/helpers/components/setupCacheInvalidation.cy.ts +512 -0
- package/cypress/e2e/helpers/components/setupCacheOnResourceReady.cy.ts +497 -0
- package/cypress/e2e/helpers/components/setupComponentSubscriptions.cy.ts +239 -0
- package/cypress/e2e/helpers/components/setupOnSaveReset.cy.ts +393 -0
- package/cypress/e2e/helpers/datas/checkValueInIntervalRecursive.cy.ts +563 -0
- package/cypress/e2e/helpers/datas/dataBuilder.cy.ts +508 -0
- package/cypress/e2e/helpers/datas/filterGenerator.cy.ts +285 -0
- package/cypress/e2e/helpers/datas/filterObjectByDateAfter.cy.ts +389 -0
- package/cypress/e2e/helpers/datas/filterObjectByDateInterval.cy.ts +613 -0
- package/cypress/e2e/helpers/datas/filterObjectById.cy.ts +276 -0
- package/cypress/e2e/helpers/datas/filterObjectByInterval.cy.ts +237 -0
- package/cypress/e2e/helpers/datas/filterObjectByNamedValue.cy.ts +299 -0
- package/cypress/e2e/helpers/datas/filterObjectByType.cy.ts +307 -0
- package/cypress/e2e/helpers/datas/filterObjectByValue.cy.ts +375 -0
- package/cypress/e2e/helpers/datas/sort.cy.ts +293 -0
- package/cypress/e2e/helpers/ui/formatDate.cy.ts +233 -0
- package/cypress/e2e/helpers/utils/requestNavigation.cy.ts +257 -0
- package/cypress/e2e/helpers/utils/uniq.cy.ts +160 -0
- package/cypress/support/e2e.ts +1 -0
- package/cypress.config.ts +2 -0
- package/dist/index.js +585 -550
- package/package.json +11 -9
- package/scripts/init.js +355 -0
- package/src/components/solid-boilerplate.ts +3 -6
- package/src/helpers/components/componentObjectHandler.ts +5 -7
- package/src/helpers/components/componentObjectsHandler.ts +8 -3
- package/src/helpers/components/orbitComponent.ts +87 -68
- package/src/helpers/components/setupCacheInvalidation.ts +50 -23
- package/src/helpers/components/setupCacheOnResourceReady.ts +42 -23
- package/src/helpers/components/setupComponentSubscriptions.ts +10 -9
- package/src/helpers/components/setupOnSaveReset.ts +27 -5
- package/src/helpers/datas/checkValueInIntervalRecursive.ts +66 -0
- package/src/helpers/datas/dataBuilder.ts +4 -4
- package/src/helpers/datas/filterGenerator.ts +13 -10
- package/src/helpers/datas/filterObjectByDateAfter.ts +3 -3
- package/src/helpers/datas/filterObjectByDateInterval.ts +44 -0
- package/src/helpers/datas/filterObjectById.ts +7 -6
- package/src/helpers/datas/filterObjectByInterval.ts +6 -110
- package/src/helpers/datas/filterObjectByNamedValue.ts +35 -33
- package/src/helpers/datas/filterObjectByType.ts +3 -3
- package/src/helpers/datas/filterObjectByValue.ts +17 -16
- package/src/helpers/datas/sort.ts +50 -23
- package/src/helpers/index.ts +6 -4
- package/src/helpers/ui/formatDate.ts +14 -1
- package/src/helpers/utils/requestNavigation.ts +5 -2
- package/src/helpers/utils/uniq.ts +1 -1
package/.gitlab-ci.yml
CHANGED
package/AGENTS.md
ADDED
|
@@ -0,0 +1,518 @@
|
|
|
1
|
+
# AGENTS.md
|
|
2
|
+
|
|
3
|
+
This file contains guidelines for agentic coding assistants working in this repository.
|
|
4
|
+
|
|
5
|
+
If the file `scripts/init.js` exists, ask the user to run `npm run init` first.
|
|
6
|
+
|
|
7
|
+
## Commands
|
|
8
|
+
|
|
9
|
+
### Build & Development
|
|
10
|
+
|
|
11
|
+
- `npm run build` - Build production bundle (includes lit-localize build)
|
|
12
|
+
- `npm run watch` - Development server with hot reload
|
|
13
|
+
- `npm run serve` - Preview production build locally
|
|
14
|
+
|
|
15
|
+
### Testing (Cypress)
|
|
16
|
+
|
|
17
|
+
- `npm run cy:run` - Run all Cypress component tests
|
|
18
|
+
- `npm run cy:open` - Open Cypress interactive test runner
|
|
19
|
+
- **Single test**: Run specific test by filtering with `--spec` (e.g., `npm run cy:run -- --spec "cypress/component/solid-boilerplate.cy.ts"`)
|
|
20
|
+
|
|
21
|
+
### Linting & Formatting
|
|
22
|
+
|
|
23
|
+
- `npx biome check .` - Check code for issues
|
|
24
|
+
- `npx biome check . --write` - Auto-fix linting issues and format code
|
|
25
|
+
|
|
26
|
+
### Storybook
|
|
27
|
+
|
|
28
|
+
- `npm run storybook` - Start Storybook dev server (port 6006)
|
|
29
|
+
- `npm run build-storybook` - Build static Storybook
|
|
30
|
+
|
|
31
|
+
### Localization
|
|
32
|
+
|
|
33
|
+
- `npm run locale:extract` - Extract translatable strings to XLIFF files
|
|
34
|
+
- `npm run locale:build` - Build localization for production
|
|
35
|
+
|
|
36
|
+
**Important**: Always run `npm run locale:build` before building for production. The build commands (`npm run build`, `npm run build-storybook`) also run this automatically.
|
|
37
|
+
|
|
38
|
+
## Code Style Guidelines
|
|
39
|
+
|
|
40
|
+
### Formatting (Biome.js)
|
|
41
|
+
|
|
42
|
+
- Use Biome.js for all formatting and linting
|
|
43
|
+
- 2-space indentation (no tabs)
|
|
44
|
+
- Double quotes for strings
|
|
45
|
+
- Semicolons required at end of statements
|
|
46
|
+
- Imports are auto-organized (run `npx biome check . --write` to organize)
|
|
47
|
+
|
|
48
|
+
### TypeScript
|
|
49
|
+
|
|
50
|
+
- Strict mode enabled in tsconfig.json
|
|
51
|
+
- Use type annotations explicitly (no inferrable types rule)
|
|
52
|
+
- `any` type is allowed but avoid when possible
|
|
53
|
+
- Define types in `src/component.d.ts` for shared types
|
|
54
|
+
|
|
55
|
+
### Component Conventions
|
|
56
|
+
|
|
57
|
+
#### Naming
|
|
58
|
+
|
|
59
|
+
- Web component element names: kebab-case (e.g., `solid-boilerplate`)
|
|
60
|
+
- Class names: PascalCase (e.g., `SolidBoilerplate`)
|
|
61
|
+
- Properties/attributes: kebab-case in HTML (e.g., `data-src`), camelCase in JS (e.g., `dataSrc`)
|
|
62
|
+
- Private methods: prefix with `_` (e.g., `_afterAttach`, `_navigate`)
|
|
63
|
+
|
|
64
|
+
#### Lit Decorators
|
|
65
|
+
|
|
66
|
+
- `@customElement("element-name")` - Define web component
|
|
67
|
+
- `@property({ attribute: "data-src", reflect: true })` - Public attributes that reflect to DOM
|
|
68
|
+
- `@property({ attribute: false, type: Object })` - For object properties (no attribute reflection)
|
|
69
|
+
- `@state()` - Private reactive state
|
|
70
|
+
- `@localized()` - For components using i18n (from @lit/localize)
|
|
71
|
+
|
|
72
|
+
Properties can have default values:
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
@property({ attribute: "header", type: String })
|
|
76
|
+
header?: string = "Default Header";
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
#### Event Handlers
|
|
80
|
+
|
|
81
|
+
All event handlers should prevent default behavior:
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
_handleEvent(e: Event) {
|
|
85
|
+
e.preventDefault();
|
|
86
|
+
// handler logic
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
#### Component Base Classes
|
|
91
|
+
|
|
92
|
+
This boilerplate provides three base component classes with different capabilities:
|
|
93
|
+
|
|
94
|
+
##### ComponentObjectHandler
|
|
95
|
+
|
|
96
|
+
Use for components that display **a single resource**. Simplest base class with minimal overhead.
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
import { ComponentObjectHandler } from "@helpers";
|
|
100
|
+
|
|
101
|
+
@customElement("my-card")
|
|
102
|
+
export class MyCard extends ComponentObjectHandler {
|
|
103
|
+
@property({ attribute: false, type: Object })
|
|
104
|
+
object: Resource = { "@id": "" };
|
|
105
|
+
|
|
106
|
+
render() {
|
|
107
|
+
return html`<div>${this.object.name}</div>`;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
**Features:**
|
|
113
|
+
|
|
114
|
+
- Single `object` property
|
|
115
|
+
- `isType(type)` method to check object type
|
|
116
|
+
|
|
117
|
+
**When to use:**
|
|
118
|
+
|
|
119
|
+
- Displaying a single item/detail view
|
|
120
|
+
- Is used within another OrbitComponent
|
|
121
|
+
- No data fetching required (object is passed in as property)
|
|
122
|
+
|
|
123
|
+
**Example components:** `<sample-object>`, `<user-card>`, `<some-item>`
|
|
124
|
+
|
|
125
|
+
##### ComponentObjectsHandler
|
|
126
|
+
|
|
127
|
+
Use for components that display **an array/collection of resources**.
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
import { ComponentObjectsHandler } from "@helpers";
|
|
131
|
+
|
|
132
|
+
@customElement("my-list")
|
|
133
|
+
export class MyList extends ComponentObjectsHandler {
|
|
134
|
+
@property({ attribute: false })
|
|
135
|
+
objects?: Resource[] = [];
|
|
136
|
+
|
|
137
|
+
render() {
|
|
138
|
+
return html`<ul>
|
|
139
|
+
${this.objects?.map((obj) => html`<li>${obj.name}</li>`)}
|
|
140
|
+
</ul>`;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
**Features:**
|
|
146
|
+
|
|
147
|
+
- `objects` property (array of resources)
|
|
148
|
+
- `hasType(type)` method to check if any object has a specific type
|
|
149
|
+
|
|
150
|
+
**When to use:**
|
|
151
|
+
|
|
152
|
+
- Displaying a list/grid of items
|
|
153
|
+
- Is used within another OrbitComponent
|
|
154
|
+
- No data fetching required (objects are passed in as property)
|
|
155
|
+
|
|
156
|
+
**Example components:** `<sample-objects>`, `<card-grid>`, `<item-list>`
|
|
157
|
+
|
|
158
|
+
##### OrbitComponent
|
|
159
|
+
|
|
160
|
+
Use for components that need **Orbit integration and data fetching**. Extends `ComponentObjectsHandler` with full Orbit features.
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
import * as utils from "@helpers";
|
|
164
|
+
|
|
165
|
+
@customElement("my-component")
|
|
166
|
+
export class MyComponent extends utils.OrbitComponent {
|
|
167
|
+
// Required: define expected properties
|
|
168
|
+
cherryPickedProperties: PropertiesPicker[] = [
|
|
169
|
+
{ key: "name", value: "name" },
|
|
170
|
+
];
|
|
171
|
+
|
|
172
|
+
// Optional: setup cache invalidation
|
|
173
|
+
async _afterAttach() {
|
|
174
|
+
super._afterAttach();
|
|
175
|
+
utils.setupCacheInvalidation(this, {
|
|
176
|
+
keywords: ["some-keyword"],
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
// Get references to other Orbit components
|
|
180
|
+
this.someComponent = this.orbit?.getComponent("some-component");
|
|
181
|
+
// Can then be accessed with this.someComponent.instance
|
|
182
|
+
|
|
183
|
+
return Promise.resolve();
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Optional: transform responses, supports nested cherry-picking
|
|
187
|
+
async _responseAdaptator(response: Resource): Promise<Resource> {
|
|
188
|
+
if (response?._originalResource?.hasType("some:Type")) {
|
|
189
|
+
response.nestedObjects = await Promise.all(
|
|
190
|
+
(await response._originalResource["ldp:contains"]).map((item) => {
|
|
191
|
+
return this._getProxyValue(item["@id"], false, [
|
|
192
|
+
{ key: "name", value: "name" },
|
|
193
|
+
{ key: "updated_at", value: "updated_at", cast: formatDate },
|
|
194
|
+
{ key: "items", value: "items", expand: true, cast: (arr) => sort(arr, "name") },
|
|
195
|
+
]);
|
|
196
|
+
}),
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
return Promise.resolve(response);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Use Lit Task for async data fetching with error handling
|
|
203
|
+
_getResource = new Task(this, {
|
|
204
|
+
task: async ([dataSrc, objSrc]) => {
|
|
205
|
+
// Check gatekeeper conditions
|
|
206
|
+
if (
|
|
207
|
+
!dataSrc ||
|
|
208
|
+
!this.orbit ||
|
|
209
|
+
(!this.noRouter &&
|
|
210
|
+
this.route &&
|
|
211
|
+
this.currentRoute &&
|
|
212
|
+
!this.route.startsWith(this.currentRoute))
|
|
213
|
+
)
|
|
214
|
+
return;
|
|
215
|
+
|
|
216
|
+
// Wait for dependent components
|
|
217
|
+
if (!this.someComponent.instance?.ready) {
|
|
218
|
+
await new Promise((resolve) => {
|
|
219
|
+
this.someComponent.instance?.addEventListener("component-ready", () => resolve(true));
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Fetch and cache data
|
|
224
|
+
if (!this.hasCachedDatas || this.oldDataSrc !== dataSrc) {
|
|
225
|
+
if (!dataSrc) return;
|
|
226
|
+
// _getProxyValue methods takes an URL to a JSONLD or a Resource and handle conversion by itself
|
|
227
|
+
this.objects = (await this._getProxyValue(dataSrc)) as Resource[];
|
|
228
|
+
this.hasCachedDatas = true;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (this.oldDataSrc !== dataSrc) {
|
|
232
|
+
this.oldDataSrc = dataSrc;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Find specific object if objSrc provided
|
|
236
|
+
if (objSrc) {
|
|
237
|
+
this.object = this.objects.find((obj: Resource) => obj["@id"] === objSrc);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return sort(this.objects, "name", "asc");
|
|
241
|
+
},
|
|
242
|
+
args: () => [
|
|
243
|
+
this.defaultDataSrc,
|
|
244
|
+
this.dataSrc,
|
|
245
|
+
this.caching,
|
|
246
|
+
this.currentRoute,
|
|
247
|
+
],
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
// Event handlers always prevent default
|
|
251
|
+
_handleEvent(e: Event) {
|
|
252
|
+
e.preventDefault();
|
|
253
|
+
// handler logic
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Render with gatekeeper pattern
|
|
257
|
+
render() {
|
|
258
|
+
return (
|
|
259
|
+
this.gatekeeper() ||
|
|
260
|
+
this._getResource.render({
|
|
261
|
+
pending: () => html`<solid-loader></solid-loader>`,
|
|
262
|
+
error: (e) => {
|
|
263
|
+
console.warn("[my-component] Task error:", e);
|
|
264
|
+
return nothing;
|
|
265
|
+
},
|
|
266
|
+
complete: (datas) => html`
|
|
267
|
+
<div>
|
|
268
|
+
${this.object
|
|
269
|
+
? html`<detail-component .object=${this.object}></detail-component>`
|
|
270
|
+
: datas
|
|
271
|
+
? html`<list-component .objects=${datas}></list-component>`
|
|
272
|
+
: html`No data available`}
|
|
273
|
+
</div>
|
|
274
|
+
`,
|
|
275
|
+
})
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
**Features:**
|
|
282
|
+
|
|
283
|
+
- Inherits from `ComponentObjectsHandler` (has `objects` and `object` properties)
|
|
284
|
+
- `cherryPickedProperties` for declarative data fetching
|
|
285
|
+
- `_getProxyValue(url)` method to fetch and transform resources
|
|
286
|
+
- `_responseAdaptator(response)` for custom response transformations
|
|
287
|
+
- `gatekeeper()` for route-aware rendering optimization
|
|
288
|
+
- `orbit` property with Orbit integration
|
|
289
|
+
- `route`, `defaultDataSrc`, `dataSrc` properties for routing
|
|
290
|
+
- Cache invalidation via `setupCacheInvalidation()`
|
|
291
|
+
- Both `hasType()` and `isType()` methods
|
|
292
|
+
|
|
293
|
+
**When to use:**
|
|
294
|
+
|
|
295
|
+
- Need to fetch data from a URL (`data-src`)
|
|
296
|
+
- Need caching and automatic updates
|
|
297
|
+
- Need routing awareness
|
|
298
|
+
- Need to interact with other Orbit components
|
|
299
|
+
- Both single object and list display scenarios
|
|
300
|
+
|
|
301
|
+
**Example components:** `<solid-xyz>`, `<project-list>`, `<user-profile>` (when fetching from URL)
|
|
302
|
+
|
|
303
|
+
**Decision Tree:**
|
|
304
|
+
|
|
305
|
+
```
|
|
306
|
+
Is called from another OrbitComponent and does not fetch new data?
|
|
307
|
+
├─ Yes ──> Display single object?
|
|
308
|
+
│ ├─ Yes ──> ComponentObjectHandler
|
|
309
|
+
│ └─ No ──> ComponentObjectsHandler (list)
|
|
310
|
+
│
|
|
311
|
+
└─ No ──> OrbitComponent
|
|
312
|
+
(named solid-xyz, with all Orbit features)
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
**Typical Pattern:**
|
|
316
|
+
|
|
317
|
+
```html
|
|
318
|
+
<!-- OrbitComponent fetches data and passes to handlers -->
|
|
319
|
+
<solid-awesome-component data-src="https://localhost:8000/datas">
|
|
320
|
+
<!-- Renders either detail or list view -->
|
|
321
|
+
</solid-awesome-component>
|
|
322
|
+
|
|
323
|
+
<!-- Inside awesome-component render -->
|
|
324
|
+
<my-list .objects=${datas} @clicked=${this._openModal}></my-list>
|
|
325
|
+
<my-detail .object=${this.object} @close=${this._closeModal}></my-detail>
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
#### Style Import
|
|
329
|
+
|
|
330
|
+
Import SCSS files inline using `?inline` query parameter:
|
|
331
|
+
|
|
332
|
+
```typescript
|
|
333
|
+
import ComponentStyle from "@styles/component.scss?inline";
|
|
334
|
+
|
|
335
|
+
static styles = css`
|
|
336
|
+
${unsafeCSS(ComponentStyle)}
|
|
337
|
+
`;
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
Alternatively, use inline CSS directly:
|
|
341
|
+
|
|
342
|
+
```typescript
|
|
343
|
+
static styles = css`
|
|
344
|
+
.modal {
|
|
345
|
+
position: fixed;
|
|
346
|
+
top: 0;
|
|
347
|
+
left: 0;
|
|
348
|
+
right: 0;
|
|
349
|
+
bottom: 0;
|
|
350
|
+
background-color: rgba(0, 2, 49, 0.2);
|
|
351
|
+
z-index: 9999;
|
|
352
|
+
display: flex;
|
|
353
|
+
justify-content: center;
|
|
354
|
+
align-items: center;
|
|
355
|
+
}
|
|
356
|
+
`;
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
### Helper Functions
|
|
360
|
+
|
|
361
|
+
- `utils.sort(arr, key, order)` - Sort arrays (supports nested keys, numbers, strings, dates)
|
|
362
|
+
- `utils.formatDate(date)` - Format dates using Intl
|
|
363
|
+
- `utils.filterGenerator` - Create filter functions
|
|
364
|
+
- `utils.uniq()` - Generate unique IDs
|
|
365
|
+
- `utils.requestNavigation(route, resource)` - Navigation wrapper
|
|
366
|
+
- `utils.setupCacheInvalidation(component, { keywords })` - Setup cache invalidation on save events
|
|
367
|
+
|
|
368
|
+
### Navigation Patterns
|
|
369
|
+
|
|
370
|
+
Use `requestNavigation` for programmatic navigation:
|
|
371
|
+
|
|
372
|
+
```typescript
|
|
373
|
+
// Navigate to route with a resource ID
|
|
374
|
+
requestNavigation(this.route, resource["@id"]);
|
|
375
|
+
|
|
376
|
+
// Navigate without resource
|
|
377
|
+
requestNavigation(this.orbit.getComponent("another-component")?.route);
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
For route-based navigation with rdf-type matching:
|
|
381
|
+
|
|
382
|
+
```typescript
|
|
383
|
+
const rdfType = e.detail["@type"]?.at(-1) ?? e.detail["@type"];
|
|
384
|
+
if (rdfType) {
|
|
385
|
+
const compatibleComponents = window.orbit?.components?.filter(
|
|
386
|
+
(c) => c?.routeAttributes?.["rdf-type"] === rdfType,
|
|
387
|
+
);
|
|
388
|
+
if (compatibleComponents?.[0]?.route) {
|
|
389
|
+
requestNavigation(compatibleComponents[0]?.route, e.detail["@id"]);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
### Conditional Attributes
|
|
395
|
+
|
|
396
|
+
Use `nothing` for conditional attributes (e.g., to avoid `disabled="false"`):
|
|
397
|
+
|
|
398
|
+
```typescript
|
|
399
|
+
<some-button
|
|
400
|
+
type="primary"
|
|
401
|
+
disabled=${!this.someOtherComponent?.route || nothing}
|
|
402
|
+
label=${msg("Create new")}
|
|
403
|
+
></some-button>
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
This pattern prevents invalid HTML like `disabled="false"`.
|
|
407
|
+
|
|
408
|
+
### i18n
|
|
409
|
+
|
|
410
|
+
Use `msg()` from `@lit/localize` for static strings:
|
|
411
|
+
|
|
412
|
+
```typescript
|
|
413
|
+
import { msg, str } from "@lit/localize";
|
|
414
|
+
|
|
415
|
+
@localized()
|
|
416
|
+
export class MyComponent extends LitElement {
|
|
417
|
+
render() {
|
|
418
|
+
return html`<button label=${msg("Click me")}></button>`;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
Use `str()` for strings with embedded variables:
|
|
424
|
+
|
|
425
|
+
```typescript
|
|
426
|
+
return html`${msg(str`Hello ${this.name}`)}`;
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
Add `@localized()` decorator when using `msg()` or `str()` to ensure reactivity.
|
|
430
|
+
|
|
431
|
+
### Cherry-Picked Properties Pattern
|
|
432
|
+
|
|
433
|
+
Define expected resource properties:
|
|
434
|
+
|
|
435
|
+
```typescript
|
|
436
|
+
cherryPickedProperties: PropertiesPicker[] = [
|
|
437
|
+
{ key: "name", value: "name" }, // Simple property
|
|
438
|
+
{ key: "project", value: "projectId", cast: (r) => r["@id"] }, // Cast value
|
|
439
|
+
{ key: "project", value: "project", expand: true }, // Expand nested object
|
|
440
|
+
];
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
Advanced pattern with helper functions in cast:
|
|
444
|
+
|
|
445
|
+
```typescript
|
|
446
|
+
cherryPickedProperties: PropertiesPicker[] = [
|
|
447
|
+
{ key: "created_at", value: "created_at", cast: formatDate },
|
|
448
|
+
{ key: "updated_at", value: "updated_at", cast: formatDate },
|
|
449
|
+
{ key: "name", value: "name" },
|
|
450
|
+
{
|
|
451
|
+
key: "categories",
|
|
452
|
+
value: "categories",
|
|
453
|
+
expand: true,
|
|
454
|
+
cast: (c: Resource[]) => sort(c, "name"),
|
|
455
|
+
},
|
|
456
|
+
];
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
### Error Handling
|
|
460
|
+
|
|
461
|
+
- Try-catch in async methods, log errors in dev mode only:
|
|
462
|
+
|
|
463
|
+
```typescript
|
|
464
|
+
try {
|
|
465
|
+
// code
|
|
466
|
+
} catch (e) {
|
|
467
|
+
if (import.meta.env.DEV) console.error(e);
|
|
468
|
+
}
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
### File Structure
|
|
472
|
+
|
|
473
|
+
- `src/components/` - Web component implementations
|
|
474
|
+
- `src/helpers/` - Utility functions and base classes
|
|
475
|
+
- `src/styles/` - SCSS stylesheets
|
|
476
|
+
- `src/mocks/` - Mock data for testing
|
|
477
|
+
- `cypress/component/` - Component tests (.cy.ts files)
|
|
478
|
+
- `stories/` - Storybook stories (.stories.ts files)
|
|
479
|
+
|
|
480
|
+
### Orbit Integration
|
|
481
|
+
|
|
482
|
+
- Import `@src/initializer` before `@customElement` decorator if component will be used in Orbit
|
|
483
|
+
- Use `gatekeeper()` method to prevent unnecessary renders
|
|
484
|
+
- Check `this.orbit` exists before using Orbit features
|
|
485
|
+
- Get references to other Orbit components via `this.orbit?.getComponent("component-name")`
|
|
486
|
+
- Wait for component readiness using `addEventListener` on component's ready event:
|
|
487
|
+
|
|
488
|
+
```typescript
|
|
489
|
+
if (!this.someComponent.ready) {
|
|
490
|
+
await new Promise((resolve) => {
|
|
491
|
+
this.someComponent.addEventListener("ready", () => resolve(true));
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
### Testing
|
|
497
|
+
|
|
498
|
+
- Tests use Cypress with `cypress-ct-lit` framework
|
|
499
|
+
- Import `/src/index.ts` in `cypress/support/component.ts`
|
|
500
|
+
- Use `cy.mount(html\`<component-name></component-name>\`)` pattern
|
|
501
|
+
- Test files named `*.cy.ts` in `cypress/component/`
|
|
502
|
+
|
|
503
|
+
### Commit Messages
|
|
504
|
+
|
|
505
|
+
Follow conventional commits pattern seen in repo:
|
|
506
|
+
|
|
507
|
+
- `major:` - Breaking changes
|
|
508
|
+
- `minor:` - New features, non-breaking changes
|
|
509
|
+
- `fix:` - Bug fixes
|
|
510
|
+
- `chore:` - Maintenance tasks
|
|
511
|
+
|
|
512
|
+
### Important Notes
|
|
513
|
+
|
|
514
|
+
- This is a **library**, not an app - no default index.html
|
|
515
|
+
- Use Storybook for development, not a dev server with HTML files
|
|
516
|
+
- Always run linting (`npx biome check . --write`) before committing
|
|
517
|
+
- Build fails without locale build - ensure localization is extracted and built for production
|
|
518
|
+
- Avoid blind copy-paste from boilerplate - understand each file's purpose
|
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# Startin'blox components repository boilerplate
|
|
2
2
|
|
|
3
|
+
See [Creating a New Component Repository](#creating-a-new-component-repository) to quick-start a new component repository from this boilerplate.
|
|
4
|
+
|
|
3
5
|
## Introduction
|
|
4
6
|
|
|
5
7
|
This repository provides a boilerplate for developing Startin'blox web components. It streamlines the creation and deployment of web components, whether used standalone or integrated within the Startin'blox ecosystem (Lit, Startin'blox Core, Store, Router, and Orbit). The primary objective is to enable developers to concentrate on web component logic by abstracting common complexities and providing essential functionalities out-of-the-box.
|
|
@@ -75,38 +77,29 @@ For production usage, components can be served via a CDN like JSDelivr or self-h
|
|
|
75
77
|
|
|
76
78
|
To initialize a new component repository from this boilerplate:
|
|
77
79
|
|
|
78
|
-
1. **
|
|
80
|
+
1. **Create a new repository:**
|
|
81
|
+
Create a new repository on [git.startinblox.com/components](https://git.startinblox.com/components/).
|
|
82
|
+
|
|
83
|
+
2. **Clone this repository:**
|
|
79
84
|
|
|
80
85
|
```bash
|
|
81
86
|
git clone https://git.startinblox.com/components/solid-boilerplate.git your-new-component-name
|
|
82
87
|
cd your-new-component-name
|
|
83
88
|
```
|
|
84
89
|
|
|
85
|
-
|
|
90
|
+
3. **Execute the initialization script:**
|
|
86
91
|
|
|
87
92
|
```bash
|
|
88
|
-
|
|
93
|
+
npm install
|
|
94
|
+
npm run init
|
|
89
95
|
```
|
|
90
96
|
|
|
91
|
-
|
|
92
|
-
Modify the `name`, `description`, and `repository.url` fields in [`package.json`](package.json) to reflect your new component.
|
|
93
|
-
|
|
94
|
-
4. **Initial commit:**
|
|
95
|
-
Commit the changes with a `major: Initial commit` message.
|
|
96
|
-
|
|
97
|
-
5. **Create a new repository:**
|
|
98
|
-
Create a new repository on [git.startinblox.com/components](https://git.startinblox.com/components/).
|
|
97
|
+
4. **Follow pose-initialization script instructions**
|
|
99
98
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
```bash
|
|
103
|
-
git remote add origin https://git.startinblox.com/components/your-component-name.git
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
7. **Configure Gitlab settings:**
|
|
99
|
+
5. **Configure Gitlab settings:**
|
|
107
100
|
Set up branch protection rules, enforce FF merge only, and configure cloning instead of fetching in your new repository's Gitlab settings.
|
|
108
101
|
|
|
109
|
-
|
|
102
|
+
6. **Push to `origin master`:**
|
|
110
103
|
|
|
111
104
|
```bash
|
|
112
105
|
git push -u origin master
|
|
@@ -188,6 +181,10 @@ async _afterAttach() {
|
|
|
188
181
|
* `_responseAdaptator(res)`: An asynchronous method called before `_getProxyValue` returns its result, allowing for response adaptation (e.g., prefixing a `name` property based on `type`).
|
|
189
182
|
* `gatekeeper`: A pre-written method for components intended to operate exclusively within Orbit, simplifying gatekeeping logic.
|
|
190
183
|
|
|
184
|
+
**Events:**
|
|
185
|
+
|
|
186
|
+
* `component-ready`: Dispatched after the component is attached to the DOM and fully initialized. The event detail includes the component configuration.
|
|
187
|
+
|
|
191
188
|
`OrbitComponent` inherits methods from `ObjectsHandler`.
|
|
192
189
|
|
|
193
190
|
### `ObjectsHandler` methods
|