@askrjs/lucide 0.0.9 → 0.2.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 CHANGED
@@ -11,13 +11,15 @@ Thin Askr wrappers for the [Lucide](https://lucide.dev) SVG icon set.
11
11
  npm install @askrjs/lucide
12
12
  ```
13
13
 
14
- Requires `@askrjs/askr` `>=0.0.37` as a peer dependency.
15
- The repo toolchain expects Node `20.19+` for local builds and tests.
14
+ Requires `@askrjs/askr` `>=0.2.0 <0.3.0` as a peer dependency.
15
+ The repo toolchain expects Node `24+` for local builds and tests.
16
16
 
17
17
  ## Usage
18
18
 
19
19
  ```tsx
20
- import { SearchIcon, XIcon, MenuIcon } from "@askrjs/lucide";
20
+ import { SearchIcon } from "@askrjs/lucide/icons/search";
21
+ import { XIcon } from "@askrjs/lucide/icons/x";
22
+ import { MenuIcon } from "@askrjs/lucide/icons/menu";
21
23
 
22
24
  function App() {
23
25
  return (
@@ -59,14 +61,19 @@ Passing a `title` removes `aria-hidden` and renders a `<title>` element inside t
59
61
 
60
62
  ## Tree shaking
61
63
 
62
- Each icon is a named export. Import only what you use:
64
+ For production applications, prefer per-icon subpath imports. They avoid loading
65
+ the roughly 2,000-module root barrel in development servers and bundlers that do
66
+ not aggressively eliminate unused exports:
63
67
 
64
68
  ```tsx
65
- import { SearchIcon } from "@askrjs/lucide";
69
+ import { SearchIcon } from "@askrjs/lucide/icons/search";
66
70
  ```
67
71
 
72
+ The root barrel is convenient for prototyping. If you use it in production,
73
+ verify tree shaking in your bundler:
74
+
68
75
  ```tsx
69
- import { SearchIcon } from "@askrjs/lucide/icons/search";
76
+ import { SearchIcon } from "@askrjs/lucide";
70
77
  ```
71
78
 
72
79
  The package is marked `"sideEffects": false` and built with `preserveModules`, so bundlers can eliminate unused icons completely.
@@ -1 +1 @@
1
- {"version":3,"file":"create-icon.d.ts","names":[],"sources":["../src/create-icon.tsx"],"mappings":";;iBA6CgB,WAAW,qBAAqB,UAAU;QAG7B,QAAA,+CAAS"}
1
+ {"version":3,"file":"create-icon.d.ts","names":[],"sources":["../src/create-icon.tsx"],"mappings":";;iBAoEgB,WAAW,qBAAqB,UAAU;QAG7B,QAAA,+CAAS"}
@@ -11,24 +11,42 @@ const SAFE_TAGS = /* @__PURE__ */ new Set([
11
11
  "polyline",
12
12
  "rect"
13
13
  ]);
14
+ const SAFE_ATTRIBUTES = /* @__PURE__ */ new Set([
15
+ "cx",
16
+ "cy",
17
+ "d",
18
+ "fill",
19
+ "height",
20
+ "points",
21
+ "r",
22
+ "rx",
23
+ "ry",
24
+ "width",
25
+ "x",
26
+ "x1",
27
+ "x2",
28
+ "y",
29
+ "y1",
30
+ "y2"
31
+ ]);
14
32
  const URL_ATTRIBUTE = /^(?:href|src|xlink:href)$/i;
15
33
  const URL_VALUE = /url\s*\(/i;
16
- function copyDefinition(iconNode) {
17
- if (!Array.isArray(iconNode)) throw new TypeError("icon definition must be an array");
34
+ function copyDefinition(displayName, iconNode) {
35
+ if (!Array.isArray(iconNode)) throw new TypeError(`${displayName}: icon definition must be an array`);
18
36
  return iconNode.map((entry) => {
19
- if (!Array.isArray(entry) || entry.length !== 2 || !SAFE_TAGS.has(entry[0])) throw new TypeError("icon definition contains an unsafe SVG element");
37
+ if (!Array.isArray(entry) || entry.length !== 2 || !SAFE_TAGS.has(entry[0])) throw new TypeError(`${displayName}: icon definition contains an unsafe SVG element`);
20
38
  const attributes = entry[1];
21
- if (!attributes || typeof attributes !== "object" || Array.isArray(attributes)) throw new TypeError("icon definition attributes must be an object");
39
+ if (!attributes || typeof attributes !== "object" || Array.isArray(attributes)) throw new TypeError(`${displayName}: icon definition attributes must be an object`);
22
40
  const copied = Object.create(null);
23
41
  for (const [name, value] of Object.entries(attributes)) {
24
- if (/^on/i.test(name) || /^(?:__proto__|constructor|prototype)$/i.test(name) || URL_ATTRIBUTE.test(name) || typeof value !== "string" || URL_VALUE.test(value)) throw new TypeError("icon definition contains an unsafe SVG attribute");
42
+ if (/^on/i.test(name) || /^(?:__proto__|constructor|prototype)$/i.test(name) || URL_ATTRIBUTE.test(name) || !SAFE_ATTRIBUTES.has(name) || typeof value !== "string" || URL_VALUE.test(value)) throw new TypeError(`${displayName}: icon definition contains an unsafe SVG attribute (${name})`);
25
43
  copied[name] = value;
26
44
  }
27
45
  return [entry[0], Object.freeze(copied)];
28
46
  });
29
47
  }
30
48
  function createIcon(displayName, iconNode) {
31
- const definition = Object.freeze(copyDefinition(iconNode));
49
+ const definition = Object.freeze(copyDefinition(displayName, iconNode));
32
50
  function Icon({ ...rest }) {
33
51
  return IconBase({
34
52
  ...rest,
@@ -1 +1 @@
1
- {"version":3,"file":"create-icon.js","names":["createSvgNode"],"sources":["../src/create-icon.tsx"],"sourcesContent":["import { jsx as createSvgNode } from \"@askrjs/askr/jsx-runtime\";\nimport { IconBase } from \"@askrjs/askr/foundations/icon\";\nimport type { IconNode, IconProps } from \"./types\";\n\nconst SAFE_TAGS = new Set([\n \"circle\",\n \"ellipse\",\n \"g\",\n \"line\",\n \"path\",\n \"polygon\",\n \"polyline\",\n \"rect\",\n]);\nconst URL_ATTRIBUTE = /^(?:href|src|xlink:href)$/i;\nconst URL_VALUE = /url\\s*\\(/i;\n\nfunction copyDefinition(iconNode: IconNode): IconNode {\n if (!Array.isArray(iconNode)) throw new TypeError(\"icon definition must be an array\");\n\n return iconNode.map((entry) => {\n if (!Array.isArray(entry) || entry.length !== 2 || !SAFE_TAGS.has(entry[0])) {\n throw new TypeError(\"icon definition contains an unsafe SVG element\");\n }\n const attributes = entry[1];\n if (!attributes || typeof attributes !== \"object\" || Array.isArray(attributes)) {\n throw new TypeError(\"icon definition attributes must be an object\");\n }\n const copied = Object.create(null) as Record<string, string>;\n for (const [name, value] of Object.entries(attributes)) {\n if (\n /^on/i.test(name) ||\n /^(?:__proto__|constructor|prototype)$/i.test(name) ||\n URL_ATTRIBUTE.test(name) ||\n typeof value !== \"string\" ||\n URL_VALUE.test(value)\n ) {\n throw new TypeError(\"icon definition contains an unsafe SVG attribute\");\n }\n copied[name] = value;\n }\n return [entry[0], Object.freeze(copied)] as const;\n });\n}\n\nexport function createIcon(displayName: string, iconNode: IconNode) {\n const definition = Object.freeze(copyDefinition(iconNode));\n\n function Icon({ ...rest }: IconProps) {\n return IconBase({\n ...rest,\n iconName: displayName,\n children: definition.map(([tag, attrs], i) =>\n createSvgNode(tag, attrs as Record<string, unknown>, i),\n ),\n });\n }\n\n Icon.displayName = displayName;\n return Icon;\n}\n"],"mappings":";;;AAIA,MAAM,4BAAY,IAAI,IAAI;CACxB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;AACD,MAAM,gBAAgB;AACtB,MAAM,YAAY;AAElB,SAAS,eAAe,UAA8B;CACpD,IAAI,CAAC,MAAM,QAAQ,QAAQ,GAAG,MAAM,IAAI,UAAU,kCAAkC;CAEpF,OAAO,SAAS,KAAK,UAAU;EAC7B,IAAI,CAAC,MAAM,QAAQ,KAAK,KAAK,MAAM,WAAW,KAAK,CAAC,UAAU,IAAI,MAAM,EAAE,GACxE,MAAM,IAAI,UAAU,gDAAgD;EAEtE,MAAM,aAAa,MAAM;EACzB,IAAI,CAAC,cAAc,OAAO,eAAe,YAAY,MAAM,QAAQ,UAAU,GAC3E,MAAM,IAAI,UAAU,8CAA8C;EAEpE,MAAM,SAAS,OAAO,OAAO,IAAI;EACjC,KAAK,MAAM,CAAC,MAAM,UAAU,OAAO,QAAQ,UAAU,GAAG;GACtD,IACE,OAAO,KAAK,IAAI,KAChB,yCAAyC,KAAK,IAAI,KAClD,cAAc,KAAK,IAAI,KACvB,OAAO,UAAU,YACjB,UAAU,KAAK,KAAK,GAEpB,MAAM,IAAI,UAAU,kDAAkD;GAExE,OAAO,QAAQ;EACjB;EACA,OAAO,CAAC,MAAM,IAAI,OAAO,OAAO,MAAM,CAAC;CACzC,CAAC;AACH;AAEA,SAAgB,WAAW,aAAqB,UAAoB;CAClE,MAAM,aAAa,OAAO,OAAO,eAAe,QAAQ,CAAC;CAEzD,SAAS,KAAK,EAAE,GAAG,QAAmB;EACpC,OAAO,SAAS;GACd,GAAG;GACH,UAAU;GACV,UAAU,WAAW,KAAK,CAAC,KAAK,QAAQ,MACtCA,IAAc,KAAK,OAAkC,CAAC,CACxD;EACF,CAAC;CACH;CAEA,KAAK,cAAc;CACnB,OAAO;AACT"}
1
+ {"version":3,"file":"create-icon.js","names":["createSvgNode"],"sources":["../src/create-icon.tsx"],"sourcesContent":["import { jsx as createSvgNode } from \"@askrjs/askr/jsx-runtime\";\nimport { IconBase } from \"@askrjs/askr/foundations/icon\";\nimport type { IconNode, IconProps } from \"./types\";\n\nconst SAFE_TAGS = new Set([\n \"circle\",\n \"ellipse\",\n \"g\",\n \"line\",\n \"path\",\n \"polygon\",\n \"polyline\",\n \"rect\",\n]);\nconst SAFE_ATTRIBUTES = new Set([\n \"cx\",\n \"cy\",\n \"d\",\n \"fill\",\n \"height\",\n \"points\",\n \"r\",\n \"rx\",\n \"ry\",\n \"width\",\n \"x\",\n \"x1\",\n \"x2\",\n \"y\",\n \"y1\",\n \"y2\",\n]);\nconst URL_ATTRIBUTE = /^(?:href|src|xlink:href)$/i;\nconst URL_VALUE = /url\\s*\\(/i;\n\nfunction copyDefinition(displayName: string, iconNode: IconNode): IconNode {\n if (!Array.isArray(iconNode)) {\n throw new TypeError(`${displayName}: icon definition must be an array`);\n }\n\n return iconNode.map((entry) => {\n if (!Array.isArray(entry) || entry.length !== 2 || !SAFE_TAGS.has(entry[0])) {\n throw new TypeError(`${displayName}: icon definition contains an unsafe SVG element`);\n }\n const attributes = entry[1];\n if (!attributes || typeof attributes !== \"object\" || Array.isArray(attributes)) {\n throw new TypeError(`${displayName}: icon definition attributes must be an object`);\n }\n const copied = Object.create(null) as Record<string, string>;\n for (const [name, value] of Object.entries(attributes)) {\n if (\n /^on/i.test(name) ||\n /^(?:__proto__|constructor|prototype)$/i.test(name) ||\n URL_ATTRIBUTE.test(name) ||\n !SAFE_ATTRIBUTES.has(name) ||\n typeof value !== \"string\" ||\n URL_VALUE.test(value)\n ) {\n throw new TypeError(\n `${displayName}: icon definition contains an unsafe SVG attribute (${name})`,\n );\n }\n copied[name] = value;\n }\n return [entry[0], Object.freeze(copied)] as const;\n });\n}\n\nexport function createIcon(displayName: string, iconNode: IconNode) {\n const definition = Object.freeze(copyDefinition(displayName, iconNode));\n\n function Icon({ ...rest }: IconProps) {\n return IconBase({\n ...rest,\n iconName: displayName,\n children: definition.map(([tag, attrs], i) =>\n createSvgNode(tag, attrs as Record<string, unknown>, i),\n ),\n });\n }\n\n Icon.displayName = displayName;\n return Icon;\n}\n"],"mappings":";;;AAIA,MAAM,4BAAY,IAAI,IAAI;CACxB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;AACD,MAAM,kCAAkB,IAAI,IAAI;CAC9B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;AACD,MAAM,gBAAgB;AACtB,MAAM,YAAY;AAElB,SAAS,eAAe,aAAqB,UAA8B;CACzE,IAAI,CAAC,MAAM,QAAQ,QAAQ,GACzB,MAAM,IAAI,UAAU,GAAG,YAAY,mCAAmC;CAGxE,OAAO,SAAS,KAAK,UAAU;EAC7B,IAAI,CAAC,MAAM,QAAQ,KAAK,KAAK,MAAM,WAAW,KAAK,CAAC,UAAU,IAAI,MAAM,EAAE,GACxE,MAAM,IAAI,UAAU,GAAG,YAAY,iDAAiD;EAEtF,MAAM,aAAa,MAAM;EACzB,IAAI,CAAC,cAAc,OAAO,eAAe,YAAY,MAAM,QAAQ,UAAU,GAC3E,MAAM,IAAI,UAAU,GAAG,YAAY,+CAA+C;EAEpF,MAAM,SAAS,OAAO,OAAO,IAAI;EACjC,KAAK,MAAM,CAAC,MAAM,UAAU,OAAO,QAAQ,UAAU,GAAG;GACtD,IACE,OAAO,KAAK,IAAI,KAChB,yCAAyC,KAAK,IAAI,KAClD,cAAc,KAAK,IAAI,KACvB,CAAC,gBAAgB,IAAI,IAAI,KACzB,OAAO,UAAU,YACjB,UAAU,KAAK,KAAK,GAEpB,MAAM,IAAI,UACR,GAAG,YAAY,sDAAsD,KAAK,EAC5E;GAEF,OAAO,QAAQ;EACjB;EACA,OAAO,CAAC,MAAM,IAAI,OAAO,OAAO,MAAM,CAAC;CACzC,CAAC;AACH;AAEA,SAAgB,WAAW,aAAqB,UAAoB;CAClE,MAAM,aAAa,OAAO,OAAO,eAAe,aAAa,QAAQ,CAAC;CAEtE,SAAS,KAAK,EAAE,GAAG,QAAmB;EACpC,OAAO,SAAS;GACd,GAAG;GACH,UAAU;GACV,UAAU,WAAW,KAAK,CAAC,KAAK,QAAQ,MACtCA,IAAc,KAAK,OAAkC,CAAC,CACxD;EACF,CAAC;CACH;CAEA,KAAK,cAAc;CACnB,OAAO;AACT"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@askrjs/lucide",
3
- "version": "0.0.9",
3
+ "version": "0.2.1",
4
4
  "description": "Thin Askr wrappers for the Lucide SVG icon set",
5
5
  "keywords": [
6
6
  "askr",
@@ -66,7 +66,7 @@
66
66
  "test:unit": "vp test run -c vitest.config.ts"
67
67
  },
68
68
  "devDependencies": {
69
- "@askrjs/askr": ">=0.0.88 <0.1.0",
69
+ "@askrjs/askr": ">=0.2.0 <0.3.0",
70
70
  "@vitest/coverage-v8": "^4.1.10",
71
71
  "jsdom": "^30.0.1",
72
72
  "lucide": "1.30.0",
@@ -75,7 +75,7 @@
75
75
  "vite-plus": "^0.2.8"
76
76
  },
77
77
  "peerDependencies": {
78
- "@askrjs/askr": ">=0.0.88 <0.1.0"
78
+ "@askrjs/askr": ">=0.2.0 <0.3.0"
79
79
  },
80
80
  "engines": {
81
81
  "node": ">=24.0.0"