@dynamic-icon/svelte 0.1.0 → 0.1.2

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
@@ -1,15 +1,27 @@
1
1
  # @dynamic-icon/svelte
2
2
 
3
- Renders icons from database records. Knows nothing about any icon library: sources are registered with
4
- [`@dynamic-icon/svelte-config`](../svelte-config) (a peer dependency, so there is exactly one registry).
3
+ > Experimental package — do not use in production.
4
+
5
+ Svelte 5 component that renders icons from database records (`{ webType: 'lucide', webRef: 'car-front' }`).
6
+ Icon-library agnostic: you register the icons you use once. Built for pages with hundreds of icons.
7
+
8
+ ```bash
9
+ pnpm add @dynamic-icon/svelte lucide-svelte
10
+ ```
11
+
12
+ ```ts
13
+ // app root, once, same on server and client
14
+ import { configureIcons } from '@dynamic-icon/svelte';
15
+ import CarFront from 'lucide-svelte/icons/car-front';
16
+
17
+ configureIcons({ sources: { lucide: { 'car-front': CarFront } } });
18
+ ```
5
19
 
6
20
  ```svelte
7
21
  <DynamicIcon {...record.icon} size={24} class="text-primary" />
8
22
  ```
9
23
 
10
- Props: `webType`, `webRef`, `color`, `media`, `scale`, `size` (px or CSS length), `strokeWidth`, `class`, `fallback` (snippet).
24
+ Icons that can't be resolved render nothing (or your `fallback`) and are reported once through `onLog`.
11
25
 
12
- - `color` is a CSS colour (`#fff`, `rgb()`, `var()`, `red`) applied via `style`, or otherwise a utility class.
13
- - `scale` multiplies a numeric `size` and rounds it; string sizes are used as-is.
14
- - Only `image` and `markup` icons get a wrapper `<span>` (they need a sized box); everything else renders one element.
15
- - Install both packages; the host app calls `configureIcons()` once. See the config package.
26
+ **Full documentation** (props, configuration, logging, security, SSR, performance, architecture): [`llms-full.txt`](./llms-full.txt)
27
+ and [`llms.txt`](./llms.txt), shipped in the npm package, e.g. `https://unpkg.com/@dynamic-icon/svelte/llms-full.txt`.
@@ -9,7 +9,7 @@
9
9
  * `class` and `color` land on the rendered element; `image`/`markup` get a sized wrapper to carry them.
10
10
  */
11
11
  import type { IconProps } from './types.js';
12
- import { getIconConfig, resolveIcon } from '@dynamic-icon/svelte-config';
12
+ import { getIconConfig, resolveIcon } from './config.js';
13
13
  import { boxSize, boxStyle, classes, colorStyle, textStyle } from './utils.js';
14
14
 
15
15
  const config = getIconConfig();
@@ -34,7 +34,8 @@
34
34
 
