@dynamic-icon/svelte 0.1.1 → 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,35 +1,27 @@
1
1
  # @dynamic-icon/svelte
2
2
 
3
- Svelte 5 component that renders icons from database records. It knows nothing about any icon library: you register
4
- sources once with `configureIcons` (backed by the framework-free [`@dynamic-icon/core`](../core), installed automatically).
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
+ ```
5
11
 
6
12
  ```ts
7
- // app root, once (identically on server and client)
13
+ // app root, once, same on server and client
8
14
  import { configureIcons } from '@dynamic-icon/svelte';
9
15
  import CarFront from 'lucide-svelte/icons/car-front';
10
16
 
11
- configureIcons({ sources: { lucide: { 'car-front': CarFront } }, fallback: CircleHelp });
17
+ configureIcons({ sources: { lucide: { 'car-front': CarFront } } });
12
18
  ```
13
19
 
14
20
  ```svelte
15
21
  <DynamicIcon {...record.icon} size={24} class="text-primary" />
16
22
  ```
17
23
 
18
- Props: `webType`, `webRef`, `color`, `media`, `scale`, `size`, `strokeWidth`, `class`, `fallback` (snippet).
19
-
20
- | Prop | Notes |
21
- | --- | --- |
22
- | `webType` / `webRef` | `image`, `emoji`, `markup`, a font type (`material`, `font-awesome`, `remix`) or a configured source. |
23
- | `color` | A CSS colour (`#fff`, `rgb()`, `oklch()`, `var()`, `red`) goes in `style`; anything else is added as classes. |
24
- | `size` | px for numbers; a plain CSS length for strings (`1.5rem`). Invalid values (including `24px;...`) use the default size. |
25
- | `scale` | Multiplies a numeric `size`, rounded. Ignored if not a positive finite number. |
26
- | `fallback` | Snippet shown when unresolved; wins over `configureIcons({ fallback })`. |
27
-
28
- - Only `image` and `markup` icons get a wrapper `<span>`; everything else is a single element. All icons are `aria-hidden`
29
- (decorative): put meaning in adjacent text, or give the parent an `aria-label`.
30
- - `markup` renders only through your `sanitize` function; without one it shows the fallback. `image` uses `loading="lazy"`.
31
- - Icon components receive `size`, `strokeWidth`, `class`, `style` (CSS colours only) and `aria-hidden`; `lucide-svelte` icons
32
- accept these as-is. Custom icons should spread rest props onto their root `<svg>`.
24
+ Icons that can't be resolved render nothing (or your `fallback`) and are reported once through `onLog`.
33
25
 
