@jini-ai/ui 0.4.6 → 0.4.8
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 +33 -0
- package/dist/features/admin-widgets/components/InfoTip.d.ts +23 -2
- package/dist/features/admin-widgets/components/InfoTip.hooks.d.ts +22 -2
- package/dist/features/admin-widgets/components/InfoTip.hooks.js +75 -28
- package/dist/features/admin-widgets/components/InfoTip.js +12 -4
- package/dist/features/admin-widgets/components/InfoTip.rules.d.ts +72 -0
- package/dist/features/admin-widgets/components/InfoTip.rules.js +68 -0
- package/dist/features/admin-widgets/components/SuggestInput/SuggestInput.d.ts +36 -0
- package/dist/features/admin-widgets/components/SuggestInput/SuggestInput.hooks.d.ts +58 -0
- package/dist/features/admin-widgets/components/SuggestInput/SuggestInput.hooks.js +66 -0
- package/dist/features/admin-widgets/components/SuggestInput/SuggestInput.js +24 -0
- package/dist/features/admin-widgets/components/useDebouncedHtmlPreview.hooks.d.ts +23 -0
- package/dist/features/admin-widgets/components/useDebouncedHtmlPreview.hooks.js +28 -0
- package/dist/features/admin-widgets/index.d.ts +9 -0
- package/dist/features/admin-widgets/index.js +5 -0
- package/dist/features/execution/constants.js +11 -10
- package/dist/features/execution/http-adapter.d.ts +3 -1
- package/dist/features/execution/http-adapter.js +9 -4
- package/dist/features/execution/index.d.ts +1 -1
- package/dist/features/execution/index.js +1 -1
- package/dist/features/execution/react/components/ByokProviderForm.d.ts +18 -1
- package/dist/features/execution/react/components/ByokProviderForm.js +14 -16
- package/dist/features/execution/react/components/LocalCliAgentCard.d.ts +5 -1
- package/dist/features/execution/react/components/LocalCliAgentCard.js +12 -87
- package/dist/features/execution/react/components/LocalCliAgentList.d.ts +4 -1
- package/dist/features/execution/react/components/LocalCliAgentList.js +13 -4
- package/dist/features/execution/react/components/LocalCliAgentSettings.d.ts +20 -0
- package/dist/features/execution/react/components/LocalCliAgentSettings.js +20 -0
- package/dist/features/execution/react/hooks/use-byok-field-validation.hooks.d.ts +24 -0
- package/dist/features/execution/react/hooks/use-byok-field-validation.hooks.js +27 -0
- package/dist/features/execution/react/hooks/useExecutionTab.js +10 -1
- package/dist/features/execution/react/hooks/useLocalCliAgentCard.d.ts +48 -0
- package/dist/features/execution/react/hooks/useLocalCliAgentCard.js +110 -0
- package/dist/features/execution/react/hooks/useLocalCliAgentList.d.ts +19 -0
- package/dist/features/execution/react/hooks/useLocalCliAgentList.js +13 -0
- package/dist/features/execution/rules.d.ts +13 -0
- package/dist/features/execution/rules.js +14 -0
- package/dist/features/execution/types.d.ts +2 -0
- package/dist/features/html-editor/canvas-embed-placeholders.d.ts +13 -1
- package/dist/features/html-editor/canvas-embed-placeholders.js +61 -12
- package/dist/features/html-editor/dom.d.ts +5 -0
- package/dist/features/html-editor/dom.js +10 -0
- package/dist/features/html-editor/editor-blocks.d.ts +71 -0
- package/dist/features/html-editor/editor-blocks.js +185 -0
- package/dist/features/html-editor/editor-serialization.d.ts +24 -0
- package/dist/features/html-editor/editor-serialization.js +110 -0
- package/dist/features/html-editor/index.d.ts +5 -0
- package/dist/features/html-editor/index.js +3 -0
- package/dist/features/html-editor/protected-elements.d.ts +64 -0
- package/dist/features/html-editor/protected-elements.js +189 -0
- package/dist/features/html-editor/react/components/InteractiveHtmlEditor.d.ts +6 -4
- package/dist/features/html-editor/react/components/InteractiveHtmlEditor.js +3 -3
- package/dist/features/html-editor/react/hooks/useInteractiveHtmlEditor.d.ts +9 -1
- package/dist/features/html-editor/react/hooks/useInteractiveHtmlEditor.js +107 -196
- package/dist/features/html-editor/source-splice.d.ts +23 -0
- package/dist/features/html-editor/source-splice.js +45 -0
- package/dist/features/i18n/dictionaries/settings-dialog.ar.js +4 -0
- package/dist/features/i18n/dictionaries/settings-dialog.bn.js +4 -0
- package/dist/features/i18n/dictionaries/settings-dialog.de.js +4 -0
- package/dist/features/i18n/dictionaries/settings-dialog.en.js +4 -0
- package/dist/features/i18n/dictionaries/settings-dialog.es.js +4 -0
- package/dist/features/i18n/dictionaries/settings-dialog.fa.js +4 -0
- package/dist/features/i18n/dictionaries/settings-dialog.fr.js +4 -0
- package/dist/features/i18n/dictionaries/settings-dialog.hi.js +4 -0
- package/dist/features/i18n/dictionaries/settings-dialog.hu.js +4 -0
- package/dist/features/i18n/dictionaries/settings-dialog.id.js +4 -0
- package/dist/features/i18n/dictionaries/settings-dialog.it.js +4 -0
- package/dist/features/i18n/dictionaries/settings-dialog.ja.js +4 -0
- package/dist/features/i18n/dictionaries/settings-dialog.ko.js +4 -0
- package/dist/features/i18n/dictionaries/settings-dialog.pl.js +4 -0
- package/dist/features/i18n/dictionaries/settings-dialog.pt-BR.js +4 -0
- package/dist/features/i18n/dictionaries/settings-dialog.ru.js +4 -0
- package/dist/features/i18n/dictionaries/settings-dialog.th.js +4 -0
- package/dist/features/i18n/dictionaries/settings-dialog.tr.js +4 -0
- package/dist/features/i18n/dictionaries/settings-dialog.uk.js +4 -0
- package/dist/features/i18n/dictionaries/settings-dialog.ur.js +4 -0
- package/dist/features/i18n/dictionaries/settings-dialog.zh-CN.js +4 -0
- package/dist/features/i18n/dictionaries/settings-dialog.zh-TW.js +4 -0
- package/dist/features/interactive-ui/interactive-ui.css +1 -1
- package/dist/features/interactive-ui/providers/native/data-table.js +8 -1
- package/dist/features/interactive-ui/providers/native/data-table.manifest.d.ts +3 -5
- package/dist/features/interactive-ui/providers/native/data-table.manifest.js +5 -7
- package/dist/features/interactive-ui/providers/recharts/bar-chart.js +8 -3
- package/dist/features/interactive-ui/providers/recharts/chart-config.d.ts +40 -0
- package/dist/features/interactive-ui/providers/recharts/chart-config.js +32 -0
- package/dist/features/interactive-ui/providers/recharts/chart-tokens.d.ts +18 -12
- package/dist/features/interactive-ui/providers/recharts/chart-tokens.js +6 -11
- package/dist/features/interactive-ui/providers/recharts/chart.d.ts +31 -0
- package/dist/features/interactive-ui/providers/recharts/chart.js +34 -0
- package/dist/features/interactive-ui/providers/recharts/line-chart.js +10 -3
- package/dist/features/interactive-ui/providers/recharts/pie-chart.js +8 -3
- package/dist/features/interactive-ui/providers/shadcn/button.d.ts +1 -1
- package/dist/features/interactive-ui/providers/shadcn/data-table.d.ts +5 -1
- package/dist/features/interactive-ui/providers/shadcn/data-table.js +26 -2
- package/dist/features/interactive-ui/providers/shadcn/data-table.manifest.d.ts +8 -1
- package/dist/features/interactive-ui/providers/shadcn/data-table.manifest.js +6 -3
- package/dist/features/interactive-ui/table-data.d.ts +25 -0
- package/dist/features/interactive-ui/table-data.js +36 -0
- package/dist/features/mcp-ui/escape.js +2 -6
- package/dist/features/mcp-ui/sandbox-proxy-link-forwarder.d.ts +26 -0
- package/dist/features/mcp-ui/sandbox-proxy-link-forwarder.js +40 -0
- package/dist/features/mcp-ui/sandbox-proxy.d.ts +5 -1
- package/dist/features/mcp-ui/sandbox-proxy.js +11 -0
- package/dist/features/mcp-ui/surfaces/bridge.js +9 -3
- package/dist/features/mcp-ui/surfaces/document.d.ts +11 -4
- package/dist/features/mcp-ui/surfaces/document.js +25 -3
- package/dist/features/mcp-ui/surfaces/outcome.js +1 -1
- package/dist/features/media-providers/constants.d.ts +0 -7
- package/dist/features/media-providers/constants.js +7 -15
- package/dist/features/media-providers/react/components/MediaProvidersTab.js +3 -2
- package/dist/features/media-providers/react/hooks/useMediaProvidersTab.js +6 -3
- package/dist/features/memory/dependencies.d.ts +2 -0
- package/dist/features/memory/dependencies.js +9 -25
- package/dist/features/memory/index.d.ts +1 -0
- package/dist/features/memory/index.js +1 -0
- package/dist/features/memory/react/MemoryPorts.d.ts +17 -0
- package/dist/features/memory/react/MemoryPorts.js +15 -0
- package/dist/features/memory/react/hooks/useMemoryConfig.hooks.d.ts +2 -2
- package/dist/features/memory/react/hooks/useMemoryConfig.hooks.js +7 -7
- package/dist/features/memory/react/hooks/useMemoryConnectors.hooks.d.ts +2 -3
- package/dist/features/memory/react/hooks/useMemoryConnectors.hooks.js +4 -5
- package/dist/features/memory/react/hooks/useMemoryEntries.hooks.d.ts +2 -3
- package/dist/features/memory/react/hooks/useMemoryEntries.hooks.js +4 -5
- package/dist/features/memory/react/hooks/useMemoryExtractions.hooks.js +2 -2
- package/dist/features/panel-kit/fetch-query/adapter.react.d.ts +2 -2
- package/dist/features/panel-kit/fetch-query/adapter.react.js +5 -3
- package/dist/features/panel-kit/fetch-query/adapter.tanstack.d.ts +11 -0
- package/dist/features/panel-kit/fetch-query/adapter.tanstack.js +212 -0
- package/dist/features/panel-kit/fetch-query/binding.react.d.ts +17 -0
- package/dist/features/panel-kit/fetch-query/binding.react.js +24 -0
- package/dist/features/panel-kit/fetch-query/environment.browser.d.ts +1 -1
- package/dist/features/panel-kit/fetch-query/index.d.ts +6 -3
- package/dist/features/panel-kit/fetch-query/index.js +2 -1
- package/dist/features/panel-kit/fetch-query/tanstack.d.ts +8 -0
- package/dist/features/panel-kit/fetch-query/tanstack.js +4 -0
- package/dist/features/panel-kit/helpers/standing-draft-recovery-message.d.ts +3 -1
- package/dist/features/panel-kit/helpers/standing-draft-recovery-message.js +9 -2
- package/dist/features/settings/dialog/styles/settings-dialog.css +169 -21
- package/dist/features/tabbed-dialog/styles/tabbed-dialog.css +91 -0
- package/dist/react/components/RemixIcon.d.ts +1 -41
- package/dist/react/components/RemixIcon.js +3 -176
- package/dist/react/components/RemixIcon.stylesheet.d.ts +84 -0
- package/dist/react/components/RemixIcon.stylesheet.js +208 -0
- package/dist/react/components/remixicon-font-recovery.d.ts +55 -0
- package/dist/react/components/remixicon-font-recovery.js +110 -0
- package/dist/styles/admin.css +8 -2
- package/package.json +25 -8
- package/dist/features/panel-kit/fetch-query/cache.d.ts +0 -123
- package/dist/features/panel-kit/fetch-query/cache.js +0 -231
- package/dist/features/panel-kit/fetch-query/types.d.ts +0 -91
- package/dist/features/panel-kit/fetch-query/types.js +0 -2
|
@@ -35,6 +35,10 @@
|
|
|
35
35
|
border-radius: var(--jini-radius-lg);
|
|
36
36
|
box-shadow: var(--jini-shadow-lg);
|
|
37
37
|
overflow: hidden;
|
|
38
|
+
/* Layout follows the dialog's OWN width, not the viewport's: an inline settings page beside an
|
|
39
|
+
expanded nav and an open assistant dock is narrow on a wide screen, and a 375px modal is narrow
|
|
40
|
+
on any screen. See the `@container jini-tabbed-dialog` block at the end. */
|
|
41
|
+
container: jini-tabbed-dialog / inline-size;
|
|
38
42
|
}
|
|
39
43
|
|
|
40
44
|
/* Page (non-modal) variant: the shell renders without `onClose`, so it should
|
|
@@ -271,3 +275,90 @@
|
|
|
271
275
|
.jini-tabbed-dialog-sidebar-toggle:focus-visible {
|
|
272
276
|
opacity: 1;
|
|
273
277
|
}
|
|
278
|
+
|
|
279
|
+
/* ============================================================
|
|
280
|
+
Narrow dialog: the side nav becomes one top tab row
|
|
281
|
+
============================================================
|
|
282
|
+
A 240px side nav inside a ~500px dialog left the form a thin column with
|
|
283
|
+
its controls spilling past the edge (Chat settings at 500px, Chrome check
|
|
284
|
+
2026-10-11). Below 600px of DIALOG width (a 40rem dialog, e.g. Sites
|
|
285
|
+
settings, keeps its desktop look) the nav turns into a horizontal,
|
|
286
|
+
swipeable tab row over a full-width panel. The collapse chevron has
|
|
287
|
+
nothing to reclaim there, so it hides, and a collapsed nav reappears as
|
|
288
|
+
the row: a narrow dialog never loses its tabs. Modal-only spacing skips
|
|
289
|
+
the inline (page) variant, whose host owns its page insets. */
|
|
290
|
+
@container jini-tabbed-dialog (max-width: 600px) {
|
|
291
|
+
.jini-tabbed-dialog:not(.jini-tabbed-dialog--inline) .jini-tabbed-dialog-head {
|
|
292
|
+
padding: 20px 56px 8px 16px;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
.jini-tabbed-dialog:not(.jini-tabbed-dialog--inline) .jini-tabbed-dialog-chrome {
|
|
296
|
+
top: 16px;
|
|
297
|
+
right: 16px;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
.jini-tabbed-dialog:not(.jini-tabbed-dialog--inline) .jini-tabbed-dialog-head-line h2,
|
|
301
|
+
.jini-tabbed-dialog:not(.jini-tabbed-dialog--inline) .jini-tabbed-dialog-head h2 {
|
|
302
|
+
font-size: 22px;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
.jini-tabbed-dialog-body,
|
|
306
|
+
.jini-tabbed-dialog-sidebar-collapsed .jini-tabbed-dialog-body {
|
|
307
|
+
grid-template-columns: minmax(0, 1fr);
|
|
308
|
+
grid-template-rows: auto minmax(0, 1fr);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
.jini-tabbed-dialog-sidebar,
|
|
312
|
+
.jini-tabbed-dialog-sidebar-collapsed .jini-tabbed-dialog-sidebar {
|
|
313
|
+
display: flex;
|
|
314
|
+
flex-direction: row;
|
|
315
|
+
gap: 2px;
|
|
316
|
+
padding: 0 16px;
|
|
317
|
+
background: transparent;
|
|
318
|
+
border-right: 0;
|
|
319
|
+
box-shadow: inset 0 -1px 0 var(--jini-border);
|
|
320
|
+
overflow-x: auto;
|
|
321
|
+
overflow-y: hidden;
|
|
322
|
+
overscroll-behavior-x: contain;
|
|
323
|
+
scrollbar-width: none;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
.jini-tabbed-dialog-sidebar::-webkit-scrollbar {
|
|
327
|
+
display: none;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
.jini-tabbed-dialog-nav-item {
|
|
331
|
+
flex: 0 0 auto;
|
|
332
|
+
width: auto;
|
|
333
|
+
grid-template-columns: auto auto;
|
|
334
|
+
gap: 6px;
|
|
335
|
+
padding: 9px 10px;
|
|
336
|
+
border: 0;
|
|
337
|
+
border-bottom: 2px solid transparent;
|
|
338
|
+
border-radius: 0;
|
|
339
|
+
white-space: nowrap;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
.jini-tabbed-dialog-nav-item:hover {
|
|
343
|
+
background: transparent;
|
|
344
|
+
color: var(--jini-text);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
.jini-tabbed-dialog-nav-item.active,
|
|
348
|
+
.jini-tabbed-dialog-nav-item.active:hover {
|
|
349
|
+
background: transparent;
|
|
350
|
+
border-bottom-color: var(--jini-accent);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
.jini-tabbed-dialog-nav-text small {
|
|
354
|
+
display: none;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
.jini-tabbed-dialog-sidebar-toggle {
|
|
358
|
+
display: none;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
.jini-tabbed-dialog:not(.jini-tabbed-dialog--inline) .jini-tabbed-dialog-content {
|
|
362
|
+
padding: 14px 16px 18px;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
@@ -1,50 +1,11 @@
|
|
|
1
1
|
import type { CSSProperties } from 'react';
|
|
2
|
+
export { installRemixIconStylesheet, REMIXICON_STYLESHEET_MARKER } from './RemixIcon.stylesheet.js';
|
|
2
3
|
interface RemixIconProps {
|
|
3
4
|
name: string;
|
|
4
5
|
size?: number;
|
|
5
6
|
className?: string;
|
|
6
7
|
style?: CSSProperties;
|
|
7
8
|
}
|
|
8
|
-
/**
|
|
9
|
-
* Marks whichever stylesheet is currently serving `.ri-*` glyphs — this package's own default, or
|
|
10
|
-
* a host override that got inserted first. `ensureRemixIconStylesheet` checks for this attribute
|
|
11
|
-
* (not a specific `href`) before injecting, so a host that wants its own icon set/font just needs
|
|
12
|
-
* to add a `<link data-jini-remixicon rel="stylesheet" href="...">` (or an equivalent `<style
|
|
13
|
-
* data-jini-remixicon>`) anywhere in `<head>` before the first `RemixIcon` mounts, and this
|
|
14
|
-
* package's default never loads.
|
|
15
|
-
*
|
|
16
|
-
* Exported because every host that has needed the override so far re-declared this string
|
|
17
|
-
* literally rather than importing it -- three copies outside this file, across two apps of the
|
|
18
|
-
* one downstream checkout inspected (2026-09-06), and they do not agree on element type: one
|
|
19
|
-
* installs a `<style>`, the other a `<link>`. A copied magic string is a rename away from
|
|
20
|
-
* silently double-loading the font; importing the constant is not.
|
|
21
|
-
*/
|
|
22
|
-
export declare const REMIXICON_STYLESHEET_MARKER = "data-jini-remixicon";
|
|
23
|
-
/**
|
|
24
|
-
* Installs a caller-supplied stylesheet as the one serving `.ri-*` glyphs, claiming
|
|
25
|
-
* {@link REMIXICON_STYLESHEET_MARKER} so this package's own default never loads.
|
|
26
|
-
*
|
|
27
|
-
* This is the supported opt-in for any host whose bundler defeats the default's `import.meta.url`
|
|
28
|
-
* resolution (see {@link ensureRemixIconStylesheet}) — point it at
|
|
29
|
-
* `@jini-ai/ui/remixicon.css`, which is a normal subpath export a bundler resolves and rewrites
|
|
30
|
-
* like any other asset. Call it before the first `RemixIcon` mounts; module top level in the
|
|
31
|
-
* host's entry file is the usual place, since the default injection runs from a `useEffect` and
|
|
32
|
-
* therefore only has to lose a race against React's commit phase.
|
|
33
|
-
*
|
|
34
|
-
* Idempotent and safe to call unconditionally: it no-ops if any marked stylesheet is already
|
|
35
|
-
* present (including one a previous call installed), and no-ops outside a DOM.
|
|
36
|
-
*
|
|
37
|
-
* @param options.href URL of a stylesheet defining the `.ri-*` classes and its own `@font-face`.
|
|
38
|
-
* Resolved by the browser against the document, so relative values follow the page's base URL —
|
|
39
|
-
* pass a bundler-produced URL rather than a hand-written relative path.
|
|
40
|
-
* @returns `true` if this call inserted the stylesheet, `false` if it no-opped.
|
|
41
|
-
* @sideEffects Appends one `<link>` to `document.head`.
|
|
42
|
-
* @complexity O(1) time (one `querySelector` on an attribute selector), O(1) space.
|
|
43
|
-
* @overallScore 100
|
|
44
|
-
*/
|
|
45
|
-
export declare function installRemixIconStylesheet(options: {
|
|
46
|
-
href: string;
|
|
47
|
-
}): boolean;
|
|
48
9
|
/**
|
|
49
10
|
* Thin wrapper around a RemixIcon webfont glyph (`ri-<name>`). Loads this package's own default
|
|
50
11
|
* RemixIcon CSS/font the first time any `RemixIcon` mounts — see
|
|
@@ -52,5 +13,4 @@ export declare function installRemixIconStylesheet(options: {
|
|
|
52
13
|
* {@link installRemixIconStylesheet} for what a bundled host should call instead.
|
|
53
14
|
*/
|
|
54
15
|
export declare function RemixIcon({ name, size, className, style }: RemixIconProps): import("react").JSX.Element;
|
|
55
|
-
export {};
|
|
56
16
|
//# sourceMappingURL=RemixIcon.d.ts.map
|
|
@@ -1,180 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect } from 'react';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
* a host override that got inserted first. `ensureRemixIconStylesheet` checks for this attribute
|
|
6
|
-
* (not a specific `href`) before injecting, so a host that wants its own icon set/font just needs
|
|
7
|
-
* to add a `<link data-jini-remixicon rel="stylesheet" href="...">` (or an equivalent `<style
|
|
8
|
-
* data-jini-remixicon>`) anywhere in `<head>` before the first `RemixIcon` mounts, and this
|
|
9
|
-
* package's default never loads.
|
|
10
|
-
*
|
|
11
|
-
* Exported because every host that has needed the override so far re-declared this string
|
|
12
|
-
* literally rather than importing it -- three copies outside this file, across two apps of the
|
|
13
|
-
* one downstream checkout inspected (2026-09-06), and they do not agree on element type: one
|
|
14
|
-
* installs a `<style>`, the other a `<link>`. A copied magic string is a rename away from
|
|
15
|
-
* silently double-loading the font; importing the constant is not.
|
|
16
|
-
*/
|
|
17
|
-
export const REMIXICON_STYLESHEET_MARKER = 'data-jini-remixicon';
|
|
18
|
-
let injected = false;
|
|
19
|
-
let warned = false;
|
|
20
|
-
/**
|
|
21
|
-
* One diagnostic per page load, not one per `RemixIcon` mount — the failure this reports is a
|
|
22
|
-
* build/bundler property, so it is the same failure every time and a per-mount warning would bury
|
|
23
|
-
* the rest of the console under it.
|
|
24
|
-
*
|
|
25
|
-
* @complexity O(1) time, O(1) space.
|
|
26
|
-
* @overallScore 100
|
|
27
|
-
*/
|
|
28
|
-
function warnOnce(message) {
|
|
29
|
-
if (warned)
|
|
30
|
-
return;
|
|
31
|
-
warned = true;
|
|
32
|
-
console.warn(`[@jini-ai/ui] RemixIcon: ${message}`);
|
|
33
|
-
}
|
|
34
|
-
const OPT_IN_HINT = 'Import the stylesheet yourself and install it via ' +
|
|
35
|
-
"`installRemixIconStylesheet({ href })` — the subpath export `@jini-ai/ui/remixicon.css` " +
|
|
36
|
-
'resolves through your bundler normally, unlike this runtime default.';
|
|
37
|
-
/**
|
|
38
|
-
* Installs a caller-supplied stylesheet as the one serving `.ri-*` glyphs, claiming
|
|
39
|
-
* {@link REMIXICON_STYLESHEET_MARKER} so this package's own default never loads.
|
|
40
|
-
*
|
|
41
|
-
* This is the supported opt-in for any host whose bundler defeats the default's `import.meta.url`
|
|
42
|
-
* resolution (see {@link ensureRemixIconStylesheet}) — point it at
|
|
43
|
-
* `@jini-ai/ui/remixicon.css`, which is a normal subpath export a bundler resolves and rewrites
|
|
44
|
-
* like any other asset. Call it before the first `RemixIcon` mounts; module top level in the
|
|
45
|
-
* host's entry file is the usual place, since the default injection runs from a `useEffect` and
|
|
46
|
-
* therefore only has to lose a race against React's commit phase.
|
|
47
|
-
*
|
|
48
|
-
* Idempotent and safe to call unconditionally: it no-ops if any marked stylesheet is already
|
|
49
|
-
* present (including one a previous call installed), and no-ops outside a DOM.
|
|
50
|
-
*
|
|
51
|
-
* @param options.href URL of a stylesheet defining the `.ri-*` classes and its own `@font-face`.
|
|
52
|
-
* Resolved by the browser against the document, so relative values follow the page's base URL —
|
|
53
|
-
* pass a bundler-produced URL rather than a hand-written relative path.
|
|
54
|
-
* @returns `true` if this call inserted the stylesheet, `false` if it no-opped.
|
|
55
|
-
* @sideEffects Appends one `<link>` to `document.head`.
|
|
56
|
-
* @complexity O(1) time (one `querySelector` on an attribute selector), O(1) space.
|
|
57
|
-
* @overallScore 100
|
|
58
|
-
*/
|
|
59
|
-
export function installRemixIconStylesheet(options) {
|
|
60
|
-
if (typeof document === 'undefined')
|
|
61
|
-
return false;
|
|
62
|
-
if (document.querySelector(`[${REMIXICON_STYLESHEET_MARKER}]`)) {
|
|
63
|
-
injected = true;
|
|
64
|
-
return false;
|
|
65
|
-
}
|
|
66
|
-
const link = document.createElement('link');
|
|
67
|
-
link.rel = 'stylesheet';
|
|
68
|
-
link.setAttribute(REMIXICON_STYLESHEET_MARKER, '');
|
|
69
|
-
link.href = options.href;
|
|
70
|
-
document.head.appendChild(link);
|
|
71
|
-
injected = true;
|
|
72
|
-
return true;
|
|
73
|
-
}
|
|
74
|
-
/**
|
|
75
|
-
* Resolves this package's bundled RemixIcon CSS relative to its own module URL, or `null` when
|
|
76
|
-
* that resolution has provably produced something unusable.
|
|
77
|
-
*
|
|
78
|
-
* The `null` case is not hypothetical. Bundling a `new URL(..., import.meta.url)` asset reference
|
|
79
|
-
* into a non-ESM output (Vite/Rollup `iife`, for one) makes Rollup inline the referenced file as a
|
|
80
|
-
* `data:text/css;base64,...` href instead. That stylesheet then loads, but its own
|
|
81
|
-
* `@font-face { src: url("./remixicon.woff2") }` is relative and a `data:` URL has no location to
|
|
82
|
-
* be relative *to*, so the font request is never even attempted and every glyph renders as tofu.
|
|
83
|
-
* Injecting it is strictly worse than injecting nothing, because it also claims
|
|
84
|
-
* {@link REMIXICON_STYLESHEET_MARKER} and so suppresses any override installed later.
|
|
85
|
-
*
|
|
86
|
-
* @returns An absolute URL string, or `null` if resolution failed or yielded a `data:`/`blob:` URL.
|
|
87
|
-
* @complexity O(1) time, O(1) space.
|
|
88
|
-
* @overallScore 100
|
|
89
|
-
*/
|
|
90
|
-
function resolveDefaultStylesheetHref() {
|
|
91
|
-
let resolved;
|
|
92
|
-
try {
|
|
93
|
-
resolved = new URL('./remixicon-font/remixicon.css', import.meta.url);
|
|
94
|
-
}
|
|
95
|
-
catch {
|
|
96
|
-
return null;
|
|
97
|
-
}
|
|
98
|
-
if (resolved.protocol === 'data:' || resolved.protocol === 'blob:')
|
|
99
|
-
return null;
|
|
100
|
-
return resolved.href;
|
|
101
|
-
}
|
|
102
|
-
/**
|
|
103
|
-
* Idempotently loads this package's default RemixIcon webfont/CSS on first use, so a host gets
|
|
104
|
-
* working icons out of the box with zero setup — see {@link REMIXICON_STYLESHEET_MARKER} for how a
|
|
105
|
-
* host overrides this and {@link installRemixIconStylesheet} for the supported way to do it.
|
|
106
|
-
*
|
|
107
|
-
* **This default only survives unbundled ESM, and that is not a fixable property of this
|
|
108
|
-
* function.** It resolves the CSS with `new URL('./remixicon-font/remixicon.css',
|
|
109
|
-
* import.meta.url)`, which is correct exactly while this module is still a real ES module sitting
|
|
110
|
-
* next to its own `remixicon-font/` directory. Three bundler transformations break it three
|
|
111
|
-
* different ways. The first two were confirmed live against this component; the third was
|
|
112
|
-
* confirmed live against its sibling, and is listed because nothing exempts this file from it:
|
|
113
|
-
*
|
|
114
|
-
* - **`iife`/`umd` output** cannot express the reference at all and degrades it to an inlined
|
|
115
|
-
* `data:` URL whose own relative `@font-face` src can never resolve. Confirmed live in a
|
|
116
|
-
* downstream `lib`/`iife` widget bundle, where `document.fonts` reported the `remixicon` face
|
|
117
|
-
* as `status: "error"` with no matching resource entry at all — an unattempted fetch, not a
|
|
118
|
-
* 404. Detected and refused by {@link resolveDefaultStylesheetHref}.
|
|
119
|
-
* - **Production ESM builds** may copy the CSS as an opaque asset without descending into its
|
|
120
|
-
* `url(./remixicon.woff2)`, emitting the stylesheet without the font beside it. Confirmed live
|
|
121
|
-
* in a downstream admin SPA build, which is why that host imports the CSS through its
|
|
122
|
-
* bundler's real CSS pipeline instead of letting this default run.
|
|
123
|
-
* - **Vite dev-server dependency pre-bundling** flattens a package module into a `.vite/deps/`
|
|
124
|
-
* chunk, so an `import.meta.url`-relative URL resolves against the chunk's location instead.
|
|
125
|
-
* Observed live for this package's `AgentIcon` (fixed in 58bf42fa by inlining those assets as
|
|
126
|
-
* `data:` URIs — not an option here, see {@link installRemixIconStylesheet}), *not* observed
|
|
127
|
-
* for this component, whose two known hosts both claim the marker before this default ever
|
|
128
|
-
* runs. Worth knowing how it presented there: not as a 404, but as the dev server's SPA
|
|
129
|
-
* history fallback answering `200 text/html`.
|
|
130
|
-
*
|
|
131
|
-
* A previous version of this comment claimed "Vite/Rollup/webpack all rewrite this correctly at
|
|
132
|
-
* build time". They do not, and each downstream host that hit it rediscovered a different half
|
|
133
|
-
* of the above independently. There is no runtime-resolvable URL that survives all of it — the
|
|
134
|
-
* cannot know where, or whether, its sibling asset was emitted — so the honest contract is: the
|
|
135
|
-
* default is a convenience for unbundled ESM, and any bundled host should call
|
|
136
|
-
* {@link installRemixIconStylesheet} instead. This function's job is to make that failure *loud*
|
|
137
|
-
* rather than silent, since tofu glyphs give no clue where to look.
|
|
138
|
-
*
|
|
139
|
-
* @sideEffects Appends at most one `<link>` to `document.head`; may emit one `console.warn`.
|
|
140
|
-
* @complexity O(1) time, O(1) space.
|
|
141
|
-
* @overallScore 100
|
|
142
|
-
*/
|
|
143
|
-
function ensureRemixIconStylesheet() {
|
|
144
|
-
if (injected || typeof document === 'undefined')
|
|
145
|
-
return;
|
|
146
|
-
if (document.querySelector(`[${REMIXICON_STYLESHEET_MARKER}]`)) {
|
|
147
|
-
injected = true;
|
|
148
|
-
return;
|
|
149
|
-
}
|
|
150
|
-
const href = resolveDefaultStylesheetHref();
|
|
151
|
-
if (href === null) {
|
|
152
|
-
// Claiming the marker with a stylesheet that cannot work would block a later override, so
|
|
153
|
-
// stay out of the way entirely and let the host know why its icons are blank.
|
|
154
|
-
injected = true;
|
|
155
|
-
warnOnce(`this bundle defeated the default stylesheet's own URL resolution. ${OPT_IN_HINT}`);
|
|
156
|
-
return;
|
|
157
|
-
}
|
|
158
|
-
const link = document.createElement('link');
|
|
159
|
-
link.rel = 'stylesheet';
|
|
160
|
-
link.setAttribute(REMIXICON_STYLESHEET_MARKER, '');
|
|
161
|
-
link.href = href;
|
|
162
|
-
link.addEventListener('error', () => {
|
|
163
|
-
// The pre-bundling case above: undetectable synchronously, because the href looked perfectly
|
|
164
|
-
// well formed. Drop the dead <link> so it stops holding the marker hostage — a host can
|
|
165
|
-
// still recover by calling installRemixIconStylesheet — but leave `injected` set so a later
|
|
166
|
-
// mount does not re-request the same missing file.
|
|
167
|
-
//
|
|
168
|
-
// UNVERIFIED, and deliberately not relied on: whether this also fires for the `200 text/html`
|
|
169
|
-
// SPA-fallback shape that case actually takes. A stylesheet served as `text/html` is refused
|
|
170
|
-
// under strict MIME checking, which is specified to fire `error` — but that has not been
|
|
171
|
-
// exercised here, so treat the warning as a bonus in that shape, not a guarantee.
|
|
172
|
-
link.remove();
|
|
173
|
-
warnOnce(`the default stylesheet failed to load from ${href}. ${OPT_IN_HINT}`);
|
|
174
|
-
});
|
|
175
|
-
document.head.appendChild(link);
|
|
176
|
-
injected = true;
|
|
177
|
-
}
|
|
3
|
+
import { ensureRemixIconStylesheet } from './RemixIcon.stylesheet.js';
|
|
4
|
+
export { installRemixIconStylesheet, REMIXICON_STYLESHEET_MARKER } from './RemixIcon.stylesheet.js';
|
|
178
5
|
/**
|
|
179
6
|
* Thin wrapper around a RemixIcon webfont glyph (`ri-<name>`). Loads this package's own default
|
|
180
7
|
* RemixIcon CSS/font the first time any `RemixIcon` mounts — see
|
|
@@ -183,7 +10,7 @@ function ensureRemixIconStylesheet() {
|
|
|
183
10
|
*/
|
|
184
11
|
export function RemixIcon({ name, size = 14, className, style }) {
|
|
185
12
|
useEffect(() => {
|
|
186
|
-
ensureRemixIconStylesheet();
|
|
13
|
+
ensureRemixIconStylesheet({});
|
|
187
14
|
}, []);
|
|
188
15
|
return (_jsx("i", { className: `ri-${name}${className ? ` ${className}` : ''}`, "aria-hidden": "true", style: {
|
|
189
16
|
fontSize: size,
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Marks whichever stylesheet is currently serving `.ri-*` glyphs — this package's own default, or
|
|
3
|
+
* a host override that got inserted first. `ensureRemixIconStylesheet` checks for this attribute
|
|
4
|
+
* (not a specific `href`) before injecting, so a host that wants its own icon set/font just needs
|
|
5
|
+
* to add a `<link data-jini-remixicon rel="stylesheet" href="...">` (or an equivalent `<style
|
|
6
|
+
* data-jini-remixicon>`) anywhere in `<head>` before the first `RemixIcon` mounts, and this
|
|
7
|
+
* package's default never loads.
|
|
8
|
+
*
|
|
9
|
+
* Exported because every host that has needed the override so far re-declared this string
|
|
10
|
+
* literally rather than importing it -- three copies outside this file, across two apps of the
|
|
11
|
+
* one downstream checkout inspected (2026-09-06), and they do not agree on element type: one
|
|
12
|
+
* installs a `<style>`, the other a `<link>`. A copied magic string is a rename away from
|
|
13
|
+
* silently double-loading the font; importing the constant is not.
|
|
14
|
+
*/
|
|
15
|
+
export declare const REMIXICON_STYLESHEET_MARKER = "data-jini-remixicon";
|
|
16
|
+
/**
|
|
17
|
+
* Installs a caller-supplied stylesheet as the one serving `.ri-*` glyphs, claiming
|
|
18
|
+
* {@link REMIXICON_STYLESHEET_MARKER} so this package's own default never loads.
|
|
19
|
+
*
|
|
20
|
+
* This is the supported opt-in for any host whose bundler defeats the default's `import.meta.url`
|
|
21
|
+
* resolution (see {@link ensureRemixIconStylesheet}) — point it at
|
|
22
|
+
* `@jini-ai/ui/remixicon.css`, which is a normal subpath export a bundler resolves and rewrites
|
|
23
|
+
* like any other asset. Call it before the first `RemixIcon` mounts; module top level in the
|
|
24
|
+
* host's entry file is the usual place, since the default injection runs from a `useEffect` and
|
|
25
|
+
* therefore only has to lose a race against React's commit phase.
|
|
26
|
+
*
|
|
27
|
+
* Idempotent and safe to call unconditionally: it no-ops if any marked stylesheet is already
|
|
28
|
+
* present (including one a previous call installed), and no-ops outside a DOM.
|
|
29
|
+
*
|
|
30
|
+
* @param options.href URL of a stylesheet defining the `.ri-*` classes and its own `@font-face`.
|
|
31
|
+
* Resolved by the browser against the document, so relative values follow the page's base URL —
|
|
32
|
+
* pass a bundler-produced URL rather than a hand-written relative path.
|
|
33
|
+
* @returns `true` if this call inserted the stylesheet, `false` if it no-opped.
|
|
34
|
+
* @sideEffects Appends one `<link>` to `document.head`; probes and retries its icon font.
|
|
35
|
+
* @complexity O(n + r) including recovery, for n stylesheet rules and r bounded retries.
|
|
36
|
+
* @overallScore 100
|
|
37
|
+
*/
|
|
38
|
+
export declare function installRemixIconStylesheet(options: {
|
|
39
|
+
href: string;
|
|
40
|
+
}, _optional?: object): boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Idempotently loads this package's default RemixIcon webfont/CSS on first use, so a host gets
|
|
43
|
+
* working icons out of the box with zero setup — see {@link REMIXICON_STYLESHEET_MARKER} for how a
|
|
44
|
+
* host overrides this and {@link installRemixIconStylesheet} for the supported way to do it.
|
|
45
|
+
*
|
|
46
|
+
* **This default only survives unbundled ESM, and that is not a fixable property of this
|
|
47
|
+
* function.** It resolves the CSS with `new URL('./remixicon-font/remixicon.css',
|
|
48
|
+
* import.meta.url)`, which is correct exactly while this module is still a real ES module sitting
|
|
49
|
+
* next to its own `remixicon-font/` directory. Three bundler transformations break it three
|
|
50
|
+
* different ways. The first two were confirmed live against this component; the third was
|
|
51
|
+
* confirmed live against its sibling, and is listed because nothing exempts this file from it:
|
|
52
|
+
*
|
|
53
|
+
* - **`iife`/`umd` output** cannot express the reference at all and degrades it to an inlined
|
|
54
|
+
* `data:` URL whose own relative `@font-face` src can never resolve. Confirmed live in a
|
|
55
|
+
* downstream `lib`/`iife` widget bundle, where `document.fonts` reported the `remixicon` face
|
|
56
|
+
* as `status: "error"` with no matching resource entry at all — an unattempted fetch, not a
|
|
57
|
+
* 404. Detected and refused by {@link resolveDefaultStylesheetHref}.
|
|
58
|
+
* - **Production ESM builds** may copy the CSS as an opaque asset without descending into its
|
|
59
|
+
* `url(./remixicon.woff2)`, emitting the stylesheet without the font beside it. Confirmed live
|
|
60
|
+
* in a downstream admin SPA build, which is why that host imports the CSS through its
|
|
61
|
+
* bundler's real CSS pipeline instead of letting this default run.
|
|
62
|
+
* - **Vite dev-server dependency pre-bundling** flattens a package module into a `.vite/deps/`
|
|
63
|
+
* chunk, so an `import.meta.url`-relative URL resolves against the chunk's location instead.
|
|
64
|
+
* Observed live for this package's `AgentIcon` (fixed in 58bf42fa by inlining those assets as
|
|
65
|
+
* `data:` URIs — not an option here, see {@link installRemixIconStylesheet}), *not* observed
|
|
66
|
+
* for this component, whose two known hosts both claim the marker before this default ever
|
|
67
|
+
* runs. Worth knowing how it presented there: not as a 404, but as the dev server's SPA
|
|
68
|
+
* history fallback answering `200 text/html`.
|
|
69
|
+
*
|
|
70
|
+
* A previous version of this comment claimed "Vite/Rollup/webpack all rewrite this correctly at
|
|
71
|
+
* build time". They do not, and each downstream host that hit it rediscovered a different half
|
|
72
|
+
* of the above independently. There is no runtime-resolvable URL that survives all of it — the
|
|
73
|
+
* cannot know where, or whether, its sibling asset was emitted — so the honest contract is: the
|
|
74
|
+
* default is a convenience for unbundled ESM, and any bundled host should call
|
|
75
|
+
* {@link installRemixIconStylesheet} instead. This function's job is to make that failure *loud*
|
|
76
|
+
* rather than silent, since tofu glyphs give no clue where to look.
|
|
77
|
+
*
|
|
78
|
+
* @sideEffects Appends at most one `<link>` to `document.head`; may emit one `console.warn`.
|
|
79
|
+
* Probes and retries the installed font, including a host-provided marked override.
|
|
80
|
+
* @complexity O(n + r) including recovery, for n stylesheet rules and r bounded retries.
|
|
81
|
+
* @overallScore 100
|
|
82
|
+
*/
|
|
83
|
+
export declare function ensureRemixIconStylesheet(_input: object, _optional?: object): void;
|
|
84
|
+
//# sourceMappingURL=RemixIcon.stylesheet.d.ts.map
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { startRemixIconStylesheetRecovery } from './remixicon-font-recovery.js';
|
|
2
|
+
/** Start once per installed sheet, waiting for linked CSS before inspecting its font source. */
|
|
3
|
+
function recoverFont({ stylesheet }) {
|
|
4
|
+
if (!document.fonts || typeof FontFace === 'undefined')
|
|
5
|
+
return;
|
|
6
|
+
if (stylesheet.tagName !== 'STYLE' && stylesheet.tagName !== 'LINK')
|
|
7
|
+
return;
|
|
8
|
+
const sheet = stylesheet;
|
|
9
|
+
const start = () => startRemixIconStylesheetRecovery({
|
|
10
|
+
stylesheet: sheet,
|
|
11
|
+
fonts: document.fonts,
|
|
12
|
+
baseUrl: document.baseURI,
|
|
13
|
+
createFontFace: ({ family, source, descriptors }) => new FontFace(family, source, descriptors),
|
|
14
|
+
timers: {
|
|
15
|
+
setTimeout: (run, delay) => globalThis.setTimeout(run, delay),
|
|
16
|
+
clearTimeout: (handle) => globalThis.clearTimeout(handle),
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
if (sheet.sheet) {
|
|
20
|
+
start();
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
sheet.addEventListener('load', start, { once: true });
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Marks whichever stylesheet is currently serving `.ri-*` glyphs — this package's own default, or
|
|
27
|
+
* a host override that got inserted first. `ensureRemixIconStylesheet` checks for this attribute
|
|
28
|
+
* (not a specific `href`) before injecting, so a host that wants its own icon set/font just needs
|
|
29
|
+
* to add a `<link data-jini-remixicon rel="stylesheet" href="...">` (or an equivalent `<style
|
|
30
|
+
* data-jini-remixicon>`) anywhere in `<head>` before the first `RemixIcon` mounts, and this
|
|
31
|
+
* package's default never loads.
|
|
32
|
+
*
|
|
33
|
+
* Exported because every host that has needed the override so far re-declared this string
|
|
34
|
+
* literally rather than importing it -- three copies outside this file, across two apps of the
|
|
35
|
+
* one downstream checkout inspected (2026-09-06), and they do not agree on element type: one
|
|
36
|
+
* installs a `<style>`, the other a `<link>`. A copied magic string is a rename away from
|
|
37
|
+
* silently double-loading the font; importing the constant is not.
|
|
38
|
+
*/
|
|
39
|
+
export const REMIXICON_STYLESHEET_MARKER = 'data-jini-remixicon';
|
|
40
|
+
let injected = false;
|
|
41
|
+
let warned = false;
|
|
42
|
+
/**
|
|
43
|
+
* One diagnostic per page load, not one per `RemixIcon` mount — the failure this reports is a
|
|
44
|
+
* build/bundler property, so it is the same failure every time and a per-mount warning would bury
|
|
45
|
+
* the rest of the console under it.
|
|
46
|
+
*
|
|
47
|
+
* @complexity O(1) time, O(1) space.
|
|
48
|
+
* @overallScore 100
|
|
49
|
+
*/
|
|
50
|
+
function warnOnce(message) {
|
|
51
|
+
if (warned)
|
|
52
|
+
return;
|
|
53
|
+
warned = true;
|
|
54
|
+
console.warn(`[@jini-ai/ui] RemixIcon: ${message}`);
|
|
55
|
+
}
|
|
56
|
+
const OPT_IN_HINT = 'Import the stylesheet yourself and install it via ' +
|
|
57
|
+
"`installRemixIconStylesheet({ href })` — the subpath export `@jini-ai/ui/remixicon.css` " +
|
|
58
|
+
'resolves through your bundler normally, unlike this runtime default.';
|
|
59
|
+
/**
|
|
60
|
+
* Installs a caller-supplied stylesheet as the one serving `.ri-*` glyphs, claiming
|
|
61
|
+
* {@link REMIXICON_STYLESHEET_MARKER} so this package's own default never loads.
|
|
62
|
+
*
|
|
63
|
+
* This is the supported opt-in for any host whose bundler defeats the default's `import.meta.url`
|
|
64
|
+
* resolution (see {@link ensureRemixIconStylesheet}) — point it at
|
|
65
|
+
* `@jini-ai/ui/remixicon.css`, which is a normal subpath export a bundler resolves and rewrites
|
|
66
|
+
* like any other asset. Call it before the first `RemixIcon` mounts; module top level in the
|
|
67
|
+
* host's entry file is the usual place, since the default injection runs from a `useEffect` and
|
|
68
|
+
* therefore only has to lose a race against React's commit phase.
|
|
69
|
+
*
|
|
70
|
+
* Idempotent and safe to call unconditionally: it no-ops if any marked stylesheet is already
|
|
71
|
+
* present (including one a previous call installed), and no-ops outside a DOM.
|
|
72
|
+
*
|
|
73
|
+
* @param options.href URL of a stylesheet defining the `.ri-*` classes and its own `@font-face`.
|
|
74
|
+
* Resolved by the browser against the document, so relative values follow the page's base URL —
|
|
75
|
+
* pass a bundler-produced URL rather than a hand-written relative path.
|
|
76
|
+
* @returns `true` if this call inserted the stylesheet, `false` if it no-opped.
|
|
77
|
+
* @sideEffects Appends one `<link>` to `document.head`; probes and retries its icon font.
|
|
78
|
+
* @complexity O(n + r) including recovery, for n stylesheet rules and r bounded retries.
|
|
79
|
+
* @overallScore 100
|
|
80
|
+
*/
|
|
81
|
+
export function installRemixIconStylesheet(options, _optional = {}) {
|
|
82
|
+
if (typeof document === 'undefined')
|
|
83
|
+
return false;
|
|
84
|
+
const existing = document.querySelector(`[${REMIXICON_STYLESHEET_MARKER}]`);
|
|
85
|
+
if (existing) {
|
|
86
|
+
if (!injected)
|
|
87
|
+
recoverFont({ stylesheet: existing });
|
|
88
|
+
injected = true;
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
const link = document.createElement('link');
|
|
92
|
+
link.rel = 'stylesheet';
|
|
93
|
+
link.setAttribute(REMIXICON_STYLESHEET_MARKER, '');
|
|
94
|
+
link.href = options.href;
|
|
95
|
+
recoverFont({ stylesheet: link });
|
|
96
|
+
document.head.appendChild(link);
|
|
97
|
+
injected = true;
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Resolves this package's bundled RemixIcon CSS relative to its own module URL, or `null` when
|
|
102
|
+
* that resolution has provably produced something unusable.
|
|
103
|
+
*
|
|
104
|
+
* The `null` case is not hypothetical. Bundling a `new URL(..., import.meta.url)` asset reference
|
|
105
|
+
* into a non-ESM output (Vite/Rollup `iife`, for one) makes Rollup inline the referenced file as a
|
|
106
|
+
* `data:text/css;base64,...` href instead. That stylesheet then loads, but its own
|
|
107
|
+
* `@font-face { src: url("./remixicon.woff2") }` is relative and a `data:` URL has no location to
|
|
108
|
+
* be relative *to*, so the font request is never even attempted and every glyph renders as tofu.
|
|
109
|
+
* Injecting it is strictly worse than injecting nothing, because it also claims
|
|
110
|
+
* {@link REMIXICON_STYLESHEET_MARKER} and so suppresses any override installed later.
|
|
111
|
+
*
|
|
112
|
+
* @returns An absolute URL string, or `null` if resolution failed or yielded a `data:`/`blob:` URL.
|
|
113
|
+
* @complexity O(1) time, O(1) space.
|
|
114
|
+
* @overallScore 100
|
|
115
|
+
*/
|
|
116
|
+
function resolveDefaultStylesheetHref() {
|
|
117
|
+
let resolved;
|
|
118
|
+
try {
|
|
119
|
+
resolved = new URL('./remixicon-font/remixicon.css', import.meta.url);
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
if (resolved.protocol === 'data:' || resolved.protocol === 'blob:')
|
|
125
|
+
return null;
|
|
126
|
+
return resolved.href;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Idempotently loads this package's default RemixIcon webfont/CSS on first use, so a host gets
|
|
130
|
+
* working icons out of the box with zero setup — see {@link REMIXICON_STYLESHEET_MARKER} for how a
|
|
131
|
+
* host overrides this and {@link installRemixIconStylesheet} for the supported way to do it.
|
|
132
|
+
*
|
|
133
|
+
* **This default only survives unbundled ESM, and that is not a fixable property of this
|
|
134
|
+
* function.** It resolves the CSS with `new URL('./remixicon-font/remixicon.css',
|
|
135
|
+
* import.meta.url)`, which is correct exactly while this module is still a real ES module sitting
|
|
136
|
+
* next to its own `remixicon-font/` directory. Three bundler transformations break it three
|
|
137
|
+
* different ways. The first two were confirmed live against this component; the third was
|
|
138
|
+
* confirmed live against its sibling, and is listed because nothing exempts this file from it:
|
|
139
|
+
*
|
|
140
|
+
* - **`iife`/`umd` output** cannot express the reference at all and degrades it to an inlined
|
|
141
|
+
* `data:` URL whose own relative `@font-face` src can never resolve. Confirmed live in a
|
|
142
|
+
* downstream `lib`/`iife` widget bundle, where `document.fonts` reported the `remixicon` face
|
|
143
|
+
* as `status: "error"` with no matching resource entry at all — an unattempted fetch, not a
|
|
144
|
+
* 404. Detected and refused by {@link resolveDefaultStylesheetHref}.
|
|
145
|
+
* - **Production ESM builds** may copy the CSS as an opaque asset without descending into its
|
|
146
|
+
* `url(./remixicon.woff2)`, emitting the stylesheet without the font beside it. Confirmed live
|
|
147
|
+
* in a downstream admin SPA build, which is why that host imports the CSS through its
|
|
148
|
+
* bundler's real CSS pipeline instead of letting this default run.
|
|
149
|
+
* - **Vite dev-server dependency pre-bundling** flattens a package module into a `.vite/deps/`
|
|
150
|
+
* chunk, so an `import.meta.url`-relative URL resolves against the chunk's location instead.
|
|
151
|
+
* Observed live for this package's `AgentIcon` (fixed in 58bf42fa by inlining those assets as
|
|
152
|
+
* `data:` URIs — not an option here, see {@link installRemixIconStylesheet}), *not* observed
|
|
153
|
+
* for this component, whose two known hosts both claim the marker before this default ever
|
|
154
|
+
* runs. Worth knowing how it presented there: not as a 404, but as the dev server's SPA
|
|
155
|
+
* history fallback answering `200 text/html`.
|
|
156
|
+
*
|
|
157
|
+
* A previous version of this comment claimed "Vite/Rollup/webpack all rewrite this correctly at
|
|
158
|
+
* build time". They do not, and each downstream host that hit it rediscovered a different half
|
|
159
|
+
* of the above independently. There is no runtime-resolvable URL that survives all of it — the
|
|
160
|
+
* cannot know where, or whether, its sibling asset was emitted — so the honest contract is: the
|
|
161
|
+
* default is a convenience for unbundled ESM, and any bundled host should call
|
|
162
|
+
* {@link installRemixIconStylesheet} instead. This function's job is to make that failure *loud*
|
|
163
|
+
* rather than silent, since tofu glyphs give no clue where to look.
|
|
164
|
+
*
|
|
165
|
+
* @sideEffects Appends at most one `<link>` to `document.head`; may emit one `console.warn`.
|
|
166
|
+
* Probes and retries the installed font, including a host-provided marked override.
|
|
167
|
+
* @complexity O(n + r) including recovery, for n stylesheet rules and r bounded retries.
|
|
168
|
+
* @overallScore 100
|
|
169
|
+
*/
|
|
170
|
+
export function ensureRemixIconStylesheet(_input, _optional = {}) {
|
|
171
|
+
if (injected || typeof document === 'undefined')
|
|
172
|
+
return;
|
|
173
|
+
const existing = document.querySelector(`[${REMIXICON_STYLESHEET_MARKER}]`);
|
|
174
|
+
if (existing) {
|
|
175
|
+
recoverFont({ stylesheet: existing });
|
|
176
|
+
injected = true;
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
const href = resolveDefaultStylesheetHref();
|
|
180
|
+
if (href === null) {
|
|
181
|
+
// Claiming the marker with a stylesheet that cannot work would block a later override, so
|
|
182
|
+
// stay out of the way entirely and let the host know why its icons are blank.
|
|
183
|
+
injected = true;
|
|
184
|
+
warnOnce(`this bundle defeated the default stylesheet's own URL resolution. ${OPT_IN_HINT}`);
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
const link = document.createElement('link');
|
|
188
|
+
link.rel = 'stylesheet';
|
|
189
|
+
link.setAttribute(REMIXICON_STYLESHEET_MARKER, '');
|
|
190
|
+
link.href = href;
|
|
191
|
+
link.addEventListener('error', () => {
|
|
192
|
+
// The pre-bundling case above: undetectable synchronously, because the href looked perfectly
|
|
193
|
+
// well formed. Drop the dead <link> so it stops holding the marker hostage — a host can
|
|
194
|
+
// still recover by calling installRemixIconStylesheet — but leave `injected` set so a later
|
|
195
|
+
// mount does not re-request the same missing file.
|
|
196
|
+
//
|
|
197
|
+
// UNVERIFIED, and deliberately not relied on: whether this also fires for the `200 text/html`
|
|
198
|
+
// SPA-fallback shape that case actually takes. A stylesheet served as `text/html` is refused
|
|
199
|
+
// under strict MIME checking, which is specified to fire `error` — but that has not been
|
|
200
|
+
// exercised here, so treat the warning as a bonus in that shape, not a guarantee.
|
|
201
|
+
link.remove();
|
|
202
|
+
warnOnce(`the default stylesheet failed to load from ${href}. ${OPT_IN_HINT}`);
|
|
203
|
+
});
|
|
204
|
+
recoverFont({ stylesheet: link });
|
|
205
|
+
document.head.appendChild(link);
|
|
206
|
+
injected = true;
|
|
207
|
+
}
|
|
208
|
+
//# sourceMappingURL=RemixIcon.stylesheet.js.map
|