lapikit 0.6.11 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -30,4 +30,5 @@ export type KitComponentScan = {
30
30
  export type ComponentInfo = {
31
31
  name: string;
32
32
  ref: string;
33
+ direct?: boolean;
33
34
  };
@@ -10,41 +10,49 @@ function getCoords(e) {
10
10
  return { x: e.clientX, y: e.clientY };
11
11
  }
12
12
  export function ripple(el, options = {}) {
13
- const rippleContainer = document.createElement('div');
13
+ // created on first interaction, removed once every ripple has faded out
14
+ let rippleContainer;
14
15
  const activeRipples = new Set();
15
- addClasses();
16
- setOptions(options);
17
16
  function isAriaDisabled() {
18
17
  return el.getAttribute('aria-disabled') === 'true';
19
18
  }
20
- function addClasses(center) {
21
- const shouldBeCentered = center || options.center;
22
- if (!rippleContainer.classList.contains('kit-ripple--effect')) {
23
- rippleContainer.classList.add('kit-ripple--effect');
19
+ function applyOptions(container) {
20
+ if (options.component) {
21
+ container.style.setProperty('--system-ripple-radius', `var(--kit-${options.component}-radius)`);
24
22
  }
25
- if (!shouldBeCentered && rippleContainer.classList.contains('kit-ripple--center')) {
26
- rippleContainer.classList.remove('kit-ripple--center');
23
+ if (options.color) {
24
+ container.style.setProperty('--system-ripple-color', options.color);
27
25
  }
28
- if (shouldBeCentered) {
29
- rippleContainer.classList.add('kit-ripple--center');
26
+ const duration = resolveDuration(options);
27
+ if (duration) {
28
+ container.style.setProperty('--system-animation-ripple-duration', `${duration}ms`);
30
29
  }
31
30
  }
32
- function setOptions(newOptions) {
33
- if (newOptions.disabled || isAriaDisabled()) {
34
- rippleContainer.remove();
31
+ function mountContainer(center) {
32
+ if (!rippleContainer) {
33
+ rippleContainer = document.createElement('div');
34
+ rippleContainer.classList.add('kit-ripple--effect');
35
+ applyOptions(rippleContainer);
35
36
  }
36
- else {
37
+ rippleContainer.classList.toggle('kit-ripple--center', !!(center || options.center));
38
+ if (!rippleContainer.isConnected) {
37
39
  el.appendChild(rippleContainer);
38
40
  }
39
- if (newOptions.component) {
40
- rippleContainer.style.setProperty('--system-ripple-radius', `var(--kit-${newOptions.component}-radius)`);
41
- }
42
- if (newOptions.color) {
43
- rippleContainer.style.setProperty('--system-ripple-color', newOptions.color);
41
+ return rippleContainer;
42
+ }
43
+ function unmountContainer() {
44
+ rippleContainer?.remove();
45
+ rippleContainer = undefined;
46
+ }
47
+ function setOptions(newOptions) {
48
+ options = newOptions;
49
+ if (newOptions.disabled || isAriaDisabled()) {
50
+ activeRipples.forEach((cleanup) => cleanup());
51
+ activeRipples.clear();
52
+ unmountContainer();
44
53
  }
45
- const duration = resolveDuration(newOptions);
46
- if (duration) {
47
- rippleContainer.style.setProperty('--system-animation-ripple-duration', `${duration}ms`);
54
+ else if (rippleContainer) {
55
+ applyOptions(rippleContainer);
48
56
  }
49
57
  }
50
58
  function createRipple(e, center) {
@@ -60,7 +68,7 @@ export function ripple(el, options = {}) {
60
68
  createRipple(click, true);
61
69
  return;
62
70
  }
63
- addClasses(center);
71
+ const container = mountContainer(center);
64
72
  const rect = el.getBoundingClientRect();
65
73
  const { x: clientX, y: clientY } = getCoords(e);
66
74
  const x = clientX - rect.left > el.offsetWidth / 2 ? 0 : el.offsetWidth;
@@ -71,7 +79,7 @@ export function ripple(el, options = {}) {
71
79
  ripple.style.left = `${clientX - rect.left - radius}px`;
72
80
  ripple.style.top = `${clientY - rect.top - radius}px`;
73
81
  ripple.style.width = ripple.style.height = `${radius * 2}px`;
74
- rippleContainer.appendChild(ripple);
82
+ container.appendChild(ripple);
75
83
  let timeoutId;
76
84
  function cleanup() {
77
85
  if (timeoutId) {
@@ -80,12 +88,18 @@ export function ripple(el, options = {}) {
80
88
  cancelEvents.forEach((event) => el.removeEventListener(event, removeRipple));
81
89
  ripple.remove();
82
90
  }
91
+ function release() {
92
+ activeRipples.delete(cleanup);
93
+ if (activeRipples.size === 0) {
94
+ unmountContainer();
95
+ }
96
+ }
83
97
  function removeRipple() {
84
98
  ripple.style.opacity = '0';
85
99
  cancelEvents.forEach((event) => el.removeEventListener(event, removeRipple));
86
100
  timeoutId = setTimeout(() => {
87
101
  ripple.remove();
88
- activeRipples.delete(cleanup);
102
+ release();
89
103
  }, resolveDuration(options) || 1000);
90
104
  }
91
105
  activeRipples.add(cleanup);
@@ -99,10 +113,9 @@ export function ripple(el, options = {}) {
99
113
  });
100
114
  activeRipples.forEach((cleanup) => cleanup());
101
115
  activeRipples.clear();
102
- rippleContainer.remove();
116
+ unmountContainer();
103
117
  },
104
118
  update(newOptions) {
105
- options = newOptions;
106
119
  setOptions(newOptions);
107
120
  }
108
121
  };
@@ -158,9 +158,15 @@
158
158
  --kit-list-item-hover-bg: color-mix(in oklab, var(--kit-list-item-fg), transparent 80%);
159
159
  --kit-list-item-active-bg: color-mix(in oklab, var(--kit-list-item-fg), transparent 92%);
160
160
  }
161
- .kit-list[data-variant='text'] :global(.kit-list-item .outline),
162
- .kit-list[data-variant='filled'] :global(.kit-list-item .outline) {
163
- display: none;
161
+ .kit-list[data-variant='outline'] :global(.kit-list-item::before) {
162
+ content: '';
163
+ position: absolute;
164
+ inset: 0;
165
+ border: 1px solid var(--kit-list-item-bd);
166
+ border-radius: inherit;
167
+ box-sizing: border-box;
168
+ z-index: 1;
169
+ pointer-events: none;
164
170
  }
165
171
 
166
172
  /**
@@ -81,7 +81,6 @@
81
81
  }}
82
82
  {...restProps}
83
83
  >
84
- <span class="outline"></span>
85
84
  {#if prepend}
86
85
  <span class="kit-list-item__prepend">
87
86
  {@render prepend?.()}
@@ -189,8 +188,4 @@
189
188
  text-overflow: ellipsis;
190
189
  white-space: nowrap;
191
190
  }
192
-
193
- .kit-list-item .outline {
194
- --outline-color: var(--kit-list-item-bd);
195
- }
196
191
  </style>
@@ -1,5 +1,7 @@
1
1
  export declare const lapikitImportsRef = "lapikit/components";
2
2
  export declare const lapikitImportsLabsRef = "lapikit/labs/components";
3
+ export declare const lapikitComponentPaths: Readonly<Record<string, string>>;
4
+ export declare const lapikitLabsComponentPaths: Readonly<Record<string, string>>;
3
5
  export declare const lapikitComponents: readonly string[];
4
6
  export declare const lapikitLabsComponents: readonly string[];
5
7
  export declare const lapikitPlugins: {
package/dist/constants.js CHANGED
@@ -1,41 +1,39 @@
1
1
  export const lapikitImportsRef = 'lapikit/components';
2
2
  export const lapikitImportsLabsRef = 'lapikit/labs/components';
3
- export const lapikitComponents = [
4
- 'app',
5
- 'btn',
6
- 'icon',
7
- 'avatar',
8
- 'dropdown',
9
- 'popover',
10
- 'tooltip',
11
- 'textfield',
12
- 'appbar',
13
- 'btn',
14
- 'icon',
15
- 'avatar',
16
- 'toolbar',
17
- 'btn',
18
- 'icon',
19
- 'avatar',
20
- 'list',
21
- 'list-item',
22
- 'dialog',
23
- 'modal',
24
- 'accordion',
25
- 'accordion-item',
26
- 'alert',
27
- 'aspect-ratio',
28
- 'spacer',
29
- 'separator',
30
- 'chip',
31
- 'card',
32
- 'card-title',
33
- 'card-content',
34
- 'card-media',
35
- 'card-actions',
36
- 'card-container'
37
- ];
38
- export const lapikitLabsComponents = ['sheet'];
3
+ export const lapikitComponentPaths = {
4
+ app: 'app/app.svelte',
5
+ appbar: 'appbar/appbar.svelte',
6
+ btn: 'btn/btn.svelte',
7
+ icon: 'icon/icon.svelte',
8
+ avatar: 'avatar/avatar.svelte',
9
+ dropdown: 'dropdown/dropdown.svelte',
10
+ popover: 'popover/popover.svelte',
11
+ tooltip: 'tooltip/tooltip.svelte',
12
+ textfield: 'textfield/textfield.svelte',
13
+ toolbar: 'toolbar/toolbar.svelte',
14
+ list: 'list/list.svelte',
15
+ 'list-item': 'list/modules/list-item.svelte',
16
+ dialog: 'dialog/dialog.svelte',
17
+ modal: 'modal/modal.svelte',
18
+ accordion: 'accordion/accordion.svelte',
19
+ 'accordion-item': 'accordion/modules/accordion-item.svelte',
20
+ alert: 'alert/alert.svelte',
21
+ 'aspect-ratio': 'aspect-ratio/aspect-ratio.svelte',
22
+ spacer: 'spacer/spacer.svelte',
23
+ separator: 'separator/separator.svelte',
24
+ chip: 'chip/chip.svelte',
25
+ card: 'card/card.svelte',
26
+ 'card-title': 'card/modules/card-title.svelte',
27
+ 'card-content': 'card/modules/card-content.svelte',
28
+ 'card-media': 'card/modules/card-media.svelte',
29
+ 'card-actions': 'card/modules/card-actions.svelte',
30
+ 'card-container': 'card/modules/card-container.svelte'
31
+ };
32
+ export const lapikitLabsComponentPaths = {
33
+ sheet: 'sheet/sheet.svelte'
34
+ };
35
+ export const lapikitComponents = Object.keys(lapikitComponentPaths);
36
+ export const lapikitLabsComponents = Object.keys(lapikitLabsComponentPaths);
39
37
  export const lapikitPlugins = {
40
38
  repl: {
41
39
  components: ['repl'],
@@ -1,10 +1,6 @@
1
1
  import type { LapikitPreprocessOptions } from './@types/index.js';
2
- /**
3
- * componentName generates the component name used in imports
4
- * @param shortName The short name of the component
5
- * @returns The component name with "Kit" prefix and the first letter capitalized
6
- */
7
2
  export declare function componentName(shortName: string): string;
3
+ export declare function collectDeclaredIdentifiers(content: string): Set<string>;
8
4
  export declare function liliCore(options?: LapikitPreprocessOptions): {
9
5
  markup({ content }: {
10
6
  content: string;
package/dist/framework.js CHANGED
@@ -1,67 +1,107 @@
1
1
  import { decodeSourceMap } from './escaping.js';
2
- import { lapikitImportsRef, lapikitImportsLabsRef, lapikitComponents, lapikitLabsComponents, lapikitPlugins } from './constants.js';
3
- /**
4
- * componentName generates the component name used in imports
5
- * @param shortName The short name of the component
6
- * @returns The component name with "Kit" prefix and the first letter capitalized
7
- */
2
+ import { lapikitImportsRef, lapikitImportsLabsRef, lapikitComponentPaths, lapikitLabsComponentPaths, lapikitPlugins } from './constants.js';
8
3
  export function componentName(shortName) {
9
4
  const pascal = shortName.replace(/(^|-)([a-z])/g, (_, __, letter) => letter.toUpperCase());
10
5
  return 'Kit' + pascal;
11
6
  }
7
+ export function collectDeclaredIdentifiers(content) {
8
+ const declared = new Set();
9
+ const scriptRegex = /<script\b[^>]*>([\s\S]*?)<\/script>/g;
10
+ for (const [, rawScript] of content.matchAll(scriptRegex)) {
11
+ const script = rawScript.replace(/\/\*[\s\S]*?\*\//g, '').replace(/(^|[^:])\/\/.*$/gm, '$1');
12
+ const importRegex = /\bimport\s+(?:type\s+)?([^'";]*?)\s+from\s*['"]/g;
13
+ for (const [, clause] of script.matchAll(importRegex)) {
14
+ const braces = clause.match(/\{([^}]*)\}/);
15
+ if (braces) {
16
+ for (const specifier of braces[1].split(',')) {
17
+ const local = specifier
18
+ .trim()
19
+ .replace(/^type\s+/, '')
20
+ .split(/\s+as\s+/)
21
+ .pop();
22
+ if (local)
23
+ declared.add(local.trim());
24
+ }
25
+ }
26
+ const outside = clause.replace(/\{[^}]*\}/, '');
27
+ const namespace = outside.match(/\*\s*as\s+([\w$]+)/);
28
+ if (namespace)
29
+ declared.add(namespace[1]);
30
+ const defaultImport = outside.match(/^\s*([\w$]+)/);
31
+ if (defaultImport)
32
+ declared.add(defaultImport[1]);
33
+ }
34
+ const declarationRegex = /\b(?:const|let|var|function|class)\s+([\w$]+)/g;
35
+ for (const [, name] of script.matchAll(declarationRegex)) {
36
+ declared.add(name);
37
+ }
38
+ }
39
+ return declared;
40
+ }
12
41
  export function liliCore(options) {
13
42
  return {
14
43
  markup({ content }) {
15
- const allComponents = [...lapikitComponents, ...lapikitLabsComponents];
16
- const componentToRef = new Map();
17
- lapikitComponents.forEach((comp) => {
18
- componentToRef.set(comp, lapikitImportsRef);
19
- });
20
- lapikitLabsComponents.forEach((comp) => {
21
- componentToRef.set(comp, lapikitImportsLabsRef);
22
- });
44
+ if (!content.includes('<kit:'))
45
+ return;
46
+ const componentInfo = new Map();
47
+ for (const [shortName, path] of Object.entries(lapikitComponentPaths)) {
48
+ componentInfo.set(shortName, {
49
+ name: componentName(shortName),
50
+ ref: `${lapikitImportsRef}/${path}`,
51
+ direct: true
52
+ });
53
+ }
54
+ for (const [shortName, path] of Object.entries(lapikitLabsComponentPaths)) {
55
+ componentInfo.set(shortName, {
56
+ name: componentName(shortName),
57
+ ref: `${lapikitImportsLabsRef}/${path}`,
58
+ direct: true
59
+ });
60
+ }
23
61
  // plugins
24
62
  if (options?.plugins) {
25
63
  options.plugins.forEach((pluginKey) => {
26
64
  const plugin = lapikitPlugins[pluginKey];
27
65
  if (plugin) {
28
66
  plugin.components.forEach((comp) => {
29
- if (!allComponents.includes(comp)) {
30
- allComponents.push(comp);
31
- }
32
- componentToRef.set(comp, plugin.ref);
67
+ componentInfo.set(comp, { name: componentName(comp), ref: plugin.ref });
33
68
  });
34
69
  }
35
70
  });
36
71
  }
37
- if (!content.includes('<kit:'))
38
- return;
39
- const componentInfo = new Map();
40
- for (const shortName of allComponents) {
41
- componentInfo.set(shortName, {
42
- name: componentName(shortName),
43
- ref: componentToRef.get(shortName) || lapikitImportsRef
44
- });
45
- }
46
72
  const scanResult = decodeSourceMap(content, componentInfo);
47
73
  if (!scanResult.changed)
48
74
  return;
49
75
  let processedContent = scanResult.code;
50
76
  const importedComponents = scanResult.importedComponents;
77
+ // components already declared by the user (manual import, alias...) are not imported again
78
+ const declared = collectDeclaredIdentifiers(content);
79
+ declared.forEach((name) => importedComponents.delete(name));
51
80
  if (importedComponents.size > 0) {
81
+ const directNames = new Set();
82
+ componentInfo.forEach((info) => {
83
+ if (info.direct)
84
+ directNames.add(info.name);
85
+ });
86
+ const directImports = [];
52
87
  const importsByRef = new Map();
53
88
  importedComponents.forEach((ref, component) => {
89
+ if (directNames.has(component)) {
90
+ directImports.push(`\n\timport ${component} from '${ref}';`);
91
+ return;
92
+ }
54
93
  if (!importsByRef.has(ref)) {
55
94
  importsByRef.set(ref, []);
56
95
  }
57
96
  importsByRef.get(ref).push(component);
58
97
  });
59
- const importLines = Array.from(importsByRef.entries())
60
- .map(([ref, components]) => {
61
- const imports = components.join(', ');
62
- return `\n\timport { ${imports} } from '${ref}';`;
63
- })
64
- .join('');
98
+ const importLines = directImports.join('') +
99
+ Array.from(importsByRef.entries())
100
+ .map(([ref, components]) => {
101
+ const imports = components.join(', ');
102
+ return `\n\timport { ${imports} } from '${ref}';`;
103
+ })
104
+ .join('');
65
105
  const scriptRegex = /<script(?![^>]*\bmodule\b)([^>]*)>/;
66
106
  const scriptMatch = processedContent.match(scriptRegex);
67
107
  if (scriptMatch && scriptMatch.index !== undefined) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lapikit",
3
- "version": "0.6.11",
3
+ "version": "0.7.0",
4
4
  "license": "MIT",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -60,11 +60,21 @@
60
60
  "svelte": "./dist/components/index.js",
61
61
  "default": "./dist/components/index.js"
62
62
  },
63
+ "./components/*": {
64
+ "types": "./dist/components/*.d.ts",
65
+ "svelte": "./dist/components/*",
66
+ "default": "./dist/components/*"
67
+ },
63
68
  "./labs/components": {
64
69
  "types": "./dist/labs/index.d.ts",
65
70
  "svelte": "./dist/labs/index.js",
66
71
  "default": "./dist/labs/index.js"
67
72
  },
73
+ "./labs/components/*": {
74
+ "types": "./dist/labs/*.d.ts",
75
+ "svelte": "./dist/labs/*",
76
+ "default": "./dist/labs/*"
77
+ },
68
78
  "./actions": {
69
79
  "default": "./dist/hooks/actions.js"
70
80
  }