@startinblox/boilerplate 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 (54) hide show
  1. package/.gitlab-ci.yml +57 -0
  2. package/.storybook/main.ts +15 -0
  3. package/.storybook/preview-head.html +8 -0
  4. package/.storybook/preview.ts +22 -0
  5. package/LICENSE +21 -0
  6. package/README.md +85 -0
  7. package/biome.json +39 -0
  8. package/cypress/component/solid-boilerplate.cy.ts +9 -0
  9. package/cypress/cypress.d.ts +1 -0
  10. package/cypress/support/component-index.html +12 -0
  11. package/cypress/support/component.ts +17 -0
  12. package/cypress.config.ts +11 -0
  13. package/dist/boilerplate.css +1 -0
  14. package/dist/index.js +1213 -0
  15. package/lit-localize.json +15 -0
  16. package/locales/en.xlf +13 -0
  17. package/package.json +92 -0
  18. package/postcss.config.js +8 -0
  19. package/src/component.d.ts +161 -0
  20. package/src/components/solid-boilerplate.ts +79 -0
  21. package/src/components/ui/sample-object.ts +37 -0
  22. package/src/components/ui/sample-objects.ts +40 -0
  23. package/src/context.json +1 -0
  24. package/src/helpers/components/componentObjectHandler.ts +100 -0
  25. package/src/helpers/components/componentObjectsHandler.ts +44 -0
  26. package/src/helpers/components/orbitComponent.ts +241 -0
  27. package/src/helpers/components/setupCacheInvalidation.ts +37 -0
  28. package/src/helpers/components/setupCacheOnResourceReady.ts +32 -0
  29. package/src/helpers/components/setupComponentSubscriptions.ts +73 -0
  30. package/src/helpers/components/setupOnSaveReset.ts +20 -0
  31. package/src/helpers/datas/dataBuilder.ts +43 -0
  32. package/src/helpers/datas/filterGenerator.ts +29 -0
  33. package/src/helpers/datas/filterObjectByDateAfter.ts +80 -0
  34. package/src/helpers/datas/filterObjectById.ts +54 -0
  35. package/src/helpers/datas/filterObjectByInterval.ts +133 -0
  36. package/src/helpers/datas/filterObjectByNamedValue.ts +103 -0
  37. package/src/helpers/datas/filterObjectByType.ts +30 -0
  38. package/src/helpers/datas/filterObjectByValue.ts +81 -0
  39. package/src/helpers/datas/sort.ts +40 -0
  40. package/src/helpers/i18n/configureLocalization.ts +17 -0
  41. package/src/helpers/index.ts +41 -0
  42. package/src/helpers/ui/formatDate.ts +18 -0
  43. package/src/helpers/ui/lipsum.ts +12 -0
  44. package/src/helpers/utils/requestNavigation.ts +12 -0
  45. package/src/helpers/utils/uniq.ts +6 -0
  46. package/src/index.ts +7 -0
  47. package/src/initializer.ts +11 -0
  48. package/src/mocks/orbit.mock.ts +33 -0
  49. package/src/mocks/user.mock.ts +67 -0
  50. package/src/styles/component-sample.scss +4 -0
  51. package/src/styles/index.scss +16 -0
  52. package/stories/sample-objects.stories.ts +47 -0
  53. package/tsconfig.json +36 -0
  54. package/vite.config.ts +44 -0
