slicejs-cli 3.5.1 → 3.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +81 -26
- package/client.js +73 -23
- package/commands/buildProduction/buildProduction.js +6 -3
- package/commands/doctor/doctor.js +68 -3
- package/commands/getComponent/getComponent.js +33 -25
- package/commands/init/init.js +176 -49
- package/commands/utils/PackageManager.js +148 -0
- package/commands/utils/VersionChecker.js +6 -4
- package/commands/utils/sliceScripts.js +23 -0
- package/commands/utils/updateManager.js +54 -35
- package/package.json +12 -1
- package/post.js +13 -19
- package/.github/ISSUE_TEMPLATE/bug_report.md +0 -29
- package/.github/ISSUE_TEMPLATE/feature_request.md +0 -25
- package/.github/pull_request_template.md +0 -22
- package/.github/workflows/ci.yml +0 -43
- package/AGENTS.md +0 -247
- package/CODE_OF_CONDUCT.md +0 -126
- package/ECOSYSTEM.md +0 -9
- package/docs/superpowers/specs/2026-05-10-pwa-generate-design.md +0 -182
- package/playwright.config.js +0 -51
- package/tests/build-command-integration.test.js +0 -87
- package/tests/build-production-e2e.test.js +0 -140
- package/tests/builder-edge-cases.test.js +0 -322
- package/tests/bundle-generate-e2e.test.js +0 -115
- package/tests/bundle-generator.test.js +0 -691
- package/tests/bundle-v2-register-output.test.js +0 -470
- package/tests/bundling-dependency-edges.test.js +0 -127
- package/tests/bundling-imports-unit.test.js +0 -267
- package/tests/client-launcher-contract.test.js +0 -211
- package/tests/client-update-flow-contract.test.js +0 -272
- package/tests/commands-component-crud.test.js +0 -102
- package/tests/commands-doctor.test.js +0 -80
- package/tests/commands-version-checker.test.js +0 -37
- package/tests/component-registry-parse.test.js +0 -34
- package/tests/dependency-analyzer.test.js +0 -24
- package/tests/e2e/bundles.spec.js +0 -91
- package/tests/e2e/dependency-scenarios.spec.js +0 -56
- package/tests/e2e/fixtures/components/Service/FetchManager/FetchManager.js +0 -136
- package/tests/e2e/fixtures/components/Service/IndexedDbManager/IndexedDbManager.js +0 -149
- package/tests/e2e/fixtures/components/Service/LocalStorageManager/LocalStorageManager.js +0 -45
- package/tests/e2e/fixtures/components/Visual/Button/Button.css +0 -106
- package/tests/e2e/fixtures/components/Visual/Button/Button.html +0 -5
- package/tests/e2e/fixtures/components/Visual/Button/Button.js +0 -158
- package/tests/e2e/fixtures/components/Visual/Link/Link.js +0 -33
- package/tests/e2e/fixtures/components/Visual/Loading/Loading.css +0 -56
- package/tests/e2e/fixtures/components/Visual/Loading/Loading.html +0 -83
- package/tests/e2e/fixtures/components/Visual/Loading/Loading.js +0 -164
- package/tests/e2e/fixtures/components/Visual/MultiRoute/MultiRoute.js +0 -167
- package/tests/e2e/fixtures/components/Visual/Navbar/Navbar.css +0 -116
- package/tests/e2e/fixtures/components/Visual/Navbar/Navbar.html +0 -44
- package/tests/e2e/fixtures/components/Visual/Navbar/Navbar.js +0 -180
- package/tests/e2e/fixtures/components/Visual/NotFound/NotFound.js +0 -20
- package/tests/e2e/fixtures/components/Visual/Route/Route.js +0 -181
- package/tests/e2e/fixtures/components/registry.json +0 -12
- package/tests/e2e/fixtures/vendor-components.mjs +0 -65
- package/tests/e2e/navigation.spec.js +0 -44
- package/tests/e2e/render.spec.js +0 -34
- package/tests/e2e/serve.mjs +0 -264
- package/tests/e2e/shared-deps.spec.js +0 -61
- package/tests/e2e/unminified.spec.js +0 -33
- package/tests/e2e-serve.test.js +0 -148
- package/tests/fixtures/components.js +0 -8
- package/tests/fixtures/sliceConfig.json +0 -74
- package/tests/getcomponent.test.js +0 -407
- package/tests/helpers/setup.js +0 -102
- package/tests/init-command-contract.test.js +0 -46
- package/tests/local-cli-delegation.test.js +0 -81
- package/tests/path-helper.test.js +0 -206
- package/tests/perf-budget.test.js +0 -86
- package/tests/postinstall-command.test.js +0 -72
- package/tests/types-breakage.test.js +0 -491
- package/tests/types-generator-errors.test.js +0 -361
- package/tests/types-generator.test.js +0 -346
- package/tests/update-manager-notifications.test.js +0 -88
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import { test, expect } from '@playwright/test';
|
|
2
|
-
|
|
3
|
-
function trackErrors(page) {
|
|
4
|
-
const errors = [];
|
|
5
|
-
page.on('console', (m) => m.type() === 'error' && errors.push(m.text()));
|
|
6
|
-
page.on('pageerror', (e) => errors.push(String(e)));
|
|
7
|
-
return errors;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
test.describe('default-export shared dependency', () => {
|
|
11
|
-
test('/defaultdep resolves a default export at runtime', async ({ page }) => {
|
|
12
|
-
const errors = trackErrors(page);
|
|
13
|
-
await page.goto('/defaultdep');
|
|
14
|
-
|
|
15
|
-
await expect(page.locator('slice-defaultdeppage')).toBeAttached();
|
|
16
|
-
await expect(page.locator('slice-defaultdeppage')).toHaveAttribute('data-cfg-title', 'Configured');
|
|
17
|
-
await expect(page.locator('slice-defaultdeppage')).toHaveAttribute(
|
|
18
|
-
'data-cfg-tagline',
|
|
19
|
-
'default-export-works'
|
|
20
|
-
);
|
|
21
|
-
|
|
22
|
-
expect(errors, `console errors:\n${errors.join('\n')}`).toEqual([]);
|
|
23
|
-
});
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
test.describe('component CSS application', () => {
|
|
27
|
-
test('/cssprobe applies the bundled component stylesheet', async ({ page }) => {
|
|
28
|
-
const errors = trackErrors(page);
|
|
29
|
-
await page.goto('/cssprobe');
|
|
30
|
-
|
|
31
|
-
const marker = page.locator('slice-cssprobepage .css-probe-marker');
|
|
32
|
-
await expect(marker).toBeVisible();
|
|
33
|
-
await expect(marker).toHaveCSS('color', 'rgb(7, 113, 219)');
|
|
34
|
-
await expect(marker).toHaveCSS('font-weight', '700');
|
|
35
|
-
|
|
36
|
-
expect(errors, `console errors:\n${errors.join('\n')}`).toEqual([]);
|
|
37
|
-
});
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
test.describe('transitive dependency of a shared module', () => {
|
|
41
|
-
// mid.js (imported by the component) itself imports leaf.js. The bundler must
|
|
42
|
-
// recursively inline leaf.js and bind its exports inside mid's scope, or the
|
|
43
|
-
// page breaks at runtime.
|
|
44
|
-
test('/transitive renders and the transitively-imported helper works', async ({ page }) => {
|
|
45
|
-
const errors = trackErrors(page);
|
|
46
|
-
await page.goto('/transitive');
|
|
47
|
-
|
|
48
|
-
await expect(page.locator('slice-transitivepage')).toBeAttached();
|
|
49
|
-
await expect(page.locator('slice-transitivepage')).toHaveAttribute(
|
|
50
|
-
'data-transitive',
|
|
51
|
-
'mid(leaf-value)[leaf:leaf-value]'
|
|
52
|
-
);
|
|
53
|
-
|
|
54
|
-
expect(errors, `console errors:\n${errors.join('\n')}`).toEqual([]);
|
|
55
|
-
});
|
|
56
|
-
});
|
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
export default class FetchManager {
|
|
2
|
-
constructor(props) {
|
|
3
|
-
const { baseUrl, timeout } = props;
|
|
4
|
-
if (baseUrl !== undefined) {
|
|
5
|
-
this.baseUrl = baseUrl;
|
|
6
|
-
}
|
|
7
|
-
this.methods = ['GET', 'POST', 'PUT', 'DELETE'];
|
|
8
|
-
this.lastRequest = null;
|
|
9
|
-
this.cacheEnabled = false;
|
|
10
|
-
this.defaultHeaders = {};
|
|
11
|
-
timeout ? (this.timeout = timeout) : (this.timeout = 10000);
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
async request(
|
|
15
|
-
method,
|
|
16
|
-
data,
|
|
17
|
-
endpoint,
|
|
18
|
-
onRequestSuccess,
|
|
19
|
-
onRequestError,
|
|
20
|
-
refetchOnError = false,
|
|
21
|
-
requestOptions = {}
|
|
22
|
-
) {
|
|
23
|
-
if (!this.methods.includes(method)) throw new Error('Invalid method');
|
|
24
|
-
if (data && typeof data !== 'object') throw new Error('Invalid data, not JSON');
|
|
25
|
-
const controller = new AbortController();
|
|
26
|
-
|
|
27
|
-
let options;
|
|
28
|
-
if (method !== 'GET') {
|
|
29
|
-
options = {
|
|
30
|
-
method: method,
|
|
31
|
-
headers: {
|
|
32
|
-
'Content-Type': 'application/json',
|
|
33
|
-
...this.defaultHeaders,
|
|
34
|
-
...requestOptions.headers,
|
|
35
|
-
},
|
|
36
|
-
signal: controller.signal,
|
|
37
|
-
};
|
|
38
|
-
} else {
|
|
39
|
-
options = {
|
|
40
|
-
method: method,
|
|
41
|
-
headers: {
|
|
42
|
-
...this.defaultHeaders,
|
|
43
|
-
...requestOptions.headers,
|
|
44
|
-
},
|
|
45
|
-
signal: controller.signal,
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
if (data) {
|
|
50
|
-
options.body = JSON.stringify(data);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
let loading;
|
|
54
|
-
if (!slice.controller.getComponent('Loading')) {
|
|
55
|
-
loading = await slice.build('Loading', { sliceId: 'Loading' });
|
|
56
|
-
} else {
|
|
57
|
-
loading = slice.controller.getComponent('Loading');
|
|
58
|
-
}
|
|
59
|
-
loading.start();
|
|
60
|
-
const timeoutId = setTimeout(() => controller.abort(), this.timeout || 10000);
|
|
61
|
-
|
|
62
|
-
try {
|
|
63
|
-
let response;
|
|
64
|
-
|
|
65
|
-
// Check if cache is enabled and a cached response exists
|
|
66
|
-
if (this.cacheEnabled && this.lastRequest && this.lastRequest.endpoint === endpoint) {
|
|
67
|
-
loading.stop();
|
|
68
|
-
return this.lastRequest.output;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
if (this.baseUrl !== undefined) {
|
|
72
|
-
response = await fetch(this.baseUrl + endpoint, options);
|
|
73
|
-
} else {
|
|
74
|
-
response = await fetch(endpoint, options);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
if (response.ok) {
|
|
78
|
-
if (typeof onRequestSuccess === 'function') {
|
|
79
|
-
onRequestSuccess(data, response);
|
|
80
|
-
}
|
|
81
|
-
} else {
|
|
82
|
-
if (typeof onRequestError === 'function') {
|
|
83
|
-
onRequestError(data, response);
|
|
84
|
-
}
|
|
85
|
-
if (refetchOnError) {
|
|
86
|
-
// Retry once on error; pass false to avoid infinite recursion when
|
|
87
|
-
// the endpoint keeps failing.
|
|
88
|
-
return await this.request(
|
|
89
|
-
method,
|
|
90
|
-
data,
|
|
91
|
-
endpoint,
|
|
92
|
-
onRequestSuccess,
|
|
93
|
-
onRequestError,
|
|
94
|
-
false,
|
|
95
|
-
requestOptions
|
|
96
|
-
);
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
let output = await response.json();
|
|
101
|
-
loading.stop();
|
|
102
|
-
|
|
103
|
-
// Cache the parsed response if cache is enabled
|
|
104
|
-
if (this.cacheEnabled) {
|
|
105
|
-
this.lastRequest = { data, output, endpoint };
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
return output;
|
|
109
|
-
} catch (error) {
|
|
110
|
-
// slice.logger.logError signature is (componentSliceId, message, error).
|
|
111
|
-
if (error.message === 'Failed to fetch') {
|
|
112
|
-
slice.logger.logError('FetchManager', 'Lost internet connection', error);
|
|
113
|
-
} else {
|
|
114
|
-
slice.logger.logError('FetchManager', 'Request failed', error);
|
|
115
|
-
}
|
|
116
|
-
loading.stop();
|
|
117
|
-
throw error;
|
|
118
|
-
} finally {
|
|
119
|
-
clearTimeout(timeoutId);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// Enable or disable caching of responses
|
|
124
|
-
enableCache() {
|
|
125
|
-
this.cacheEnabled = true;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
disableCache() {
|
|
129
|
-
this.cacheEnabled = false;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// Set default headers for all requests
|
|
133
|
-
setDefaultHeaders(headers) {
|
|
134
|
-
this.defaultHeaders = headers;
|
|
135
|
-
}
|
|
136
|
-
}
|
|
@@ -1,149 +0,0 @@
|
|
|
1
|
-
export default class IndexedDbManager {
|
|
2
|
-
constructor(props = {}) {
|
|
3
|
-
// Slice builds services with a single props object, e.g.
|
|
4
|
-
// slice.build('IndexedDbManager', { databaseName, storeName }). The legacy
|
|
5
|
-
// positional form `new IndexedDbManager(dbName, storeName)` still works.
|
|
6
|
-
if (typeof props === 'string') {
|
|
7
|
-
this.databaseName = props;
|
|
8
|
-
this.storeName = arguments[1];
|
|
9
|
-
} else {
|
|
10
|
-
this.databaseName = props.databaseName;
|
|
11
|
-
this.storeName = props.storeName;
|
|
12
|
-
}
|
|
13
|
-
this.db = null;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
async openDatabase() {
|
|
17
|
-
return new Promise((resolve, reject) => {
|
|
18
|
-
const request = indexedDB.open(this.databaseName);
|
|
19
|
-
|
|
20
|
-
request.onupgradeneeded = (event) => {
|
|
21
|
-
const db = event.target.result;
|
|
22
|
-
if (!db.objectStoreNames.contains(this.storeName)) {
|
|
23
|
-
db.createObjectStore(this.storeName, {
|
|
24
|
-
keyPath: 'id',
|
|
25
|
-
autoIncrement: true,
|
|
26
|
-
});
|
|
27
|
-
}
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
request.onsuccess = (event) => {
|
|
31
|
-
this.db = event.target.result;
|
|
32
|
-
resolve(this.db);
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
request.onerror = (event) => {
|
|
36
|
-
reject(new Error(`Error opening IndexedDB: ${event.target.error}`));
|
|
37
|
-
};
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
closeDatabase() {
|
|
42
|
-
if (this.db) {
|
|
43
|
-
this.db.close();
|
|
44
|
-
this.db = null;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
async addItem(item) {
|
|
49
|
-
const db = await this.openDatabase();
|
|
50
|
-
return new Promise((resolve, reject) => {
|
|
51
|
-
const transaction = db.transaction([this.storeName], 'readwrite');
|
|
52
|
-
const store = transaction.objectStore(this.storeName);
|
|
53
|
-
const request = store.add(item);
|
|
54
|
-
|
|
55
|
-
request.onsuccess = () => {
|
|
56
|
-
resolve(request.result);
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
request.onerror = (event) => {
|
|
60
|
-
reject(new Error(`Error adding item to IndexedDB: ${event.target.error}`));
|
|
61
|
-
};
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
async updateItem(item) {
|
|
66
|
-
const db = await this.openDatabase();
|
|
67
|
-
return new Promise((resolve, reject) => {
|
|
68
|
-
const transaction = db.transaction([this.storeName], 'readwrite');
|
|
69
|
-
const store = transaction.objectStore(this.storeName);
|
|
70
|
-
const request = store.put(item);
|
|
71
|
-
|
|
72
|
-
request.onsuccess = () => {
|
|
73
|
-
resolve(request.result);
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
request.onerror = (event) => {
|
|
77
|
-
reject(new Error(`Error updating item in IndexedDB: ${event.target.error}`));
|
|
78
|
-
};
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
async getItem(id) {
|
|
83
|
-
const db = await this.openDatabase();
|
|
84
|
-
return new Promise((resolve, reject) => {
|
|
85
|
-
const transaction = db.transaction([this.storeName], 'readonly');
|
|
86
|
-
const store = transaction.objectStore(this.storeName);
|
|
87
|
-
const request = store.get(id);
|
|
88
|
-
|
|
89
|
-
request.onsuccess = () => {
|
|
90
|
-
resolve(request.result);
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
request.onerror = (event) => {
|
|
94
|
-
reject(new Error(`Error getting item from IndexedDB: ${event.target.error}`));
|
|
95
|
-
};
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
async deleteItem(id) {
|
|
100
|
-
const db = await this.openDatabase();
|
|
101
|
-
return new Promise((resolve, reject) => {
|
|
102
|
-
const transaction = db.transaction([this.storeName], 'readwrite');
|
|
103
|
-
const store = transaction.objectStore(this.storeName);
|
|
104
|
-
const request = store.delete(id);
|
|
105
|
-
|
|
106
|
-
request.onsuccess = () => {
|
|
107
|
-
resolve();
|
|
108
|
-
};
|
|
109
|
-
|
|
110
|
-
request.onerror = (event) => {
|
|
111
|
-
reject(new Error(`Error deleting item from IndexedDB: ${event.target.error}`));
|
|
112
|
-
};
|
|
113
|
-
});
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
async getAllItems() {
|
|
117
|
-
const db = await this.openDatabase();
|
|
118
|
-
return new Promise((resolve, reject) => {
|
|
119
|
-
const transaction = db.transaction([this.storeName], 'readonly');
|
|
120
|
-
const store = transaction.objectStore(this.storeName);
|
|
121
|
-
const request = store.getAll();
|
|
122
|
-
|
|
123
|
-
request.onsuccess = () => {
|
|
124
|
-
resolve(request.result);
|
|
125
|
-
};
|
|
126
|
-
|
|
127
|
-
request.onerror = (event) => {
|
|
128
|
-
reject(new Error(`Error getting items from IndexedDB: ${event.target.error}`));
|
|
129
|
-
};
|
|
130
|
-
});
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
async clearItems() {
|
|
134
|
-
const db = await this.openDatabase();
|
|
135
|
-
return new Promise((resolve, reject) => {
|
|
136
|
-
const transaction = db.transaction([this.storeName], 'readwrite');
|
|
137
|
-
const store = transaction.objectStore(this.storeName);
|
|
138
|
-
const request = store.clear();
|
|
139
|
-
|
|
140
|
-
request.onsuccess = () => {
|
|
141
|
-
resolve();
|
|
142
|
-
};
|
|
143
|
-
|
|
144
|
-
request.onerror = (event) => {
|
|
145
|
-
reject(new Error(`Error clearing items in IndexedDB: ${event.target.error}`));
|
|
146
|
-
};
|
|
147
|
-
});
|
|
148
|
-
}
|
|
149
|
-
}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
export default class LocalStorageManager {
|
|
2
|
-
constructor() {
|
|
3
|
-
// No se necesitan propiedades en este caso
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
getItem(key) {
|
|
7
|
-
try {
|
|
8
|
-
const item = localStorage.getItem(key);
|
|
9
|
-
return item ? JSON.parse(item) : null;
|
|
10
|
-
} catch (error) {
|
|
11
|
-
console.error(`Error getting item from localStorage: ${error.message}`);
|
|
12
|
-
return null;
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
setItem(key, value) {
|
|
17
|
-
try {
|
|
18
|
-
localStorage.setItem(key, JSON.stringify(value));
|
|
19
|
-
return true;
|
|
20
|
-
} catch (error) {
|
|
21
|
-
console.error(`Error setting item in localStorage: ${error.message}`);
|
|
22
|
-
return false;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
removeItem(key) {
|
|
27
|
-
try {
|
|
28
|
-
localStorage.removeItem(key);
|
|
29
|
-
return true;
|
|
30
|
-
} catch (error) {
|
|
31
|
-
console.error(`Error removing item from localStorage: ${error.message}`);
|
|
32
|
-
return false;
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
clear() {
|
|
37
|
-
try {
|
|
38
|
-
localStorage.clear();
|
|
39
|
-
return true;
|
|
40
|
-
} catch (error) {
|
|
41
|
-
console.error(`Error clearing localStorage: ${error.message}`);
|
|
42
|
-
return false;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
}
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
/* Encapsulated under the custom element so the button styles never leak. */
|
|
2
|
-
|
|
3
|
-
slice-button .slice_button_container {
|
|
4
|
-
padding: 10px;
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
slice-button .slice_button_value {
|
|
8
|
-
user-select: none;
|
|
9
|
-
cursor: pointer;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
slice-button .slice_button {
|
|
13
|
-
display: flex;
|
|
14
|
-
align-items: center;
|
|
15
|
-
justify-content: center;
|
|
16
|
-
cursor: pointer;
|
|
17
|
-
overflow: hidden;
|
|
18
|
-
position: relative;
|
|
19
|
-
max-width: fit-content;
|
|
20
|
-
background-color: var(--primary-color);
|
|
21
|
-
color: var(--primary-color-contrast);
|
|
22
|
-
border-radius: var(--border-radius-slice);
|
|
23
|
-
border: var(--slice-border) solid var(--primary-color);
|
|
24
|
-
font-weight: 800;
|
|
25
|
-
min-width: 100%;
|
|
26
|
-
padding: 10px;
|
|
27
|
-
-webkit-transition-duration: 0.4s; /* Safari */
|
|
28
|
-
transition-duration: 0.4s;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
slice-button .slice_button:focus-visible {
|
|
32
|
-
outline: none;
|
|
33
|
-
box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary-color) 45%, transparent);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
slice-button .slice_button:after {
|
|
37
|
-
content: "";
|
|
38
|
-
width: 100%;
|
|
39
|
-
height: 100%;
|
|
40
|
-
border-radius: 100%;
|
|
41
|
-
background: color-mix(in srgb, var(--primary-color-contrast) 32%, transparent);
|
|
42
|
-
position: absolute;
|
|
43
|
-
display: block;
|
|
44
|
-
opacity: 0;
|
|
45
|
-
scale: 10;
|
|
46
|
-
transition: all 1s;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
slice-button .slice_button:active {
|
|
50
|
-
transform: translateY(5px);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
slice-button .slice_button:active:after {
|
|
54
|
-
scale: 0;
|
|
55
|
-
padding: 0;
|
|
56
|
-
margin: 0;
|
|
57
|
-
opacity: 1;
|
|
58
|
-
transition: 0s;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/* ----------------------------------------------------------------- variants
|
|
62
|
-
Every variant derives from the theme tokens — they automatically follow the
|
|
63
|
-
active theme. `filled` is the original solid look; the modifiers are declared
|
|
64
|
-
after the base rule so they win on equal specificity. */
|
|
65
|
-
|
|
66
|
-
slice-button .slice_button--filled {
|
|
67
|
-
background-color: var(--primary-color);
|
|
68
|
-
color: var(--primary-color-contrast);
|
|
69
|
-
border-color: var(--primary-color);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
slice-button .slice_button--outlined {
|
|
73
|
-
background-color: transparent;
|
|
74
|
-
color: var(--primary-color);
|
|
75
|
-
border-color: var(--primary-color);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
slice-button .slice_button--ghost {
|
|
79
|
-
background-color: transparent;
|
|
80
|
-
color: var(--primary-color);
|
|
81
|
-
border-color: transparent;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
slice-button .slice_button--soft {
|
|
85
|
-
background-color: color-mix(in srgb, var(--primary-color) 16%, transparent);
|
|
86
|
-
color: var(--primary-color);
|
|
87
|
-
border-color: transparent;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/* Hover tint for the non-filled variants */
|
|
91
|
-
slice-button .slice_button--outlined:hover,
|
|
92
|
-
slice-button .slice_button--ghost:hover {
|
|
93
|
-
background-color: color-mix(in srgb, var(--primary-color) 12%, transparent);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
slice-button .slice_button--soft:hover {
|
|
97
|
-
background-color: color-mix(in srgb, var(--primary-color) 26%, transparent);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/* The ripple flash uses a primary-color tint on the light/transparent variants
|
|
101
|
-
(the base contrast flash would be invisible on them). */
|
|
102
|
-
slice-button .slice_button--outlined:after,
|
|
103
|
-
slice-button .slice_button--ghost:after,
|
|
104
|
-
slice-button .slice_button--soft:after {
|
|
105
|
-
background: color-mix(in srgb, var(--primary-color) 22%, transparent);
|
|
106
|
-
}
|
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
// Warns once per deprecated prop name (kept module-scoped so each component
|
|
2
|
-
// reports a given alias only once per session).
|
|
3
|
-
const _sliceDeprecated = new Set();
|
|
4
|
-
function deprecate(oldName, newName) {
|
|
5
|
-
if (_sliceDeprecated.has(oldName)) return;
|
|
6
|
-
_sliceDeprecated.add(oldName);
|
|
7
|
-
console.warn(`[Slice] "${oldName}" is deprecated; use "${newName}" instead.`);
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export default class Button extends HTMLElement {
|
|
11
|
-
|
|
12
|
-
static props = {
|
|
13
|
-
value: {
|
|
14
|
-
type: 'string',
|
|
15
|
-
default: 'Button',
|
|
16
|
-
required: false
|
|
17
|
-
},
|
|
18
|
-
// Visual style, all derived from theme tokens. `filled` preserves the
|
|
19
|
-
// original solid look.
|
|
20
|
-
variant: {
|
|
21
|
-
type: 'string',
|
|
22
|
-
default: 'filled',
|
|
23
|
-
required: false,
|
|
24
|
-
allowedValues: ['filled', 'outlined', 'ghost', 'soft']
|
|
25
|
-
},
|
|
26
|
-
// Canonical click handler. `onClickCallback` is kept as a deprecated alias.
|
|
27
|
-
onClick: {
|
|
28
|
-
type: 'function',
|
|
29
|
-
default: null
|
|
30
|
-
},
|
|
31
|
-
onClickCallback: {
|
|
32
|
-
type: 'function',
|
|
33
|
-
default: null
|
|
34
|
-
},
|
|
35
|
-
customColor: {
|
|
36
|
-
type: 'object',
|
|
37
|
-
default: null
|
|
38
|
-
},
|
|
39
|
-
icon: {
|
|
40
|
-
type: 'object',
|
|
41
|
-
default: null
|
|
42
|
-
}
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
constructor(props) {
|
|
46
|
-
super();
|
|
47
|
-
slice.attachTemplate(this);
|
|
48
|
-
this.$value = this.querySelector('.slice_button_value');
|
|
49
|
-
this.$button = this.querySelector('.slice_button');
|
|
50
|
-
this.$container = this.querySelector('.slice_button_container');
|
|
51
|
-
|
|
52
|
-
// type="button" prevents accidental form submission when used inside a form.
|
|
53
|
-
this.$button.setAttribute('type', 'button');
|
|
54
|
-
|
|
55
|
-
// Listener is attached unconditionally; the handler is resolved at click
|
|
56
|
-
// time so it works whether it arrived via onClick or the legacy alias.
|
|
57
|
-
this.$container.addEventListener('click', async () => {
|
|
58
|
-
if (this._onClick) await this._onClick();
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
slice.controller.setComponentProps(this, props);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
async init() {
|
|
65
|
-
if (this.icon) {
|
|
66
|
-
this.$icon = await slice.build('Icon', {
|
|
67
|
-
name: this.icon.name,
|
|
68
|
-
iconStyle: this.icon.iconStyle,
|
|
69
|
-
size: '20px',
|
|
70
|
-
color: 'currentColor',
|
|
71
|
-
});
|
|
72
|
-
this.$button.insertBefore(this.$icon, this.$value);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
get onClick() {
|
|
77
|
-
return this._onClick;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
set onClick(value) {
|
|
81
|
-
if (typeof value === 'function') this._onClick = value;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// Deprecated alias for onClick.
|
|
85
|
-
set onClickCallback(value) {
|
|
86
|
-
if (typeof value === 'function') {
|
|
87
|
-
this._onClick ??= value;
|
|
88
|
-
deprecate('onClickCallback', 'onClick');
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
get icon() {
|
|
93
|
-
return this._icon;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
set icon(value) {
|
|
97
|
-
this._icon = value;
|
|
98
|
-
if (!this.$icon) return;
|
|
99
|
-
this.$icon.name = value.name;
|
|
100
|
-
this.$icon.iconStyle = value.iconStyle;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
get variant() {
|
|
104
|
-
return this._variant;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
set variant(value) {
|
|
108
|
-
const allowed = ['filled', 'outlined', 'ghost', 'soft'];
|
|
109
|
-
const next = allowed.includes(value) ? value : 'filled';
|
|
110
|
-
if (this._variant) {
|
|
111
|
-
this.$button.classList.remove('slice_button--' + this._variant);
|
|
112
|
-
}
|
|
113
|
-
this._variant = next;
|
|
114
|
-
this.$button.classList.add('slice_button--' + next);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
get value() {
|
|
118
|
-
return this._value;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
set value(value) {
|
|
122
|
-
this._value = value;
|
|
123
|
-
this.$value.textContent = value;
|
|
124
|
-
// Keep an accessible name even for icon-only buttons.
|
|
125
|
-
if (value) this.$button.setAttribute('aria-label', value);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
get customColor() {
|
|
129
|
-
return this._customColor;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// Canonical shape: { background, text }. Legacy { button, label } still works.
|
|
133
|
-
set customColor(value) {
|
|
134
|
-
this._customColor = value;
|
|
135
|
-
if (!value) return;
|
|
136
|
-
|
|
137
|
-
if (value.button || value.label) {
|
|
138
|
-
deprecate('customColor { button, label }', 'customColor { background, text }');
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
const background = value.background ?? value.button;
|
|
142
|
-
const text = value.text ?? value.label;
|
|
143
|
-
|
|
144
|
-
if (background) {
|
|
145
|
-
this.$button.style.backgroundColor = background;
|
|
146
|
-
this.$button.style.borderColor = background;
|
|
147
|
-
}
|
|
148
|
-
if (text) {
|
|
149
|
-
this.$button.style.color = text;
|
|
150
|
-
this.$value.style.color = text;
|
|
151
|
-
if (this.$icon) {
|
|
152
|
-
this.$icon.style.color = text;
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
customElements.define('slice-button', Button);
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
export default class Link extends HTMLElement {
|
|
2
|
-
constructor(props = {}) {
|
|
3
|
-
super();
|
|
4
|
-
this.props = props;
|
|
5
|
-
this.render(props);
|
|
6
|
-
this.init();
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
init() {
|
|
10
|
-
this.addEventListener('click', this.onClick);
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
async onClick(event) {
|
|
14
|
-
event.preventDefault();
|
|
15
|
-
const path = this.querySelector('a')?.getAttribute('href');
|
|
16
|
-
if (path) slice.router.navigate(path);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
// Built with DOM APIs (setAttribute / textContent) instead of an innerHTML
|
|
20
|
-
// template so a `path` like `javascript:...` or text containing markup can't
|
|
21
|
-
// inject into the document.
|
|
22
|
-
render(props = {}) {
|
|
23
|
-
const { path = '#', classes = '', text = '' } = props;
|
|
24
|
-
const anchor = document.createElement('a');
|
|
25
|
-
anchor.setAttribute('href', path);
|
|
26
|
-
anchor.setAttribute('data-route', '');
|
|
27
|
-
if (classes) anchor.className = classes;
|
|
28
|
-
anchor.textContent = text;
|
|
29
|
-
this.replaceChildren(anchor);
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
customElements.define('slice-link', Link);
|