34
- AI-readable docs: [`llms.txt`](./llms.txt) and [`llms-full.txt`](./llms-full.txt), shipped in the npm package, e.g.
35
- `https://unpkg.com/@dynamic-icon/svelte/llms-full.txt` once published.
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`.
package/dist/index.d.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  export { default as DynamicIcon } from "./DynamicIcon.svelte";
2
2
  export { configureIcons, resolveIcon } from "./config.js";
3
- export type { IconComponent, IconComponentProps, IconConfig, IconEntry, IconMedia, IconProps, IconSource } from "./types.js";
3
+ export type { IconComponent, IconComponentProps, IconConfig, IconEntry, IconLogEntry, IconMedia, IconProps, IconSource } from "./types.js";
package/dist/types.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import type { Component, ComponentType, Snippet, SvelteComponent } from "svelte";
2
2
  import type { IconConfig as CoreIconConfig, IconSource as CoreIconSource } from "@dynamic-icon/core";
3
+ export type { IconLogEntry } from "@dynamic-icon/core";
3
4
  /** Props `DynamicIcon` passes to every icon component. `lucide-svelte` icons already accept them. */
4
5
  export interface IconComponentProps {
5
6
  size?: number | string;
package/llms-full.txt CHANGED
@@ -44,7 +44,7 @@ configureIcons({
44
44
  | `sanitize` | `(html: string) => string` | none | Required for `markup`. Without it, `markup` icons show the fallback. |
45
45
  | `fallback` | `Component` | none | Rendered when nothing resolves (unknown icon, empty record, failed source). A per-instance `fallback` snippet wins. |
46
46
  | `defaults` | `{ size?: number; strokeWidth?: number }` | `{ size: 24, strokeWidth: 2 }` | Must be positive finite numbers or it throws. |
47
- | `onError` | `(error: unknown) => void` | `console.error` | Receives errors thrown by function sources or `sanitize`. |
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
48
 
49
49
  Semantics: validates eagerly; on invalid input it throws and the previous configuration stays active; on success it replaces the
50
50
  whole configuration (idempotent). Record sources are held by reference, never copied (lookups use `Object.hasOwn`, so
@@ -80,46 +80,64 @@ accept these. Custom icons should spread rest props onto their root `<svg>`. Exp
80
80
  3. `markup` with `webRef` and a configured `sanitize` -> sized wrapper `<span>` with `{@html sanitize(webRef)}`.
81
81
  4. `emoji` -> `<span style="font-size">`.
82
82
  5. Font type -> `<i class="{webRef} ...">`.
83
- 6. Else: per-instance `fallback` snippet, else `configureIcons({ fallback })`, else nothing.
83
+ 6. Else: per-instance `fallback` snippet, else `configureIcons({ fallback })`, else **nothing: no DOM node is rendered** (layout collapses, no placeholder).
84
84
 
85
- ## 5. Security model
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
86
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.
87
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.
88
105
  - `size` strings must match a plain CSS length; numbers must be finite and > 0.
89
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.
90
107
  - `media.link` is not scheme-filtered; validate image URLs upstream if they are user-supplied.
91
108
  - Registry keys are looked up with `Object.hasOwn`: prototype keys cannot resolve.
92
- - A throwing function source or sanitizer cannot crash the page: it is reported to `onError` and the icon renders as unresolved.
109
+ - A throwing function source or sanitizer cannot crash the page: it is logged through `onLog` and the icon renders as unresolved.
93
110
 
94
- ## 6. SSR rules
111
+ ## 7. SSR rules
95
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.
96
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.
97
114
  - Components read the config once at instance creation; configure before the first render.
98
115
  - Nothing touches `window`/`document` at import or render time.
99
116
 
100
- ## 7. Performance notes
117
+ ## 8. Performance notes
101
118
  - Per icon: one registry lookup (`Object.hasOwn` + index), one `$derived`, plain string building. No `tailwind-merge`/`clsx`, no string case conversion.
102
119
  - Bundle: the package imports no icon library. Only icons you list in `sources` ship. Use per-icon imports; never `import * as Icons`.
103
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.
104
121
  - Only `image`/`markup` add a wrapper element. Component icons render a single element.
105
122
  - Wrapper styles are scoped CSS in the component (Tailwind does not scan `node_modules`).
106
123
 
107
- ## 8. Architecture
124
+ ## 9. Architecture
108
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.
109
126
  - `@dynamic-icon/svelte`: `DynamicIcon.svelte`, typed wrappers fixing `C = IconComponent`, small pure helpers (`utils.ts`). Ships `.svelte` source via `svelte-package`.
110
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.
111
128
  - Adding another framework: create `@dynamic-icon/<framework>` with a component calling `getIconConfig<Component>()`; reuse core and `utils` logic.
112
129
 
113
- ## 9. Common mistakes
130
+ ## 10. Common mistakes
114
131
  - `import * as Icons from 'lucide-svelte'` as a source: ships every icon. Register individual icons.
115
132
  - Using PascalCase keys with kebab-case DB values: use a function source (`(ref) => map[toPascal(ref)]`) or kebab-case keys.
116
133
  - Calling `configureIcons` in a component or per request.
117
- - Expecting `markup` to render without `sanitize`.
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.
118
136
  - Passing `size="24px;..."` or `size="calc(...)"`: falls back to the default size.
119
137
  - Expecting Tailwind conflict resolution between `color` and `class`: classes are concatenated, not merged.
120
138
  - Using `webType: 'image'|'emoji'|'markup'` as a source name: throws at configure time.
121
139
 
122
- ## 10. Types
140
+ ## 11. Types
123
141
  `IconEntry<T extends string = string>` { name?, webType?, webRef?, color?, media?, scale? }; `IconProps<T>` adds { size?, class?, strokeWidth?, fallback? };
124
- `IconMedia` { link, caption? }; `IconConfig`, `IconSource`, `IconComponent`, `IconComponentProps` exported from `@dynamic-icon/svelte`.
125
- Core exports `IconConfig<C>`, `IconSource<C>`, `ResolvedIconConfig<C>`.
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 CHANGED
@@ -4,9 +4,10 @@
4
4
 
5
5
  Key facts for code generation:
6
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?, onError? })` exactly once, at module scope of the app root, with identical config on server and client. Never inside a component or per request.
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
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
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.
10
11
  - Libraries that render `DynamicIcon` need no setup; only the host app configures.
11
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.
12
13
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dynamic-icon/svelte",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Tiny Svelte 5 component that renders database-driven icons. Sources are plugged in via @dynamic-icon/core.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -48,7 +48,7 @@
48
48
  "access": "public"
49
49
  },
50
50
  "dependencies": {
51
- "@dynamic-icon/core": "^0.1.1"
51
+ "@dynamic-icon/core": "^0.1.2"
52
52
  },
53
53
  "scripts": {
54
54
  "build": "svelte-package -i src -o dist",