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