@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.
Files changed (49) hide show
  1. package/.gitlab-ci.yml +8 -2
  2. package/AGENTS.md +516 -0
  3. package/cypress/component/no-component-test.cy.ts +9 -0
  4. package/cypress/e2e/helpers/components/setupCacheInvalidation.cy.ts +512 -0
  5. package/cypress/e2e/helpers/components/setupCacheOnResourceReady.cy.ts +483 -0
  6. package/cypress/e2e/helpers/components/setupComponentSubscriptions.cy.ts +239 -0
  7. package/cypress/e2e/helpers/components/setupOnSaveReset.cy.ts +380 -0
  8. package/cypress/e2e/helpers/datas/checkValueInIntervalRecursive.cy.ts +563 -0
  9. package/cypress/e2e/helpers/datas/dataBuilder.cy.ts +508 -0
  10. package/cypress/e2e/helpers/datas/filterGenerator.cy.ts +285 -0
  11. package/cypress/e2e/helpers/datas/filterObjectByDateAfter.cy.ts +389 -0
  12. package/cypress/e2e/helpers/datas/filterObjectByDateInterval.cy.ts +613 -0
  13. package/cypress/e2e/helpers/datas/filterObjectById.cy.ts +276 -0
  14. package/cypress/e2e/helpers/datas/filterObjectByInterval.cy.ts +237 -0
  15. package/cypress/e2e/helpers/datas/filterObjectByNamedValue.cy.ts +299 -0
  16. package/cypress/e2e/helpers/datas/filterObjectByType.cy.ts +307 -0
  17. package/cypress/e2e/helpers/datas/filterObjectByValue.cy.ts +375 -0
  18. package/cypress/e2e/helpers/datas/sort.cy.ts +293 -0
  19. package/cypress/e2e/helpers/ui/formatDate.cy.ts +233 -0
  20. package/cypress/e2e/helpers/utils/requestNavigation.cy.ts +257 -0
  21. package/cypress/e2e/helpers/utils/uniq.cy.ts +160 -0
  22. package/cypress/support/e2e.ts +1 -0
  23. package/cypress.config.ts +2 -0
  24. package/dist/index.js +1102 -1002
  25. package/package.json +10 -10
  26. package/src/components/solid-boilerplate.ts +76 -0
  27. package/src/helpers/components/componentObjectHandler.ts +5 -7
  28. package/src/helpers/components/componentObjectsHandler.ts +8 -3
  29. package/src/helpers/components/orbitComponent.ts +87 -68
  30. package/src/helpers/components/setupCacheInvalidation.ts +50 -23
  31. package/src/helpers/components/setupCacheOnResourceReady.ts +42 -23
  32. package/src/helpers/components/setupComponentSubscriptions.ts +10 -9
  33. package/src/helpers/components/setupOnSaveReset.ts +27 -5
  34. package/src/helpers/datas/checkValueInIntervalRecursive.ts +66 -0
  35. package/src/helpers/datas/dataBuilder.ts +4 -4
  36. package/src/helpers/datas/filterGenerator.ts +13 -10
  37. package/src/helpers/datas/filterObjectByDateAfter.ts +3 -3
  38. package/src/helpers/datas/filterObjectByDateInterval.ts +44 -0
  39. package/src/helpers/datas/filterObjectById.ts +7 -6
  40. package/src/helpers/datas/filterObjectByInterval.ts +6 -110
  41. package/src/helpers/datas/filterObjectByNamedValue.ts +35 -33
  42. package/src/helpers/datas/filterObjectByType.ts +3 -3
  43. package/src/helpers/datas/filterObjectByValue.ts +17 -16
  44. package/src/helpers/datas/sort.ts +50 -23
  45. package/src/helpers/index.ts +2 -0
  46. package/src/helpers/ui/formatDate.ts +14 -1
  47. package/src/helpers/utils/requestNavigation.ts +5 -2
  48. package/src/helpers/utils/uniq.ts +1 -1
  49. 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.9.0
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
@@ -0,0 +1,9 @@
1
+ import { html } from "lit";
2
+
3
+ describe("no test", () => {
4
+ it("be ready for testing", () => {
5
+ cy.mount(html`<div></div>`);
6
+
7
+ cy.get("div").should("exist");
8
+ });
9
+ });