@@ -0,0 +1,15 @@
1
+ {
2
+ "$schema": "https://raw.githubusercontent.com/lit/lit/main/packages/localize-tools/config.schema.json",
3
+ "sourceLocale": "en",
4
+ "targetLocales": ["en"],
5
+ "tsConfig": "./tsconfig.json",
6
+ "output": {
7
+ "mode": "runtime",
8
+ "outputDir": "./src/generated/locales",
9
+ "localeCodesModule": "./src/generated/locale-codes.ts"
10
+ },
11
+ "interchange": {
12
+ "format": "xliff",
13
+ "xliffDir": "./locales/"
14
+ }
15
+ }
package/locales/en.xlf ADDED
@@ -0,0 +1,13 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
3
+ <file target-language="en" source-language="en" original="lit-localize-inputs" datatype="plaintext">
4
+ <body>
5
+ <trans-unit id="se975788b1544119d">
6
+ <source>Here are all of our objects:</source>
7
+ </trans-unit>
8
+ <trans-unit id="s9cb0b52fd65ea225">
9
+ <source>Sample object named: <x id="0" equiv-text="${this.object.name}"/></source>
10
+ </trans-unit>
11
+ </body>
12
+ </file>
13
+ </xliff>
package/package.json ADDED
@@ -0,0 +1,92 @@
1
+ {
2
+ "name": "@startinblox/boilerplate",
3
+ "version": "3.0.0",
4
+ "description": "Startin'blox Boilerplate",
5
+ "main": "dist/index.js",
6
+ "type": "module",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://git.startinblox.com/components/solid-boilerplate.git"
10
+ },
11
+ "author": "startinblox",
12
+ "license": "MIT",
13
+ "release": {
14
+ "branches": [
15
+ "master",
16
+ {
17
+ "name": "beta",
18
+ "prerelease": true
19
+ },
20
+ {
21
+ "name": "alpha",
22
+ "prerelease": true
23
+ }
24
+ ],
25
+ "plugins": [
26
+ [
27
+ "@semantic-release/commit-analyzer",
28
+ {
29
+ "preset": "angular",
30
+ "releaseRules": [
31
+ {
32
+ "type": "major",
33
+ "release": "major"
34
+ },
35
+ {
36
+ "type": "minor",
37
+ "release": "minor"
38
+ },
39
+ {
40
+ "type": "*",
41
+ "release": "patch"
42
+ }
43
+ ]
44
+ }
45
+ ],
46
+ "@semantic-release/release-notes-generator",
47
+ "@semantic-release/gitlab",
48
+ "@semantic-release/npm"
49
+ ]
50
+ },
51
+ "publishConfig": {
52
+ "access": "public"
53
+ },
54
+ "scripts": {
55
+ "watch": "lit-localize build && vite",
56
+ "build": "lit-localize build && vite build",
57
+ "serve": "vite preview",
58
+ "cy:open": "cypress open --component",
59
+ "cy:run": "lit-localize build && cypress run --component",
60
+ "storybook": "lit-localize build && storybook dev -p 6006",
61
+ "build-storybook": "lit-localize build && storybook build",
62
+ "locale:extract": "lit-localize extract",
63
+ "locale:build": "lit-localize build"
64
+ },
65
+ "browserslist": [
66
+ "last 2 Chrome versions"
67
+ ],
68
+ "dependencies": {
69
+ "@lit/localize": "^0.12.2",
70
+ "@lit/task": "^1.0.2",
71
+ "autoprefixer": "^10.4.21",
72
+ "cssnano": "^7.0.6",
73
+ "lit": "^3.2.1",
74
+ "postcss": "^8.5.3",
75
+ "postcss-preset-env": "^9.6.0",
76
+ "postcss-scss": "^4.0.9",
77
+ "sass": "^1.86.0",
78
+ "unplugin-icons": "^0.19.3",
79
+ "vite": "^6.2.2"
80
+ },
81
+ "devDependencies": {
82
+ "@lit/localize-tools": "^0.8.0",
83
+ "@storybook/addon-essentials": "^8.6.7",
84
+ "@storybook/blocks": "^8.5.8",
85
+ "@storybook/web-components": "^8.5.8",
86
+ "@storybook/web-components-vite": "^8.6.7",
87
+ "cypress": "^14.2.0",
88
+ "cypress-ct-lit": "^1.0.0",
89
+ "lorem-ipsum": "^2.0.8",
90
+ "storybook": "^8.6.7"
91
+ }
92
+ }
@@ -0,0 +1,8 @@
1
+ import autoprefixer from "autoprefixer";
2
+ import cssnano from "cssnano";
3
+ import postcssPresetEnv from "postcss-preset-env";
4
+
5
+ export default {
6
+ syntax: "postcss-scss",
7
+ plugins: [autoprefixer, cssnano, postcssPresetEnv],
8
+ };
@@ -0,0 +1,161 @@
1
+ /// <reference types="vite/client" />
2
+ /// <reference types="unplugin-icons/types/raw" />
3
+ /// <reference types="unplugin-icons/types/web-components" />
4
+
5
+ import type { TemplateResult } from "lit";
6
+
7
+ interface PWAIcons {
8
+ src: string;
9
+ sizes: string;
10
+ type: string;
11
+ purpose: string;
12
+ }
13
+
14
+ interface PWA {
15
+ dir: string;
16
+ icons: PWAIcons[];
17
+ start_url: string;
18
+ display: string;
19
+ orientation: string;
20
+ background_color: string;
21
+ theme_color: string;
22
+ }
23
+
24
+ interface I18n {
25
+ lang: string;
26
+ force: boolean;
27
+ }
28
+
29
+ interface ComponentParameters {
30
+ replacement?: string;
31
+ bindResources?: string;
32
+ bindUser?: string;
33
+ dataSrc?: string;
34
+ nestedField?: string;
35
+ authority?: string;
36
+ authorityName?: string;
37
+ noRender?: string;
38
+ }
39
+
40
+ interface OrbitComponent {
41
+ type?: string;
42
+ parameters: ComponentParameters;
43
+ route: string | false;
44
+ integration?: string[];
45
+ uniq?: string;
46
+ experimental?: string[];
47
+ routeAttributes?: {
48
+ "rdf-type": string;
49
+ "use-id": string;
50
+ };
51
+ defaultRoute?: boolean;
52
+ extensions?: OrbitComponent[];
53
+ attributes?: Record<string, any>;
54
+ instance?: Element;
55
+ }
56
+
57
+ interface OrbitClient {
58
+ name: string;
59
+ logo: string;
60
+ server?: string;
61
+ css?: string[] | string | false;
62
+ description?: string;
63
+ favicon?: string;
64
+ defaultAvatar?: string;
65
+ i18n?: I18n;
66
+ pwa?: PWA;
67
+ }
68
+
69
+ interface NpmPackage {
70
+ package: string;
71
+ version: string;
72
+ path: string;
73
+ }
74
+
75
+ export interface Orbit {
76
+ client: OrbitClient;
77
+ components: OrbitComponent[];
78
+ npm?: NpmPackage[];
79
+ }
80
+
81
+ export interface LiveOrbit extends Orbit {
82
+ componentSet: Set<string>;
83
+ federations: Record<string, any>;
84
+ getDefaultRoute: () => string;
85
+ getComponent: (type: string) => OrbitComponent | undefined;
86
+ getComponentFromRoute: (route: string) => OrbitComponent | undefined;
87
+ getRoute: (
88
+ type: string,
89
+ returnFirst?: boolean,
90
+ ignoreError?: boolean
91
+ ) => string | false;
92
+ Swal: any;
93
+ defaultRoute: string;
94
+ }
95
+
96
+ type Context = Record<string, string | { "@id": string }>;
97
+ type Permission = "add" | "delete" | "change" | "control" | "view" | string;
98
+ type DateTime = string;
99
+
100
+ interface LimitedResource {
101
+ "@id": string;
102
+ "@type"?: string | string[] | Promise<string | string[]>;
103
+ _originalResource?: Resource;
104
+ properties?: string[] | Promise<string[]>;
105
+ permissions?: Permission[];
106
+ clientContext?: Context | Promise<Context>;
107
+ serverContext?: Context | Promise<Context>;
108
+ }
109
+
110
+ interface Resource extends LimitedResource {
111
+ [key: string]: any;
112
+ }
113
+
114
+ interface UnknownResource {
115
+ [key: string]: any;
116
+ }
117
+
118
+ interface Container<T> extends Resource {
119
+ "ldp:contains": T[];
120
+ }
121
+
122
+ type ProxyValue<T> = T extends Array<infer U> ? U[] : Resource & T;
123
+
124
+ interface User extends Resource {
125
+ account?: Account;
126
+ first_name?: string;
127
+ last_name?: string;
128
+ name?: string;
129
+ username?: string;
130
+ email?: string;
131
+ }
132
+
133
+ interface Account extends Resource {
134
+ picture?: string | null;
135
+ }
136
+
137
+ interface PropertiesPicker {
138
+ key: string;
139
+ value: string;
140
+ cast?: function;
141
+ expand?: boolean;
142
+ }
143
+
144
+ type TemplateResultOrSymbol = TemplateResult | symbol;
145
+
146
+ export declare global {
147
+ interface Window {
148
+ orbit: LiveOrbit;
149
+ sibStore: { getData: ProxyValue<Resource | Container>; [key: string]: any };
150
+ sibRouter: { [key: string]: any };
151
+ }
152
+ interface Element {
153
+ currentRouteName: string;
154
+ }
155
+ interface Event {
156
+ [key: string]: any;
157
+ }
158
+ interface EventTarget {
159
+ [key: string]: any;
160
+ }
161
+ }
@@ -0,0 +1,79 @@
1
+ import { css, html, nothing } from "lit";
2
+ import { customElement } from "lit/decorators.js";
3
+ import { Task } from "@lit/task";
4
+
5
+ import type { PropertiesPicker } from "@src/component";
6
+
7
+ import "@src/initializer";
8
+ import * as utils from "@helpers";
9
+
10
+ @customElement("solid-boilerplate")
11
+ export class SolidBoilerplate extends utils.OrbitComponent {
12
+ async _afterAttach() {
13
+ // Eg. https://api.server/some-keyword/1/ should trigger an update:
14
+ utils.setupCacheInvalidation(this, {
15
+ keywords: ["some-keyword", "for-invalidating", "cache"],
16
+ });
17
+ return Promise.resolve();
18
+ }
19
+
20
+ static styles = css`
21
+ div {
22
+ background-color: red;
23
+ position: absolute;
24
+ top: 0;
25
+ left: 0;
26
+ right: 0;
27
+ bottom: 0;
28
+ }
29
+ `;
30
+
31
+ cherryPickedProperties: PropertiesPicker[] = [
32
+ { key: "name", value: "name" },
33
+ ];
34
+
35
+ _getResource = new Task(this, {
36
+ task: async ([dataSrc]) => {
37
+ if (
38
+ !dataSrc ||
39
+ !this.orbit ||
40
+ (!this.noRouter &&
41
+ this.route &&
42
+ this.currentRoute &&
43
+ !this.route.startsWith(this.currentRoute))
44
+ )
45
+ return;
46
+
47
+ if (!this.hasCachedDatas || this.oldDataSrc !== dataSrc) {
48
+ if (!dataSrc) return;
49
+ this.datas = await this._getProxyValue(dataSrc);
50
+ this.hasCachedDatas = true;
51
+ }
52
+
53
+ if (this.oldDataSrc !== dataSrc) {
54
+ this.oldDataSrc = dataSrc;
55
+ }
56
+
57
+ return utils.sort(this.datas, "name", "asc");
58
+ },
59
+ args: () => [this.dataSrc, this.caching, this.currentRoute],
60
+ });
61
+
62
+ render() {
63
+ return (
64
+ this.gatekeeper() ||
65
+ this._getResource.render({
66
+ pending: () => html`<solid-loader></solid-loader>`,
67
+ complete: (datas) => {
68
+ if (!datas) {
69
+ return nothing;
70
+ }
71
+
72
+ return html`<div>
73
+ <sample-objects .objects=${datas}></sample-objects>
74
+ </div>`;
75
+ },
76
+ })
77
+ );
78
+ }
79
+ }
@@ -0,0 +1,37 @@
1
+ import { localized, msg, str } from "@lit/localize";
2
+ import { css, html, nothing, unsafeCSS } from "lit";
3
+ import { customElement, property } from "lit/decorators.js";
4
+
5
+ import "@src/initializer";
6
+ import { ComponentObjectHandler } from "@helpers";
7
+
8
+ import type { LimitedResource } from "@src/component";
9
+
10
+ import ComponentStyle from "@styles/component-sample.scss?inline";
11
+
12
+ export type ComponentProps = LimitedResource & {
13
+ name?: string;
14
+ };
15
+
16
+ @customElement("sample-object")
17
+ @localized()
18
+ export class SampleObject extends ComponentObjectHandler {
19
+ static styles = css`
20
+ ${unsafeCSS(ComponentStyle)}
21
+ `;
22
+
23
+ @property({ attribute: false, type: Object })
24
+ object: ComponentProps = { "@id": "" };
25
+
26
+ render() {
27
+ // Eg. reject object if its type is "some:Type"
28
+ if (this.isType("some:Type")) {
29
+ return nothing;
30
+ }
31
+
32
+ // Eg. use localization to display object's name
33
+ return html`<li>
34
+ ${msg(str`Sample object named: ${this.object.name}`)}
35
+ </li>`;
36
+ }
37
+ }
@@ -0,0 +1,40 @@
1
+ import { localized, msg } from "@lit/localize";
2
+ import { css, html, nothing, unsafeCSS } from "lit";
3
+ import { customElement, property } from "lit/decorators.js";
4
+
5
+ import "@src/initializer";
6
+ import { ComponentObjectsHandler } from "@helpers";
7
+
8
+ import type { LimitedResource } from "@src/component";
9
+
10
+ import ComponentStyle from "@styles/component-sample.scss?inline";
11
+
12
+ export type ComponentProps = LimitedResource & {
13
+ name?: string;
14
+ };
15
+
16
+ @customElement("sample-objects")
17
+ @localized()
18
+ export class SampleObjects extends ComponentObjectsHandler {
19
+ static styles = css`
20
+ ${unsafeCSS(ComponentStyle)}
21
+ `;
22
+
23
+ @property({ attribute: false, type: Object })
24
+ objects: ComponentProps[] = [];
25
+
26
+ render() {
27
+ // Eg. reject if any object has a type "some:Type"
28
+ if (this.hasType("some:Type")) {
29
+ return nothing;
30
+ }
31
+
32
+ // Eg. display a sample-object for each object
33
+ return html`${msg("Here are all of our objects:")}<br />
34
+ <ul>
35
+ ${this.objects.map(
36
+ (obj) => html`<sample-object .object=${obj}></sample-object>`
37
+ )}
38
+ </ul>`;
39
+ }
40
+ }
@@ -0,0 +1 @@
1
+ {}
@@ -0,0 +1,100 @@
1
+ import type {
2
+ LimitedResource,
3
+ Resource,
4
+ TemplateResultOrSymbol,
5
+ } from "@src/component";
6
+ import { LitElement, nothing } from "lit";
7
+
8
+ import * as rdf from "@src/initializer";
9
+
10
+ const rdfs = Object.values(rdf);
11
+
12
+ export class ComponentObjectHandler extends LitElement {
13
+ object?: LimitedResource = {
14
+ "@id": ""
15
+ };
16
+
17
+ isType = (type: string, obj: Resource = (this.object as Resource)) => {
18
+ const typeValue = obj["@type"];
19
+ if (Array.isArray(typeValue)) {
20
+ return typeValue.includes(type);
21
+ }
22
+ if (typeof typeValue === "string") {
23
+ return typeValue === type;
24
+ }
25
+ return false;
26
+ };
27
+
28
+ private getNestedProperty(path: string, obj: Resource = (this.object as Resource)): any {
29
+ return path.split(".").reduce((subObj: any, key) => {
30
+ if (subObj === undefined) {
31
+ return undefined;
32
+ }
33
+
34
+ const arrayMatch = key.match(/(.*)\[(.*)\]/);
35
+ if (arrayMatch) {
36
+ const baseKey = arrayMatch[1];
37
+ const index = arrayMatch[2];
38
+
39
+ const array = subObj[baseKey];
40
+
41
+ if (!Array.isArray(array)) {
42
+ return undefined;
43
+ }
44
+
45
+ if (index === "") {
46
+ if (array.length > 0) {
47
+ return array;
48
+ }
49
+ return undefined;
50
+ }
51
+
52
+ const numericIndex = Number.parseInt(index, 10);
53
+ if (
54
+ Number.isNaN(numericIndex) ||
55
+ numericIndex < 0 ||
56
+ numericIndex >= array.length
57
+ ) {
58
+ return undefined;
59
+ }
60
+
61
+ return subObj[baseKey][numericIndex];
62
+ }
63
+ return subObj && subObj[key] !== "undefined" ? subObj[key] : undefined;
64
+ }, obj);
65
+ }
66
+
67
+ renderTemplateWhenWith(
68
+ requiredProperties: (string | string[])[],
69
+ template: () => TemplateResultOrSymbol,
70
+ obj: Resource = (this.object as Resource)
71
+ ): TemplateResultOrSymbol {
72
+ const tester = (
73
+ property: string | string[],
74
+ cb: (some: string) => boolean
75
+ ) =>
76
+ Array.isArray(property)
77
+ ? property.some((subProp) => cb(subProp))
78
+ : cb(property);
79
+
80
+ const typeProperties = requiredProperties.filter((property) =>
81
+ tester(property, (p) => rdfs.includes(p))
82
+ );
83
+
84
+ const keyProperties = requiredProperties.filter((property) =>
85
+ tester(property, (p) => !rdfs.includes(p))
86
+ );
87
+
88
+ if (
89
+ keyProperties.every((property) =>
90
+ tester(property, (p) => this.getNestedProperty(p, obj))
91
+ ) &&
92
+ typeProperties.every((property) =>
93
+ tester(property, (p) => this.isType(p, obj))
94
+ )
95
+ ) {
96
+ return template.call(this);
97
+ }
98
+ return nothing;
99
+ }
100
+ }
@@ -0,0 +1,44 @@
1
+ import type { Resource, TemplateResultOrSymbol } from "@src/component";
2
+ import { LitElement, nothing } from "lit";
3
+
4
+ import * as rdf from "@src/initializer";
5
+ import { property } from "lit/decorators.js";
6
+
7
+ const rdfs = Object.values(rdf);
8
+
9
+ export class ComponentObjectsHandler extends LitElement {
10
+ @property({ attribute: false })
11
+ objects?: Resource[] = [];
12
+
13
+ hasType = (type: string, objs: Resource[] = this.objects as Resource[]) => {
14
+ const typeValue = new Set(objs.flatMap((o) => o["@type"]));
15
+ return typeValue.has(type);
16
+ };
17
+
18
+ renderTemplateWhenWith(
19
+ requiredProperties: (string | string[])[],
20
+ template: () => TemplateResultOrSymbol,
21
+ objs: Resource[] = this.objects as Resource[]
22
+ ): TemplateResultOrSymbol {
23
+ const tester = (
24
+ property: string | string[],
25
+ cb: (some: string) => boolean
26
+ ) =>
27
+ Array.isArray(property)
28
+ ? property.some((subProp) => cb(subProp))
29
+ : cb(property);
30
+
31
+ const typeProperties = requiredProperties.filter((property) =>
32
+ tester(property, (p) => rdfs.includes(p))
33
+ );
34
+
35
+ if (
36
+ typeProperties.every((property) =>
37
+ tester(property, (p) => this.hasType(p, objs))
38
+ )
39
+ ) {
40
+ return template.call(this);
41
+ }
42
+ return nothing;
43
+ }
44
+ }