35
35
  {#if Comp}
36
36
  <Comp
37
- size={boxSize(size, scale)}
37
+ aria-hidden="true"
38
+ size={boxSize(size, scale, config.size)}
38
39
  {strokeWidth}
39
40
  class={classes(color, className)}
40
41
  style={colorStyle(color)}
@@ -42,28 +43,31 @@
42
43
  {:else if webType === 'image' && media?.link}
43
44
  <span
44
45
  class={classes(color, className, 'u-icon')}
45
- style={boxStyle(boxSize(size, scale), color)}
46
- role="presentation"
46
+ style={boxStyle(boxSize(size, scale, config.size), color)}
47
+ aria-hidden="true"
47
48
  >
48
49
  <img src={media.link} alt={media.caption ?? ''} loading="lazy" decoding="async" />
49
50
  </span>
50
51
  {:else if webType === 'markup' && webRef && config.sanitize}
51
52
  <span
52
53
  class={classes(color, className, 'u-icon u-markup')}
53
- style={boxStyle(boxSize(size, scale), color)}
54
- role="presentation"
54
+ style={boxStyle(boxSize(size, scale, config.size), color)}
55
+ aria-hidden="true"
55
56
  >
56
57
  {@html config.sanitize(webRef)}
57
58
  </span>
58
59
  {:else if webType === 'emoji' && webRef}
59
- <span class={classes(color, className)} style={textStyle(boxSize(size, scale), color)}>{webRef}</span>
60
+ <span aria-hidden="true" class={classes(color, className)} style={textStyle(boxSize(size, scale, config.size), color)}
61
+ >{webRef}</span
62
+ >
60
63
  {:else if webType && webRef && config.fontTypes.has(webType)}
61
- <i class={classes(color, className, webRef)} style={textStyle(boxSize(size, scale), color)}></i>
64
+ <i aria-hidden="true" class={classes(color, className, webRef)} style={textStyle(boxSize(size, scale, config.size), color)}></i>
62
65
  {:else if fallback}
63
66
  {@render fallback()}
64
67
  {:else if Fallback}
65
68
  <Fallback
66
- size={boxSize(size, scale)}
69
+ aria-hidden="true"
70
+ size={boxSize(size, scale, config.size)}
67
71
  {strokeWidth}
68
72
  class={classes(color, className)}
69
73
  style={colorStyle(color)}
@@ -0,0 +1,8 @@
1
+ import type { ResolvedIconConfig } from "@dynamic-icon/core";
2
+ import type { IconComponent, IconConfig } from "./types.js";
3
+ /** Sets the icon configuration. Call once at app start, identically on server and client. */
4
+ export declare const configureIcons: (config: IconConfig) => void;
5
+ /** Resolves a component for `webType`/`webRef`, or `undefined`. Usable outside components. */
6
+ export declare const resolveIcon: (webType: string | undefined, webRef: string | undefined) => IconComponent | undefined;
7
+ /** @internal Read by `DynamicIcon` once per instance. */
8
+ export declare const getIconConfig: () => ResolvedIconConfig<IconComponent>;
package/dist/config.js ADDED
@@ -0,0 +1,9 @@
1
+ // Typed entry points over the framework-free registry in @dynamic-icon/core.
2
+ // This is the only place that fixes the registry's component type to `IconComponent`.
3
+ import { configureIcons as configure, getIconConfig as getConfig, resolveIcon as resolve } from "@dynamic-icon/core";
4
+ /** Sets the icon configuration. Call once at app start, identically on server and client. */
5
+ export const configureIcons = (config) => configure(config);
6
+ /** Resolves a component for `webType`/`webRef`, or `undefined`. Usable outside components. */
7
+ export const resolveIcon = (webType, webRef) => resolve(webType, webRef);
8
+ /** @internal Read by `DynamicIcon` once per instance. */
9
+ export const getIconConfig = () => getConfig();
package/dist/index.d.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  export { default as DynamicIcon } from "./DynamicIcon.svelte";
2
- export type { IconEntry, IconMedia, IconProps } from "./types.js";
2
+ export { configureIcons, resolveIcon } from "./config.js";
3
+ export type { IconComponent, IconComponentProps, IconConfig, IconEntry, IconLogEntry, IconMedia, IconProps, IconSource } from "./types.js";
package/dist/index.js CHANGED
@@ -1 +1,2 @@
1
1
  export { default as DynamicIcon } from "./DynamicIcon.svelte";
2
+ export { configureIcons, resolveIcon } from "./config.js";
package/dist/types.d.ts CHANGED
@@ -1,4 +1,23 @@
1
- import type { Snippet } from "svelte";
1
+ import type { Component, ComponentType, Snippet, SvelteComponent } from "svelte";
2
+ import type { IconConfig as CoreIconConfig, IconSource as CoreIconSource } from "@dynamic-icon/core";
3
+ export type { IconLogEntry } from "@dynamic-icon/core";
4
+ /** Props `DynamicIcon` passes to every icon component. `lucide-svelte` icons already accept them. */
5
+ export interface IconComponentProps {
6
+ size?: number | string;
7
+ strokeWidth?: number;
8
+ class?: string;
9
+ /** Only set when `color` is a CSS colour. */
10
+ style?: string;
11
+ "aria-hidden"?: "true";
12
+ }
13
+ /**
14
+ * Any Svelte component usable as an icon (Svelte 5 or legacy class-based, e.g. `lucide-svelte`).
15
+ * Props are `any` on purpose: library icons declare their own prop types, which are never exactly
16
+ * `IconComponentProps`. Custom icons should accept those props and spread the rest onto their root element.
17
+ */
18
+ export type IconComponent = Component<any, any, any> | ComponentType<SvelteComponent<any, any, any>>;
19
+ export type IconSource = CoreIconSource<IconComponent>;
20
+ export type IconConfig = CoreIconConfig<IconComponent>;
2
21
  /** Media attached to an `image` icon. Structurally compatible with CMS file objects. */
3
22
  export interface IconMedia {
4
23
  link: string;
@@ -12,7 +31,7 @@ export interface IconEntry<T extends string = string> {
12
31
  webType?: T;
13
32
  /** Identifier within the source ('car-front'), icon-font classes, emoji, or raw markup. */
14
33
  webRef?: string;
15
- /** A Tailwind/utility class (`text-primary`) or a CSS colour (`#fff`, `rgb()`, `var()`, `red`). */
34
+ /** A utility class (`text-primary`) or a CSS colour (`#fff`, `rgb()`, `var()`, `red`). */
16
35
  color?: string;
17
36
  /** Image for the `image` type. */
18
37
  media?: IconMedia;
@@ -20,7 +39,7 @@ export interface IconEntry<T extends string = string> {
20
39
  scale?: number;
21
40
  }
22
41
  export interface IconProps<T extends string = string> extends IconEntry<T> {
23
- /** Visual size: px when a number, any CSS length when a string. */
42
+ /** px when a number, a CSS length (`1.5rem`) when a string. Invalid values fall back to the default size. */
24
43
  size?: number | string;
25
44
  class?: string;
26
45
  strokeWidth?: number;
package/dist/utils.d.ts CHANGED
@@ -1,6 +1,9 @@
1
1
  export declare const isCssColor: (color: string | undefined) => color is string;
2
- /** Applies the per-source `scale` to numeric sizes, rounded to avoid blurry sub-pixel rendering. */
3
- export declare const boxSize: (size: number | string, scale: number | undefined) => number | string;
2
+ /**
3
+ * Final icon size. Numbers must be finite and positive and get the per-source `scale` (rounded to avoid
4
+ * blurry sub-pixel rendering); strings must be a plain CSS length. Invalid input yields `fallback`.
5
+ */
6
+ export declare const boxSize: (size: number | string, scale: number | undefined, fallback: number) => number | string;
4
7
  /** `style` for icon components: only a CSS colour needs it. */
5
8
  export declare const colorStyle: (color: string | undefined) => string | undefined;
6
9
  /** `style` for the sized box around `image` / `markup` icons. */
package/dist/utils.js CHANGED
@@ -5,8 +5,20 @@
5
5
  */
6
6
  const CSS_COLOR = /^(?:#[\da-f]{3,8}|[a-z]+|[a-z]+(?:-[a-z]+)*\([^;{}"'<>]*\))$/i;
7
7
  export const isCssColor = (color) => color !== undefined && CSS_COLOR.test(color);
8
- /** Applies the per-source `scale` to numeric sizes, rounded to avoid blurry sub-pixel rendering. */
9
- export const boxSize = (size, scale) => typeof size === "number" && scale ? Math.round(size * scale) : size;
8
+ /** A plain CSS length. Anything else (e.g. `24px;background:url(x)`) never reaches a `style` attribute. */
9
+ const CSS_LENGTH = /^(?:\d+|\d*\.\d+)(?:px|r?em|ch|ex|vw|vh|vmin|vmax|%|pt|cm|mm|in)$/i;
10
+ /**
11
+ * Final icon size. Numbers must be finite and positive and get the per-source `scale` (rounded to avoid
12
+ * blurry sub-pixel rendering); strings must be a plain CSS length. Invalid input yields `fallback`.
13
+ */
14
+ export const boxSize = (size, scale, fallback) => {
15
+ if (typeof size === "number") {
16
+ if (!(size > 0) || !Number.isFinite(size))
17
+ return fallback;
18
+ return typeof scale === "number" && scale > 0 && Number.isFinite(scale) ? Math.round(size * scale) || 1 : size;
19
+ }
20
+ return CSS_LENGTH.test(size) ? size : fallback;
21
+ };
10
22
  const cssLength = (size) => (typeof size === "number" ? `${size}px` : size);
11
23
  const colorDecl = (color) => (isCssColor(color) ? `;color:${color}` : "");
12
24
  /** `style` for icon components: only a CSS colour needs it. */
package/llms-full.txt ADDED
@@ -0,0 +1,143 @@
1
+ # @dynamic-icon/svelte and @dynamic-icon/core: full documentation
2
+
3
+ Two packages. `@dynamic-icon/core` is a framework-free registry (pure TypeScript, zero dependencies). `@dynamic-icon/svelte`
4
+ is the Svelte 5 component plus typed re-exports. Installing the Svelte package pulls in core.
5
+
6
+ ## 1. Purpose
7
+
8
+ Render icons whose identity is stored in a database record: `{ webType: 'lucide', webRef: 'car-front', color: 'text-primary' }`.
9
+ The package resolves `webType` + `webRef` to a component (or renders an image, emoji, icon-font `<i>`, or sanitized markup).
10
+ Designed for components that render ~1,000 icons at once.
11
+
12
+ ## 2. Quick start
13
+
14
+ ```ts
15
+ // src/hooks.client.ts and src/hooks.server.ts (or one shared module imported by both): once, module scope
16
+ import { configureIcons } from '@dynamic-icon/svelte';
17
+ import CarFront from 'lucide-svelte/icons/car-front';
18
+ import House from 'lucide-svelte/icons/house';
19
+ import CircleHelp from 'lucide-svelte/icons/circle-help';
20
+
21
+ configureIcons({
22
+ sources: { lucide: { 'car-front': CarFront, house: House } },
23
+ fallback: CircleHelp
24
+ });
25
+ ```
26
+
27
+ ```svelte
28
+ <script lang="ts">
29
+ import { DynamicIcon } from '@dynamic-icon/svelte';
30
+ let { icon } = $props(); // { webType, webRef, color?, media?, scale? } from the database
31
+ </script>
32
+
33
+ <DynamicIcon {...icon} size={20} class="mr-2" />
34
+ ```
35
+
36
+ ## 3. API
37
+
38
+ ### `configureIcons(config: IconConfig): void` (from `@dynamic-icon/svelte`; generic `configureIcons<C>` in core)
39
+
40
+ | Field | Type | Default | Meaning |
41
+ | --- | --- | --- | --- |
42
+ | `sources` | `Record<webType, Record<ref, Component> \| (ref) => Component \| undefined>` | `{}` | Component sources by `webType`. Names `image`, `emoji`, `markup` are reserved and throw. |
43
+ | `fontTypes` | `string[]` | `['material','font-awesome','remix']` | `webType`s rendered as `<i class={webRef}>`. Must not collide with sources or reserved names. |
44
+ | `sanitize` | `(html: string) => string` | none | Required for `markup`. Without it, `markup` icons show the fallback. |
45
+ | `fallback` | `Component` | none | Rendered when nothing resolves (unknown icon, empty record, failed source). A per-instance `fallback` snippet wins. |
46
+ | `defaults` | `{ size?: number; strokeWidth?: number }` | `{ size: 24, strokeWidth: 2 }` | Must be positive finite numbers or it throws. |
47
+ | `onLog` | `(entry: { type: 'error' \| 'warn'; message: string; error?: unknown }) => void` | `console.warn` / `console.error` | Diagnostics (see section 5). Override to silence or route, e.g. dev only. |
48
+
49
+ Semantics: validates eagerly; on invalid input it throws and the previous configuration stays active; on success it replaces the
50
+ whole configuration (idempotent). Record sources are held by reference, never copied (lookups use `Object.hasOwn`, so
51
+ `constructor`/`__proto__` never resolve); treat them as read-only, since later mutations are visible. Function sources are used as-is (memoise them yourself if expensive).
52
+
53
+ ### `resolveIcon(webType, webRef): Component | undefined`
54
+ Same lookup the component uses, for use outside components. Never throws.
55
+
56
+ ### `<DynamicIcon />` props
57
+
58
+ | Prop | Type | Notes |
59
+ | --- | --- | --- |
60
+ | `webType` | `string` | Which renderer/source. |
61
+ | `webRef` | `string` | Identifier within the source; class string for font types; the emoji; or raw markup. |
62
+ | `color` | `string` | CSS colour (hex, keyword, `rgb()/hsl()/oklch()/var()/color-mix()`) -> inline `color:`; anything else -> appended to `class`. |
63
+ | `media` | `{ link: string; caption?: string }` | For `image`. `caption` becomes `alt`. Rendered `loading="lazy" decoding="async"`. |
64
+ | `size` | `number \| string` | px if number; plain CSS length if string. Invalid (NaN, <=0, `24px;x:y`, `calc()`) uses `defaults.size`. |
65
+ | `scale` | `number` | Multiplies numeric `size`, rounded (min 1). Ignored for strings or non-positive/non-finite values. |
66
+ | `strokeWidth` | `number` | Passed to icon components. |
67
+ | `class` | `string` | Plain string (not clsx arrays). |
68
+ | `fallback` | `Snippet` | Per-instance fallback. |
69
+
70
+ Extra record fields such as `name` are accepted and ignored, so `{...record}` spreading works.
71
+
72
+ ### Contract for icon components
73
+ They receive `size`, `strokeWidth`, `class`, `style` (only when `color` is a CSS colour) and `aria-hidden="true"`. `lucide-svelte` icons
74
+ accept these. Custom icons should spread rest props onto their root `<svg>`. Exported type `IconComponent` is intentionally
75
+ `Component<any, any, any> | ComponentType<SvelteComponent<any, any, any>>` because library icons never match one exact prop type.
76
+
77
+ ## 4. Resolution order (per instance)
78
+ 1. A configured source resolves `webType`/`webRef` -> render that component (no wrapper).
79
+ 2. `image` with `media.link` -> sized wrapper `<span>` + `<img>`.
80
+ 3. `markup` with `webRef` and a configured `sanitize` -> sized wrapper `<span>` with `{@html sanitize(webRef)}`.
81
+ 4. `emoji` -> `<span style="font-size">`.
82
+ 5. Font type -> `<i class="{webRef} ...">`.
83
+ 6. Else: per-instance `fallback` snippet, else `configureIcons({ fallback })`, else **nothing: no DOM node is rendered** (layout collapses, no placeholder).
84
+
85
+ ## 5. Undefined icons and logging
86
+ An icon that cannot be resolved never throws; it follows the fallback chain in section 4 and is reported through `onLog`.
87
+
88
+ | When | type | message |
89
+ | --- | --- | --- |
90
+ | `webType` is a configured source, ref missing | `warn` | `icon "x" not found in source "lucide"` |
91
+ | `webType` is not a source, built-in or font type | `warn` | `unknown webType "x" ...` |
92
+ | `markup` without `sanitize` | `error` | `"markup" icon not rendered: configureIcons({ sanitize }) is not set` |
93
+ | function source throws | `error` (+ `error`) | `source "x" threw while resolving "y"` |
94
+ | `sanitize` throws | `error` (+ `error`) | `sanitize threw; icon rendered empty` |
95
+
96
+ - Not logged: empty records (no `webType`/`webRef`), built-in types, font types, `image` without `media.link`.
97
+ - Each distinct message is delivered once per configuration: 1,000 icons with the same typo log once; a server does not log per request. After 500 distinct messages one suppression notice is sent and the rest are dropped.
98
+ - A throwing `onLog` is swallowed. Default sink: `console.warn` / `console.error` (the original error is the second argument).
99
+ - Dev-only in a Vite app: `configureIcons({ ..., onLog: import.meta.env.DEV ? undefined : () => {} })` (`undefined` keeps the console default). Route to monitoring: `onLog: (e) => Sentry.captureMessage(e.message, e.type === 'error' ? 'error' : 'warning')`.
100
+ - Logging happens during resolution, so it also runs on the server (once per distinct message per process).
101
+
102
+ ## 6. Security model
103
+ - DB-controlled strings reach the DOM only as: escaped text, class tokens, `<img src>`, a validated CSS colour, a validated CSS length, and (for `markup`) sanitizer output.
104
+ - `color` is only placed in `style` if it matches a strict colour grammar (no `;`, braces, quotes); otherwise it is a class token, which cannot inject declarations.
105
+ - `size` strings must match a plain CSS length; numbers must be finite and > 0.
106
+ - `markup` is never rendered unsanitized. Provide a real sanitizer (e.g. DOMPurify on the client, an SSR-capable one on the server); an identity function is unsafe.
107
+ - `media.link` is not scheme-filtered; validate image URLs upstream if they are user-supplied.
108
+ - Registry keys are looked up with `Object.hasOwn`: prototype keys cannot resolve.
109
+ - A throwing function source or sanitizer cannot crash the page: it is logged through `onLog` and the icon renders as unresolved.
110
+
111
+ ## 7. SSR rules
112
+ - The configuration is app-wide data, identical for all requests, so a process-wide singleton is safe. Never mutate a source record per request (it is shared by reference). Per-request or per-tenant icon sets are not supported; use a function source that reads your own request-scoped state only if you accept that trade-off.
113
+ - Call `configureIcons` once at module scope, on both server and client, with the same config (otherwise hydration markup can differ). Do not call it inside components or per request.
114
+ - Components read the config once at instance creation; configure before the first render.
115
+ - Nothing touches `window`/`document` at import or render time.
116
+
117
+ ## 8. Performance notes
118
+ - Per icon: one registry lookup (`Object.hasOwn` + index), one `$derived`, plain string building. No `tailwind-merge`/`clsx`, no string case conversion.
119
+ - Bundle: the package imports no icon library. Only icons you list in `sources` ship. Use per-icon imports; never `import * as Icons`.
120
+ - Measured (Node, 1,000 icons): `Map.get` ~16-19 ns, `Object.hasOwn` + index ~23 ns, `globalThis[Symbol]` ~5 ns. Sources are held by reference, so the registry adds no copy of your icon data; the difference between strategies is a few ns per icon, negligible next to component instantiation.
121
+ - Only `image`/`markup` add a wrapper element. Component icons render a single element.
122
+ - Wrapper styles are scoped CSS in the component (Tailwind does not scan `node_modules`).
123
+
124
+ ## 9. Architecture
125
+ - `@dynamic-icon/core`: pure TypeScript (`lib: ES2022`, no DOM, no deps). `configureIcons`, `resolveIcon`, `getIconConfig`, generic over the component type `C`. State: one holder object at `globalThis[Symbol.for('@dynamic-icon/core@1')]`, captured per module so reads are plain property access; duplicate installs share it; the `@1` is the holder format version.
126
+ - `@dynamic-icon/svelte`: `DynamicIcon.svelte`, typed wrappers fixing `C = IconComponent`, small pure helpers (`utils.ts`). Ships `.svelte` source via `svelte-package`.
127
+ - Store choice: a global holder (not Svelte context) with sources held by reference. Context would add a `getContext` per instance, only works during component init, needs a provider around every consumer (including other libraries' components and separately mounted roots), and buys only per-request isolation that an app-wide icon set does not need. Why not Svelte context: the registry is app-wide config; context adds a lookup per instance and only works during component init. Why not per-instance props: 1,000 icons would each carry the config.
128
+ - Adding another framework: create `@dynamic-icon/<framework>` with a component calling `getIconConfig<Component>()`; reuse core and `utils` logic.
129
+
130
+ ## 10. Common mistakes
131
+ - `import * as Icons from 'lucide-svelte'` as a source: ships every icon. Register individual icons.
132
+ - Using PascalCase keys with kebab-case DB values: use a function source (`(ref) => map[toPascal(ref)]`) or kebab-case keys.
133
+ - Calling `configureIcons` in a component or per request.
134
+ - Expecting `markup` to render without `sanitize` (it logs an error and shows the fallback).
135
+ - Wanting silent production: pass an `onLog` that returns early outside dev; do not rely on the console default.
136
+ - Passing `size="24px;..."` or `size="calc(...)"`: falls back to the default size.
137
+ - Expecting Tailwind conflict resolution between `color` and `class`: classes are concatenated, not merged.
138
+ - Using `webType: 'image'|'emoji'|'markup'` as a source name: throws at configure time.
139
+
140
+ ## 11. Types
141
+ `IconEntry<T extends string = string>` { name?, webType?, webRef?, color?, media?, scale? }; `IconProps<T>` adds { size?, class?, strokeWidth?, fallback? };
142
+ `IconMedia` { link, caption? }; `IconLogEntry` { type: 'error' | 'warn'; message; error? }; `IconConfig`, `IconSource`, `IconComponent`, `IconComponentProps` exported from `@dynamic-icon/svelte`.
143
+ Core exports `IconConfig<C>`, `IconSource<C>`, `IconLogEntry`, `ResolvedIconConfig<C>`.
package/llms.txt ADDED
@@ -0,0 +1,22 @@
1
+ # @dynamic-icon/svelte
2
+
3
+ > Svelte 5 component that renders icons from database records (`{ webType: 'lucide', webRef: 'car-front' }`). Icon-library agnostic and pluggable: the app registers icon sources once with `configureIcons()`. Built for pages rendering hundreds or thousands of icons: one registry lookup and one derived value per icon, no class-merging library, no icon library imported by the package.
4
+
5
+ Key facts for code generation:
6
+ - Install `@dynamic-icon/svelte` (peer: `svelte@^5`). It depends on `@dynamic-icon/core`, a zero-dependency registry; do not install core separately.
7
+ - Call `configureIcons({ sources, fallback?, defaults?, sanitize?, fontTypes?, onLog? })` exactly once, at module scope of the app root, with identical config on server and client. Never inside a component or per request.
8
+ - Register only the icons you need, with per-icon imports (`import CarFront from 'lucide-svelte/icons/car-front'`), keyed by the identifier stored in the database. Never `import * as` a whole library: it defeats tree-shaking.
9
+ - Render with `<DynamicIcon {...record.icon} size={24} class="text-primary" />`.
10
+ - Unresolvable icons render the fallback or nothing and are reported once each through `onLog({ type: 'error' | 'warn', message, error? })` (default: console). Override it to log only in development.
11
+ - Libraries that render `DynamicIcon` need no setup; only the host app configures.
12
+ - Built-in `webType`s: `image` (uses `media.link`), `emoji`, `markup` (needs `sanitize`), and icon fonts `material`, `font-awesome`, `remix` (`<i class={webRef}>`). Any other `webType` must be a configured source.
13
+
14
+ ## Docs
15
+
16
+ - [Full documentation](https://unpkg.com/@dynamic-icon/svelte/llms-full.txt): API, configuration, resolution order, security model, SSR rules, performance notes, architecture
17
+ - [Core registry](https://unpkg.com/@dynamic-icon/core/llms.txt): the framework-free registry used underneath
18
+ - [README](https://unpkg.com/@dynamic-icon/svelte/README.md): human-oriented quick start
19
+
20
+ ## Optional
21
+
22
+ - Same files via jsDelivr: https://cdn.jsdelivr.net/npm/@dynamic-icon/svelte/llms-full.txt
package/package.json CHANGED
@@ -1,13 +1,7 @@
1
1
  {
2
2
  "name": "@dynamic-icon/svelte",
3
- "version": "0.1.0",
4
- "description": "Tiny Svelte 5 component that renders database-driven icons. Sources are plugged in via @dynamic-icon/svelte-config.",
5
- "keywords": [
6
- "svelte",
7
- "svelte5",
8
- "icons",
9
- "dynamic-icon"
10
- ],
3
+ "version": "0.1.2",
4
+ "description": "Tiny Svelte 5 component that renders database-driven icons. Sources are plugged in via @dynamic-icon/core.",
11
5
  "license": "MIT",
12
6
  "type": "module",
13
7
  "sideEffects": [
@@ -21,28 +15,31 @@
21
15
  "svelte": "./dist/index.js",
22
16
  "default": "./dist/index.js"
23
17
  },
18
+ "./llms.txt": "./llms.txt",
19
+ "./llms-full.txt": "./llms-full.txt",
24
20
  "./package.json": "./package.json"
25
21
  },
26
22
  "files": [
27
23
  "dist",
28
24
  "README.md",
29
- "LICENSE"
25
+ "LICENSE",
26
+ "llms.txt",
27
+ "llms-full.txt"
30
28
  ],
31
29
  "peerDependencies": {
32
- "svelte": "^5.0.0",
33
- "@dynamic-icon/svelte-config": "^0.1.0"
30
+ "svelte": "^5.0.0"
34
31
  },
35
32
  "devDependencies": {
36
33
  "@sveltejs/package": "^2.3.0",
37
34
  "@sveltejs/vite-plugin-svelte": "^4.0.0",
38
35
  "@types/node": "^22.0.0",
39
36
  "jsdom": "^25.0.0",
37
+ "publint": "^0.2.12",
40
38
  "svelte": "^5.0.0",
41
39
  "svelte-check": "^4.0.0",
42
40
  "typescript": "^5.6.0",
43
41
  "vite": "^5.4.0",
44
- "vitest": "^2.1.0",
45
- "@dynamic-icon/svelte-config": "0.1.0"
42
+ "vitest": "^2.1.0"
46
43
  },
47
44
  "engines": {
48
45
  "node": ">=18"
@@ -50,11 +47,18 @@
50
47
  "publishConfig": {
51
48
  "access": "public"
52
49
  },
50
+ "dependencies": {
51
+ "@dynamic-icon/core": "^0.1.2"
52
+ },
53
53
  "scripts": {
54
54
  "build": "svelte-package -i src -o dist",
55
+ "prebuild": "pnpm --filter @dynamic-icon/core run build",
55
56
  "typecheck": "svelte-check --tsconfig ./tsconfig.json",
57
+ "pretypecheck": "pnpm --filter @dynamic-icon/core run build",
56
58
  "typecheck:test": "svelte-check --tsconfig ./tsconfig.test.json",
59
+ "pretypecheck:test": "pnpm --filter @dynamic-icon/core run build",
57
60
  "test": "vitest run",
58
- "test:watch": "vitest"
61
+ "test:watch": "vitest",
62
+ "lint:package": "publint"
59
63
  }
60
64
  }