@inkra/react 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +113 -3
- package/dist/Button.js +3 -2
- package/dist/PencilIcon.d.ts +10 -0
- package/dist/PencilIcon.js +83 -39
- package/dist/PencilLine.js +9 -1
- package/dist/PencilText.js +15 -9
- package/dist/accordion/Accordion.js +65 -5
- package/dist/alert-dialog/index.js +14 -4
- package/dist/audio-player/index.d.ts +2 -0
- package/dist/audio-player/index.js +131 -34
- package/dist/breadcrumb/index.js +4 -1
- package/dist/calendar/index.d.ts +4 -1
- package/dist/calendar/index.js +37 -12
- package/dist/calendar/locales.d.ts +7 -0
- package/dist/calendar/locales.js +2 -0
- package/dist/carousel/index.js +20 -10
- package/dist/choice-grid/index.d.ts +6 -1
- package/dist/choice-grid/index.js +63 -33
- package/dist/color-picker/GradientEditor.d.ts +40 -0
- package/dist/color-picker/GradientEditor.js +64 -0
- package/dist/color-picker/gradient.d.ts +25 -0
- package/dist/color-picker/gradient.js +60 -0
- package/dist/color-picker/index.d.ts +27 -7
- package/dist/color-picker/index.js +144 -40
- package/dist/color-swatch/index.d.ts +10 -0
- package/dist/color-swatch/index.js +9 -0
- package/dist/command/index.js +27 -4
- package/dist/content/index.d.ts +5 -0
- package/dist/content/index.js +15 -3
- package/dist/context-menu/index.js +1 -1
- package/dist/data-table/index.d.ts +7 -1
- package/dist/data-table/index.js +67 -16
- package/dist/data-table/model.d.ts +2 -0
- package/dist/data-table/model.js +7 -0
- package/dist/date-picker/index.d.ts +2 -0
- package/dist/date-picker/index.js +20 -21
- package/dist/day-agenda/index.d.ts +6 -1
- package/dist/day-agenda/index.js +49 -21
- package/dist/day-agenda/layout.js +65 -9
- package/dist/display/ProgressRing.js +7 -3
- package/dist/display/index.d.ts +4 -1
- package/dist/display/index.js +3 -2
- package/dist/dockable-panel/index.js +15 -13
- package/dist/drawer/index.js +11 -5
- package/dist/editable/index.d.ts +24 -2
- package/dist/editable/index.js +148 -40
- package/dist/empty/illustrations.d.ts +8 -0
- package/dist/empty/illustrations.js +78 -0
- package/dist/empty/index.d.ts +6 -1
- package/dist/empty/index.js +11 -3
- package/dist/flip/index.js +28 -8
- package/dist/form/Field.js +11 -10
- package/dist/form/Input.js +4 -1
- package/dist/form/Textarea.d.ts +3 -1
- package/dist/form/Textarea.js +9 -5
- package/dist/heatmap/index.js +4 -1
- package/dist/hotkey/index.d.ts +1 -0
- package/dist/hotkey/index.js +9 -4
- package/dist/hover-card/index.js +1 -1
- package/dist/i18n/catalog.d.ts +5 -0
- package/dist/i18n/catalog.js +289 -0
- package/dist/i18n/index.d.ts +23 -0
- package/dist/i18n/index.js +50 -0
- package/dist/image-cropper/index.js +21 -17
- package/dist/image-viewer/VideoResource.d.ts +21 -0
- package/dist/image-viewer/VideoResource.js +196 -0
- package/dist/image-viewer/index.d.ts +14 -0
- package/dist/image-viewer/index.js +141 -86
- package/dist/image-viewer/lucideGeometry.d.ts +3 -0
- package/dist/image-viewer/lucideGeometry.js +19 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/input-number/index.js +13 -5
- package/dist/internal/cache.d.ts +4 -0
- package/dist/internal/cache.js +18 -1
- package/dist/internal/iconGeometry.d.ts +2 -0
- package/dist/internal/iconGeometry.js +32 -0
- package/dist/internal/renderTokens.d.ts +4 -0
- package/dist/internal/renderTokens.js +4 -0
- package/dist/internal/stroke.d.ts +7 -0
- package/dist/internal/stroke.js +63 -7
- package/dist/internal/surfaceBudget.d.ts +20 -0
- package/dist/internal/surfaceBudget.js +69 -0
- package/dist/internal/surfaceRenderer.js +87 -28
- package/dist/internal/surfaceVisibility.d.ts +3 -0
- package/dist/internal/surfaceVisibility.js +60 -0
- package/dist/internal/textRenderer.d.ts +3 -0
- package/dist/internal/textRenderer.js +13 -5
- package/dist/internal/usePencilSurface.js +17 -1
- package/dist/internal/wash.js +4 -3
- package/dist/journey-map/index.d.ts +1 -1
- package/dist/journey-map/index.js +6 -3
- package/dist/journey-map/terrain.js +10 -5
- package/dist/keypad/index.js +4 -1
- package/dist/layout/index.js +1 -1
- package/dist/log/index.js +42 -33
- package/dist/markdown/CodeBlock.d.ts +19 -0
- package/dist/markdown/CodeBlock.js +86 -0
- package/dist/markdown/Deletion.d.ts +2 -0
- package/dist/markdown/Deletion.js +69 -0
- package/dist/markdown/icons.d.ts +14 -0
- package/dist/markdown/icons.js +16 -0
- package/dist/markdown/index.d.ts +17 -0
- package/dist/markdown/index.js +177 -0
- package/dist/node-graph/index.d.ts +3 -1
- package/dist/node-graph/index.js +63 -28
- package/dist/number-ticker/index.js +4 -1
- package/dist/number-ticker/precision.js +9 -2
- package/dist/overflow-list/index.js +9 -6
- package/dist/overlay/Overlay.js +8 -3
- package/dist/overlay/Popover.js +3 -3
- package/dist/overlay/themeBridge.js +14 -2
- package/dist/pagination/index.d.ts +10 -1
- package/dist/pagination/index.js +13 -9
- package/dist/pan-zoom/index.d.ts +2 -0
- package/dist/pan-zoom/index.js +12 -8
- package/dist/pencil.d.ts +1 -0
- package/dist/pencil.js +4 -4
- package/dist/radar/index.d.ts +2 -0
- package/dist/radar/index.js +44 -21
- package/dist/range-slider/index.js +4 -3
- package/dist/rating/index.d.ts +4 -0
- package/dist/rating/index.js +131 -24
- package/dist/resizable/index.d.ts +1 -1
- package/dist/resizable/index.js +5 -2
- package/dist/scroll-area/index.d.ts +1 -1
- package/dist/scroll-area/index.js +5 -2
- package/dist/select/MultipleSelect.js +5 -4
- package/dist/select/SearchableSelect.js +64 -7
- package/dist/select/Select.d.ts +1 -0
- package/dist/select/Select.js +6 -2
- package/dist/select/StepperSelect.d.ts +29 -0
- package/dist/select/StepperSelect.js +122 -0
- package/dist/select/index.d.ts +2 -0
- package/dist/select/index.js +1 -0
- package/dist/select/stepperModel.d.ts +7 -0
- package/dist/select/stepperModel.js +29 -0
- package/dist/selection/Checkbox.js +4 -3
- package/dist/selection/RadioGroup.js +29 -13
- package/dist/selection/Switch.js +4 -3
- package/dist/selection/useNativeChecked.d.ts +2 -1
- package/dist/selection/useNativeChecked.js +26 -8
- package/dist/slider/index.js +50 -6
- package/dist/sortable/index.js +17 -11
- package/dist/sparkline/index.js +21 -6
- package/dist/sparkline/model.js +11 -4
- package/dist/steps/index.js +4 -1
- package/dist/styles/accordion.css +228 -0
- package/dist/styles/alert-dialog.css +139 -0
- package/dist/styles/audio-player.css +22 -0
- package/dist/styles/base.css +638 -0
- package/dist/styles/breadcrumb.css +7 -0
- package/dist/styles/calendar.css +62 -0
- package/dist/styles/carousel.css +173 -0
- package/dist/styles/choice-grid.css +226 -0
- package/dist/styles/color-picker.css +281 -0
- package/dist/styles/color-swatch.css +47 -0
- package/dist/styles/command.css +52 -0
- package/dist/styles/content.css +79 -0
- package/dist/styles/context-menu.css +35 -0
- package/dist/styles/data-table.css +31 -0
- package/dist/styles/date-picker.css +107 -0
- package/dist/styles/day-agenda.css +30 -0
- package/dist/styles/display.css +304 -0
- package/dist/styles/dockable-panel.css +110 -0
- package/dist/styles/drawer.css +133 -0
- package/dist/styles/dropdown-menu.css +68 -0
- package/dist/styles/editable.css +145 -0
- package/dist/styles/empty.css +108 -0
- package/dist/styles/flip.css +101 -0
- package/dist/styles/form.css +634 -0
- package/dist/styles/heatmap.css +256 -0
- package/dist/styles/hotkey.css +189 -0
- package/dist/styles/hover-card.css +27 -0
- package/dist/styles/image-cropper.css +205 -0
- package/dist/styles/image-viewer.css +422 -0
- package/dist/styles/input-number.css +21 -0
- package/dist/styles/journey-map.css +19 -0
- package/dist/styles/keypad.css +92 -0
- package/dist/styles/layout.css +72 -0
- package/dist/styles/leaflet.css +661 -0
- package/dist/styles/log.css +203 -0
- package/dist/styles/markdown.css +379 -0
- package/dist/styles/node-graph.css +142 -0
- package/dist/styles/number-ticker.css +83 -0
- package/dist/styles/overflow-list.css +51 -0
- package/dist/styles/overlay.css +284 -0
- package/dist/styles/pagination.css +21 -0
- package/dist/styles/pan-zoom.css +136 -0
- package/dist/styles/radar.css +1 -0
- package/dist/styles/rating.css +121 -0
- package/dist/styles/resizable.css +108 -0
- package/dist/styles/scroll-area.css +122 -0
- package/dist/styles/select.css +554 -0
- package/dist/styles/selection.css +264 -0
- package/dist/styles/slider.css +145 -0
- package/dist/styles/slot.css +109 -0
- package/dist/styles/sortable.css +159 -0
- package/dist/styles/sparkline.css +24 -0
- package/dist/styles/steps.css +27 -0
- package/dist/styles/tabs.css +79 -0
- package/dist/styles/tag-input.css +213 -0
- package/dist/styles/time-picker.css +66 -0
- package/dist/styles/timeline.css +24 -0
- package/dist/styles/toast.css +160 -0
- package/dist/styles/toggle-group.css +107 -0
- package/dist/styles/tooltip.css +78 -0
- package/dist/styles/transfer.css +148 -0
- package/dist/styles/tree-select.css +131 -0
- package/dist/styles/tree.css +59 -0
- package/dist/styles/typewriter.css +62 -0
- package/dist/styles/upload.css +60 -0
- package/dist/styles/virtual-list.css +112 -0
- package/dist/styles.css +2091 -421
- package/dist/tabs/index.d.ts +9 -0
- package/dist/tabs/index.js +69 -16
- package/dist/tabs/useOverflow.d.ts +20 -0
- package/dist/tabs/useOverflow.js +145 -0
- package/dist/tag-input/index.d.ts +7 -2
- package/dist/tag-input/index.js +81 -42
- package/dist/theme/index.d.ts +11 -0
- package/dist/theme/index.js +23 -0
- package/dist/time-picker/index.js +45 -27
- package/dist/timeline/index.js +4 -1
- package/dist/toast/index.d.ts +19 -11
- package/dist/toast/index.js +51 -8
- package/dist/toast/manager.d.ts +42 -0
- package/dist/toast/manager.js +85 -0
- package/dist/toast/useToastNavigation.d.ts +3 -0
- package/dist/toast/useToastNavigation.js +75 -0
- package/dist/tokens.css +4 -0
- package/dist/tooltip/Tooltip.js +3 -3
- package/dist/transfer/index.d.ts +5 -0
- package/dist/transfer/index.js +120 -53
- package/dist/transfer/model.d.ts +4 -0
- package/dist/transfer/model.js +37 -11
- package/dist/tree/index.d.ts +5 -1
- package/dist/tree/index.js +11 -6
- package/dist/tree-select/index.js +70 -31
- package/dist/tree-select/model.d.ts +11 -5
- package/dist/tree-select/model.js +111 -83
- package/dist/typewriter/index.js +10 -10
- package/dist/upload/index.js +50 -33
- package/dist/virtual-list/index.js +11 -6
- package/package.json +423 -3
|
@@ -9,6 +9,11 @@ export const pencilLettering = Object.freeze({
|
|
|
9
9
|
});
|
|
10
10
|
export const pencilHandwriting = '"Trip Map Hand","HanziPen SC","Hannotate SC","Kaiti SC","STKaiti",cursive';
|
|
11
11
|
const TEXT_PIXEL_LIMIT = 524288;
|
|
12
|
+
export function boundedTextResolution(width, height, dpr) {
|
|
13
|
+
if (![width, height, dpr].every(value => Number.isFinite(value) && value > 0))
|
|
14
|
+
return 0;
|
|
15
|
+
return Math.floor(Math.min(Math.max(2, Math.min(4, Math.ceil(dpr * 2))), Math.sqrt(TEXT_PIXEL_LIMIT / (width * height)), 8192 / width, 8192 / height));
|
|
16
|
+
}
|
|
12
17
|
const textCache = new BoundedCache(160, 16 * 1024 * 1024);
|
|
13
18
|
const glyphCache = new BoundedCache(512, 8 * 1024 * 1024);
|
|
14
19
|
const clamp = (value) => Math.max(0, Math.min(1, value));
|
|
@@ -75,14 +80,15 @@ export function renderPencilText(text, options) {
|
|
|
75
80
|
if (text.length > 256 || !Number.isFinite(size) || size <= 0 || size > 128) {
|
|
76
81
|
throw new RangeError('Pencil text exceeds the bounded renderer input');
|
|
77
82
|
}
|
|
78
|
-
|
|
79
|
-
const font =
|
|
83
|
+
let resolution = Math.max(2, Math.min(4, Math.ceil(dpr * 2)));
|
|
84
|
+
const font = `${options.style ?? 'normal'} ${options.weight ?? '400'} ${size}px ${family}`;
|
|
80
85
|
const fontReady = document.fonts?.check(font, text) ?? false;
|
|
81
86
|
const key = cache ? JSON.stringify([
|
|
82
87
|
PENCIL_RENDERER_VERSION,
|
|
88
|
+
'adaptive-text-raster-v1',
|
|
83
89
|
text,
|
|
84
90
|
size,
|
|
85
|
-
|
|
91
|
+
font,
|
|
86
92
|
color,
|
|
87
93
|
seed,
|
|
88
94
|
dpr,
|
|
@@ -113,7 +119,9 @@ export function renderPencilText(text, options) {
|
|
|
113
119
|
const descent = Math.ceil(size * 0.35);
|
|
114
120
|
const width = advance + padding * 2;
|
|
115
121
|
const height = ascent + descent + padding * 2;
|
|
116
|
-
|
|
122
|
+
// Wide lines on Retina displays can retain pencil ink at a lower sampling rate.
|
|
123
|
+
resolution = boundedTextResolution(width, height, dpr);
|
|
124
|
+
if (resolution < 1) {
|
|
117
125
|
throw new RangeError('Pencil text exceeds the bounded pixel budget');
|
|
118
126
|
}
|
|
119
127
|
mask.width = width * resolution;
|
|
@@ -131,7 +139,7 @@ export function renderPencilText(text, options) {
|
|
|
131
139
|
const characterSeed = (options.glyphSeed ^ Math.imul(character.charCodeAt(0), 16777619)
|
|
132
140
|
^ Math.imul(index % 3, 374761393)) >>> 0;
|
|
133
141
|
const glyphKey = JSON.stringify([
|
|
134
|
-
PENCIL_RENDERER_VERSION, character, size,
|
|
142
|
+
PENCIL_RENDERER_VERSION, character, size, font, color, characterSeed,
|
|
135
143
|
resolution, settings, document.fonts?.check(font, character),
|
|
136
144
|
]);
|
|
137
145
|
let glyph = glyphCache.get(glyphKey);
|
|
@@ -3,9 +3,13 @@ import { PENCIL_RENDERER_VERSION, effectiveDpr } from './cache.js';
|
|
|
3
3
|
import { observeCanvasRecovery } from './recovery.js';
|
|
4
4
|
import { seedToNumber } from './random.js';
|
|
5
5
|
import { drawPencilSurface } from './surfaceRenderer.js';
|
|
6
|
+
import { releaseSurfaceBitmaps } from './surfaceBudget.js';
|
|
7
|
+
import { observeSurfaceViewport, surfaceIsNearViewport } from './surfaceVisibility.js';
|
|
6
8
|
import { maxStrokeRoughness, pencilRenderDefaults, readRenderToken } from './renderTokens.js';
|
|
7
9
|
function token(style, name, fallback) {
|
|
8
|
-
|
|
10
|
+
const value = style.getPropertyValue(name).trim() || fallback;
|
|
11
|
+
// Offscreen material canvases have no inherited DOM foreground.
|
|
12
|
+
return value.toLowerCase() === 'currentcolor' ? style.color : value;
|
|
9
13
|
}
|
|
10
14
|
export function usePencilSurface(rootRef, canvasRef, borderRef, variant, seed, clipContent, radius) {
|
|
11
15
|
useBrowserLayoutEffect(() => {
|
|
@@ -16,10 +20,18 @@ export function usePencilSurface(rootRef, canvasRef, borderRef, variant, seed, c
|
|
|
16
20
|
let frame = 0;
|
|
17
21
|
let lastPaint = '';
|
|
18
22
|
let disposed = false;
|
|
23
|
+
let deferOffscreen = false;
|
|
19
24
|
const paint = () => {
|
|
20
25
|
frame = 0;
|
|
21
26
|
if (disposed)
|
|
22
27
|
return;
|
|
28
|
+
if (deferOffscreen && !surfaceIsNearViewport(root)) {
|
|
29
|
+
// A new canvas otherwise retains its implicit 300x150 blank bitmap.
|
|
30
|
+
// Keep valid painted pixels when merely scrolling out of view.
|
|
31
|
+
if (!lastPaint)
|
|
32
|
+
releaseSurfaceBitmaps(canvas, borderRef.current);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
23
35
|
const width = root.clientWidth;
|
|
24
36
|
const height = root.clientHeight;
|
|
25
37
|
if (width < 6 || height < 6)
|
|
@@ -80,6 +92,7 @@ export function usePencilSurface(rootRef, canvasRef, borderRef, variant, seed, c
|
|
|
80
92
|
lastPaint = paintKey;
|
|
81
93
|
}
|
|
82
94
|
catch {
|
|
95
|
+
releaseSurfaceBitmaps(canvas, borderRef.current);
|
|
83
96
|
root.dataset.pencilFallback = 'true';
|
|
84
97
|
root.style.removeProperty('--inkra-pencil-clip');
|
|
85
98
|
lastPaint = '';
|
|
@@ -89,6 +102,8 @@ export function usePencilSurface(rootRef, canvasRef, borderRef, variant, seed, c
|
|
|
89
102
|
if (!frame)
|
|
90
103
|
frame = requestAnimationFrame(paint);
|
|
91
104
|
};
|
|
105
|
+
const stopViewport = observeSurfaceViewport(root, schedule);
|
|
106
|
+
deferOffscreen = stopViewport !== undefined;
|
|
92
107
|
const resize = typeof ResizeObserver === 'undefined' ? null : new ResizeObserver(schedule);
|
|
93
108
|
resize?.observe(root);
|
|
94
109
|
window.addEventListener('resize', schedule, { passive: true });
|
|
@@ -106,6 +121,7 @@ export function usePencilSurface(rootRef, canvasRef, borderRef, variant, seed, c
|
|
|
106
121
|
return () => {
|
|
107
122
|
disposed = true;
|
|
108
123
|
stopRecovery();
|
|
124
|
+
stopViewport?.();
|
|
109
125
|
resize?.disconnect();
|
|
110
126
|
inheritedStyle.disconnect();
|
|
111
127
|
colorScheme.removeEventListener('change', schedule);
|
package/dist/internal/wash.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BoundedCache, PENCIL_RENDERER_VERSION, canvasByteCost, canvasIsUsable } from './cache.js';
|
|
1
|
+
import { BoundedCache, PENCIL_RENDERER_VERSION, boundedCanvasSize, canvasByteCost, canvasIsUsable } from './cache.js';
|
|
2
2
|
import { pencilStroke } from './stroke.js';
|
|
3
3
|
const washCache = new BoundedCache(64, 32 * 1024 * 1024);
|
|
4
4
|
function randomAt(index, seed) {
|
|
@@ -9,9 +9,10 @@ function randomAt(index, seed) {
|
|
|
9
9
|
function makeWash(width, height, color, seed, dpr, settings) {
|
|
10
10
|
const { strength, spacing, roughness, inset, radius, edgeReach, angle } = settings;
|
|
11
11
|
const bleed = edgeReach ? Math.ceil(edgeReach + 4) : 0;
|
|
12
|
+
const pixels = boundedCanvasSize(width + bleed * 2, height + bleed * 2, dpr);
|
|
12
13
|
const canvas = document.createElement('canvas');
|
|
13
|
-
canvas.width =
|
|
14
|
-
canvas.height =
|
|
14
|
+
canvas.width = pixels.width;
|
|
15
|
+
canvas.height = pixels.height;
|
|
15
16
|
const context = canvas.getContext('2d');
|
|
16
17
|
if (!context)
|
|
17
18
|
return canvas;
|
|
@@ -36,4 +36,4 @@ export interface JourneyMapProps extends HTMLAttributes<HTMLDivElement> {
|
|
|
36
36
|
attribution?: string;
|
|
37
37
|
};
|
|
38
38
|
}
|
|
39
|
-
export declare function JourneyMap({ places, routes, geography, landCover, water, selectedPlace, onPlaceSelect, attribution, attributionDisplay, height, interactive, scrollWheelZoom, showControls, showPlaceList, focusSelectedPlace, labels, className, ...props }: JourneyMapProps): import("react").JSX.Element;
|
|
39
|
+
export declare function JourneyMap({ places, routes, geography, landCover, water, selectedPlace, onPlaceSelect, attribution, attributionDisplay, height, interactive, scrollWheelZoom, showControls, showPlaceList, focusSelectedPlace, labels: labelOverrides, className, ...props }: JourneyMapProps): import("react").JSX.Element;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useInkraLocale } from '../i18n/index.js';
|
|
2
3
|
import { useEffect, useRef, useState } from 'react';
|
|
3
4
|
import { createPortal } from 'react-dom';
|
|
4
5
|
import { Button } from '../Button.js';
|
|
@@ -9,10 +10,12 @@ import { TravelIcon } from '../internal/travelIcons.js';
|
|
|
9
10
|
import { paintTerrain, paintStop, paintSea } from './terrain.js';
|
|
10
11
|
import { MapScale } from './MapScale.js';
|
|
11
12
|
import { seedToNumber } from '../internal/random.js';
|
|
12
|
-
const defaults = { zoomIn: '
|
|
13
|
+
const defaults = (t) => ({ zoomIn: t('common.zoomIn'), zoomOut: t('common.zoomOut'), reset: t('common.resetView'), unavailable: t('map.unavailable') });
|
|
13
14
|
const noRoutes = [];
|
|
14
15
|
const valid = (place) => Number.isFinite(place.latitude) && Number.isFinite(place.longitude) && Math.abs(place.latitude) <= 85 && Math.abs(place.longitude) <= 180;
|
|
15
|
-
export function JourneyMap({ places, routes = noRoutes, geography, landCover, water, selectedPlace, onPlaceSelect, attribution, attributionDisplay = 'inline', height = 480, interactive = true, scrollWheelZoom = false, showControls = true, showPlaceList = false, focusSelectedPlace = false, labels
|
|
16
|
+
export function JourneyMap({ places, routes = noRoutes, geography, landCover, water, selectedPlace, onPlaceSelect, attribution, attributionDisplay = 'inline', height = 480, interactive = true, scrollWheelZoom = false, showControls = true, showPlaceList = false, focusSelectedPlace = false, labels: labelOverrides, className, ...props }) {
|
|
17
|
+
const { t } = useInkraLocale();
|
|
18
|
+
const labels = { ...defaults(t), ...labelOverrides };
|
|
16
19
|
const root = useRef(null);
|
|
17
20
|
const host = useRef(null);
|
|
18
21
|
const mapRef = useRef(null);
|
|
@@ -193,5 +196,5 @@ export function JourneyMap({ places, routes = noRoutes, geography, landCover, wa
|
|
|
193
196
|
else
|
|
194
197
|
map.scrollWheelZoom.disable();
|
|
195
198
|
}, [interactive, scrollWheelZoom, ready]);
|
|
196
|
-
return _jsxs("div", { ...props, ref: root, className: ['inkra-journey-map', className].filter(Boolean).join(' '), children: [placeLabels.map(({ place, node }) => createPortal(_jsx(PencilText, { children: place.label }), node, place.id)), _jsxs("div", { className: "inkra-journey-map__viewport", style: { height: Math.max(240, Math.min(800, height)) }, children: [attributionDisplay === 'popover' && _jsx("div", { className: "inkra-journey-map__credits", children: _jsxs(Popover.Root, { children: [_jsx(Popover.Trigger, { asChild: true, children: _jsx(Button, { size: "icon", variant: "ghost", "aria-label": labels.attribution ?? '
|
|
199
|
+
return _jsxs("div", { ...props, ref: root, className: ['inkra-journey-map', className].filter(Boolean).join(' '), children: [placeLabels.map(({ place, node }) => createPortal(_jsx(PencilText, { children: place.label }), node, place.id)), _jsxs("div", { className: "inkra-journey-map__viewport", style: { height: Math.max(240, Math.min(800, height)) }, children: [attributionDisplay === 'popover' && _jsx("div", { className: "inkra-journey-map__credits", children: _jsxs(Popover.Root, { children: [_jsx(Popover.Trigger, { asChild: true, children: _jsx(Button, { size: "icon", variant: "ghost", "aria-label": labels.attribution ?? t('map.credits'), title: labels.attribution ?? t('map.credits'), children: _jsx(TravelIcon, { name: "info" }) }) }), _jsx(Popover.Portal, { children: _jsxs(Popover.Content, { align: "end", sideOffset: 8, collisionPadding: 20, className: "inkra-journey-map__credits-content", children: [_jsx(Popover.Title, { children: labels.attribution ?? t('map.credits') }), _jsx(Popover.Description, { children: attribution })] }) })] }) }), _jsx("div", { ref: host, className: "inkra-journey-map__host", "aria-label": props['aria-label'] ?? t('map.label') }), showControls && _jsxs("div", { className: "inkra-journey-map__tools", children: [_jsx(Button, { size: "icon", variant: "ghost", disabled: !ready, "aria-label": labels.zoomIn, title: labels.zoomIn, onClick: () => { mapRef.current?.stop(); mapRef.current?.zoomIn(); }, children: _jsx(TravelIcon, { name: "plus" }) }), _jsx(Button, { size: "icon", variant: "ghost", disabled: !ready, "aria-label": labels.zoomOut, title: labels.zoomOut, onClick: () => { mapRef.current?.stop(); mapRef.current?.zoomOut(); }, children: _jsx(TravelIcon, { name: "minus" }) }), _jsx(Button, { size: "icon", variant: "ghost", disabled: !ready, "aria-label": labels.reset, title: labels.reset, onClick: () => { mapRef.current?.stop(); fitRef.current(); }, children: _jsx(TravelIcon, { name: "reset" }) })] }), ready && _jsx(MapScale, { map: mapRef.current }), failed && _jsx("p", { role: "status", children: labels.unavailable })] }), attributionDisplay === 'inline' && _jsx("p", { className: "inkra-journey-map__attribution", children: attribution }), (showPlaceList || !ready || failed) && _jsx("ol", { className: "inkra-journey-map__places", children: places.filter(valid).map((place, index) => _jsx("li", { children: _jsxs("button", { type: "button", "aria-pressed": selectedPlace === place.id, onClick: () => { mapRef.current?.panTo([place.latitude, place.longitude], { animate: false }); onPlaceSelect?.(place); }, children: [_jsx("span", { "aria-hidden": "true", children: index + 1 }), _jsx(PencilText, { children: place.label })] }) }, place.id)) })] });
|
|
197
200
|
}
|
|
@@ -55,11 +55,16 @@ export function paintTerrain(ctx, data, project, width, height, dark, defaultKin
|
|
|
55
55
|
const [fill, ink] = palette[kind] ?? palette.land;
|
|
56
56
|
for (const [polygonIndex, polygon] of polygons.entries()) {
|
|
57
57
|
const rings = polygon.map(ring => ring.map(project));
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
58
|
+
let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;
|
|
59
|
+
for (const ring of rings)
|
|
60
|
+
for (const [x, y] of ring) {
|
|
61
|
+
minX = Math.min(minX, x);
|
|
62
|
+
maxX = Math.max(maxX, x);
|
|
63
|
+
minY = Math.min(minY, y);
|
|
64
|
+
maxY = Math.max(maxY, y);
|
|
65
|
+
}
|
|
66
|
+
const left = Math.max(0, minX), right = Math.min(width, maxX);
|
|
67
|
+
const top = Math.max(0, minY), bottom = Math.min(height, maxY);
|
|
63
68
|
if (left >= right || top >= bottom)
|
|
64
69
|
continue;
|
|
65
70
|
ctx.save();
|
package/dist/keypad/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useInkraLocale } from '../i18n/index.js';
|
|
2
3
|
import { forwardRef, useId, useRef, useState } from 'react';
|
|
3
4
|
import { PencilSurface } from '../PencilSurface.js';
|
|
4
5
|
import { PencilText } from '../PencilText.js';
|
|
@@ -20,7 +21,9 @@ function keyVariant(key) {
|
|
|
20
21
|
return 'secondary';
|
|
21
22
|
return 'default';
|
|
22
23
|
}
|
|
23
|
-
export const Keypad = forwardRef(function Keypad(
|
|
24
|
+
export const Keypad = forwardRef(function Keypad(inkraProps, ref) {
|
|
25
|
+
const { t } = useInkraLocale();
|
|
26
|
+
const { 'aria-label': ariaLabel, className, defaultValue = '', disabled = false, emptyContent = null, label = t('keypad.label'), layout, maxLength, onConfirm, onKeyPress, onValueChange, value, ...props } = inkraProps;
|
|
24
27
|
const id = useId();
|
|
25
28
|
const controlled = value !== undefined;
|
|
26
29
|
const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue);
|
package/dist/layout/index.js
CHANGED
|
@@ -56,7 +56,7 @@ const LayoutSidebar = forwardRef(function LayoutSidebar({ as: Element = 'aside',
|
|
|
56
56
|
const LayoutContent = forwardRef(function LayoutContent({ as: Element = 'div', padding = 5, className, style, ...props }, ref) {
|
|
57
57
|
return _jsx(Box, { as: Element, ...props, ref: ref, className: classes('inkra-layout__content', className), style: { padding: space(padding), ...style } });
|
|
58
58
|
});
|
|
59
|
-
export const Layout = Object.freeze({
|
|
59
|
+
export const Layout = /* @__PURE__ */ Object.freeze({
|
|
60
60
|
Root: LayoutRoot, Header: LayoutHeader, Body: LayoutBody,
|
|
61
61
|
Sidebar: LayoutSidebar, Content: LayoutContent, Footer: LayoutFooter,
|
|
62
62
|
});
|
package/dist/log/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useInkraLocale } from '../i18n/index.js';
|
|
2
3
|
import { forwardRef, useCallback, useMemo, useRef, useState, } from 'react';
|
|
3
4
|
import { Button } from '../Button.js';
|
|
4
5
|
import { PencilIcon } from '../PencilIcon.js';
|
|
@@ -7,16 +8,16 @@ import { PencilSurface } from '../PencilSurface.js';
|
|
|
7
8
|
import { PencilText } from '../PencilText.js';
|
|
8
9
|
import { useBrowserLayoutEffect } from '../internal/useBrowserLayoutEffect.js';
|
|
9
10
|
import { isNearLogBottom, shouldFollowLogUpdate } from './model.js';
|
|
10
|
-
const defaultLabels = {
|
|
11
|
-
filters: '
|
|
12
|
-
allLevels: '
|
|
13
|
-
pause: '
|
|
14
|
-
resume: '
|
|
15
|
-
latest: '
|
|
16
|
-
empty: '
|
|
17
|
-
noMatches: '
|
|
18
|
-
list: '
|
|
19
|
-
};
|
|
11
|
+
const defaultLabels = (t) => ({
|
|
12
|
+
filters: t('log.filters'),
|
|
13
|
+
allLevels: t('common.all'),
|
|
14
|
+
pause: t('log.pause'),
|
|
15
|
+
resume: t('log.resume'),
|
|
16
|
+
latest: t('log.latest'),
|
|
17
|
+
empty: t('log.empty'),
|
|
18
|
+
noMatches: t('log.noMatches'),
|
|
19
|
+
list: t('log.list'),
|
|
20
|
+
});
|
|
20
21
|
const defaultTones = {
|
|
21
22
|
debug: 'neutral',
|
|
22
23
|
info: 'info',
|
|
@@ -42,7 +43,8 @@ function normalizedThreshold(value) {
|
|
|
42
43
|
return Number.isFinite(value) ? Math.max(0, value) : 32;
|
|
43
44
|
}
|
|
44
45
|
export const Log = forwardRef(function Log({ 'aria-label': ariaLabel, className, defaultFilteredLevels, defaultPaused = false, empty, filteredLevels, items, labels: labelOverrides, levels, nearBottomThreshold, noMatches, onFilteredLevelsChange, onPausedChange, paused, showFollowControl = true, showLevelFilter = true, viewportProps, ...props }, ref) {
|
|
45
|
-
const
|
|
46
|
+
const { t } = useInkraLocale();
|
|
47
|
+
const labels = { ...defaultLabels(t), ...labelOverrides };
|
|
46
48
|
const resolvedLevels = useMemo(() => {
|
|
47
49
|
if (levels)
|
|
48
50
|
return levels;
|
|
@@ -56,9 +58,11 @@ export const Log = forwardRef(function Log({ 'aria-label': ariaLabel, className,
|
|
|
56
58
|
}, [items, levels]);
|
|
57
59
|
const availableLevels = useMemo(() => new Set(resolvedLevels.map(level => level.value)), [resolvedLevels]);
|
|
58
60
|
const [uncontrolledLevels, setUncontrolledLevels] = useState(defaultFilteredLevels);
|
|
59
|
-
const
|
|
61
|
+
const requestedLevels = filteredLevels ?? uncontrolledLevels;
|
|
62
|
+
const selectedLevels = useMemo(() => requestedLevels?.filter(level => availableLevels.has(level)), [requestedLevels, availableLevels]);
|
|
63
|
+
const selectedLevelSet = useMemo(() => selectedLevels === undefined ? undefined : new Set(selectedLevels), [selectedLevels]);
|
|
60
64
|
const allLevelsSelected = selectedLevels == null
|
|
61
|
-
|| (selectedLevels.length === resolvedLevels.length && resolvedLevels.every(level =>
|
|
65
|
+
|| (selectedLevels.length === resolvedLevels.length && resolvedLevels.every(level => selectedLevelSet.has(level.value)));
|
|
62
66
|
const [uncontrolledPaused, setUncontrolledPaused] = useState(defaultPaused);
|
|
63
67
|
const resolvedPaused = paused ?? uncontrolledPaused;
|
|
64
68
|
const [nearBottom, setNearBottom] = useState(true);
|
|
@@ -68,16 +72,20 @@ export const Log = forwardRef(function Log({ 'aria-label': ariaLabel, className,
|
|
|
68
72
|
const previousPausedRef = useRef(resolvedPaused);
|
|
69
73
|
const nearBottomRef = useRef(true);
|
|
70
74
|
const threshold = normalizedThreshold(nearBottomThreshold);
|
|
71
|
-
const visibleItems =
|
|
75
|
+
const visibleItems = useMemo(() => selectedLevelSet === undefined
|
|
72
76
|
? items
|
|
73
|
-
: items.filter(item => item.level != null &&
|
|
74
|
-
const itemIds = items.map(item => item.id);
|
|
75
|
-
const visibleIds = visibleItems.map(item => item.id);
|
|
77
|
+
: items.filter(item => item.level != null && selectedLevelSet.has(item.level)), [items, selectedLevelSet]);
|
|
78
|
+
const itemIds = useMemo(() => items.map(item => item.id), [items]);
|
|
79
|
+
const visibleIds = useMemo(() => visibleItems.map(item => item.id), [visibleItems]);
|
|
76
80
|
const scrollToLatest = useCallback((behavior = 'smooth') => {
|
|
77
81
|
const viewport = viewportRef.current;
|
|
78
82
|
if (!viewport)
|
|
79
83
|
return;
|
|
80
|
-
|
|
84
|
+
const resolvedBehavior = behavior === 'smooth'
|
|
85
|
+
&& viewport.ownerDocument.defaultView?.matchMedia('(prefers-reduced-motion: reduce)').matches
|
|
86
|
+
? 'auto'
|
|
87
|
+
: behavior;
|
|
88
|
+
viewport.scrollTo({ top: viewport.scrollHeight, behavior: resolvedBehavior });
|
|
81
89
|
nearBottomRef.current = true;
|
|
82
90
|
setNearBottom(true);
|
|
83
91
|
}, []);
|
|
@@ -99,7 +107,7 @@ export const Log = forwardRef(function Log({ 'aria-label': ariaLabel, className,
|
|
|
99
107
|
previousItemIdsRef.current = itemIds;
|
|
100
108
|
previousVisibleIdsRef.current = visibleIds;
|
|
101
109
|
previousPausedRef.current = resolvedPaused;
|
|
102
|
-
}, [itemIds
|
|
110
|
+
}, [itemIds, resolvedPaused, scrollToLatest, visibleIds]);
|
|
103
111
|
function changePaused(nextPaused) {
|
|
104
112
|
if (paused === undefined)
|
|
105
113
|
setUncontrolledPaused(nextPaused);
|
|
@@ -135,25 +143,26 @@ export const Log = forwardRef(function Log({ 'aria-label': ariaLabel, className,
|
|
|
135
143
|
const viewportLabel = viewportProps?.['aria-label'] ?? ariaLabel ?? labels.list;
|
|
136
144
|
const viewportClassName = classes('inkra-log__viewport', viewportProps?.className);
|
|
137
145
|
const viewportTabIndex = viewportProps?.tabIndex ?? 0;
|
|
138
|
-
const levelMap = new Map(resolvedLevels.map(level => [level.value, level]));
|
|
146
|
+
const levelMap = useMemo(() => new Map(resolvedLevels.map(level => [level.value, level])), [resolvedLevels]);
|
|
147
|
+
const showLatest = (!nearBottom || resolvedPaused) && visibleItems.length > 0;
|
|
139
148
|
return (_jsxs(PencilSurface, { ...props, as: "div", className: classes('inkra-log', className), "data-paused": resolvedPaused || undefined, "data-toolbar": ((showLevelFilter && resolvedLevels.length > 0) || showFollowControl) || undefined, ref: ref, variant: "paper", children: [(showLevelFilter && resolvedLevels.length > 0) || showFollowControl ? _jsxs("div", { className: "inkra-log__toolbar", children: [showLevelFilter && resolvedLevels.length > 0 ? _jsxs("div", { "aria-label": labels.filters, className: "inkra-log__filters", role: "group", children: [_jsx(Button, { "aria-pressed": !hasFilter, size: "sm", variant: !hasFilter ? 'secondary' : 'ghost', onClick: () => changeLevels(undefined), children: labels.allLevels }), resolvedLevels.map(level => {
|
|
140
|
-
const selected = allLevelsSelected ||
|
|
149
|
+
const selected = allLevelsSelected || selectedLevelSet?.has(level.value) === true;
|
|
141
150
|
return _jsx(Button, { "aria-pressed": selected, "data-level": level.value, "data-tone": level.tone ?? defaultTones[level.value] ?? 'neutral', size: "sm", variant: selected ? 'secondary' : 'ghost', onClick: () => {
|
|
142
151
|
const active = selectedLevels ?? resolvedLevels.map(option => option.value);
|
|
143
152
|
changeLevels(selected
|
|
144
153
|
? active.filter(value => value !== level.value)
|
|
145
154
|
: [...active, level.value]);
|
|
146
155
|
}, children: level.label }, level.value);
|
|
147
|
-
})] }) : null, showFollowControl ? _jsx(Button, { "aria-pressed": resolvedPaused, className: "inkra-log__follow-control", size: "sm", startIcon: _jsx(PencilIcon, { paths: resolvedPaused ? followPaths : pausePaths, size: 18, sourceSize: 24 }), variant: "outline", onClick: () => changePaused(!resolvedPaused), children: resolvedPaused ? labels.resume : labels.pause }) : null, _jsx(PencilLine, { className: "inkra-log__toolbar-divider", seed: "inkra-log-toolbar-divider" })] }) : null, _jsx("div", { ...viewportProps, "aria-label": viewportLabel, className: viewportClassName, onKeyDown: handleViewportKeyDown, onScroll: handleScroll, ref: viewportRef, role: "region", tabIndex: viewportTabIndex, children: visibleItems.length > 0 ? _jsx("ol", { className: "inkra-log__list", children: visibleItems.map((item, index) => {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
156
|
+
})] }) : null, showFollowControl ? _jsx(Button, { "aria-pressed": resolvedPaused, className: "inkra-log__follow-control", size: "sm", startIcon: _jsx(PencilIcon, { paths: resolvedPaused ? followPaths : pausePaths, size: 18, sourceSize: 24 }), variant: "outline", onClick: () => changePaused(!resolvedPaused), children: resolvedPaused ? labels.resume : labels.pause }) : null, _jsx(PencilLine, { className: "inkra-log__toolbar-divider", seed: "inkra-log-toolbar-divider" })] }) : null, _jsxs("div", { className: "inkra-log__viewport-frame", children: [_jsx(PencilSurface, { "aria-hidden": "true", as: "span", className: "inkra-log__focus-ring", variant: "outline", seed: "inkra-log-focus" }), _jsx("div", { ...viewportProps, "aria-label": viewportLabel, className: viewportClassName, onKeyDown: handleViewportKeyDown, onScroll: handleScroll, ref: viewportRef, role: "region", tabIndex: viewportTabIndex, children: visibleItems.length > 0 ? _jsx("ol", { className: "inkra-log__list", children: visibleItems.map((item, index) => {
|
|
157
|
+
const definition = item.level ? levelMap.get(item.level) : undefined;
|
|
158
|
+
const tone = definition?.tone ?? (item.level ? defaultTones[item.level] : undefined) ?? 'neutral';
|
|
159
|
+
return _jsxs("li", { className: "inkra-log__item", "data-has-icon": item.icon != null || undefined, "data-level": item.level, "data-tone": tone, children: [index > 0 ? _jsx(PencilLine, { className: "inkra-log__item-divider", seed: `inkra-log-item-${item.id}-divider` }) : null, item.icon == null ? null : _jsx("span", { "aria-hidden": "true", className: "inkra-log__icon", children: item.icon }), _jsxs("div", { className: "inkra-log__record", children: [item.time == null && item.level == null ? null : _jsxs("div", { className: "inkra-log__meta", children: [item.time == null ? null : item.dateTime
|
|
160
|
+
? _jsx("time", { dateTime: item.dateTime, children: renderText(item.time) })
|
|
161
|
+
: _jsx("span", { children: renderText(item.time) }), item.level == null ? null : _jsx("span", { className: "inkra-log__level", children: renderText(definition?.label ?? item.level) })] }), _jsx("div", { className: "inkra-log__content", children: renderText(item.content) })] })] }, item.id);
|
|
162
|
+
}) }) : _jsx("div", { className: "inkra-log__empty", children: renderText(isEmpty ? (empty ?? labels.empty) : (noMatches ?? labels.noMatches)) }) })] }), showLatest ? _jsxs("div", { className: "inkra-log__latest", children: [_jsx(PencilLine, { className: "inkra-log__latest-divider", seed: "inkra-log-latest-divider" }), _jsx(Button, { size: "sm", startIcon: _jsx(PencilIcon, { paths: followPaths, size: 18, sourceSize: 24 }), variant: "secondary", onClick: () => {
|
|
163
|
+
if (resolvedPaused)
|
|
164
|
+
changePaused(false);
|
|
165
|
+
else
|
|
166
|
+
scrollToLatest();
|
|
167
|
+
}, children: resolvedPaused ? labels.resume : labels.latest })] }) : null] }));
|
|
159
168
|
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import 'prismjs/components/prism-jsx.js';
|
|
2
|
+
import 'prismjs/components/prism-typescript.js';
|
|
3
|
+
import 'prismjs/components/prism-tsx.js';
|
|
4
|
+
import 'prismjs/components/prism-bash.js';
|
|
5
|
+
import 'prismjs/components/prism-json.js';
|
|
6
|
+
import 'prismjs/components/prism-markdown.js';
|
|
7
|
+
import { type ComponentPropsWithoutRef } from 'react';
|
|
8
|
+
export interface MarkdownCodeLabels {
|
|
9
|
+
plainText: string;
|
|
10
|
+
copy: string;
|
|
11
|
+
copied: string;
|
|
12
|
+
copyFailed: string;
|
|
13
|
+
}
|
|
14
|
+
export interface MarkdownCodeBlockProps extends Omit<ComponentPropsWithoutRef<'pre'>, 'children'> {
|
|
15
|
+
code: string;
|
|
16
|
+
codeClassName?: string;
|
|
17
|
+
labels: MarkdownCodeLabels;
|
|
18
|
+
}
|
|
19
|
+
export declare function MarkdownCodeBlock({ code, codeClassName, className, labels, tabIndex: _tabIndex, ...props }: MarkdownCodeBlockProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import Prism from 'prismjs';
|
|
3
|
+
import 'prismjs/components/prism-jsx.js';
|
|
4
|
+
import 'prismjs/components/prism-typescript.js';
|
|
5
|
+
import 'prismjs/components/prism-tsx.js';
|
|
6
|
+
import 'prismjs/components/prism-bash.js';
|
|
7
|
+
import 'prismjs/components/prism-json.js';
|
|
8
|
+
import 'prismjs/components/prism-markdown.js';
|
|
9
|
+
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
10
|
+
import { Button } from '../Button.js';
|
|
11
|
+
import { Card } from '../content/index.js';
|
|
12
|
+
import { PencilIcon } from '../PencilIcon.js';
|
|
13
|
+
import { PencilSurface } from '../PencilSurface.js';
|
|
14
|
+
import { PencilText } from '../PencilText.js';
|
|
15
|
+
import { Tooltip } from '../tooltip/Tooltip.js';
|
|
16
|
+
import { codeIcons } from './icons.js';
|
|
17
|
+
Prism.manual = true;
|
|
18
|
+
const languageAliases = {
|
|
19
|
+
js: 'javascript',
|
|
20
|
+
md: 'markdown',
|
|
21
|
+
sh: 'bash',
|
|
22
|
+
shell: 'bash',
|
|
23
|
+
ts: 'typescript',
|
|
24
|
+
txt: 'plain',
|
|
25
|
+
text: 'plain',
|
|
26
|
+
plaintext: 'plain',
|
|
27
|
+
};
|
|
28
|
+
const languageNames = {
|
|
29
|
+
javascript: 'JavaScript', typescript: 'TypeScript', jsx: 'JSX', tsx: 'TSX',
|
|
30
|
+
bash: 'Bash', json: 'JSON', markdown: 'Markdown', css: 'CSS', html: 'HTML',
|
|
31
|
+
};
|
|
32
|
+
function classes(...values) {
|
|
33
|
+
return values.filter(Boolean).join(' ');
|
|
34
|
+
}
|
|
35
|
+
function flatten(stream, inheritedClasses = '') {
|
|
36
|
+
const tokens = Array.isArray(stream) ? stream : [stream];
|
|
37
|
+
return tokens.flatMap(token => {
|
|
38
|
+
if (typeof token === 'string')
|
|
39
|
+
return [{ text: token, classes: inheritedClasses }];
|
|
40
|
+
const aliases = Array.isArray(token.alias) ? token.alias : token.alias ? [token.alias] : [];
|
|
41
|
+
const tokenClasses = classes(inheritedClasses, `token-${token.type}`, ...aliases.map(alias => `token-${alias}`));
|
|
42
|
+
return flatten(token.content, tokenClasses);
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
function languageFromClassName(className) {
|
|
46
|
+
return /(?:^|\s)language-([^\s]+)/.exec(className ?? '')?.[1]?.toLowerCase();
|
|
47
|
+
}
|
|
48
|
+
function tokenize(code, language) {
|
|
49
|
+
const normalized = languageAliases[language ?? ''] ?? language ?? 'plain';
|
|
50
|
+
const grammar = Prism.languages[normalized] ?? Prism.languages.plain;
|
|
51
|
+
return flatten(Prism.tokenize(code, grammar)).flatMap(part => part.text.split(/(\r\n|\r|\n)/).filter(Boolean).map(text => ({ ...part, text })));
|
|
52
|
+
}
|
|
53
|
+
export function MarkdownCodeBlock({ code, codeClassName, className, labels, tabIndex: _tabIndex, ...props }) {
|
|
54
|
+
const language = languageFromClassName(codeClassName);
|
|
55
|
+
const normalizedLanguage = languageAliases[language ?? ''] ?? language ?? 'plain';
|
|
56
|
+
const languageLabel = normalizedLanguage === 'plain' ? labels.plainText : languageNames[normalizedLanguage] ?? language;
|
|
57
|
+
const parts = useMemo(() => tokenize(code, language), [code, language]);
|
|
58
|
+
const [status, setStatus] = useState('idle');
|
|
59
|
+
const request = useRef(0);
|
|
60
|
+
const timer = useRef(undefined);
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
setStatus('idle');
|
|
63
|
+
return () => { request.current += 1; clearTimeout(timer.current); };
|
|
64
|
+
}, [code]);
|
|
65
|
+
const copyLabel = status === 'copied' ? labels.copied : status === 'failed' ? labels.copyFailed : labels.copy;
|
|
66
|
+
async function copyCode() {
|
|
67
|
+
const id = ++request.current;
|
|
68
|
+
clearTimeout(timer.current);
|
|
69
|
+
setStatus('copying');
|
|
70
|
+
try {
|
|
71
|
+
await navigator.clipboard.writeText(code);
|
|
72
|
+
if (request.current !== id)
|
|
73
|
+
return;
|
|
74
|
+
setStatus('copied');
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
if (request.current !== id)
|
|
78
|
+
return;
|
|
79
|
+
setStatus('failed');
|
|
80
|
+
}
|
|
81
|
+
timer.current = setTimeout(() => setStatus('idle'), 2000);
|
|
82
|
+
}
|
|
83
|
+
return _jsxs(Card.Root, { className: "inkra-markdown__code-frame", radius: 6, seed: `markdown-code-frame-${language ?? 'plain'}`, children: [_jsx(Card.Header, { className: "inkra-markdown__code-header", align: "center", action: _jsx(Tooltip.Provider, { children: _jsxs(Tooltip.Root, { children: [_jsx(Tooltip.Trigger, { asChild: true, children: _jsx(Button, { size: "icon", variant: "ghost", "aria-label": copyLabel, "aria-busy": status === 'copying' || undefined, disabled: status === 'copying', onClick: () => void copyCode(), children: _jsx(PencilIcon, { paths: status === 'copied' ? codeIcons.check : codeIcons.copy, size: 22 }) }) }), _jsx(Tooltip.Portal, { children: _jsx(Tooltip.Content, { children: copyLabel }) })] }) }), children: _jsxs("span", { className: "inkra-markdown__code-language", children: [_jsx(PencilIcon, { paths: normalizedLanguage === 'bash' ? codeIcons.terminal : codeIcons.code, size: 22 }), _jsx(PencilText, { children: languageLabel })] }) }), _jsx("pre", { ...props, className: classes('inkra-markdown__code-surface', className), "data-language": language ?? 'plain', tabIndex: 0, children: _jsx("code", { className: classes('inkra-markdown__code', codeClassName), children: parts.map((part, index) => /\S/.test(part.text)
|
|
84
|
+
? _jsx(PencilText, { className: classes('inkra-markdown__code-token', part.classes), seed: `markdown-code-${language ?? 'plain'}-${index}-${part.text}`, children: part.text }, index)
|
|
85
|
+
: part.text) }) }), _jsx(PencilSurface, { "aria-hidden": "true", as: "span", className: "inkra-markdown__code-focus", radius: 6, seed: "markdown-code-focus", variant: "outline" }), _jsx("span", { className: "inkra-markdown__code-status", role: "status", children: status === 'copied' ? labels.copied : status === 'failed' ? labels.copyFailed : '' })] });
|
|
86
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useRef, useState } from 'react';
|
|
3
|
+
import { PencilLine } from '../PencilLine.js';
|
|
4
|
+
import { useBrowserLayoutEffect } from '../internal/useBrowserLayoutEffect.js';
|
|
5
|
+
export function Deletion({ children, className, ...props }) {
|
|
6
|
+
const ref = useRef(null);
|
|
7
|
+
const [lines, setLines] = useState([]);
|
|
8
|
+
useBrowserLayoutEffect(() => {
|
|
9
|
+
const node = ref.current;
|
|
10
|
+
if (!node || !document.createElement('canvas').getContext('2d'))
|
|
11
|
+
return;
|
|
12
|
+
let disposed = false;
|
|
13
|
+
let frame = 0;
|
|
14
|
+
const measure = () => {
|
|
15
|
+
frame = 0;
|
|
16
|
+
if (disposed)
|
|
17
|
+
return;
|
|
18
|
+
const box = node.getBoundingClientRect();
|
|
19
|
+
const scaleX = node.offsetWidth ? box.width / node.offsetWidth : 1;
|
|
20
|
+
const scaleY = node.offsetHeight ? box.height / node.offsetHeight : 1;
|
|
21
|
+
const fragments = [];
|
|
22
|
+
const walker = document.createTreeWalker(node, NodeFilter.SHOW_TEXT);
|
|
23
|
+
const range = document.createRange();
|
|
24
|
+
// Use native line fragments, not one line through the whole wrapped block.
|
|
25
|
+
for (let text = walker.nextNode(); text; text = walker.nextNode()) {
|
|
26
|
+
if (!text.textContent?.trim() || text.parentElement?.closest('[aria-hidden="true"]'))
|
|
27
|
+
continue;
|
|
28
|
+
range.selectNodeContents(text);
|
|
29
|
+
for (const rect of range.getClientRects()) {
|
|
30
|
+
if (!rect.width || !rect.height)
|
|
31
|
+
continue;
|
|
32
|
+
const top = (rect.top - box.top + rect.height * 0.52) / (scaleY || 1) - 3;
|
|
33
|
+
const left = (rect.left - box.left) / (scaleX || 1);
|
|
34
|
+
const width = rect.width / (scaleX || 1);
|
|
35
|
+
const line = fragments.find(item => Math.abs(item.top - top) < 3);
|
|
36
|
+
if (line) {
|
|
37
|
+
const end = Math.max(line.left + line.width, left + width);
|
|
38
|
+
line.left = Math.min(line.left, left);
|
|
39
|
+
line.width = end - line.left;
|
|
40
|
+
}
|
|
41
|
+
else
|
|
42
|
+
fragments.push({ left, top, width });
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const next = fragments.map(line => ({
|
|
46
|
+
left: Math.round(line.left * 2) / 2,
|
|
47
|
+
top: Math.round(line.top * 2) / 2,
|
|
48
|
+
width: Math.round(line.width * 2) / 2,
|
|
49
|
+
}));
|
|
50
|
+
setLines(previous => JSON.stringify(previous) === JSON.stringify(next) ? previous : next);
|
|
51
|
+
};
|
|
52
|
+
const schedule = () => {
|
|
53
|
+
if (!disposed && !frame)
|
|
54
|
+
frame = requestAnimationFrame(measure);
|
|
55
|
+
};
|
|
56
|
+
const observer = typeof ResizeObserver === 'undefined' ? null : new ResizeObserver(schedule);
|
|
57
|
+
observer?.observe(node);
|
|
58
|
+
document.fonts?.addEventListener('loadingdone', schedule);
|
|
59
|
+
document.fonts?.ready.then(schedule);
|
|
60
|
+
measure();
|
|
61
|
+
return () => {
|
|
62
|
+
disposed = true;
|
|
63
|
+
observer?.disconnect();
|
|
64
|
+
document.fonts?.removeEventListener('loadingdone', schedule);
|
|
65
|
+
cancelAnimationFrame(frame);
|
|
66
|
+
};
|
|
67
|
+
}, [children]);
|
|
68
|
+
return _jsxs("del", { ...props, className: ['inkra-markdown__deleted', className].filter(Boolean).join(' '), ref: ref, "data-pencil-strike-ready": lines.length > 0 || undefined, children: [children, lines.map((line, index) => _jsx(PencilLine, { className: "inkra-markdown__strike", seed: `markdown-strike-${index}`, weight: 0.7, roughness: 0.4, style: { left: line.left, top: line.top, width: line.width } }, index))] });
|
|
69
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { __iconData as copy } from 'lucide-react/dist/esm/icons/copy.mjs';
|
|
2
|
+
import { __iconData as check } from 'lucide-react/dist/esm/icons/check.mjs';
|
|
3
|
+
import { __iconData as terminal } from 'lucide-react/dist/esm/icons/terminal.mjs';
|
|
4
|
+
import { __iconData as code } from 'lucide-react/dist/esm/icons/code-xml.mjs';
|
|
5
|
+
function paths(data) {
|
|
6
|
+
return data.node.flatMap(([tag, attributes]) => {
|
|
7
|
+
if (tag === 'path')
|
|
8
|
+
return [{ d: attributes.d }];
|
|
9
|
+
if (tag !== 'rect')
|
|
10
|
+
return [];
|
|
11
|
+
const { x = 0, y = 0, width = 0, height = 0, rx = 0 } = Object.fromEntries(Object.entries(attributes).map(([key, value]) => [key, Number(value)]));
|
|
12
|
+
const r = Math.min(rx, width / 2, height / 2);
|
|
13
|
+
return [{ d: `M${x + r} ${y}H${x + width - r}Q${x + width} ${y} ${x + width} ${y + r}V${y + height - r}Q${x + width} ${y + height} ${x + width - r} ${y + height}H${x + r}Q${x} ${y + height} ${x} ${y + height - r}V${y + r}Q${x} ${y} ${x + r} ${y}Z` }];
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
export const codeIcons = { copy: paths(copy), check: paths(check), terminal: paths(terminal), code: paths(code) };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Components } from 'react-markdown';
|
|
2
|
+
import { type ComponentPropsWithoutRef } from 'react';
|
|
3
|
+
import { type MarkdownCodeLabels } from './CodeBlock.js';
|
|
4
|
+
export interface MarkdownLabels extends Partial<MarkdownCodeLabels> {
|
|
5
|
+
code?: string;
|
|
6
|
+
table?: string;
|
|
7
|
+
taskChecked?: string;
|
|
8
|
+
taskUnchecked?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface MarkdownProps extends Omit<ComponentPropsWithoutRef<'div'>, 'children'> {
|
|
11
|
+
children: string;
|
|
12
|
+
components?: Components;
|
|
13
|
+
headingLevelOffset?: number;
|
|
14
|
+
labels?: MarkdownLabels;
|
|
15
|
+
transformUrl?: (url: string, kind: 'link' | 'image') => string | null | undefined;
|
|
16
|
+
}
|
|
17
|
+
export declare const Markdown: import("react").ForwardRefExoticComponent<MarkdownProps & import("react").RefAttributes<HTMLDivElement>>;
|