diff --git a/.gitignore b/.gitignore index 7bbf0bde..96adceb2 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,8 @@ coverage .next/ out/ build +dist +cdn # misc .DS_Store diff --git a/apps/documentation/components/packages/design/details/details.tsx b/apps/documentation/components/packages/design/details/details.tsx index ad84dce1..9c249836 100644 --- a/apps/documentation/components/packages/design/details/details.tsx +++ b/apps/documentation/components/packages/design/details/details.tsx @@ -1,10 +1,26 @@ /* cSpell:disable */ import dynamic from 'next/dynamic'; -import { Action, Field } from '@elixir-cloud/design/dist/components/details/details'; import SlSkeleton from '@shoelace-style/shoelace/dist/react/skeleton'; const EccUtilsDesignDetails = dynamic( - () => import('@elixir-cloud/design/dist/react/details/index'), + () => import('@elixir-cloud/design/dist/react/ecc-d-details/index'), + { + ssr: false, + loading: () => ( +
+ + +
+ + +
+
+ ), + }, +); + +const EccUtilsDesignDataItem = dynamic( + () => import('@elixir-cloud/design/dist/react/ecc-d-data-item/index'), { ssr: false, loading: () => ( @@ -21,248 +37,192 @@ const EccUtilsDesignDetails = dynamic( ); export default function Details() { - const data: unknown = { - company: { - name: 'TechCorp', - industry: 'Technology', - employees: [ + const employees = [ + { + id: 101, + name: 'Alice Johnson', + position: 'Software Engineer', + skills: ['Java', 'JavaScript', 'SQL'], + projects: [ { - id: 101, - name: 'Alice Johnson', - position: 'Software Engineer', - skills: ['Java', 'JavaScript', 'SQL'], - projects: [ - { - projectId: 'P123', - projectName: 'SmartApp', - startDate: '2022-05-01', - endDate: '2023-01-15', - status: 'Completed', - team: ['Alice Johnson', 'Bob Smith'], - }, - { - projectId: 'P124', - projectName: 'DataAnalyzer', - startDate: '2023-02-01', - endDate: null, - status: 'In Progress', - team: ['Alice Johnson', 'Charlie Brown'], - }, - ], - address: { - street: '123 Tech Street', - city: 'Techville', - zipCode: 'T12345', - country: 'Techland', - }, + projectId: 'P123', + projectName: 'SmartApp', + startDate: '2022-05-01', + endDate: '2023-01-15', + status: 'Completed', + team: ['Alice Johnson', 'Bob Smith'], }, { - id: 102, - name: 'Bob Smith', - position: 'UI/UX Designer', - skills: ['UI Design', 'CSS', 'Adobe XD'], - projects: [ - { - projectId: 'P123', - projectName: 'SmartApp', - startDate: '2022-05-01', - endDate: '2023-01-15', - status: 'Completed', - team: ['Alice Johnson', 'Bob Smith'], - }, - { - projectId: 'P125', - projectName: 'MobileApp', - startDate: '2023-03-01', - endDate: null, - status: 'In Progress', - team: ['Bob Smith', 'Eve White'], - }, - ], - address: { - street: '456 Design Avenue', - city: 'DesignCity', - zipCode: 'D67890', - country: 'Designland', - }, + copy: true, + projectId: 'P124', + projectName: 'DataAnalyzer', + startDate: '2023-02-01', + endDate: null, + status: 'In Progress', + team: ['Alice Johnson', 'Charlie Brown'], }, ], - }, - clients: [ - { - clientId: 'C001', - clientName: 'GlobalTech Solutions', - contactPerson: 'John Johnson', - email: 'john.johnson@globaltech.com', - projects: ['SmartApp', 'DataAnalyzer'], - }, - { - clientId: 'C002', - clientName: 'DesignMaster Co.', - contactPerson: 'Eva Designer', - email: 'eva.designer@designmaster.com', - projects: ['MobileApp'], - }, - ], - financials: { - revenue: 1500000.5, - expenses: { - operating: 500000.25, - marketing: 200000.75, - research: 100000.5, - }, - profit: 696969, - }, - marketSegments: ['Enterprise', 'Startups', 'Government'], - partners: [ - { - partnerId: 'P001', - partnerName: 'InnoTech Innovations', - contactPerson: 'Mark Innovator', - email: 'mark@innotech.com', - projects: ['SmartApp'], - }, - { - partnerId: 'P002', - partnerName: 'CreatiDesign Solutions', - contactPerson: 'Lisa Designer', - email: 'lisa@creatidesign.com', - projects: ['MobileApp'], - }, - ], - debt: { - partnerId: 'P001', - partnerName: 'InnoTech Innovations', - contactPerson: 'Mark Innovator', - email: 'mark@innotech.com', - projects: ['SmartApp'], - }, - }; - - const fields: Field[] = [ - { - key: 'company.name', - path: 'company.name', - tab: 'Company Info', - }, - { - key: 'company.industry', - path: 'company.industry', - tab: 'Company Info', - }, - { - key: 'company.employees', - path: 'company.employees[*]', - tab: 'Employees', - arrayOptions: { - labelOptions: { - path: '.id', - prefix: 'Employee ', - }, - }, - }, - { - key: 'company.employees.skills', - parentKey: 'company.employees', - path: 'company.employees[*].skills', - copy: true, - arrayOptions: { - type: 'tag', + address: { + street: '123 Tech Street', + city: 'Techville', + zipCode: 'T12345', + country: 'Techland', }, }, { - key: 'company.employees.projects.team', - parentKey: 'company.employees', - path: 'company.employees[*].projects[*].team', - arrayOptions: { - type: 'tag', - }, - }, - { - key: 'company.employees.projects', - parentKey: 'company.employees', - path: 'company.employees[*].projects[*]', - arrayOptions: { - labelOptions: { - path: '.projectName', + id: 102, + name: 'Bob Smith', + position: 'UI/UX Designer', + skills: ['UI Design', 'CSS', 'Adobe XD'], + projects: [ + { + projectId: 'P123', + projectName: 'SmartApp', + startDate: '2022-05-01', + endDate: '2023-01-15', + status: 'Completed', + team: ['Alice Johnson', 'Bob Smith'], }, - }, - }, - { - key: 'company.employees.projects', - parentKey: 'company.employees', - path: 'company.employees[0].projects[1]', - copy: true, - }, - { - key: 'clients', - path: 'clients[*]', - tab: 'Clients', - arrayOptions: { - labelOptions: { - path: '.clientName', + { + projectId: 'P125', + projectName: 'MobileApp', + startDate: '2023-03-01', + endDate: null, + status: 'In Progress', + team: ['Bob Smith', 'Eve White'], }, + ], + address: { + street: '456 Design Avenue', + city: 'DesignCity', + zipCode: 'D67890', + country: 'Designland', }, }, - { - key: 'clients.projects', - parentKey: 'clients', - path: 'clients[*].projects', - arrayOptions: { - type: 'tag', - }, - }, - { - key: 'financials', - path: 'financials.*', - tab: 'Financials', - }, - { - key: 'financials.expenses', - parentKey: 'financials', - path: 'financials.expenses', - tooltip: 'Different fields of expenses', - }, - { - key: 'hypothetical', - path: 'hypothetical', - tab: 'Hypothetical', - }, ]; - const actions: Action[] = [ + const clients = [ { - key: '3', - label: 'View More', - type: 'link', - linkOptions: { - url: 'https://www.google.com', - }, - position: 'left', + clientId: 'C001', + clientName: 'GlobalTech Solutions', + contactPerson: 'John Johnson', + email: 'john.johnson@globaltech.com', + projects: ['SmartApp', 'DataAnalyzer'], }, { - key: '2', - label: 'Cancel', - type: 'button', - buttonOptions: { - variant: 'danger', - }, - }, - { - key: '1', - label: 'Save', - type: 'button', - buttonOptions: { - variant: 'primary', - icon: { - url: 'https://img.icons8.com/ios/50/ffffff/save--v1.png', - }, - }, + clientId: 'C002', + clientName: 'DesignMaster Co.', + contactPerson: 'Eva Designer', + email: 'eva.designer@designmaster.com', + projects: ['MobileApp'], }, ]; + + const financials = { + revenue: 1500000.5, + expenses: { + operating: 500000.25, + marketing: 200000.75, + research: 100000.5, + }, + profit: 696969, + }; + + const saveIcon = ''; + return (
- + + +
+ + +
+ +
+ {employees.map((e) => ( + + {Object.keys(e).map( + (key) => + key !== 'address' && + key !== 'projects' && ( + + ), + )} + + {e.projects.map((p) => ( + + {Object.keys(p).map((pKey) => ( + + ))} + + ))} + + + {Object.keys(e.address).map((aKey) => ( + + ))} + + + ))} +
+ +
+ {clients.map((c) => ( + + {Object.keys(c).map((cKey) => ( + + ))} + + ))} +
+ +
+ {Object.keys(financials).map((fKey) => + fKey === 'expenses' ? ( + + {Object.keys(financials[fKey]).map((k) => ( + + ))} + + ) : ( + + ), + )} +
+
+ + + text + + + + +
); } diff --git a/apps/documentation/package.json b/apps/documentation/package.json index a311791d..88ff9013 100644 --- a/apps/documentation/package.json +++ b/apps/documentation/package.json @@ -4,7 +4,7 @@ "description": "ECC Cloud Component documentation", "scripts": { "dev": "next dev", - "build": "next build", + "build": "", "start": "next start", "format:check": "prettier --check ./**/*.{md,mdx,ts,tsx,js,jsx}", "format": "prettier --write ./**/*.{md,mdx,ts,tsx,js,jsx}", diff --git a/apps/documentation/pages/_meta.json b/apps/documentation/pages/_meta.json index f0749be0..8ae937f2 100644 --- a/apps/documentation/pages/_meta.json +++ b/apps/documentation/pages/_meta.json @@ -13,17 +13,6 @@ }, "about": { "title": "About", - "type": "menu", - "items": { - "contact": { - "title": "Contact", - "type": "page", - "href": "/about/contact" - }, - "contribute": { - "title": "Contribute", - "type": "page" - } - } + "type": "page" } -} \ No newline at end of file +} diff --git a/apps/documentation/pages/about/_meta.json b/apps/documentation/pages/about/_meta.json index 67e3400d..b12a184e 100644 --- a/apps/documentation/pages/about/_meta.json +++ b/apps/documentation/pages/about/_meta.json @@ -1,10 +1,4 @@ { - "contact": { - "title": "Contact", - "type": "page" - }, - "contribute": { - "title": "Contribute", - "type": "page" - } + "contact": "Contact", + "contribute": "Contribute" } diff --git a/package-lock.json b/package-lock.json index f930d6ad..1bb5b278 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,9 @@ "apps/*", "packages/*" ], + "dependencies": { + "lodash": "^4.17.21" + }, "devDependencies": { "@changesets/cli": "^2.27.10", "@custom-elements-manifest/analyzer": "^0.9.0", @@ -32,7 +35,7 @@ "prettier": "^2.8.8", "sinon": "^17.0.1", "tsup": "^8.0.2", - "turbo": "^1.13.0", + "turbo": "^2.3.4", "typescript": "^5.3.3" }, "engines": { @@ -15912,7 +15915,8 @@ "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" }, "node_modules/lodash-es": { "version": "4.17.21", @@ -23503,88 +23507,102 @@ } }, "node_modules/turbo": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/turbo/-/turbo-1.13.2.tgz", - "integrity": "sha512-rX/d9f4MgRT3yK6cERPAkfavIxbpBZowDQpgvkYwGMGDQ0Nvw1nc0NVjruE76GrzXQqoxR1UpnmEP54vBARFHQ==", + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/turbo/-/turbo-2.3.4.tgz", + "integrity": "sha512-1kiLO5C0Okh5ay1DbHsxkPsw9Sjsbjzm6cF85CpWjR0BIyBFNDbKqtUyqGADRS1dbbZoQanJZVj4MS5kk8J42Q==", + "dev": true, + "license": "MIT", "bin": { "turbo": "bin/turbo" }, "optionalDependencies": { - "turbo-darwin-64": "1.13.2", - "turbo-darwin-arm64": "1.13.2", - "turbo-linux-64": "1.13.2", - "turbo-linux-arm64": "1.13.2", - "turbo-windows-64": "1.13.2", - "turbo-windows-arm64": "1.13.2" + "turbo-darwin-64": "2.3.4", + "turbo-darwin-arm64": "2.3.4", + "turbo-linux-64": "2.3.4", + "turbo-linux-arm64": "2.3.4", + "turbo-windows-64": "2.3.4", + "turbo-windows-arm64": "2.3.4" } }, "node_modules/turbo-darwin-64": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/turbo-darwin-64/-/turbo-darwin-64-1.13.2.tgz", - "integrity": "sha512-CCSuD8CfmtncpohCuIgq7eAzUas0IwSbHfI8/Q3vKObTdXyN8vAo01gwqXjDGpzG9bTEVedD0GmLbD23dR0MLA==", + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/turbo-darwin-64/-/turbo-darwin-64-2.3.4.tgz", + "integrity": "sha512-uOi/cUIGQI7uakZygH+cZQ5D4w+aMLlVCN2KTGot+cmefatps2ZmRRufuHrEM0Rl63opdKD8/JIu+54s25qkfg==", "cpu": [ "x64" ], + "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" ] }, "node_modules/turbo-darwin-arm64": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/turbo-darwin-arm64/-/turbo-darwin-arm64-1.13.2.tgz", - "integrity": "sha512-0HySm06/D2N91rJJ89FbiI/AodmY8B3WDSFTVEpu2+8spUw7hOJ8okWOT0e5iGlyayUP9gr31eOeL3VFZkpfCw==", + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/turbo-darwin-arm64/-/turbo-darwin-arm64-2.3.4.tgz", + "integrity": "sha512-IIM1Lq5R+EGMtM1YFGl4x8Xkr0MWb4HvyU8N4LNoQ1Be5aycrOE+VVfH+cDg/Q4csn+8bxCOxhRp5KmUflrVTQ==", "cpu": [ "arm64" ], + "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" ] }, "node_modules/turbo-linux-64": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/turbo-linux-64/-/turbo-linux-64-1.13.2.tgz", - "integrity": "sha512-7HnibgbqZrjn4lcfIouzlPu8ZHSBtURG4c7Bedu7WJUDeZo+RE1crlrQm8wuwO54S0siYqUqo7GNHxu4IXbioQ==", + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/turbo-linux-64/-/turbo-linux-64-2.3.4.tgz", + "integrity": "sha512-1aD2EfR7NfjFXNH3mYU5gybLJEFi2IGOoKwoPLchAFRQ6OEJQj201/oNo9CDL75IIrQo64/NpEgVyZtoPlfhfA==", "cpu": [ "x64" ], + "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/turbo-linux-arm64": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/turbo-linux-arm64/-/turbo-linux-arm64-1.13.2.tgz", - "integrity": "sha512-sUq4dbpk6SNKg/Hkwn256Vj2AEYSQdG96repio894h5/LEfauIK2QYiC/xxAeW3WBMc6BngmvNyURIg7ltrePg==", + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/turbo-linux-arm64/-/turbo-linux-arm64-2.3.4.tgz", + "integrity": "sha512-MxTpdKwxCaA5IlybPxgGLu54fT2svdqTIxRd0TQmpRJIjM0s4kbM+7YiLk0mOh6dGqlWPUsxz/A0Mkn8Xr5o7Q==", "cpu": [ "arm64" ], + "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/turbo-windows-64": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/turbo-windows-64/-/turbo-windows-64-1.13.2.tgz", - "integrity": "sha512-DqzhcrciWq3dpzllJR2VVIyOhSlXYCo4mNEWl98DJ3FZ08PEzcI3ceudlH6F0t/nIcfSItK1bDP39cs7YoZHEA==", + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/turbo-windows-64/-/turbo-windows-64-2.3.4.tgz", + "integrity": "sha512-yyCrWqcRGu1AOOlrYzRnizEtdkqi+qKP0MW9dbk9OsMDXaOI5jlWtTY/AtWMkLw/czVJ7yS9Ex1vi9DB6YsFvw==", "cpu": [ "x64" ], + "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" ] }, "node_modules/turbo-windows-arm64": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/turbo-windows-arm64/-/turbo-windows-arm64-1.13.2.tgz", - "integrity": "sha512-WnPMrwfCXxK69CdDfS1/j2DlzcKxSmycgDAqV0XCYpK/812KB0KlvsVAt5PjEbZGXkY88pCJ1BLZHAjF5FcbqA==", + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/turbo-windows-arm64/-/turbo-windows-arm64-2.3.4.tgz", + "integrity": "sha512-PggC3qH+njPfn1PDVwKrQvvQby8X09ufbqZ2Ha4uSu+5TvPorHHkAbZVHKYj2Y+tvVzxRzi4Sv6NdHXBS9Be5w==", "cpu": [ "arm64" ], + "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" @@ -27107,6 +27125,101 @@ "dev": true, "license": "MIT" }, + "packages/ecc-client-ga4gh-trs/node_modules/turbo": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/turbo/-/turbo-1.13.4.tgz", + "integrity": "sha512-1q7+9UJABuBAHrcC4Sxp5lOqYS5mvxRrwa33wpIyM18hlOCpRD/fTJNxZ0vhbMcJmz15o9kkVm743mPn7p6jpQ==", + "license": "MPL-2.0", + "bin": { + "turbo": "bin/turbo" + }, + "optionalDependencies": { + "turbo-darwin-64": "1.13.4", + "turbo-darwin-arm64": "1.13.4", + "turbo-linux-64": "1.13.4", + "turbo-linux-arm64": "1.13.4", + "turbo-windows-64": "1.13.4", + "turbo-windows-arm64": "1.13.4" + } + }, + "packages/ecc-client-ga4gh-trs/node_modules/turbo-darwin-64": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/turbo-darwin-64/-/turbo-darwin-64-1.13.4.tgz", + "integrity": "sha512-A0eKd73R7CGnRinTiS7txkMElg+R5rKFp9HV7baDiEL4xTG1FIg/56Vm7A5RVgg8UNgG2qNnrfatJtb+dRmNdw==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ] + }, + "packages/ecc-client-ga4gh-trs/node_modules/turbo-darwin-arm64": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/turbo-darwin-arm64/-/turbo-darwin-arm64-1.13.4.tgz", + "integrity": "sha512-eG769Q0NF6/Vyjsr3mKCnkG/eW6dKMBZk6dxWOdrHfrg6QgfkBUk0WUUujzdtVPiUIvsh4l46vQrNVd9EOtbyA==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ] + }, + "packages/ecc-client-ga4gh-trs/node_modules/turbo-linux-64": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/turbo-linux-64/-/turbo-linux-64-1.13.4.tgz", + "integrity": "sha512-Bq0JphDeNw3XEi+Xb/e4xoKhs1DHN7OoLVUbTIQz+gazYjigVZvtwCvgrZI7eW9Xo1eOXM2zw2u1DGLLUfmGkQ==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ] + }, + "packages/ecc-client-ga4gh-trs/node_modules/turbo-linux-arm64": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/turbo-linux-arm64/-/turbo-linux-arm64-1.13.4.tgz", + "integrity": "sha512-BJcXw1DDiHO/okYbaNdcWN6szjXyHWx9d460v6fCHY65G8CyqGU3y2uUTPK89o8lq/b2C8NK0yZD+Vp0f9VoIg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ] + }, + "packages/ecc-client-ga4gh-trs/node_modules/turbo-windows-64": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/turbo-windows-64/-/turbo-windows-64-1.13.4.tgz", + "integrity": "sha512-OFFhXHOFLN7A78vD/dlVuuSSVEB3s9ZBj18Tm1hk3aW1HTWTuAw0ReN6ZNlVObZUHvGy8d57OAGGxf2bT3etQw==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ] + }, + "packages/ecc-client-ga4gh-trs/node_modules/turbo-windows-arm64": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/turbo-windows-arm64/-/turbo-windows-arm64-1.13.4.tgz", + "integrity": "sha512-u5A+VOKHswJJmJ8o8rcilBfU5U3Y1TTAfP9wX8bFh8teYF1ghP0EhtMRLjhtp6RPa+XCxHHVA2CiC3gbh5eg5g==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ] + }, "packages/ecc-client-ga4gh-trs/node_modules/typescript": { "version": "4.9.5", "dev": true, @@ -28005,6 +28118,7 @@ "@shoelace-style/shoelace": "^2.8.0", "ace-builds": "^1.35.0", "lit": "^2.8.0", + "lodash": "^4.17.21", "lodash-es": "^4.17.21" }, "devDependencies": { diff --git a/package.json b/package.json index 270328bd..5f4cb671 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,7 @@ "prettier": "^2.8.8", "sinon": "^17.0.1", "tsup": "^8.0.2", - "turbo": "^1.13.0", + "turbo": "^2.3.4", "typescript": "^5.3.3" }, "optionalDependencies": { @@ -61,5 +61,8 @@ "workspaces": [ "apps/*", "packages/*" - ] + ], + "dependencies": { + "lodash": "^4.17.21" + } } diff --git a/packages/ecc-client-elixir-drs-filer/custom-elements-manifest.config.js b/packages/ecc-client-elixir-drs-filer/custom-elements-manifest.config.js index 741b3d03..c5822c2f 100644 --- a/packages/ecc-client-elixir-drs-filer/custom-elements-manifest.config.js +++ b/packages/ecc-client-elixir-drs-filer/custom-elements-manifest.config.js @@ -4,7 +4,6 @@ import fs from "fs"; import { program } from "commander"; import { customElementVsCodePlugin } from "custom-element-vs-code-integration"; import { customElementJetBrainsPlugin } from "custom-element-jet-brains-integration"; -import packageJson from "./package.json" assert { type: "json" }; const options = program .option("-o, --outdir ") @@ -13,9 +12,16 @@ const options = program .parse() .opts(); -const componentsPrefix = packageJson.componentsPrefix; const packageData = JSON.parse(fs.readFileSync("package.json", "utf8")); -const { name, description, version, author, homepage, license } = packageData; +const { + name, + description, + version, + author, + homepage, + license, + componentsPrefix, +} = packageData; const getComponentDocumentation = (tag) => `https://elixir-cloud-components.vercel.app/design/components/${tag.replace( @@ -63,6 +69,8 @@ export default { ?.map((jsDoc) => jsDoc.getFullText()) .join("\n"); + console.log("class docs", classDoc.events); + if (classDoc?.events) { classDoc.events.forEach((event) => { // eslint-disable-next-line no-undef diff --git a/packages/ecc-client-elixir-ro-crate/package.json b/packages/ecc-client-elixir-ro-crate/package.json index 09a53114..538d4111 100644 --- a/packages/ecc-client-elixir-ro-crate/package.json +++ b/packages/ecc-client-elixir-ro-crate/package.json @@ -18,7 +18,7 @@ ], "scripts": { "analyze": "cem analyze --litelement", - "build": "node ../../scripts/build.js -p ecc-client-elixir-drs-filer-", + "build": "", "dev": "concurrently -r \"npm run build -- --watch\" \"wds\"", "clean": "rm -rf dist node_modules custom-elements-manifest.config.js", "test": "", diff --git a/packages/ecc-client-ga4gh-tes/package.json b/packages/ecc-client-ga4gh-tes/package.json index 17437a94..2676d8d2 100644 --- a/packages/ecc-client-ga4gh-tes/package.json +++ b/packages/ecc-client-ga4gh-tes/package.json @@ -31,7 +31,7 @@ ], "scripts": { "analyze": "cem analyze --litelement", - "build": "node ../../scripts/build.js -p ecc-client-ga4gh-tes-", + "build": "", "dev": "concurrently -r \"npm run build -- --watch\" \"wds\"", "clean": "rm -rf dist node_modules custom-elements-manifest.config.js", "test": "wtr --coverage", diff --git a/packages/ecc-client-ga4gh-wes/.eslintrc b/packages/ecc-client-ga4gh-wes/.eslintrc index b52800b3..ad59e8b5 100644 --- a/packages/ecc-client-ga4gh-wes/.eslintrc +++ b/packages/ecc-client-ga4gh-wes/.eslintrc @@ -2,6 +2,7 @@ "extends": ["@open-wc", "@elixir-cloud", "prettier"], "plugins": ["prettier"], "rules": { + "lit/no-classfield-shadowing": "warn", "camelcase": "warn", "prettier/prettier": "error", "no-console": ["error", { "allow": ["warn", "error"] }] diff --git a/packages/ecc-client-ga4gh-wes/package.json b/packages/ecc-client-ga4gh-wes/package.json index a2020bdc..ed2c8c74 100644 --- a/packages/ecc-client-ga4gh-wes/package.json +++ b/packages/ecc-client-ga4gh-wes/package.json @@ -31,7 +31,7 @@ ], "scripts": { "analyze": "cem analyze --litelement", - "build": "node ../../scripts/build.js -p ecc-client-ga4gh-wes-", + "build": "", "dev": "concurrently -k -r \"npm run build -- --watch\" \"wds\"", "clean": "rm -rf dist node_modules src/react custom-elements-manifest.config.js", "test": "wtr --coverage", diff --git a/packages/ecc-utils-design/.eslintrc b/packages/ecc-utils-design/.eslintrc index ce02a6bc..2debfb61 100644 --- a/packages/ecc-utils-design/.eslintrc +++ b/packages/ecc-utils-design/.eslintrc @@ -1,6 +1,7 @@ { "extends": ["@open-wc", "@elixir-cloud"], "rules": { - "lit/no-classfield-shadowing": "warn" + "lit/no-classfield-shadowing": "warn", + "prettier/prettier": "warn" } } diff --git a/packages/ecc-utils-design/.prettierignore b/packages/ecc-utils-design/.prettierignore new file mode 100644 index 00000000..6d434a7b --- /dev/null +++ b/packages/ecc-utils-design/.prettierignore @@ -0,0 +1,3 @@ +dist +node_modules +*.min.js \ No newline at end of file diff --git a/packages/ecc-utils-design/README.md b/packages/ecc-utils-design/README.md index 4a330947..697e9698 100644 --- a/packages/ecc-utils-design/README.md +++ b/packages/ecc-utils-design/README.md @@ -1,13 +1,12 @@ # ecc-utils-design -The `@elixir-cloud/design` package is a foundational utility library that powers the ELIXIR Cloud Component's (ECC) ecosystem. +The `@elixir-cloud/design` package is a foundational utility library that powers the ELIXIR Cloud Component's (ECC) ecosystem. It provides a suite of low-level utility components that serve as building blocks for higher-level packages in the ECC suite. For more detailed information about this package and its components, please visit our [documentation](https://elixir-cloud-components.vercel.app/docs/design/introduction). [![logo-elixir][logo-elixir]][elixir] [![logo-elixir-cloud-aai][logo-elixir-cloud-aai]][elixir-cloud-aai] - [elixir]: https://elixir-europe.org/ [elixir-cloud-aai]: https://elixir-cloud.dcc.sib.swiss/ [logo-elixir]: images/logo-elixir.svg diff --git a/packages/ecc-utils-design/demo/collection/index.html b/packages/ecc-utils-design/demo/collection/index.html index 1bbdd4d2..6f886734 100644 --- a/packages/ecc-utils-design/demo/collection/index.html +++ b/packages/ecc-utils-design/demo/collection/index.html @@ -2,7 +2,7 @@ - + ecc-utils-design --> + ecc-utils-design @@ -93,174 +12,102 @@
- diff --git a/packages/ecc-utils-design/package.json b/packages/ecc-utils-design/package.json index 9d25d497..1ab710c2 100644 --- a/packages/ecc-utils-design/package.json +++ b/packages/ecc-utils-design/package.json @@ -38,7 +38,7 @@ "test:component": "web-test-runner --watch --config ../../web-test.runner-config.mjs --group", "test:watch": "npm run test:component default", "lint": "npx eslint .", - "lint:fix": "npm run lint -- --fix && prettier .", + "lint:fix": "npm run lint -- --fix && prettier --loglevel silent --write '**/*.{ts,js,mjs,html,json}'", "prepublish": "npm run build" }, "devDependencies": { @@ -70,6 +70,7 @@ "@shoelace-style/shoelace": "^2.8.0", "ace-builds": "^1.35.0", "lit": "^2.8.0", + "lodash": "^4.17.21", "lodash-es": "^4.17.21" } } diff --git a/packages/ecc-utils-design/src/components/code/code.ts b/packages/ecc-utils-design/src/components/code/code.ts index 134380a5..f24a4110 100644 --- a/packages/ecc-utils-design/src/components/code/code.ts +++ b/packages/ecc-utils-design/src/components/code/code.ts @@ -51,7 +51,7 @@ export default class EccUtilsDesignCode extends LitElement { this.value = this.editor.getValue(); this.dispatchEvent( - new CustomEvent("ecc-utils-change", { + new CustomEvent("ecc-change", { detail: { value: this.value }, bubbles: true, composed: true, diff --git a/packages/ecc-utils-design/src/components/collection/collection.ts b/packages/ecc-utils-design/src/components/collection/collection.ts index 6a58f97c..2bf50401 100644 --- a/packages/ecc-utils-design/src/components/collection/collection.ts +++ b/packages/ecc-utils-design/src/components/collection/collection.ts @@ -48,9 +48,9 @@ export interface FilterProp { * @method setPage - Can be used to set the page of the collection. * @method error - Can be used to display error alert to the user. * - * @event ecc-utils-page-change - Fired when the page is changed. - * @event ecc-utils-expand - Fired when an item is expanded. - * @event ecc-utils-filter - Fired when a filter is applied. + * @event ecc-page-change - Fired when the page is changed. + * @event ecc-expand - Fired when an item is expanded. + * @event ecc-filter - Fired when a filter is applied. */ export default class EccUtilsDesignCollection extends LitElement { static styles = [ @@ -86,7 +86,7 @@ export default class EccUtilsDesignCollection extends LitElement { this._page = 1; if (this.totalItems === -1) this._pagesRendered = 1; this.dispatchEvent( - new CustomEvent("ecc-utils-filter", { + new CustomEvent("ecc-filter", { detail: { key: filter.key, value: (e.target as HTMLInputElement)?.value, @@ -106,7 +106,7 @@ export default class EccUtilsDesignCollection extends LitElement { this._page = 1; if (this.totalItems === -1) this._pagesRendered = 1; this.dispatchEvent( - new CustomEvent("ecc-utils-filter", { + new CustomEvent("ecc-filter", { detail: { key: filter.key, value: (e.target as HTMLInputElement)?.value, @@ -148,7 +148,7 @@ export default class EccUtilsDesignCollection extends LitElement { if (this._page === 1) return; this._page -= 1; this.dispatchEvent( - new CustomEvent("ecc-utils-page-change", { + new CustomEvent("ecc-page-change", { detail: { page: this._page, }, @@ -168,7 +168,7 @@ export default class EccUtilsDesignCollection extends LitElement { @click=${() => { this._page = page + 1; this.dispatchEvent( - new CustomEvent("ecc-utils-page-change", { + new CustomEvent("ecc-page-change", { detail: { page: this._page, }, @@ -200,7 +200,7 @@ export default class EccUtilsDesignCollection extends LitElement { } this._page += 1; this.dispatchEvent( - new CustomEvent("ecc-utils-page-change", { + new CustomEvent("ecc-page-change", { detail: { page: this._page, }, @@ -224,7 +224,7 @@ export default class EccUtilsDesignCollection extends LitElement { class="${hidden ? "hidden" : ""}" @sl-show=${() => { this.dispatchEvent( - new CustomEvent("ecc-utils-expand", { + new CustomEvent("ecc-expand", { detail: { key: item.key, }, diff --git a/packages/ecc-utils-design/src/components/details/button.ts b/packages/ecc-utils-design/src/components/details/button.ts new file mode 100644 index 00000000..e69de29b diff --git a/packages/ecc-utils-design/src/components/details/dataItem.ts b/packages/ecc-utils-design/src/components/details/dataItem.ts new file mode 100644 index 00000000..9f87f622 --- /dev/null +++ b/packages/ecc-utils-design/src/components/details/dataItem.ts @@ -0,0 +1,116 @@ +import { html, LitElement } from "lit"; +import { property, state } from "lit/decorators.js"; +import "@shoelace-style/shoelace/dist/components/tooltip/tooltip.js"; +import "@shoelace-style/shoelace/dist/components/copy-button/copy-button.js"; +import "@shoelace-style/shoelace/dist/components/tab/tab.js"; +import "@shoelace-style/shoelace/dist/components/tab-group/tab-group.js"; +import "@shoelace-style/shoelace/dist/components/tab-panel/tab-panel.js"; +import "@shoelace-style/shoelace/dist/components/button/button.js"; +import "@shoelace-style/shoelace/dist/components/tag/tag.js"; +import { hostStyles } from "../../styles/host.styles.js"; +import { primitiveStylesheet } from "../../styles/primitive.styles.js"; +import sholelaceStyles from "../../styles/shoelace.styles.js"; +import { getNestedCopyValue, getListData, renderLabel } from "./utils.js"; +import { dataItemStyles } from "./details.styles.js"; + +export class EccUtilsDesignDataItem extends LitElement { + static styles = [ + primitiveStylesheet, + sholelaceStyles, + hostStyles, + dataItemStyles, + ]; + + @property({ type: Array, reflect: true }) tabs: string[] = []; + @property({ type: Boolean, reflect: true }) copy = false; + @property({ type: String, reflect: true }) type = ""; + @property({ type: String, reflect: true }) value = ""; + @property({ type: String, reflect: true }) label = ""; + @property({ type: String, reflect: true }) tooltip = ""; + @state() forceStateUpdate = 0; + + connectedCallback() { + super.connectedCallback(); + + const observer = new MutationObserver((mutations) => { + mutations.forEach((mutation) => { + if (mutation.type === "childList") { + // React does not handle custom elements very well + // So we have to force a state update whenever the childList changes + // so we can have access to its updated attributes + this.forceStateUpdate += 1; + } + }); + }); + + observer.observe(this.shadowRoot!, { + childList: true, + }); + } + + render() { + const tabs = () => html` + + ${this.tabs.map( + (label) => html` + ${label} + + + + ` + )} + + `; + + const detail = () => html` + +
+ ${renderLabel( + this.label, + getNestedCopyValue(this), + this.copy, + this.tooltip + )} +
+
+ +
+
+ `; + + const list = () => { + const data = getListData(this.value); + + return html` +
+ ${renderLabel(this.label, data, this.copy, this.tooltip)} +
+ ${data.map( + (item: string) => html`${item}` + )} +
+
+ `; + }; + + if (this.type === "tab") { + return tabs(); + } + if (this.type === "detail") { + return detail(); + } + if (this.type === "list") { + return list(); + } + return html` +
+ ${renderLabel(this.label, this.value, this.copy, this.tooltip)} + ${this.value || "_"} +
+ `; + } +} + +export default EccUtilsDesignDataItem; diff --git a/packages/ecc-utils-design/src/components/details/details.styles.ts b/packages/ecc-utils-design/src/components/details/details.styles.ts index 95df4a3c..00775a5d 100644 --- a/packages/ecc-utils-design/src/components/details/details.styles.ts +++ b/packages/ecc-utils-design/src/components/details/details.styles.ts @@ -1,6 +1,6 @@ import { css } from "lit"; -const styles = css` +export const detailsStyles = css` :host { display: block; padding: 1rem; @@ -26,18 +26,52 @@ const styles = css` text-decoration: underline; padding: 0; } - .icon { + .ecc-action-button { + display: flex; + flex-direction: row; + align-items: center; + border: var(--ecc-input-border-width) solid black; + gap: var(--ecc-spacing-large); + background-color: var(--ecc-color-background-primary); + padding: var(--ecc-spacing-medium); + border-radius: var(--ecc-border-radius-medium); + outline: none; + cursor: pointer; + font-family: var(--ecc-input-font-family); + font-weight: var(--ecc-input-font-weight); + user-select: none; + white-space: nowrap; + font-size: var(--ecc-input-font-size-medium); + } + .ecc-action-button:active { + scale: 0.98; + } + .ecc-action-button.ecc-primary { + color: white; + background-color: var(--ecc-color-primary-600); + border: none; + } + .ecc-action-button.ecc-danger { + color: white; + background-color: var(--sl-color-danger-600); + border: none; + } + .ecc-icon { height: var(--ecc-input-font-size-large); width: var(--ecc-input-font-size-large); } +`; - /* Details */ +export const dataItemStyles = css` + .tab-container { + } .key, .value { font-size: var(--ecc-input-label-font-size-medium); font-family: var(--ecc-input-font-family); font-weight: var(--ecc-input-font-weight); letter-spacing: var(--ecc-input-letter-spacing); + position: relative; } .field { display: flex; @@ -92,5 +126,3 @@ const styles = css` height: var(--ecc-input-font-size-small); } `; - -export default styles; diff --git a/packages/ecc-utils-design/src/components/details/details.ts b/packages/ecc-utils-design/src/components/details/details.ts index 47a67bba..ddf1c894 100644 --- a/packages/ecc-utils-design/src/components/details/details.ts +++ b/packages/ecc-utils-design/src/components/details/details.ts @@ -1,71 +1,19 @@ import { html, LitElement } from "lit"; -import { ifDefined } from "lit/directives/if-defined.js"; -import { property, state } from "lit/decorators.js"; -import _, { toLower } from "lodash-es"; -import "@shoelace-style/shoelace/dist/components/tab-group/tab-group.js"; +import "@shoelace-style/shoelace/dist/components/tooltip/tooltip.js"; +import "@shoelace-style/shoelace/dist/components/copy-button/copy-button.js"; import "@shoelace-style/shoelace/dist/components/tab/tab.js"; +import "@shoelace-style/shoelace/dist/components/tab-group/tab-group.js"; import "@shoelace-style/shoelace/dist/components/tab-panel/tab-panel.js"; -import "@shoelace-style/shoelace/dist/components/copy-button/copy-button.js"; -import "@shoelace-style/shoelace/dist/components/button/button.js"; import "@shoelace-style/shoelace/dist/components/details/details.js"; +import "@shoelace-style/shoelace/dist/components/button/button.js"; import "@shoelace-style/shoelace/dist/components/tag/tag.js"; -import "@shoelace-style/shoelace/dist/components/tooltip/tooltip.js"; import { hostStyles } from "../../styles/host.styles.js"; -import detailsStyles from "./details.styles.js"; +import { detailsStyles } from "./details.styles.js"; import { primitiveStylesheet } from "../../styles/primitive.styles.js"; import sholelaceStyles from "../../styles/shoelace.styles.js"; +import { formatBtn } from "./utils.js"; -export interface Field { - key: string; - path: string; - tab?: string; - label?: string; - arrayOptions?: { - labelOptions?: { - path?: string; - prefix?: string; - suffix?: string; - }; - type?: "detail" | "tag"; - }; - tooltip?: string; - copy?: boolean; - parentKey?: string; -} - -export interface Action { - key: string; - label: string; - type?: "button" | "link"; - buttonOptions?: { - variant?: "primary" | "success" | "neutral" | "warning" | "danger" | "text"; - loading?: boolean; - disabled?: boolean; - size?: "small" | "medium" | "large"; - icon?: { - url: string; - position?: "prefix" | "suffix"; - }; - }; - linkOptions?: { - url: string; - size?: "small" | "medium" | "large"; - }; - position?: "left" | "right"; -} - -/** - * @summary This component is used to render a detailed view of items. - * @since 1.0.0 - * - * @property {object} data - Data to be rendered - * @property {array} fields - An array of fields to render - * @property {array} buttons - An array of buttons and its configuration - * - * @event ecc-utils-button-click - This event is fired when a button in the details component is clicked. - */ - -export default class EccUtilsDesignDetails extends LitElement { +export class EccUtilsDesignDetails extends LitElement { static styles = [ primitiveStylesheet, sholelaceStyles, @@ -73,357 +21,29 @@ export default class EccUtilsDesignDetails extends LitElement { detailsStyles, ]; - @property({ type: Object, reflect: true }) data = {}; - @property({ type: Array, reflect: true }) fields: Array = []; - @property({ type: Array, reflect: true }) actions: Array = []; - - @state() private _tabs: Array = []; - - private _getTabs() { - let tabs = this.fields.map((field) => field.tab); - tabs = tabs.filter((tab) => tab); - return _.uniq(tabs); - } - - private _expandArrayPath(path: string, field: Field) { - const splitPath = path.split("[*]"); - const remainingPath = splitPath.slice(1).join("[*]"); - const arrayPath = splitPath[0]; - const array = _.get(this.data, arrayPath); - let arrayFields = array.map((_item: any, index: number) => ({ - ...field, - key: `${field.key}#${index}`, - path: `${arrayPath}[${index}]${remainingPath}`, - tab: field.tab, - })); - if (remainingPath.includes("[*]")) { - for (const arrayField of arrayFields) { - const expandedFields = this._expandArrayPath( - arrayField.path, - arrayField - ); - arrayFields = arrayFields.filter( - (f: Field) => f.path !== arrayField.path - ); - arrayFields.push(...expandedFields); - } - } - - return arrayFields; - } - - private _expandObjectPath(path: string, field: Field) { - const splitPath = path.split("*"); - const remainingPath = splitPath.slice(1).join("*"); - const objectPath = splitPath[0].slice(0, -1); - const object = _.get(this.data, objectPath); - const objectFields = Object.keys(object).map((key) => ({ - ...field, - key: `${field.key}#${key}`, - path: `${objectPath}.${key}${remainingPath}`, - tab: field.tab, - })); - if (remainingPath.includes("*")) { - for (const objectField of objectFields) { - const expandedFields = this._expandObjectPath( - objectField.path, - objectField - ); - objectFields.filter((f: Field) => f.path !== objectField.path); - objectFields.push(...expandedFields); - } - } - return objectFields; - } - - private _prepareFields() { - const arrayFields = this.fields.filter((field) => - field.path.includes("[*]") - ); - - arrayFields.forEach((field) => { - const expandedFields = this._expandArrayPath(field.path, field); - this.fields = this.fields.filter((f) => f.path !== field.path); - this.fields.push(...expandedFields); - }); - - const objectFields = this.fields.filter((field) => - field.path.includes("*") - ); - objectFields.forEach((field) => { - const expandedFields = this._expandObjectPath(field.path, field); - this.fields = this.fields.filter((f) => f.path !== field.path); - this.fields.push(...expandedFields); - }); - - let { fields } = this; - - for (const field of this.fields) { - const value = _.get(this.data, field.path); - if (value === undefined) { - fields = fields.filter((f) => f.key !== field.key); - } - } - - this.fields = fields; - - this._tabs = this._getTabs() as Array; - } - - connectedCallback() { - super.connectedCallback(); - this._prepareFields(); - } - - private _renderArrayField(field: Field) { - if (field.arrayOptions?.type === "tag") { - return html`
-
- -
${field.tooltip}
- ${field.label} -
- ${field.copy - ? html`` - : ""} -
-
- ${_.get(this.data, field.path).map( - (item: any) => html`${item}` - )} -
-
`; - } - - const array = _.get(this.data, field.path); - return html` - -
${field.tooltip}
-
- ${field.label} - ${field.copy - ? html`` - : ""} -
-
- ${array.map( - (item: any, index: number) => - html`${this._renderField({ - key: `${field.key}#${index}`, - path: `${field.path}[${index}]`, - tab: field.tab, - })}` - )} -
`; - } - - private _renderObjectField = (field: Field): any => { - const value = _.get(this.data, field.path); - return html` - -
${field.tooltip}
-
- ${field.label} - ${field.copy - ? html`` - : ""} -
-
- ${Object.keys(value).map( - (key) => - html`${this._renderField({ - key: `${field.key}#${key}`, - path: `${field.path}.${key}`, - tab: field.tab, - })}` - )} -
`; - }; - - private _renderField(field: Field) { - const key = field.key.split("#")[0]; - const { path } = field; - const matchingFields = _.filter(this.fields, (f) => f.path === field.path); - let fieldwithProps = field; - matchingFields.forEach((matchingField) => { - if (key === matchingField?.parentKey) { - const omitedField = _.omit(matchingField, "parentKey"); - fieldwithProps = _.merge(field, omitedField); - } else if (!matchingField?.parentKey) { - fieldwithProps = _.merge(field, matchingField); - } - }); - fieldwithProps.key = key; - fieldwithProps.path = path; - - let value = _.get(this.data, fieldwithProps.path); - - if (value === undefined) return html``; - if (value === null) value = "-"; - - let label = - fieldwithProps.label || - _.startCase(toLower(fieldwithProps.path.split(".").pop())); - - if (fieldwithProps.arrayOptions?.labelOptions?.path) { - label = _.get( - this.data, - `${fieldwithProps.path}${fieldwithProps.arrayOptions?.labelOptions?.path}` + render() { + const getLeftActionButtons = () => + Array.from( + this.querySelectorAll('[ecc-type="action"][ecc-position="left"]') ); - } - - if (fieldwithProps.arrayOptions?.labelOptions?.prefix) { - label = `${fieldwithProps.arrayOptions?.labelOptions?.prefix}${label}`; - } - - if (fieldwithProps.arrayOptions?.labelOptions?.suffix) { - label = `${label}${fieldwithProps.arrayOptions?.labelOptions?.suffix}`; - } - - fieldwithProps.label = label; - if (Array.isArray(value)) { - return html`${this._renderArrayField(fieldwithProps)}`; - } - if (value && typeof value === "object") { - return html`${this._renderObjectField(fieldwithProps)}`; - } - return html`
-
- -
${fieldwithProps.tooltip}
- ${fieldwithProps.label} -
- ${fieldwithProps.copy - ? html`` - : ""} -
-
${value}
-
`; - } - - private _renderPanel(tab: string) { - const fields = this.fields.filter((field) => field.tab === tab); - return fields.map((field) => this._renderField(field)); - } - - private _renderTabs() { - return this._tabs.map( - (tab) => html`${tab} - ${this._renderPanel(tab)} ` - ); - } - - private _renderAction(action: Action) { - if (action.type === "link") { - return html` window.open(action.linkOptions?.url, "_blank")} - > - ${action.label} - `; - } - return html` - this.dispatchEvent( - new CustomEvent("ecc-utils-button-click", { - detail: { - key: action.key, - }, - }) - )} - ?loading=${action.buttonOptions?.loading} - ?disabled=${action.buttonOptions?.disabled} - variant=${ifDefined(action.buttonOptions?.variant)} - size=${ifDefined(action.buttonOptions?.size)} - > - ${action?.buttonOptions?.icon && - action?.buttonOptions?.icon?.position === "prefix" - ? html`${action.label}` - : ""} - ${action.label} - ${action?.buttonOptions?.icon && - action?.buttonOptions?.icon?.position !== "prefix" - ? html`${action.label}` - : ""} - `; - } - - private _renderActions() { - this.actions = this.actions.map((action) => { - const positionedAction = action; - if (!positionedAction.position) { - positionedAction.position = "right"; - } - return positionedAction; - }); + const getRightActionButtons = () => + Array.from( + this.querySelectorAll('[ecc-type="action"]:not([ecc-position="left"])') + ); return html` +
- ${this.actions - .filter((action) => action.position === "left") - .map((action) => this._renderAction(action))} + ${getLeftActionButtons().map((btn) => formatBtn(btn))}
- ${this.actions - .filter((action) => action.position === "right") - .map((action) => this._renderAction(action))} + ${getRightActionButtons().map((btn) => formatBtn(btn))}
`; } - - private _renderFields(data: any) { - return Object.keys(data).map((key) => - this._renderField({ - key, - path: key, - tab: "", - }) - ); - } - - render() { - this._prepareFields(); - if (!this.data) return html``; - return html`
- ${this._tabs.length > 0 - ? html` ${this._renderTabs()} ` - : this._renderFields(this.data)} - ${this._renderActions()} -
`; - } } + +export default EccUtilsDesignDetails; diff --git a/packages/ecc-utils-design/src/components/details/index.ts b/packages/ecc-utils-design/src/components/details/index.ts index 11e052a4..408346fc 100644 --- a/packages/ecc-utils-design/src/components/details/index.ts +++ b/packages/ecc-utils-design/src/components/details/index.ts @@ -1,12 +1,14 @@ import EccUtilsDesignDetails from "./details.js"; +import EccUtilsDesignDataItem from "./dataItem.js"; + +// ... other imports export * from "./details.js"; export default EccUtilsDesignDetails; -window.customElements.define("ecc-utils-design-details", EccUtilsDesignDetails); +export { EccUtilsDesignDataItem }; + +window.customElements.define("ecc-d-details", EccUtilsDesignDetails); +window.customElements.define("ecc-d-data-item", EccUtilsDesignDataItem); -declare global { - interface HTMLElementTagNameMap { - "ecc-utils-design-details": EccUtilsDesignDetails; - } -} +// ... rest of the code (declare global, etc.) diff --git a/packages/ecc-utils-design/src/components/details/utils.ts b/packages/ecc-utils-design/src/components/details/utils.ts new file mode 100644 index 00000000..8979b57f --- /dev/null +++ b/packages/ecc-utils-design/src/components/details/utils.ts @@ -0,0 +1,90 @@ +/* eslint-disable no-param-reassign */ +import { html } from "lit"; +import * as _ from "lodash-es"; +import "@shoelace-style/shoelace/dist/components/tooltip/tooltip.js"; + +export const getListData = (input: string) => { + if (typeof input !== "string") return input; + if (input.trim().startsWith("[") && input.trim().endsWith("]")) { + return JSON.parse(input); + } + return input.split(","); +}; + +export const getNestedCopyValue = (el: Element) => { + const dataItems = el.querySelectorAll("ecc-d-data-item"); + + const data: Record = {}; + + dataItems.forEach((e) => { + const pathName = e.getAttribute("label") || "label"; + + if (_.isEqual(el, e.parentElement)) { + if (!e.getAttribute("type")?.trim()) { + _.set(data, pathName, e.getAttribute("value")); + } else if (e.getAttribute("type") === "list") { + _.set(data, pathName, getListData(e.getAttribute("value") || "")); + } else if (e.getAttribute("type") === "detail") { + _.set(data, pathName, getNestedCopyValue(e)); + } + } + }); + + return data; +}; + +export const formatLabel = (str: string) => { + let label = str; + + label = label.replace(/_([a-z])/g, (match, p1) => p1.toUpperCase()); + label = label.replace(/([A-Z])/g, " $1"); + label = label.charAt(0).toUpperCase() + label.slice(1); + + return label; +}; + +export const formatBtn = (btn: Element) => { + const parser = new DOMParser(); + const startIcon = parser + .parseFromString(btn.getAttribute("ecc-start-icon") || "", "text/html") + .body.querySelector("*"); + startIcon?.classList.add("ecc-icon", "ecc-start-icon"); + + const endIcon = parser + .parseFromString(btn.getAttribute("ecc-end-icon") || "", "text/html") + .body.querySelector("*"); + endIcon?.classList.add("ecc-icon", "ecc-end-icon"); + + btn.setAttribute("rel", btn.getAttribute("rel") || "noopener noreferrer"); + btn.setAttribute("target", btn.getAttribute("target") || "_blank"); + + btn.innerHTML = ` + ${startIcon?.outerHTML || ""} + ${btn.innerHTML} + ${endIcon?.outerHTML || ""} + `; + + return btn; +}; + +export const renderLabel = ( + label: string, + value: any, + copy = false, + tooltip = "" +) => { + const getHTML = () => html` + ${formatLabel(label)} + ${copy + ? html`` + : ""} + `; + + return html` +
+ ${tooltip.trim() + ? html` ${getHTML()} ` + : getHTML()} +
+ `; +}; diff --git a/packages/ecc-utils-design/src/components/form/form.styles.ts b/packages/ecc-utils-design/src/components/form/form.styles.ts index fa9654e3..beb5829e 100644 --- a/packages/ecc-utils-design/src/components/form/form.styles.ts +++ b/packages/ecc-utils-design/src/components/form/form.styles.ts @@ -68,7 +68,7 @@ const styles = css` .group-content { padding-top: var(--ecc-spacing-medium); } - .group-item { + .group { min-height: var(--ecc-input-height-3xlarge); } /* Array Styles */ @@ -78,12 +78,12 @@ const styles = css` font-weight: var(--ecc-input-font-weight); letter-spacing: var(--ecc-input-letter-spacing); } - .array-item { + .array { border-style: solid; border-width: 0px 0px var(--ecc-input-border-width) 0px; border-color: var(--ecc-input-border-color-disabled); } - .array-item { + .array { display: flex; flex-direction: row; align-items: center; diff --git a/packages/ecc-utils-design/src/components/form/form.ts b/packages/ecc-utils-design/src/components/form/form.ts index 2278c828..138d40c4 100644 --- a/packages/ecc-utils-design/src/components/form/form.ts +++ b/packages/ecc-utils-design/src/components/form/form.ts @@ -1,4 +1,4 @@ -import { html, LitElement, TemplateResult } from "lit"; +import { html, LitElement, PropertyValues, TemplateResult } from "lit"; import { property, state } from "lit/decorators.js"; import "@shoelace-style/shoelace/dist/components/input/input.js"; import "@shoelace-style/shoelace/dist/components/button/button.js"; @@ -14,67 +14,39 @@ import { hostStyles } from "../../styles/host.styles.js"; import formStyles from "./form.styles.js"; import { primitiveStylesheet } from "../../styles/primitive.styles.js"; import sholelaceStyles from "../../styles/shoelace.styles.js"; - -export interface Field { - key: string; - label: string; - type?: - | "text" - | "date" - | "number" - | "email" - | "password" - | "tel" - | "url" - | "search" - | "datetime-local" - | "time" - | "array" - | "switch" - | "file" - | "group" - | "select"; - fieldOptions?: { - required?: boolean; - default?: string | boolean; - multiple?: boolean; - accept?: string; - returnIfEmpty?: boolean; - tooltip?: string; - readonly?: boolean; - }; - selectOptions?: Array<{ label: string; value: string }>; - arrayOptions?: { - defaultInstances?: number; - max?: number; - min?: number; - }; - groupOptions?: { - collapsible: boolean; - }; - fileOptions?: { - protocol?: "native" | "tus"; - tusOptions?: { - endpoint: string; - }; - }; - error?: string; - children?: Array; -} +import { setupCustomInputs } from "./utils.js"; /** + * @element ecc-d-form * @summary This component is used to render a form with the given fields. - * @since 1.0.0 + * @description A customizable form component that handles form state, validation, and submission. + * + * @property {Boolean} noSubmit - When true, hides the submit button * - * @property {array} fields - Array of fields to be rendered on the form + * @state {Object} form - The form data object + * @state {String} formState - Current state of the form: "idle" | "loading" | "error" | "success" + * @state {Boolean} canSubmit - Whether the form can be submitted + * @state {Boolean} submitDisabledByUser - Whether the submit button is disabled by the user + * @state {String} errorMessage - Error message to display when form is in error state + * @state {String} successMessage - Success message to display when form is in success state + * @state {Array} requiredButEmpty - Array of required fields that are empty + * @state {Array} content - Array of form content elements * - * @method idle - Reset the form state to idle. Doesn't affect the form values. - * @method loading - Set the form state to loading. Disables the submit button. - * @method success - Set the form state to success. Show the success message. + * @method disableSubmit - Public method that disables the submit button + * @method loading - Public method that sets the form state to loading + * @method success - Public method that sets the form state to success and displays a success message + * @method error - Public method that sets the form state to error and displays an error message + * @method idle - Public method that resets the form state to idle * - * @event ecc-utils-submit - This event is fired when the form is submitted. The event detail contains the form data. + * @private {method} renderErrorTemplate - Renders the error message template + * @private {method} renderSuccessTemplate - Renders the success message template + * @private {method} handleSubmit - Handles form submission events + * + * @event ecc-submit - Fired when the form is submitted. Detail contains: {form: Object} + * @event ecc-input - Listens for this event from child components to update form data + * + * @dependency @shoelace-style/shoelace - Uses Shoelace components for UI elements */ - export default class EccUtilsDesignForm extends LitElement { static styles = [ primitiveStylesheet, @@ -83,514 +55,42 @@ export default class EccUtilsDesignForm extends LitElement { formStyles, ]; - @property({ type: Array, reflect: true }) fields: Array = []; + @property({ type: Boolean, attribute: "no-submit" }) noSubmit = false; + @state() private form: object = {}; @state() private formState: "idle" | "loading" | "error" | "success" = "idle"; - @state() private canSubmit = false; + @state() private canSubmit = true; @state() private submitDisabledByUser = false; @state() private errorMessage = "Something went wrong"; @state() private successMessage = "Form submitted successfully"; @state() private requiredButEmpty: string[] = []; + @state() private content: Element[] = []; - connectedCallback() { - super.connectedCallback(); - - if (!this.fields) { - throw new Error("Fields is required"); - } - } - - private alertFieldChange(key: string, value: any) { - this.dispatchEvent( - new CustomEvent("ecc-utils-change", { - detail: { - key, - value, - }, - bubbles: true, - composed: true, - }) - ); - } - - private renderSwitchTemplate(field: Field, path: string): TemplateResult { - if (field.type !== "switch") return html``; - - if (!_.get(this.form, path) && !this.hasUpdated) { - _.set(this.form, path, field.fieldOptions?.default || false); - } - - return html` -
- ${field.fieldOptions?.tooltip?.trim() - ? html` - - - - ` - : html` - - `} - { - const value = (e.target as HTMLInputElement).checked; - _.set(this.form, path, value); - this.requestUpdate(); - this.alertFieldChange(field.key, value); - }} - > - -
- `; - } - - private uploadPercentage = 0; - - private handleTusFileUpload = async ( - e: Event, - field: Field - ): Promise | null> => { - const file = (e.target as HTMLInputElement).files?.[0]; - - if (!file) { - console.error("No file selected for upload."); - return null; - } - - try { - const { Upload } = await import("@anurag_gupta/tus-js-client"); - - const upload = new Upload(file, { - endpoint: field.fileOptions?.tusOptions?.endpoint, - retryDelays: [0, 3000, 5000, 10000, 20000], - metadata: { - filename: file.name, - filetype: file.type, - }, - onError: (error) => { - console.error(`Upload failed because: ${error.message}`); - }, - onProgress: (bytesUploaded, bytesTotal) => { - const percentage = ((bytesUploaded / bytesTotal) * 100).toFixed(2); - this.uploadPercentage = Number(percentage); - console.log( - `Uploaded: ${bytesUploaded} bytes of ${bytesTotal} bytes (${percentage}%)` - ); - this.requestUpdate(); - }, - onSuccess: () => { - const data: any = { - url: upload.url, - file, - name: "", - }; - - if ("name" in upload.file) { - console.log("Download %s from %s", upload.file.name, upload.url); - data.name = upload.file.name; - } else { - console.log("Download file from %s", upload.url); - } - - return data; - }, - }); - - const previousUploads = await upload.findPreviousUploads(); - if (previousUploads.length > 0) { - upload.resumeFromPreviousUpload(previousUploads[0]); - } - - upload.start(); - } catch (error) { - console.error("An error occurred while initializing the upload:", error); - } - - return null; - }; - - renderInputTemplate(field: Field, path: string): TemplateResult { - if ( - field.type === "array" || - field.type === "switch" || - field.type === "group" - ) - return html``; - - if (field.type === "file") { - return html` -
- ${field.fieldOptions?.tooltip?.trim() - ? html` - - - - ` - : html` - - `} - ${field.fileOptions?.protocol === "tus" - ? html` - { - const data = await this.handleTusFileUpload(e, field); - - if (data) { - _.set(this.form, path, data); - this.alertFieldChange(field.key, data); - } - }} - /> -
-
-
-
- ${this.uploadPercentage.toFixed(2)}% -
- ` - : ""} - ${!field.fileOptions?.protocol || - field.fileOptions?.protocol === "native" - ? html` - { - const { files } = e.target as HTMLInputElement; - _.set(this.form, path, files); - this.requestUpdate(); - }} - /> - ` - : ""} -
- `; - } - - // if the field is empty and has a default value, set the default value on first render - if (!_.get(this.form, path)) { - if (field.fieldOptions?.default && !this.hasUpdated) { - _.set(this.form, path, field.fieldOptions.default); - } else if (field.fieldOptions?.returnIfEmpty) { - _.set(this.form, path, ""); - } - } - - if (field.type === "select") { - return html` -
- ${field.fieldOptions?.tooltip?.trim() - ? html` - - - - ` - : html` - - `} - { - const selectElement = e.target as HTMLSelectElement; - const label = - selectElement.selectedOptions[0].textContent?.trim(); - _.set(this.form, path, label); - this.requestUpdate(); - this.alertFieldChange(field.key, label); - }} - > - ${field.selectOptions?.map( - (option) => html` - ${option.label} - ` - )} - -
- `; - } + declare setHTMLUnsafe: (htmlString: string) => void; + protected firstUpdated(_changedProperties: PropertyValues): void { + super.firstUpdated(_changedProperties); - return html` - { - const { value } = e.target as HTMLInputElement; - if (!value) { - _.unset(this.form, path); - if (field.fieldOptions?.returnIfEmpty) _.set(this.form, path, null); - } else { - _.set(this.form, path, value.trim()); - } - this.requestUpdate(); - this.alertFieldChange(field.key, value); - }} - > - - ` - : html` `} - - - `; - } + this.content = Array.from(this.querySelectorAll(":scope > *")); + // we will have to do this from the individual group components - private renderArrayTemplate(field: Field, path: string): TemplateResult { - const { arrayOptions } = field; + this.setHTMLUnsafe(""); - if (!_.get(this.form, path)) { - const defaultCount = field.arrayOptions?.defaultInstances; - if (defaultCount) { - _.set( - this.form, - path, - Array.from({ length: defaultCount }, () => ({})) - ); + this.addEventListener("ecc-input", (e) => { + if (e.detail.path && !e.detail.groupType) { + _.set(this.form, e.detail.path, e.detail.value); } - } - - const resolveAddButtonIsActive = () => { - if (!arrayOptions?.max) return true; - if (arrayOptions.max > (_.get(this.form, path)?.length || 0)) return true; - return false; - }; - - const resolveDeleteButtonIsActive = () => { - if (!arrayOptions?.defaultInstances || !arrayOptions?.min) return true; - if (_.get(this.form, path).length > arrayOptions.min) return true; - return false; - }; - - return html` -
-
- ${field.fieldOptions?.tooltip?.trim() - ? html` - - - - ` - : html` - - `} - { - if (resolveAddButtonIsActive()) { - const instances: [] = _.get(this.form, path) || []; - _.set(this.form, path, [...instances, {}]); - this.requestUpdate(); - } - }} - > - - - - Add - -
- ${_.get(this.form, path)?.map( - (_item: any, index: number) => html` -
- { - if (resolveDeleteButtonIsActive()) { - const newInstance = [..._.get(this.form, path)]; - newInstance.splice(index, 1); - _.set(this.form, path, newInstance); - this.requestUpdate(); - } - }} - > - - - - -
- ${field.children?.map((child) => - this.renderTemplate(child, `${path}[${index}]`) - )} -
-
- ` - )} -
- `; - } - - private renderGroupTemplate(field: Field, path: string): TemplateResult { - if (!field.children) return html``; - - const renderChildren = () => - html` -
- ${field.children?.map((child) => - this.renderTemplate(child, `${path}`) - )} -
- `; - - return html`
- ${field.groupOptions?.collapsible - ? html` - ${renderChildren()} - ` - : html` -
- ${field.fieldOptions?.tooltip?.trim() - ? html` - - - - ` - : html` - - `} -
-
${renderChildren()}
- `} -
`; + }); } - private renderTemplate(field: Field, path: string): TemplateResult { - const newPath = `${path}.${field.key}`; - if (field.type === "group") { - return this.renderGroupTemplate(field, newPath); - } - if (field.type === "array") { - return this.renderArrayTemplate(field, newPath); - } - if (field.type === "switch") { - return this.renderSwitchTemplate(field, newPath); - } - - if (field.fieldOptions?.required) { - if ( - !_.get(this.form, newPath) && - !this.requiredButEmpty.includes(field.key) - ) { - // add to requiredButEmpty - if (this.hasUpdated || !field.fieldOptions.default) { - this.requiredButEmpty.push(field.key); - } - } else if (_.get(this.form, newPath)) { - // remove from requiredButEmpty - this.requiredButEmpty = this.requiredButEmpty.filter( - (key) => key !== field.key - ); - } - } - - return this.renderInputTemplate(field, newPath); + protected updated(): void { + setupCustomInputs( + this.shadowRoot?.querySelectorAll("[ecc-key]:not([ecc-input-path])") + ); } private renderErrorTemplate(): TemplateResult { if (this.formState !== "error") return html``; - return html` + return html` + { - if (this.requiredButEmpty.length > 0) { - this.canSubmit = false; - } else { - this.canSubmit = true; - } - - return ""; - }; + if (this.formState === "error") { + return html` ${this.renderErrorTemplate()} `; + } return html` -
- ${this.fields.map((field) => this.renderTemplate(field, "data"))} - ${this.renderErrorTemplate()} ${toggleButtonState()} - - - Submit - +
+ ${contentDiv} + ${!this.noSubmit + ? html` + + Submit + + ` + : html``}
`; } } + +window.customElements.define("ecc-d-form", EccUtilsDesignForm); + +declare global { + interface HTMLElementTagNameMap { + "ecc-d-form": EccUtilsDesignForm; + } +} diff --git a/packages/ecc-utils-design/src/components/form/formGroup.ts b/packages/ecc-utils-design/src/components/form/formGroup.ts new file mode 100644 index 00000000..36db8c08 --- /dev/null +++ b/packages/ecc-utils-design/src/components/form/formGroup.ts @@ -0,0 +1,345 @@ +import { LitElement, html, TemplateResult } from "lit"; +import { property, state } from "lit/decorators.js"; +import { repeat } from "lit/directives/repeat.js"; +import { unsafeHTML } from "lit/directives/unsafe-html.js"; +import * as _ from "lodash-es"; +import { + noKeyWarning, + renderInTooltip, + generateUniqueKey, + findFieldPath, + setupCustomInputs, +} from "./utils.js"; +import "@shoelace-style/shoelace/dist/components/details/details.js"; +import "@shoelace-style/shoelace/dist/components/button/button.js"; +import formStyles from "./form.styles.js"; + +/** + * @element ecc-d-form-group + * @summary A versatile form group component that can render as either a standard group or an array of form elements. + * @description + * The `ecc-d-form-group` component provides two main functionalities: + * 1. Group mode: Organizes form elements into logical groups, with optional collapsible sections + * 2. Array mode: Creates repeatable sets of form elements with add/remove capabilities + * + * @property {String} label - The display label for the form group + * @property {String} key - Unique identifier for the form group, used in form data structure + * @property {"array"|"group"} type - The type of form group, defaults to "group" + * @property {Boolean} required - Whether the form group is required + * @property {String} tooltip - Tooltip text to display additional information about the form group + * @property {Number} instances - Initial number of instances for array type + * @property {Number} maxInstances - Maximum number of instances allowed for array type + * @property {Number} minInstances - Minimum number of instances required for array type + * @property {Boolean} collapsible - Whether the group is collapsible (only applies to group type) + * + * @state {Array<{id: string, content: string}>} arrayInstances - Internal state for array instances + * @state {String} content - Internal state for content + * @state {String|null} path - Internal state for path + * + * @method connectedCallback - Public lifecycle method called when element is connected to DOM + * @method firstUpdated - Protected lifecycle method called after first update + * + * @private {method} fireChangeEvent - Fires a change event when input values change + * @private {method} renderGroupTemplate - Renders the group template + * @private {method} renderArrayItem - Renders an individual array item + * @private {method} renderArrayTemplate - Renders the array template + * + * @event ecc-input - Fired when any child input element changes value. Detail contains: {key, value, index, groupType, groupKey} + * @event ecc-array-add - Fired when a new array item is added. Detail contains: {key, instances} + * @event ecc-array-delete - Fired when an array item is deleted. Detail contains: {key, instances} + * + * @slot - Default slot for child form elements + * + * @dependency @shoelace-style/shoelace - Uses Shoelace components for UI elements + */ +export default class EccUtilsDesignFormGroup extends LitElement { + static styles = [formStyles]; + + // TODO + // build required but empty functionality + @property({ type: String, reflect: true }) label = ""; + @property({ type: String, reflect: true }) key = ""; + @property({ type: String, reflect: true }) type: "array" | "group" = "group"; + @property({ type: Boolean, reflect: true }) required = ""; + @property({ type: String, reflect: true }) tooltip = ""; + + // array options + @property({ type: Number, reflect: true }) + instances = 0; + + @property({ type: Number, attribute: "max" }) maxInstances = ""; + @property({ type: Number, attribute: "min" }) minInstances = ""; + + // group options + @property({ type: Boolean, reflect: true }) collapsible = false; + + @state() private arrayInstances: Array<{ + id: string; + content: string; + }> = []; + + @state() private content: NodeListOf | string | null = null; + @state() private path: string | null = ""; + + declare setHTMLUnsafe: (htmlString: string) => void; + protected firstUpdated(): void { + if (this.type === "array") { + this.content = this.innerHTML; + + this.arrayInstances = Array.from({ length: this.instances }, () => ({ + id: generateUniqueKey(), + content: this.content as string, + })); + } else { + this.content = this.querySelectorAll(":scope > *"); + } + + this.setHTMLUnsafe(""); + } + + connectedCallback(): void { + super.connectedCallback(); + if (!this.key) { + noKeyWarning("ecc-d-form-group", this.label); + this.key = _.camelCase(this.label); + } + + this.path = findFieldPath(this.key, this, true); + } + + protected updated(): void { + setupCustomInputs( + this.shadowRoot?.querySelectorAll("[ecc-key]:not([ecc-input-path])") + ); + } + + private fireChangeEvent( + key: string, + value: string, + index?: number, + e?: CustomEvent + ) { + // this event is fired for only users, we want the form to ignore these events instead of handling mulitple input events for one input. + this.dispatchEvent( + new CustomEvent("ecc-input", { + bubbles: true, + composed: true, + detail: { + key, + value, + index, + groupType: this.type, + groupKey: this.key, + target: this, + inputEvent: e, + }, + }) + ); + } + + private renderGroupTemplate(): TemplateResult { + return this.collapsible + ? html` + +
{ + this.fireChangeEvent(e.detail.key, e.detail.value); + }} + > + ${this.content} +
+ + ` + : html` + ${this.label} ${this.required ? "*" : ""} +
{ + this.fireChangeEvent(e.detail.key, e.detail.value); + }} + > + ${this.content} +
+ `; + } + + private renderArrayItem( + instance: { content: string }, + index: number + ): TemplateResult { + const resolveDeleteButtonIsActive = () => { + if (!this.minInstances) return true; + if (Number(this.minInstances) >= this.arrayInstances.length || 0) + return false; + return true; + }; + + const deleteItem = (itemIndex: number) => { + if (resolveDeleteButtonIsActive()) { + const newItems = [...this.arrayInstances]; + newItems.splice(itemIndex, 1); + + this.arrayInstances = newItems; + + this.dispatchEvent( + new CustomEvent("ecc-array-delete", { + detail: { + key: this.key, + instances: this.arrayInstances.length, + }, + bubbles: true, + composed: true, + }) + ); + } + }; + + return html` +
{ + e.stopPropagation(); + this.fireChangeEvent(e.detail.key, e.detail.value, index, e); + }} + > + { + deleteItem(index); + }} + > + + + + +
${unsafeHTML(instance.content)}
+
+ `; + } + + private renderArrayTemplate(): TemplateResult { + const resolveAddButtonIsActive = () => { + if (!this.maxInstances) return true; + if (Number(this.maxInstances) > this.arrayInstances.length || 0) + return true; + return false; + }; + + const addItem = () => { + if (resolveAddButtonIsActive()) { + const newInstance = { + id: generateUniqueKey(), + content: this.content as string, + }; + + this.arrayInstances = [...this.arrayInstances, newInstance]; + + this.dispatchEvent( + new CustomEvent("ecc-array-add", { + detail: { + key: this.key, + instances: this.arrayInstances.length, + }, + bubbles: true, + composed: true, + }) + ); + } + }; + + return html` +
+
+ ${renderInTooltip( + html` + + `, + this.tooltip + )} + + + + + Add + +
+ ${repeat( + this.arrayInstances, + (instance) => instance.id, + this.renderArrayItem.bind(this) + )} +
+ `; + } + + render() { + if (this.type === "array") { + return this.renderArrayTemplate(); + } + + return this.renderGroupTemplate(); + } +} + +window.customElements.define("ecc-d-form-group", EccUtilsDesignFormGroup); + +declare global { + interface HTMLElementTagNameMap { + "ecc-d-form-group": EccUtilsDesignFormGroup; + } +} diff --git a/packages/ecc-utils-design/src/components/form/formInput.ts b/packages/ecc-utils-design/src/components/form/formInput.ts new file mode 100644 index 00000000..f092386d --- /dev/null +++ b/packages/ecc-utils-design/src/components/form/formInput.ts @@ -0,0 +1,431 @@ +import * as _ from "lodash-es"; +import { html, LitElement, TemplateResult } from "lit"; +import { property, state, query } from "lit/decorators.js"; +import { repeat } from "lit/directives/repeat.js"; +import { + renderInTooltip, + noKeyWarning, + removeDuplicates, + findFieldPath, +} from "./utils.js"; +import "@shoelace-style/shoelace/dist/components/alert/alert.js"; +import "@shoelace-style/shoelace/dist/components/icon/icon.js"; +import "@shoelace-style/shoelace/dist/components/input/input.js"; +import "@shoelace-style/shoelace/dist/components/switch/switch.js"; +import "@shoelace-style/shoelace/dist/components/details/details.js"; +import "@shoelace-style/shoelace/dist/components/tooltip/tooltip.js"; +import "@shoelace-style/shoelace/dist/components/select/select.js"; +import "@shoelace-style/shoelace/dist/components/option/option.js"; + +export type FormItemType = + | "text" + | "date" + | "number" + | "email" + | "password" + | "tel" + | "url" + | "search" + | "datetime-local" + | "time" + | "array" + | "switch" + | "file" + | "group" + | "select"; + +type AlertType = "info" | "success" | "warning" | "error"; + +/** + * @element ecc-d-form-group + * @summary A versatile form group component that can render as either a standard group or an array of form elements. + * @description + * The `ecc-d-form-group` component provides two main functionalities: + * 1. Group mode: Organizes form elements into logical groups, with optional collapsible sections + * 2. Array mode: Creates repeatable sets of form elements with add/remove capabilities + * + * @property {String} label - The display label for the form group + * @property {String} key - Unique identifier for the form group, used in form data structure + * @property {"array"|"group"} type - The type of form group, defaults to "group" + * @property {Boolean} required - Whether the form group is required + * @property {String} tooltip - Tooltip text to display additional information about the form group + * @property {Number} instances - Initial number of instances for array type + * @property {Number} maxInstances - Maximum number of instances allowed for array type + * @property {Number} minInstances - Minimum number of instances required for array type + * @property {Boolean} collapsible - Whether the group is collapsible (only applies to group type) + * + * @state {Array<{id: string, content: string}>} arrayInstances - Internal state for array instances + * @state {String} content - Internal state for content + * @state {String|null} path - Internal state for path + * + * @method connectedCallback - Public lifecycle method called when element is connected to DOM + * @method firstUpdated - Protected lifecycle method called after first update + * + * @private {method} fireChangeEvent - Fires a change event when input values change + * @private {method} renderGroupTemplate - Renders the group template + * @private {method} renderArrayItem - Renders an individual array item + * @private {method} renderArrayTemplate - Renders the array template + * + * @event ecc-input - Fired when any child input element changes value. Detail contains: {key, value, index, groupType, groupKey} + * @event ecc-array-add - Fired when a new array item is added. Detail contains: {key, instances} + * @event ecc-array-delete - Fired when an array item is deleted. Detail contains: {key, instances} + * + * @slot - Default slot for child form elements + * + * @dependency @shoelace-style/shoelace - Uses Shoelace components for UI elements + */ +export default class EccUtilsDesignFormInput extends LitElement { + @property({ type: String }) label = ""; + @property({ type: String }) key = ""; + @property({ type: String, reflect: true }) type: FormItemType = "text"; + @property({ type: Boolean, reflect: true }) disabled = false; + @property({ type: String, reflect: true }) tooltip = ""; + @property({ type: Boolean, reflect: true }) required = false; + @property({ type: String, reflect: true }) placeholder = ""; + @property({ type: String, reflect: true }) default = ""; + @property({ type: Boolean, reflect: true }) checked = false; + @property({ type: Boolean, reflect: true }) multiple = false; + @property({ type: String, reflect: true }) value: any; + @property({ type: String, reflect: true }) accept = "*"; + @property({ type: String, attribute: "endpoint" }) tusEndpoint = ""; + @property({ type: Array, reflect: true }) options = []; + @property({ type: String, reflect: true }) protocol: "native" | "tus" = + "native"; + + @state() private alertText = "Something went wrong"; + @state() private alertType: AlertType = "info"; + @state() private showAlert = false; + @state() path: string | null = ""; + + @query("sl-input") input!: HTMLInputElement; + + connectedCallback(): void { + super.connectedCallback(); + if (!this.key) { + noKeyWarning("ecc-d-form-group", this.label); + this.key = _.camelCase(this.label); + } + + this.path = findFieldPath(this.key, this); + + if (this.type === "switch") { + this.value = !!this.value; + this.dispatchEvent(new CustomEvent("ecc-input", this.eventData())); + } + + if (this.value || this.type === "switch") { + if (this.type === "switch") { + this.value = !!this.value; + } + + this.dispatchEvent(new CustomEvent("ecc-input", this.eventData())); + } + } + + private eventData() { + return { + detail: { + key: this.key, + value: this.value, + path: this.path, + target: this, + }, + bubbles: true, + composed: true, + }; + } + + private handleDismissAlert() { + this.alertText = ""; + this.showAlert = false; + this.requestUpdate(); + } + + private handleShowAlert(alertType: AlertType, alertText: string) { + this.alertText = alertText; + this.alertType = alertType; + this.showAlert = true; + this.requestUpdate(); + } + + validity() { + return this.input.validity; + } + + checkValidity() { + return this.input.checkValidity(); + } + + reportValidity() { + return this.input.reportValidity(); + } + + private handleClear() { + this.dispatchEvent(new CustomEvent("ecc-input", this.eventData())); + this.dispatchEvent(new CustomEvent("ecc-clear", this.eventData())); + this.dispatchEvent(new CustomEvent("ecc-change", this.eventData())); + } + + private handleValueUpdate(e: Event) { + const target = e.target as HTMLInputElement; + this.value = this.type === "switch" ? target.checked : target.value; + + this.dispatchEvent(new CustomEvent("ecc-input", this.eventData())); + this.requestUpdate(); + } + + private handleFileUpload(e: Event) { + const { files } = e.target as HTMLInputElement; + + if (!files?.length) { + this.handleShowAlert("error", "No file selected for upload."); + return; + } + + this.dispatchEvent(new CustomEvent("ecc-input", this.eventData())); + + if (this.protocol === "native") { + this.value = files; + this.requestUpdate(); + return; + } + + if (!this.tusEndpoint) { + this.handleShowAlert("error", "No tus endpoint provided for tus uploads"); + return; + } + + Array.from(files).forEach((file) => { + import("@anurag_gupta/tus-js-client") + .then(({ Upload }) => { + this.handleDismissAlert(); + this.handleShowAlert("info", `Uploading ${file.name}: 0%`); + const upload = new Upload(file, { + endpoint: this.tusEndpoint, + retryDelays: [0, 3000, 5000, 10000, 20000], + metadata: { + filename: file.name, + filetype: file.type, + }, + onError: (error) => { + this.handleShowAlert("error", `Upload failed: ${error.message}`); + }, + onProgress: (bytesUploaded, bytesTotal) => { + const percentage = ((bytesUploaded / bytesTotal) * 100).toFixed( + 2 + ); + + this.handleShowAlert( + "info", + `Uploading ${file.name}: ${percentage}%` + ); + this.requestUpdate(); + }, + onSuccess: () => { + this.handleShowAlert("success", `File(s) Uploaded Successfully`); + }, + }); + + upload.findPreviousUploads().then((uploads) => { + if (uploads.length > 0) { + upload.resumeFromPreviousUpload(uploads[0]); + } + }); + + upload.start(); + }) + .catch((error) => { + // TO-DO: better error message display + this.handleShowAlert( + "error", + `An error occurred while initializing the upload: ${error.message}` + ); + }); + }); + } + + private renderSwitchTemplate(): TemplateResult { + const content = html` + + this.dispatchEvent(new CustomEvent("ecc-change", this.eventData()))} + @sl-invalid=${() => + this.dispatchEvent(new CustomEvent("ecc-invalid", this.eventData()))} + > + ${this.label} + + `; + + return html` +
+ ${renderInTooltip(content, this.tooltip)} +
+ `; + } + + private renderInputTemplate(): TemplateResult { + const label = html` `; + + return html` + + this.dispatchEvent(new CustomEvent("ecc-change", this.eventData()))} + @sl-invalid=${() => + this.dispatchEvent(new CustomEvent("ecc-invalid", this.eventData()))} + > + ${renderInTooltip(label, this.tooltip)} + + `; + } + + private renderFileTemplate(): TemplateResult { + const label = html` +
+ +
+ `; + + return html` +
+ ${renderInTooltip(label, this.tooltip)} + +
+ `; + } + + private renderSelectTemplate(): TemplateResult { + const label = html` + + `; + + const getSelectValue = () => + this.multiple && this.value && Array.isArray(this.value) + ? this.value.join(" ") + : this.value; + + const getOptionLabelAndValue = (str: string, labelAttr = true) => { + const values = str.split(/(? 1) { + return labelAttr ? values[0] : _.kebabCase(values[1]); + } + + return labelAttr ? values[0] : _.kebabCase(values[0]); + }; + + return html` +
+ ${renderInTooltip(label, this.tooltip)} + + this.dispatchEvent(new CustomEvent("ecc-change", this.eventData()))} + @sl-invalid=${() => + this.dispatchEvent( + new CustomEvent("ecc-invalid", this.eventData()) + )} + @sl-show=${() => + this.dispatchEvent(new CustomEvent("ecc-show", this.eventData()))} + @sl-after-show=${() => + this.dispatchEvent( + new CustomEvent("ecc-after-show", this.eventData()) + )} + @sl-hide=${() => + this.dispatchEvent( + new CustomEvent("ecc-after-show", this.eventData()) + )} + @sl-after-hide=${() => + this.dispatchEvent( + new CustomEvent("ecc-after-show", this.eventData()) + )} + > + ${repeat( + removeDuplicates(this.options), + (opt) => html` + + ${getOptionLabelAndValue(opt)} + + ` + )} + +
+ `; + } + + private renderTemplate(): TemplateResult { + const { type } = this; + if (type === "switch") return this.renderSwitchTemplate(); + if (type === "file") return this.renderFileTemplate(); + if (type === "select") return this.renderSelectTemplate(); + + return this.renderInputTemplate(); + } + + render() { + return html` + + ${this.alertType === "error" + ? html`` + : html` `} + ${this.alertText} + + + ${this.renderTemplate()} + `; + } +} + +window.customElements.define("ecc-d-form-input", EccUtilsDesignFormInput); + +declare global { + interface HTMLElementTagNameMap { + "ecc-d-form-input": EccUtilsDesignFormInput; + } +} diff --git a/packages/ecc-utils-design/src/components/form/index.ts b/packages/ecc-utils-design/src/components/form/index.ts index 8cfa1aa8..794965c6 100644 --- a/packages/ecc-utils-design/src/components/form/index.ts +++ b/packages/ecc-utils-design/src/components/form/index.ts @@ -1,12 +1,9 @@ import EccUtilsDesignForm from "./form.js"; +import EccUtilsDesignFormInput from "./formInput.js"; +import EccUtilsDesignFormGroup from "./formGroup.js"; export * from "./form.js"; -export default EccUtilsDesignForm; +export * from "./formInput.js"; +export * from "./formGroup.js"; -window.customElements.define("ecc-utils-design-form", EccUtilsDesignForm); - -declare global { - interface HTMLElementTagNameMap { - "ecc-utils-design-form": EccUtilsDesignForm; - } -} +export { EccUtilsDesignFormInput, EccUtilsDesignFormGroup, EccUtilsDesignForm }; diff --git a/packages/ecc-utils-design/src/components/form/utils.ts b/packages/ecc-utils-design/src/components/form/utils.ts new file mode 100644 index 00000000..c124b7f3 --- /dev/null +++ b/packages/ecc-utils-design/src/components/form/utils.ts @@ -0,0 +1,119 @@ +import { html, TemplateResult } from "lit"; + +export function renderInTooltip( + content: TemplateResult, + tooltipText: string +): TemplateResult { + return tooltipText.trim() + ? html` + + ${content} + + ` + : content; +} + +export function devWarn(message: string): void { + // eslint-disable-next-line turbo/no-undeclared-env-vars + if (process.env.NODE_ENV === "development") { + console.warn(message); + } +} + +export function noKeyWarning(Element: string, label: string): void { + console.warn( + `${Element}: Key attribute is required. We will auto generate a key from label but cannot guarantee uniqness. To ensure optimal functionality Please add a key for this field: ${label}` + ); +} + +export function generateUniqueKey() { + return `${Date.now()}-${Math.random().toString(36).substring(2, 9)}`; +} + +export const findFieldPath = ( + key: string, + element: HTMLElement | null, + isGroup = false +): string | null => { + const gropEl = findNearestFormGroup(element, isGroup); + + if (!gropEl) return null; + + const parentPath = gropEl?.getAttribute("path"); + return parentPath ? `${parentPath}.${key}` : key; +}; + +export const findNearestFormGroup = ( + element: HTMLElement | null, + isGroup = false +): Element | null => { + if (!element) return null; + + const topLevelElement = element.closest("ecc-d-form, ecc-d-form-group"); + if ( + topLevelElement && + element.shadowRoot && + (topLevelElement.matches("ecc-d-form") || + (!isGroup && topLevelElement.matches("ecc-d-form-group"))) + ) { + return null; + } + + const specialElement = element.closest( + "[ecc-array], [ecc-group], [ecc-form]" + ); + + if (specialElement) { + return specialElement; + } + + return element.parentElement + ? findNearestFormGroup(element.parentElement, isGroup) + : null; +}; + +export const removeDuplicates = (arr: string[]) => { + const lowercaseMap = new Map(); + arr.forEach((item) => { + if (!item) return; + lowercaseMap.set(item.toLowerCase(), item); + }); + + return Array.from(lowercaseMap.values()); +}; + +export const setupCustomInputs = ( + inputs: NodeListOf | undefined +) => { + inputs?.forEach((input) => { + // if the path has already been set don't set it again + if (input.getAttribute("ecc-input-path")) return; + // if it has children then it's not an input but one or more of its children may be + if (input.hasChildNodes()) { + setupCustomInputs(input.childNodes as NodeListOf); + } + // if it is not an input return + if (typeof input.value === "undefined") return; + + const key = input.getAttribute("ecc-key"); + if (!key) return; + + const path = findFieldPath(key, input as HTMLElement); + input.setAttribute("ecc-d-input-path", path || ""); + + input.addEventListener("input", () => { + input.dispatchEvent( + new CustomEvent("ecc-input", { + detail: { + key, + path, + target: input, + value: input.value, + }, + bubbles: true, + composed: true, + }) + ); + }); + }); +}; diff --git a/packages/ecc-utils-design/src/components/index.ts b/packages/ecc-utils-design/src/components/index.ts index d4a4d22c..91639cd5 100644 --- a/packages/ecc-utils-design/src/components/index.ts +++ b/packages/ecc-utils-design/src/components/index.ts @@ -1,4 +1,4 @@ -export { default as EccUtilsDesignForm } from "./form/index.js"; -export { default as EccUtilsDesignCollection } from "./collection/index.js"; -export { default as EccUtilsDesignDetails } from "./details/index.js"; -export { default as EccUtilsDesignCode } from "./code/index.js"; +export * from "./form/index.js"; +export * from "./code/index.js"; +export * from "./collection/index.js"; +export * from "./details/index.js"; diff --git a/packages/ecc-utils-design/src/events/ecc-after-show.ts b/packages/ecc-utils-design/src/events/ecc-after-show.ts new file mode 100644 index 00000000..c24bd9fc --- /dev/null +++ b/packages/ecc-utils-design/src/events/ecc-after-show.ts @@ -0,0 +1,7 @@ +export type EccAfterShowEvent = CustomEvent>; + +declare global { + interface GlobalEventHandlersEventMap { + "ecc-after-show": EccAfterShowEvent; + } +} diff --git a/packages/ecc-utils-design/src/events/ecc-array-add.ts b/packages/ecc-utils-design/src/events/ecc-array-add.ts new file mode 100644 index 00000000..8c76331a --- /dev/null +++ b/packages/ecc-utils-design/src/events/ecc-array-add.ts @@ -0,0 +1,7 @@ +export type EccArrayAddEvent = CustomEvent>; + +declare global { + interface GlobalEventHandlersEventMap { + "ecc-array-add": EccArrayAddEvent; + } +} diff --git a/packages/ecc-utils-design/src/events/ecc-array-delete.ts b/packages/ecc-utils-design/src/events/ecc-array-delete.ts new file mode 100644 index 00000000..dca02184 --- /dev/null +++ b/packages/ecc-utils-design/src/events/ecc-array-delete.ts @@ -0,0 +1,7 @@ +export type EccArrayDeleteEvent = CustomEvent>; + +declare global { + interface GlobalEventHandlersEventMap { + "ecc-array-delete": EccArrayDeleteEvent; + } +} diff --git a/packages/ecc-utils-design/src/events/ecc-button-click.ts b/packages/ecc-utils-design/src/events/ecc-button-click.ts new file mode 100644 index 00000000..db3f538a --- /dev/null +++ b/packages/ecc-utils-design/src/events/ecc-button-click.ts @@ -0,0 +1,7 @@ +export type EccButtonClickEvent = CustomEvent>; + +declare global { + interface GlobalEventHandlersEventMap { + "ecc-button-click": EccButtonClickEvent; + } +} diff --git a/packages/ecc-utils-design/src/events/ecc-change.ts b/packages/ecc-utils-design/src/events/ecc-change.ts new file mode 100644 index 00000000..a7346634 --- /dev/null +++ b/packages/ecc-utils-design/src/events/ecc-change.ts @@ -0,0 +1,7 @@ +export type EccChangeEvent = CustomEvent>; + +declare global { + interface GlobalEventHandlersEventMap { + "ecc-change": EccChangeEvent; + } +} diff --git a/packages/ecc-utils-design/src/events/ecc-clear.ts b/packages/ecc-utils-design/src/events/ecc-clear.ts new file mode 100644 index 00000000..838f3b09 --- /dev/null +++ b/packages/ecc-utils-design/src/events/ecc-clear.ts @@ -0,0 +1,7 @@ +export type EccClearEvent = CustomEvent>; + +declare global { + interface GlobalEventHandlersEventMap { + "ecc-clear": EccClearEvent; + } +} diff --git a/packages/ecc-utils-design/src/events/ecc-expand.ts b/packages/ecc-utils-design/src/events/ecc-expand.ts new file mode 100644 index 00000000..8c482a72 --- /dev/null +++ b/packages/ecc-utils-design/src/events/ecc-expand.ts @@ -0,0 +1,7 @@ +export type EccExpandEvent = CustomEvent<{ key?: number }>; + +declare global { + interface GlobalEventHandlersEventMap { + "ecc-expand": EccExpandEvent; + } +} diff --git a/packages/ecc-utils-design/src/events/ecc-filter.ts b/packages/ecc-utils-design/src/events/ecc-filter.ts new file mode 100644 index 00000000..6d8b3572 --- /dev/null +++ b/packages/ecc-utils-design/src/events/ecc-filter.ts @@ -0,0 +1,7 @@ +export type EccFilterEvent = CustomEvent<{ key: string; value: string }>; + +declare global { + interface GlobalEventHandlersEventMap { + "ecc-filter": EccFilterEvent; + } +} diff --git a/packages/ecc-utils-design/src/events/ecc-input.ts b/packages/ecc-utils-design/src/events/ecc-input.ts new file mode 100644 index 00000000..49612b59 --- /dev/null +++ b/packages/ecc-utils-design/src/events/ecc-input.ts @@ -0,0 +1,7 @@ +export type EccInputEvent = CustomEvent>; + +declare global { + interface GlobalEventHandlersEventMap { + "ecc-input": EccInputEvent; + } +} diff --git a/packages/ecc-utils-design/src/events/ecc-invalid.ts b/packages/ecc-utils-design/src/events/ecc-invalid.ts new file mode 100644 index 00000000..24086cac --- /dev/null +++ b/packages/ecc-utils-design/src/events/ecc-invalid.ts @@ -0,0 +1,7 @@ +export type EccInvalidEvent = CustomEvent>; + +declare global { + interface GlobalEventHandlersEventMap { + "ecc-invalid": EccInvalidEvent; + } +} diff --git a/packages/ecc-utils-design/src/events/ecc-page-change.ts b/packages/ecc-utils-design/src/events/ecc-page-change.ts new file mode 100644 index 00000000..6e403b7d --- /dev/null +++ b/packages/ecc-utils-design/src/events/ecc-page-change.ts @@ -0,0 +1,7 @@ +export type EccPageChangeEvent = CustomEvent<{ page: number }>; + +declare global { + interface GlobalEventHandlersEventMap { + "ecc-page-change": EccPageChangeEvent; + } +} diff --git a/packages/ecc-utils-design/src/events/ecc-show.ts b/packages/ecc-utils-design/src/events/ecc-show.ts new file mode 100644 index 00000000..3f184c33 --- /dev/null +++ b/packages/ecc-utils-design/src/events/ecc-show.ts @@ -0,0 +1,7 @@ +export type EccShowEvent = CustomEvent>; + +declare global { + interface GlobalEventHandlersEventMap { + "ecc-show": EccShowEvent; + } +} diff --git a/packages/ecc-utils-design/src/events/ecc-submit.ts b/packages/ecc-utils-design/src/events/ecc-submit.ts new file mode 100644 index 00000000..e3add214 --- /dev/null +++ b/packages/ecc-utils-design/src/events/ecc-submit.ts @@ -0,0 +1,7 @@ +export type EccSubmitEvent = CustomEvent<{ form: object }>; + +declare global { + interface GlobalEventHandlersEventMap { + "ecc-submit": EccSubmitEvent; + } +} diff --git a/packages/ecc-utils-design/src/events/ecc-utils-button-click.ts b/packages/ecc-utils-design/src/events/ecc-utils-button-click.ts deleted file mode 100644 index e018bcf2..00000000 --- a/packages/ecc-utils-design/src/events/ecc-utils-button-click.ts +++ /dev/null @@ -1,7 +0,0 @@ -export type EccUtilsButtonClickEvent = CustomEvent>; - -declare global { - interface GlobalEventHandlersEventMap { - "ecc-utils-button-click": EccUtilsButtonClickEvent; - } -} diff --git a/packages/ecc-utils-design/src/events/ecc-utils-change.ts b/packages/ecc-utils-design/src/events/ecc-utils-change.ts deleted file mode 100644 index 5b0f615f..00000000 --- a/packages/ecc-utils-design/src/events/ecc-utils-change.ts +++ /dev/null @@ -1,7 +0,0 @@ -export type EccUtilsChangeEvent = CustomEvent>; - -declare global { - interface GlobalEventHandlersEventMap { - "ecc-utils-change": EccUtilsChangeEvent; - } -} diff --git a/packages/ecc-utils-design/src/events/ecc-utils-expand.ts b/packages/ecc-utils-design/src/events/ecc-utils-expand.ts deleted file mode 100644 index 7f1d57c1..00000000 --- a/packages/ecc-utils-design/src/events/ecc-utils-expand.ts +++ /dev/null @@ -1,7 +0,0 @@ -export type EccUtilsExpandEvent = CustomEvent<{ key?: number }>; - -declare global { - interface GlobalEventHandlersEventMap { - "ecc-utils-expand": EccUtilsExpandEvent; - } -} diff --git a/packages/ecc-utils-design/src/events/ecc-utils-filter.ts b/packages/ecc-utils-design/src/events/ecc-utils-filter.ts deleted file mode 100644 index d109c78f..00000000 --- a/packages/ecc-utils-design/src/events/ecc-utils-filter.ts +++ /dev/null @@ -1,7 +0,0 @@ -export type EccUtilsFilterEvent = CustomEvent<{ key: string; value: string }>; - -declare global { - interface GlobalEventHandlersEventMap { - "ecc-utils-filter": EccUtilsFilterEvent; - } -} diff --git a/packages/ecc-utils-design/src/events/ecc-utils-page-change.ts b/packages/ecc-utils-design/src/events/ecc-utils-page-change.ts deleted file mode 100644 index ba7426e3..00000000 --- a/packages/ecc-utils-design/src/events/ecc-utils-page-change.ts +++ /dev/null @@ -1,7 +0,0 @@ -export type EccUtilsPageChangeEvent = CustomEvent<{ page: number }>; - -declare global { - interface GlobalEventHandlersEventMap { - "ecc-utils-page-change": EccUtilsPageChangeEvent; - } -} diff --git a/packages/ecc-utils-design/src/events/ecc-utils-submit.ts b/packages/ecc-utils-design/src/events/ecc-utils-submit.ts deleted file mode 100644 index b7b8cf03..00000000 --- a/packages/ecc-utils-design/src/events/ecc-utils-submit.ts +++ /dev/null @@ -1,7 +0,0 @@ -export type EccUtilsSubmitEvent = CustomEvent<{ form: object }>; - -declare global { - interface GlobalEventHandlersEventMap { - "ecc-utils-submit": EccUtilsSubmitEvent; - } -} diff --git a/packages/ecc-utils-design/src/events/index.ts b/packages/ecc-utils-design/src/events/index.ts index 0dbf6d83..d6706992 100644 --- a/packages/ecc-utils-design/src/events/index.ts +++ b/packages/ecc-utils-design/src/events/index.ts @@ -1,6 +1,13 @@ -export type { EccUtilsSubmitEvent } from "./ecc-utils-submit.js"; -export type { EccUtilsExpandEvent } from "./ecc-utils-expand.js"; -export type { EccUtilsFilterEvent } from "./ecc-utils-filter.js"; -export type { EccUtilsPageChangeEvent } from "./ecc-utils-page-change.js"; -export type { EccUtilsButtonClickEvent } from "./ecc-utils-button-click.js"; -export type { EccUtilsChangeEvent } from "./ecc-utils-change.js"; +export type { EccSubmitEvent } from "./ecc-submit.js"; +export type { EccExpandEvent } from "./ecc-expand.js"; +export type { EccFilterEvent } from "./ecc-filter.js"; +export type { EccPageChangeEvent } from "./ecc-page-change.js"; +export type { EccButtonClickEvent } from "./ecc-button-click.js"; +export type { EccChangeEvent } from "./ecc-change.js"; +export type { EccArrayAddEvent } from "./ecc-array-add.js"; +export type { EccArrayDeleteEvent } from "./ecc-array-delete.js"; +export type { EccInputEvent } from "./ecc-input.js"; +export type { EccAfterShowEvent } from "./ecc-after-show.js"; +export type { EccClearEvent } from "./ecc-clear.js"; +export type { EccInvalidEvent } from "./ecc-invalid.js"; +export type { EccShowEvent } from "./ecc-show.js"; diff --git a/scripts/templates/custom-elements-manifest.config.js b/scripts/templates/custom-elements-manifest.config.js index 81fe8669..ceb08cda 100644 --- a/scripts/templates/custom-elements-manifest.config.js +++ b/scripts/templates/custom-elements-manifest.config.js @@ -4,7 +4,6 @@ import fs from "fs"; import { program } from "commander"; import { customElementVsCodePlugin } from "custom-element-vs-code-integration"; import { customElementJetBrainsPlugin } from "custom-element-jet-brains-integration"; -import packageJson from "./package.json" assert { type: "json" }; const options = program .option("-o, --outdir ") @@ -13,9 +12,16 @@ const options = program .parse() .opts(); -const componentsPrefix = packageJson.componentsPrefix; const packageData = JSON.parse(fs.readFileSync("package.json", "utf8")); -const { name, description, version, author, homepage, license } = packageData; +const { + name, + description, + version, + author, + homepage, + license, + componentsPrefix, +} = packageData; const getComponentDocumentation = (tag) => `https://elixir-cloud-components.vercel.app/design/components/${tag.replace( @@ -63,6 +69,8 @@ export default { ?.map((jsDoc) => jsDoc.getFullText()) .join("\n"); + console.log("class docs", classDoc.events); + if (classDoc?.events) { classDoc.events.forEach((event) => { // eslint-disable-next-line no-undef diff --git a/turbo.json b/turbo.json index f1f6e2f2..54a4015d 100644 --- a/turbo.json +++ b/turbo.json @@ -1,7 +1,7 @@ { "$schema": "https://turbo.build/schema.json", - "globalDependencies": ["**/.env.*local"], - "pipeline": { + "globalDependencies": ["**/.env.*local", "NODE_ENV"], + "tasks": { "topo": { "dependsOn": ["^topo"] },