@inkra/react 0.2.0 → 0.4.3
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 +101 -7
- 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.d.ts +1 -1
- package/dist/alert-dialog/index.js +18 -6
- package/dist/audio-player/index.d.ts +2 -0
- package/dist/audio-player/index.js +126 -31
- package/dist/calendar/index.d.ts +4 -1
- package/dist/calendar/index.js +31 -12
- package/dist/carousel/index.js +13 -6
- package/dist/choice-grid/index.d.ts +6 -1
- package/dist/choice-grid/index.js +61 -33
- package/dist/color-picker/GradientEditor.d.ts +39 -0
- package/dist/color-picker/GradientEditor.js +63 -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 +134 -32
- package/dist/color-swatch/index.d.ts +10 -0
- package/dist/color-swatch/index.js +9 -0
- package/dist/command/index.d.ts +24 -2
- package/dist/command/index.js +29 -6
- package/dist/content/index.d.ts +5 -0
- package/dist/content/index.js +12 -3
- package/dist/context-menu/index.d.ts +2 -2
- package/dist/context-menu/index.js +4 -3
- package/dist/data-table/index.d.ts +7 -1
- package/dist/data-table/index.js +64 -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 +10 -9
- package/dist/day-agenda/index.d.ts +6 -1
- package/dist/day-agenda/index.js +46 -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 +5 -5
- package/dist/drawer/index.d.ts +1 -1
- package/dist/drawer/index.js +9 -6
- package/dist/dropdown-menu/index.d.ts +1 -1
- package/dist/dropdown-menu/index.js +3 -2
- package/dist/editable/index.d.ts +24 -2
- package/dist/editable/index.js +142 -37
- 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/EditablePencilText.js +34 -0
- package/dist/form/Field.js +11 -10
- package/dist/form/Input.js +1 -1
- package/dist/form/Textarea.d.ts +3 -1
- package/dist/form/Textarea.js +6 -4
- package/dist/hotkey/index.d.ts +1 -0
- package/dist/hotkey/index.js +6 -4
- package/dist/hover-card/index.js +1 -1
- package/dist/image-cropper/index.js +4 -2
- package/dist/image-viewer/VideoResource.d.ts +21 -0
- package/dist/image-viewer/VideoResource.js +194 -0
- package/dist/image-viewer/index.d.ts +14 -0
- package/dist/image-viewer/index.js +118 -65
- package/dist/image-viewer/lucideGeometry.d.ts +3 -0
- package/dist/image-viewer/lucideGeometry.js +19 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/input-number/index.d.ts +6 -0
- package/dist/input-number/index.js +131 -15
- package/dist/input-number/model.d.ts +13 -0
- package/dist/input-number/model.js +50 -0
- 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 +2 -0
- package/dist/internal/textRenderer.js +3 -3
- package/dist/internal/usePencilSurface.js +17 -1
- package/dist/internal/wash.js +4 -3
- package/dist/journey-map/terrain.js +10 -5
- package/dist/layout/index.js +1 -1
- package/dist/log/index.js +29 -22
- 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 +175 -0
- package/dist/node-graph/index.d.ts +2 -0
- package/dist/node-graph/index.js +49 -25
- package/dist/number-ticker/index.d.ts +11 -9
- package/dist/number-ticker/index.js +14 -11
- package/dist/number-ticker/model.d.ts +38 -5
- package/dist/number-ticker/model.js +139 -10
- package/dist/number-ticker/precision.d.ts +7 -0
- package/dist/number-ticker/precision.js +141 -0
- package/dist/overflow-list/index.js +5 -2
- package/dist/overlay/Overlay.d.ts +1 -1
- package/dist/overlay/Overlay.js +5 -3
- package/dist/overlay/Popover.d.ts +1 -1
- package/dist/overlay/Popover.js +6 -5
- package/dist/overlay/themeBridge.d.ts +12 -1
- package/dist/overlay/themeBridge.js +41 -3
- package/dist/pagination/index.d.ts +10 -1
- package/dist/pagination/index.js +6 -4
- package/dist/pan-zoom/index.d.ts +2 -0
- package/dist/pan-zoom/index.js +7 -6
- package/dist/pencil.d.ts +1 -0
- package/dist/pencil.js +4 -4
- package/dist/radar/index.d.ts +4 -0
- package/dist/radar/index.js +41 -21
- package/dist/rating/index.d.ts +4 -0
- package/dist/rating/index.js +128 -24
- package/dist/resizable/index.js +1 -1
- 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 +61 -6
- package/dist/select/Select.d.ts +1 -0
- package/dist/select/Select.js +3 -1
- package/dist/select/StepperSelect.d.ts +29 -0
- package/dist/select/StepperSelect.js +119 -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 +15 -6
- package/dist/sparkline/index.js +17 -2
- package/dist/sparkline/model.js +11 -4
- 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 +557 -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 +2241 -443
- package/dist/tabs/index.d.ts +9 -0
- package/dist/tabs/index.js +66 -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 +70 -26
- package/dist/time-picker/index.js +38 -20
- package/dist/toast/index.d.ts +19 -11
- package/dist/toast/index.js +48 -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.d.ts +1 -1
- package/dist/tooltip/Tooltip.js +6 -5
- package/dist/transfer/index.d.ts +5 -0
- package/dist/transfer/index.js +93 -29
- 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 +5 -5
- package/dist/tree-select/index.js +67 -30
- 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 +30 -16
- package/dist/virtual-list/index.js +10 -7
- package/package.json +416 -5
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Inkra
|
|
2
2
|
|
|
3
|
-
Inkra is a pencil-styled React component library. It includes action, form, overlay, navigation, content and feedback components, plus Calendar, DayAgenda, JourneyMap and deterministic drawing primitives.
|
|
3
|
+
Inkra is a pencil-styled React component library. It includes action, form, overlay, navigation, content and feedback components, plus Calendar, DayAgenda, JourneyMap and deterministic drawing primitives. This document describes the APIs shipped with this package; see the repository changelog and npm registry for release status.
|
|
4
4
|
|
|
5
5
|
Source and maintenance: [inkrajs/inkra](https://github.com/inkrajs/inkra).
|
|
6
6
|
|
|
@@ -80,6 +80,18 @@ All standard `button` props, including `disabled`, `name`, `value`, `form`, `ari
|
|
|
80
80
|
|
|
81
81
|
`InputNumber` places decrease and increase buttons on either side of a centered numeric input, with no clear button. Keyboard deletion produces `null`; decimal stepping and out-of-range validation remain native. `Slider` supports single/range values, horizontal/vertical layout and `standard` or `inset` appearance.
|
|
82
82
|
|
|
83
|
+
`InputNumber` supports Shift-click and Shift-arrow increments of 10 by default.
|
|
84
|
+
Use `stepModifiers={[{ shiftKey: true, step: 10 }, { altKey: true, step: 0.1 }]}`
|
|
85
|
+
to configure exact modifier chords with absolute increments, or `[]` to disable
|
|
86
|
+
overrides. The first valid matching rule wins; unspecified modifiers must be
|
|
87
|
+
released. Values remain bounded by `min`/`max`; `step="any"` disables stepping.
|
|
88
|
+
`pressAndHold` defaults to true, with `repeatDelay={400}` and
|
|
89
|
+
`repeatInterval={80}` in milliseconds. Release, cancellation, blur, backgrounding,
|
|
90
|
+
disabling and value boundaries stop repetition. Delay/interval are bounded to
|
|
91
|
+
150-60000/32-60000 ms. Numeric values use pencil lettering at rest and during
|
|
92
|
+
button stepping. Direct text entry deliberately stays native until blur, keeping
|
|
93
|
+
browser-owned incomplete drafts such as `-`, `1.` and `1e` readable.
|
|
94
|
+
|
|
83
95
|
`Select.Root` uses string values for single selection and string arrays with `multiple`. Multi-select remains open while toggling items and submits repeated values under the same field name. `Select.Clear` is a separate button; never nest it inside the trigger.
|
|
84
96
|
|
|
85
97
|
`Select.Trigger` supports `startIcon`, `endIcon`, `hideValue`, `hideIndicator`, `iconOnly`, `fullWidth` and `textAlign`. The existing `icon` prop replaces the dropdown indicator. Icon-only mode keeps a square hit target of at least 44px and retains selected text as an accessible description. Provide a Field/ARIA name and Tooltip for icon-only triggers. Decorative slots must not contain interactive controls.
|
|
@@ -195,10 +207,9 @@ Content layout options preserve existing defaults:
|
|
|
195
207
|
- `Avatar.Root.radius` clamps to 0-8px, including pencil and image corners; `bordered={false}` removes the outline. `Avatar.Image.fit` accepts `cover | contain` (default cover).
|
|
196
208
|
- `Table.Root.striped`, `hoverable` and `stickyHeader` default to false. Constrain `Table.Container` with `style={{ maxHeight: 240 }}` for sticky scrolling. `captionSide` accepts `top | bottom`. `Head.align` and `Cell.align` accept logical `start | center | end`, retaining native alignment values for compatibility. Hover feedback does not imply row actions.
|
|
197
209
|
|
|
198
|
-
##
|
|
210
|
+
## Additional Components
|
|
199
211
|
|
|
200
|
-
These exports
|
|
201
|
-
install the local candidate to use them. The documentation playground includes
|
|
212
|
+
These exports extend the original core component set. The documentation playground includes
|
|
202
213
|
working examples, live property tabs and per-component API/accessibility notes.
|
|
203
214
|
|
|
204
215
|
| Component | Contract |
|
|
@@ -239,6 +250,77 @@ motion and SSR show deterministic final values. Intermediate frames are visual;
|
|
|
239
250
|
the polite announcement updates only at the final value. Use `getValueLabel`
|
|
240
251
|
when units or custom rendering need a complete accessible phrase.
|
|
241
252
|
|
|
253
|
+
Set `compact` to follow `locale` (Chinese units for `zh`, English units otherwise),
|
|
254
|
+
or use `compact="en"` / `compact="zh"` to select a unit table explicitly.
|
|
255
|
+
English uses this library's short-scale suffix convention: K, M, B, T, Qa, Qi,
|
|
256
|
+
Sx, Sp, Oc, No, Dc (10^3 through 10^33, increasing by 10^3).
|
|
257
|
+
Chinese uses 千 (10^3), then 万, 亿, 兆, 京, 垓, 秭, 穰, 沟, 涧, 正, 载, 极,
|
|
258
|
+
恒河沙, 阿僧祇, 那由他, 不可思议, 无量大数 (10^4 through 10^68, increasing by 10^4).
|
|
259
|
+
`numberTickerUnits` exports both tables. A space separates numbers and units by default.
|
|
260
|
+
The default is `false`, preserving existing `formatOptions` behavior. Compact mode
|
|
261
|
+
uses at most one fractional digit by default; customize precision with
|
|
262
|
+
`formatOptions`. Explicit scientific/engineering notation takes priority over compact.
|
|
263
|
+
Currency/percent affixes are preserved. By default rounding can promote the unit
|
|
264
|
+
(999,950 becomes 1 M). Deltas use the same unit
|
|
265
|
+
system with their own precision; announcements and `getValueLabel` receive the
|
|
266
|
+
unabbreviated formatted value. `renderValue` receives the abbreviated value in
|
|
267
|
+
`details.formattedValue`. NumberTicker never changes the supplied numeric value.
|
|
268
|
+
|
|
269
|
+
`compactOptions` supports:
|
|
270
|
+
|
|
271
|
+
- `minUnit` / `maxUnit`: allowed unit labels; equal bounds fix the unit. An empty
|
|
272
|
+
label denotes the unitless base. For example `maxUnit: '万'` keeps 10^8 as `10,000 万`.
|
|
273
|
+
- `separator`: defaults to one space; `''` removes it.
|
|
274
|
+
- `promotionThreshold`: positive multiplier on each unit's factor (default 1).
|
|
275
|
+
Set 10 to wait until 10,000 before entering K. `promoteAfterRounding: false`
|
|
276
|
+
compares raw magnitudes instead of rounded current-unit values.
|
|
277
|
+
- `units`: replace the table with `{ factor, label, threshold? }` entries.
|
|
278
|
+
`threshold` overrides the multiplier with an absolute display-magnitude trigger;
|
|
279
|
+
e.g. `{ factor: 10000, label: '万', threshold: 100000 }`. Binary factors work too.
|
|
280
|
+
Entries are sorted without mutation; labels/factors must be unique, factors > 1,
|
|
281
|
+
and thresholds positive, finite and strictly increasing. Invalid configurations
|
|
282
|
+
and unknown/reversed bounds throw RangeError. The unitless base is implicit.
|
|
283
|
+
- `overflow`: `unit` keeps the largest permitted unit; `scientific` switches at
|
|
284
|
+
the next unit's threshold (or extrapolates the last step at the table end).
|
|
285
|
+
Explicit `maxUnit` defaults to `unit`; otherwise the default is `scientific`.
|
|
286
|
+
|
|
287
|
+
Use `formatOptions.notation: 'scientific' | 'engineering'` for exponent notation.
|
|
288
|
+
`maximumFractionDigits` controls decimals, `minimumFractionDigits` pads zeros,
|
|
289
|
+
and `roundingMode: 'trunc'` truncates instead of rounding (`halfExpand` is default).
|
|
290
|
+
Other Intl rounding modes are supported when provided by the runtime; unsupported
|
|
291
|
+
nondefault rounding modes throw rather than silently round differently.
|
|
292
|
+
`value`, unit `factor` / `threshold`, and `promotionThreshold` accept
|
|
293
|
+
`number | bigint | string`. Use bigint for exact integers and decimal strings for
|
|
294
|
+
exact fractions or exponents beyond JavaScript's numeric range. Already-rounded
|
|
295
|
+
`number` inputs cannot regain lost digits. Decimal arithmetic uses an isolated
|
|
296
|
+
`decimal.js` context without modifying the application's Decimal settings.
|
|
297
|
+
|
|
298
|
+
```tsx
|
|
299
|
+
<NumberTicker value={9007199254740993n} showDelta />
|
|
300
|
+
<NumberTicker value="12345678901234567890.123456" formatOptions={{ maximumFractionDigits: 6 }} />
|
|
301
|
+
<NumberTicker value="1.23456789e1000" formatOptions={{ notation: 'scientific' }} />
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
Strings accept signs, decimal points, e/E exponents and outer whitespace, but
|
|
305
|
+
not grouping separators, hex, empty strings or non-finite spellings. Inputs are
|
|
306
|
+
bounded to 10,000 coefficient digits and written/normalized exponents within
|
|
307
|
+
-10,000..10,000; invalid or oversized exact inputs throw `RangeError`. Decimal
|
|
308
|
+
fractions require exact Intl decimal-string support in the runtime; unsupported
|
|
309
|
+
engines throw rather than silently lose precision. Native number NaN/Infinity
|
|
310
|
+
formatting is unchanged.
|
|
311
|
+
|
|
312
|
+
`renderValue` and `getValueLabel` retain the input type; prefer the supplied
|
|
313
|
+
formatted text when custom rendering. Deltas between bigints are bigint, between
|
|
314
|
+
numbers are number, and mixed/string deltas are exact decimal strings. Bigint
|
|
315
|
+
animation frames stay integral. Input-type changes or exact arithmetic spanning
|
|
316
|
+
more than 512 digits settle immediately, as do reduced-motion updates.
|
|
317
|
+
`createNumberFormatter` remains a native Intl formatter; use
|
|
318
|
+
`formatTickerValue(formatter, value)` for precise numeric-string formatting.
|
|
319
|
+
|
|
320
|
+
`prefix` and `suffix` accept icons, text or fragments. Pass `PencilIcon` to keep
|
|
321
|
+
the library material, and `getValueLabel` to describe icon meanings to assistive
|
|
322
|
+
technology; decorative affixes are not announced twice.
|
|
323
|
+
|
|
242
324
|
`Log` renders caller-ordered records with stable ids, optional level filters and
|
|
243
325
|
pause/follow controls. It follows only append updates while the viewport remains
|
|
244
326
|
near the bottom. The named scroll region supports Home/End and intentionally is
|
|
@@ -266,7 +348,18 @@ All examples use public sample data.
|
|
|
266
348
|
|
|
267
349
|
`PencilText` keeps its string in the DOM for selection, copy, search, and assistive technology. Browser enhancement paints the same glyph geometry with the traced C lettering preset; unavailable canvas or font loading leaves the readable DOM fallback visible.
|
|
268
350
|
|
|
269
|
-
`PencilIcon` accepts familiar SVG path data and deposits pigment along sampled geometry.
|
|
351
|
+
`PencilIcon` accepts familiar SVG path data and deposits pigment along sampled geometry.
|
|
352
|
+
Use `size` for the displayed canvas size (default 40px), `strokeWidth` (0.3-3,
|
|
353
|
+
default 1.5), `intensity` (0-1, default 1), `breaks` (0-1, default 0.18),
|
|
354
|
+
`pressureVariation` (0-1, default 0.82), and `breakCount` (integer 0-12, default 1).
|
|
355
|
+
The gap count is a maximum: short displayed paths use fewer or no gaps, while
|
|
356
|
+
larger icons can use more up to that limit. Device pixel ratio does not change
|
|
357
|
+
the count. Zero breaks or zero breakCount removes intentional gaps; zero pressure
|
|
358
|
+
variation keeps constant pressure without removing the grain or endpoint taper.
|
|
359
|
+
The matching inherited tokens are `--inkra-icon-stroke-width`,
|
|
360
|
+
`--inkra-icon-intensity`, `--inkra-icon-breaks`, `--inkra-icon-pressure-variation`
|
|
361
|
+
and `--inkra-icon-break-count`. Finite explicit props override those tokens.
|
|
362
|
+
See `docs/pencil-contract.md` in the workspace for the material contract.
|
|
270
363
|
|
|
271
364
|
`PencilLine` is an aria-hidden canvas decoration using the same pressure, grain, breaks, and repeated-pass renderer as surfaces and icons. It defaults to horizontal and supports a native vertical stroke without rotating a horizontal bitmap:
|
|
272
365
|
|
|
@@ -285,8 +378,9 @@ text within that scope without changing layout, selectable text, or labels.
|
|
|
285
378
|
|
|
286
379
|
`PencilLine` reads `--inkra-stroke-width` (0.3-3) and
|
|
287
380
|
`--inkra-stroke-amplitude` (0-4) when `weight` or `roughness` is omitted.
|
|
288
|
-
Explicit props take precedence. These tokens also scale surface outlines
|
|
289
|
-
|
|
381
|
+
Explicit props take precedence. These tokens also scale surface outlines,
|
|
382
|
+
including button/card/input borders. Icon weight uses its independent token;
|
|
383
|
+
the general amplitude token still controls icon roughness.
|
|
290
384
|
The default values are 2 and 1.5. They do not change layout or text lettering.
|
|
291
385
|
|
|
292
386
|
Surface fills read `--inkra-wash-strength` (0-1, default 0.8),
|
package/dist/Button.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { createContext, forwardRef, useContext, useId } from 'react';
|
|
2
|
+
import { createContext, forwardRef, useContext, useId, useMemo } from 'react';
|
|
3
3
|
import { PencilSurface } from './PencilSurface.js';
|
|
4
4
|
import { PencilText } from './PencilText.js';
|
|
5
5
|
import { PencilLine } from './PencilLine.js';
|
|
@@ -85,5 +85,6 @@ export const ButtonLink = forwardRef(function ButtonLink({ children, 'aria-label
|
|
|
85
85
|
export const ButtonGroup = forwardRef(function ButtonGroup({ children, className, orientation = 'horizontal', size, variant, disabled = false, fullWidth = false, equalWidth = false, ...props }, ref) {
|
|
86
86
|
const classes = ['inkra-button-group', className].filter(Boolean).join(' ');
|
|
87
87
|
const id = useId();
|
|
88
|
-
|
|
88
|
+
const context = useMemo(() => ({ orientation, size, variant, disabled }), [orientation, size, variant, disabled]);
|
|
89
|
+
return (_jsx(ButtonGroupContext.Provider, { value: context, children: _jsx(PencilSurface, { ...props, className: classes, "data-orientation": orientation, "data-full-width": fullWidth || undefined, "data-equal-width": equalWidth || undefined, "aria-disabled": disabled || undefined, radius: 8, ref: ref, role: "group", seed: `inkra-button-group-${id}`, variant: "outline", children: children }) }));
|
|
89
90
|
});
|
package/dist/PencilIcon.d.ts
CHANGED
|
@@ -17,6 +17,16 @@ export interface PencilIconProps extends Omit<CanvasHTMLAttributes<HTMLCanvasEle
|
|
|
17
17
|
size?: number;
|
|
18
18
|
sourceSize?: number;
|
|
19
19
|
active?: boolean;
|
|
20
|
+
/** Overrides icon/theme stroke weight. Clamped to 0.3-3. */
|
|
21
|
+
strokeWidth?: number;
|
|
22
|
+
/** Pigment opacity, clamped to 0-1. Defaults to the icon intensity token or 1. */
|
|
23
|
+
intensity?: number;
|
|
24
|
+
/** Stroke gap amount, clamped to 0-1. Zero disables gaps, not pigment grain. */
|
|
25
|
+
breaks?: number;
|
|
26
|
+
/** Maximum gaps per continuous path, limited by displayed length; floored/clamped to 0-12, default 1. */
|
|
27
|
+
breakCount?: number;
|
|
28
|
+
/** Pressure-driven stroke width variation, clamped to 0-1. Defaults to 0.82. */
|
|
29
|
+
pressureVariation?: number;
|
|
20
30
|
decorative?: boolean;
|
|
21
31
|
}
|
|
22
32
|
export declare const PencilIcon: import("react").ForwardRefExoticComponent<PencilIconProps & import("react").RefAttributes<HTMLCanvasElement>>;
|
package/dist/PencilIcon.js
CHANGED
|
@@ -1,32 +1,12 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { forwardRef, useEffect, useImperativeHandle, useMemo, useRef } from 'react';
|
|
3
|
-
import { BoundedCache, PENCIL_RENDERER_VERSION, canvasByteCost, canvasIsUsable, effectiveDpr, } from './internal/cache.js';
|
|
3
|
+
import { BoundedCache, PENCIL_RENDERER_VERSION, boundedCanvasSize, canvasByteCost, canvasIsUsable, effectiveDpr, } from './internal/cache.js';
|
|
4
4
|
import { observeCanvasRecovery } from './internal/recovery.js';
|
|
5
5
|
import { seedToNumber } from './internal/random.js';
|
|
6
6
|
import { pencilStroke } from './internal/stroke.js';
|
|
7
7
|
import { maxStrokeRoughness, pencilRenderDefaults, readRenderToken } from './internal/renderTokens.js';
|
|
8
|
-
|
|
8
|
+
import { cachedPathParts } from './internal/iconGeometry.js';
|
|
9
9
|
const imageCache = new BoundedCache(128, 24 * 1024 * 1024);
|
|
10
|
-
function cachedPathParts(data) {
|
|
11
|
-
const key = `${PENCIL_RENDERER_VERSION}:${data}`;
|
|
12
|
-
const cached = geometryCache.get(key);
|
|
13
|
-
if (cached)
|
|
14
|
-
return cached;
|
|
15
|
-
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
16
|
-
path.setAttribute('d', data);
|
|
17
|
-
const length = path.getTotalLength();
|
|
18
|
-
const count = Math.max(1, Math.ceil(length / 0.25));
|
|
19
|
-
const parts = [[]];
|
|
20
|
-
for (let index = 0; index <= count; index += 1) {
|
|
21
|
-
const point = path.getPointAtLength(length * index / count);
|
|
22
|
-
const activePart = parts[parts.length - 1];
|
|
23
|
-
const previous = activePart[activePart.length - 1];
|
|
24
|
-
if (previous && Math.hypot(point.x - previous[0], point.y - previous[1]) > 0.5)
|
|
25
|
-
parts.push([]);
|
|
26
|
-
parts[parts.length - 1].push([point.x, point.y]);
|
|
27
|
-
}
|
|
28
|
-
return geometryCache.set(key, parts, parts.reduce((total, part) => total + part.length, 0));
|
|
29
|
-
}
|
|
30
10
|
function drawFallbackPath(context, data, color, mode, weightScale = 1) {
|
|
31
11
|
try {
|
|
32
12
|
const path = new Path2D(data);
|
|
@@ -47,9 +27,18 @@ function renderIcon(paths, options) {
|
|
|
47
27
|
const { seed, size, sourceSize, active, color, dpr } = options;
|
|
48
28
|
const weightScale = options.weightScale ?? 1;
|
|
49
29
|
const roughnessScale = options.roughnessScale ?? 1;
|
|
30
|
+
const breaks = options.breaks ?? pencilRenderDefaults.iconBreaks;
|
|
31
|
+
const breakCount = options.breakCount ?? pencilRenderDefaults.iconBreakCount;
|
|
32
|
+
const pressureVariation = options.pressureVariation ?? pencilRenderDefaults.iconPressureVariation;
|
|
33
|
+
const variationScale = pressureVariation / pencilRenderDefaults.iconPressureVariation;
|
|
34
|
+
const pixels = Math.round(size * dpr);
|
|
35
|
+
boundedCanvasSize(pixels, pixels, 1);
|
|
36
|
+
if (!Number.isFinite(sourceSize) || sourceSize <= 0 || sourceSize > 4096 || paths.length > 1024) {
|
|
37
|
+
throw new RangeError('Pencil icon exceeds the bounded geometry budget');
|
|
38
|
+
}
|
|
50
39
|
const canvas = document.createElement('canvas');
|
|
51
|
-
canvas.width =
|
|
52
|
-
canvas.height =
|
|
40
|
+
canvas.width = pixels;
|
|
41
|
+
canvas.height = pixels;
|
|
53
42
|
const context = canvas.getContext('2d');
|
|
54
43
|
if (!context)
|
|
55
44
|
return canvas;
|
|
@@ -57,7 +46,10 @@ function renderIcon(paths, options) {
|
|
|
57
46
|
context.scale(dpr * coordinateScale, dpr * coordinateScale);
|
|
58
47
|
const offset = (40 - sourceSize) / 2;
|
|
59
48
|
context.translate(offset, offset);
|
|
49
|
+
let sampledPoints = 0;
|
|
60
50
|
paths.forEach((path, index) => {
|
|
51
|
+
if (path.d.length > 65536)
|
|
52
|
+
return;
|
|
61
53
|
const mode = path.mode ?? 'stroke';
|
|
62
54
|
const pathSeed = seed + index * 137;
|
|
63
55
|
if (mode === 'wash') {
|
|
@@ -66,7 +58,7 @@ function renderIcon(paths, options) {
|
|
|
66
58
|
context.clip(new Path2D(path.d));
|
|
67
59
|
context.globalAlpha = active ? 0.76 : 0.34;
|
|
68
60
|
for (let y = sourceSize * 0.18; y < sourceSize; y += 1.6) {
|
|
69
|
-
pencilStroke(context, [[sourceSize * 0.15, y + 3], [sourceSize * 0.85, y - 3]], color, 1.1, pathSeed + Math.round(y * 17), 0.25, 2, false, { variation: 0.85, breaks:
|
|
61
|
+
pencilStroke(context, [[sourceSize * 0.15, y + 3], [sourceSize * 0.85, y - 3]], color, 1.1 * weightScale / (pencilRenderDefaults.strokeWidth / 1.2), pathSeed + Math.round(y * 17), 0.25, 2, false, { variation: Math.min(1, 0.85 * variationScale), fitPressure: true, breaks, breakCount, breakScale: coordinateScale, continuousBreaks: true, grain: 0.7, gain: 2.2, step: 0.35 });
|
|
70
62
|
}
|
|
71
63
|
context.restore();
|
|
72
64
|
}
|
|
@@ -76,11 +68,24 @@ function renderIcon(paths, options) {
|
|
|
76
68
|
return;
|
|
77
69
|
}
|
|
78
70
|
try {
|
|
71
|
+
if (sampledPoints >= 65536) {
|
|
72
|
+
drawFallbackPath(context, path.d, color, mode, weightScale);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
79
75
|
const parts = cachedPathParts(path.d);
|
|
76
|
+
sampledPoints += parts.reduce((total, part) => total + part.length, 0);
|
|
77
|
+
if (sampledPoints > 65536) {
|
|
78
|
+
drawFallbackPath(context, path.d, color, mode, weightScale);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
80
81
|
for (const points of parts) {
|
|
81
82
|
pencilStroke(context, points, color, (mode === 'scuff' ? 0.55 : 1.65) * weightScale, pathSeed, 0.3 * roughnessScale, mode === 'scuff' ? 1 : 3, false, {
|
|
82
|
-
variation:
|
|
83
|
-
|
|
83
|
+
variation: pressureVariation,
|
|
84
|
+
fitPressure: true,
|
|
85
|
+
breaks,
|
|
86
|
+
breakCount,
|
|
87
|
+
breakScale: coordinateScale,
|
|
88
|
+
continuousBreaks: true,
|
|
84
89
|
grain: 0.68,
|
|
85
90
|
gain: mode === 'scuff' ? 1.5 : 3,
|
|
86
91
|
step: 0.35,
|
|
@@ -93,7 +98,7 @@ function renderIcon(paths, options) {
|
|
|
93
98
|
}
|
|
94
99
|
});
|
|
95
100
|
if (active) {
|
|
96
|
-
pencilStroke(context, [[sourceSize * 0.2, sourceSize - 2], [sourceSize * 0.5, sourceSize - 1.5], [sourceSize * 0.8, sourceSize - 2.2]], color, 1.1 * weightScale, seed + 977, 0.25 * roughnessScale, 3, false, { variation: 0.8, breaks:
|
|
101
|
+
pencilStroke(context, [[sourceSize * 0.2, sourceSize - 2], [sourceSize * 0.5, sourceSize - 1.5], [sourceSize * 0.8, sourceSize - 2.2]], color, 1.1 * weightScale, seed + 977, 0.25 * roughnessScale, 3, false, { variation: 0.8 * variationScale, fitPressure: true, breaks, breakCount, breakScale: coordinateScale, continuousBreaks: true, grain: 0.7, gain: 2.8, step: 0.35 });
|
|
97
102
|
}
|
|
98
103
|
if (options.badge?.paths.length) {
|
|
99
104
|
const badge = options.badge;
|
|
@@ -106,7 +111,7 @@ function renderIcon(paths, options) {
|
|
|
106
111
|
const y = position.startsWith('top') ? 0 : 40 - extent;
|
|
107
112
|
const badgeImage = renderIcon(badge.paths, {
|
|
108
113
|
seed: seed + 1543, size: size * scale, sourceSize: badgeSourceSize, active: false, color, dpr,
|
|
109
|
-
weightScale: weightScale / scale, roughnessScale,
|
|
114
|
+
weightScale: weightScale / scale, roughnessScale, breaks, breakCount, pressureVariation,
|
|
110
115
|
});
|
|
111
116
|
const mask = document.createElement('canvas');
|
|
112
117
|
mask.width = badgeImage.width;
|
|
@@ -135,29 +140,47 @@ function renderIcon(paths, options) {
|
|
|
135
140
|
}
|
|
136
141
|
return canvas;
|
|
137
142
|
}
|
|
138
|
-
export const PencilIcon = forwardRef(function PencilIcon({ active = false, badge, className = '', decorative = true, paths, seed, size = 40, sourceSize = 32, ...props }, forwardedRef) {
|
|
143
|
+
export const PencilIcon = forwardRef(function PencilIcon({ active = false, badge, className = '', decorative = true, paths, seed, size = 40, sourceSize = 32, strokeWidth, intensity, breaks, breakCount, pressureVariation, ...props }, forwardedRef) {
|
|
139
144
|
const canvasRef = useRef(null);
|
|
140
145
|
const lastPaintRef = useRef('');
|
|
141
146
|
useImperativeHandle(forwardedRef, () => canvasRef.current);
|
|
142
|
-
|
|
143
|
-
|
|
147
|
+
// Compare values, not caller object identity. Serializing each render also
|
|
148
|
+
// detects in-place edits; the effect receives a stable snapshot of that value.
|
|
149
|
+
const geometryKey = JSON.stringify(paths);
|
|
150
|
+
const badgeKey = badge ? JSON.stringify(badge) : '';
|
|
151
|
+
const stablePaths = useMemo(() => JSON.parse(geometryKey), [geometryKey]);
|
|
152
|
+
const stableBadge = useMemo(() => badgeKey ? JSON.parse(badgeKey) : undefined, [badgeKey]);
|
|
144
153
|
const resolvedSeed = seedToNumber(seed ?? geometryKey);
|
|
145
154
|
useEffect(() => {
|
|
146
155
|
const canvas = canvasRef.current;
|
|
147
156
|
if (!canvas)
|
|
148
157
|
return undefined;
|
|
149
158
|
let disposed = false;
|
|
150
|
-
const
|
|
159
|
+
const paintIcon = () => {
|
|
151
160
|
if (disposed)
|
|
152
161
|
return;
|
|
153
162
|
const dpr = effectiveDpr(3);
|
|
154
163
|
const computed = getComputedStyle(canvas);
|
|
155
164
|
const color = computed.color;
|
|
156
165
|
const rtl = computed.direction === 'rtl';
|
|
157
|
-
const
|
|
166
|
+
const iconWeight = readRenderToken(computed, '--inkra-icon-stroke-width', pencilRenderDefaults.iconStrokeWidth, 0.3, 3);
|
|
167
|
+
const weightScale = (typeof strokeWidth === 'number' && Number.isFinite(strokeWidth) ? Math.min(3, Math.max(0.3, strokeWidth)) : iconWeight) / 1.2;
|
|
168
|
+
const pigmentOpacity = typeof intensity === 'number' && Number.isFinite(intensity)
|
|
169
|
+
? Math.min(1, Math.max(0, intensity))
|
|
170
|
+
: readRenderToken(computed, '--inkra-icon-intensity', 1, 0, 1);
|
|
158
171
|
const roughnessScale = readRenderToken(computed, '--inkra-stroke-amplitude', pencilRenderDefaults.strokeRoughness, 0, maxStrokeRoughness) / 0.6;
|
|
172
|
+
const strokeBreaks = typeof breaks === 'number' && Number.isFinite(breaks)
|
|
173
|
+
? Math.min(1, Math.max(0, breaks))
|
|
174
|
+
: readRenderToken(computed, '--inkra-icon-breaks', pencilRenderDefaults.iconBreaks, 0, 1);
|
|
175
|
+
const strokeVariation = typeof pressureVariation === 'number' && Number.isFinite(pressureVariation)
|
|
176
|
+
? Math.min(1, Math.max(0, pressureVariation))
|
|
177
|
+
: readRenderToken(computed, '--inkra-icon-pressure-variation', pencilRenderDefaults.iconPressureVariation, 0, 1);
|
|
178
|
+
const strokeBreakCount = Math.floor(typeof breakCount === 'number' && Number.isFinite(breakCount)
|
|
179
|
+
? Math.min(12, Math.max(0, breakCount))
|
|
180
|
+
: readRenderToken(computed, '--inkra-icon-break-count', pencilRenderDefaults.iconBreakCount, 0, 12));
|
|
159
181
|
const key = JSON.stringify([
|
|
160
182
|
PENCIL_RENDERER_VERSION,
|
|
183
|
+
'icon-path-pressure-gaps-v4',
|
|
161
184
|
geometryKey,
|
|
162
185
|
badgeKey,
|
|
163
186
|
rtl,
|
|
@@ -167,17 +190,19 @@ export const PencilIcon = forwardRef(function PencilIcon({ active = false, badge
|
|
|
167
190
|
active,
|
|
168
191
|
color,
|
|
169
192
|
dpr,
|
|
170
|
-
weightScale, roughnessScale,
|
|
193
|
+
weightScale, roughnessScale, strokeBreaks, strokeBreakCount, strokeVariation,
|
|
171
194
|
]);
|
|
172
|
-
|
|
195
|
+
const paintKey = `${key}:${pigmentOpacity}`;
|
|
196
|
+
if (paintKey === lastPaintRef.current && canvasIsUsable(canvas))
|
|
173
197
|
return;
|
|
198
|
+
delete canvas.dataset.pencilIconReady;
|
|
174
199
|
let image = imageCache.get(key);
|
|
175
200
|
if (image && !canvasIsUsable(image)) {
|
|
176
201
|
imageCache.delete(key);
|
|
177
202
|
image = undefined;
|
|
178
203
|
}
|
|
179
204
|
if (!image) {
|
|
180
|
-
image = renderIcon(
|
|
205
|
+
image = renderIcon(stablePaths, { seed: resolvedSeed, size, sourceSize, active, color, dpr, badge: stableBadge, rtl, weightScale, roughnessScale, breaks: strokeBreaks, breakCount: strokeBreakCount, pressureVariation: strokeVariation });
|
|
181
206
|
imageCache.set(key, image, canvasByteCost(image));
|
|
182
207
|
}
|
|
183
208
|
canvas.width = image.width;
|
|
@@ -185,9 +210,27 @@ export const PencilIcon = forwardRef(function PencilIcon({ active = false, badge
|
|
|
185
210
|
const context = canvas.getContext('2d');
|
|
186
211
|
if (!context || context.isContextLost?.())
|
|
187
212
|
return;
|
|
213
|
+
// Composite once so overlapping strokes and badges share the same intensity.
|
|
214
|
+
context.save();
|
|
215
|
+
context.globalAlpha = pigmentOpacity;
|
|
188
216
|
context.drawImage(image, 0, 0);
|
|
189
|
-
|
|
217
|
+
context.restore();
|
|
218
|
+
lastPaintRef.current = paintKey;
|
|
219
|
+
canvas.dataset.pencilIconReady = 'true';
|
|
220
|
+
};
|
|
221
|
+
const paint = () => {
|
|
222
|
+
try {
|
|
223
|
+
paintIcon();
|
|
224
|
+
}
|
|
225
|
+
catch {
|
|
226
|
+
// Invalid/oversized decoration must not throw from a React effect.
|
|
227
|
+
canvas.width = canvas.height = 0;
|
|
228
|
+
lastPaintRef.current = '';
|
|
229
|
+
delete canvas.dataset.pencilIconReady;
|
|
230
|
+
}
|
|
190
231
|
};
|
|
232
|
+
const onContextLost = () => { delete canvas.dataset.pencilIconReady; lastPaintRef.current = ''; };
|
|
233
|
+
canvas.addEventListener('contextlost', onContextLost);
|
|
191
234
|
const inheritedColor = new MutationObserver(paint);
|
|
192
235
|
for (let element = canvas; element; element = element.parentElement) {
|
|
193
236
|
inheritedColor.observe(element, { attributes: true, attributeFilter: ['style', 'class', 'dir', 'data-theme', 'data-inkra-theme'] });
|
|
@@ -198,11 +241,12 @@ export const PencilIcon = forwardRef(function PencilIcon({ active = false, badge
|
|
|
198
241
|
paint();
|
|
199
242
|
return () => {
|
|
200
243
|
disposed = true;
|
|
244
|
+
canvas.removeEventListener('contextlost', onContextLost);
|
|
201
245
|
stopRecovery();
|
|
202
246
|
inheritedColor.disconnect();
|
|
203
247
|
colorScheme.removeEventListener('change', paint);
|
|
204
248
|
};
|
|
205
|
-
}, [active,
|
|
249
|
+
}, [active, stableBadge, badgeKey, geometryKey, stablePaths, resolvedSeed, size, sourceSize, strokeWidth, intensity, breaks, breakCount, pressureVariation]);
|
|
206
250
|
const accessibilityProps = decorative
|
|
207
251
|
? { 'aria-hidden': true }
|
|
208
252
|
: { role: props.role ?? 'img' };
|
package/dist/PencilLine.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { forwardRef, useEffect, useImperativeHandle, useRef } from 'react';
|
|
3
|
-
import { BoundedCache, PENCIL_RENDERER_VERSION, canvasByteCost, canvasIsUsable, effectiveDpr, } from './internal/cache.js';
|
|
3
|
+
import { BoundedCache, PENCIL_RENDERER_VERSION, boundedCanvasSize, canvasByteCost, canvasIsUsable, effectiveDpr, } from './internal/cache.js';
|
|
4
4
|
import { observeCanvasRecovery } from './internal/recovery.js';
|
|
5
5
|
import { seedToNumber } from './internal/random.js';
|
|
6
6
|
import { createPencilLinePoints, pencilLineHasDrawableSize } from './internal/line.js';
|
|
@@ -37,6 +37,14 @@ export const PencilLine = forwardRef(function PencilLine({ className = '', orien
|
|
|
37
37
|
if (!pencilLineHasDrawableSize(width, height, orientation))
|
|
38
38
|
return;
|
|
39
39
|
const dpr = effectiveDpr(2);
|
|
40
|
+
try {
|
|
41
|
+
boundedCanvasSize(width, height, dpr);
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
canvas.width = canvas.height = 0;
|
|
45
|
+
lastPaint = '';
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
40
48
|
const style = getComputedStyle(canvas);
|
|
41
49
|
const color = style.color;
|
|
42
50
|
const resolvedWeight = weight ?? readRenderToken(style, '--inkra-stroke-width', pencilRenderDefaults.strokeWidth, 0.3, 3);
|
package/dist/PencilText.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { forwardRef, useImperativeHandle, useRef, useState } from 'react';
|
|
3
3
|
import { useBrowserLayoutEffect } from './internal/useBrowserLayoutEffect.js';
|
|
4
|
-
import { effectiveDpr } from './internal/cache.js';
|
|
4
|
+
import { boundedCanvasSize, effectiveDpr } from './internal/cache.js';
|
|
5
5
|
import { observeCanvasRecovery } from './internal/recovery.js';
|
|
6
6
|
import { seedToNumber } from './internal/random.js';
|
|
7
7
|
import { pencilLettering, renderPencilText } from './internal/textRenderer.js';
|
|
@@ -108,8 +108,9 @@ function paintText(element, source, canvas, text, seed, glyphSeed) {
|
|
|
108
108
|
const dpr = effectiveDpr(2);
|
|
109
109
|
if (width * height * dpr ** 2 > 2 * 1024 * 1024 || !context || context.isContextLost?.())
|
|
110
110
|
return false;
|
|
111
|
-
|
|
112
|
-
canvas.
|
|
111
|
+
const pixels = boundedCanvasSize(width, height, dpr);
|
|
112
|
+
canvas.width = pixels.width;
|
|
113
|
+
canvas.height = pixels.height;
|
|
113
114
|
Object.assign(canvas.style, {
|
|
114
115
|
left: `-${padding}px`,
|
|
115
116
|
top: `-${padding}px`,
|
|
@@ -119,7 +120,7 @@ function paintText(element, source, canvas, text, seed, glyphSeed) {
|
|
|
119
120
|
height: `${height}px`,
|
|
120
121
|
});
|
|
121
122
|
context.scale(dpr, dpr);
|
|
122
|
-
context.font =
|
|
123
|
+
context.font = `${style.fontStyle} ${style.fontWeight} ${size}px ${style.fontFamily}`;
|
|
123
124
|
context.fontKerning = 'none';
|
|
124
125
|
const ascent = context.measureText(text).fontBoundingBoxAscent ?? size;
|
|
125
126
|
const lines = measureLocalTextLines(element, source, text, localWidth, localHeight);
|
|
@@ -133,6 +134,8 @@ function paintText(element, source, canvas, text, seed, glyphSeed) {
|
|
|
133
134
|
glyphSeed,
|
|
134
135
|
color: style.color,
|
|
135
136
|
family: style.fontFamily,
|
|
137
|
+
weight: style.fontWeight,
|
|
138
|
+
style: style.fontStyle,
|
|
136
139
|
dpr,
|
|
137
140
|
settings: {
|
|
138
141
|
variation: readRenderToken(style, '--inkra-letter-variation', pencilLettering.variation, 0, 1),
|
|
@@ -176,7 +179,7 @@ const PencilTextImplementation = forwardRef(function PencilText({ as: Element =
|
|
|
176
179
|
const context = canvas.getContext('2d');
|
|
177
180
|
if (!context || context.isContextLost?.())
|
|
178
181
|
return;
|
|
179
|
-
context.font =
|
|
182
|
+
context.font = `${style.fontStyle} ${style.fontWeight} ${style.fontSize} ${style.fontFamily}`;
|
|
180
183
|
context.fontKerning = 'none';
|
|
181
184
|
const measure = (value) => context.measureText(value).width;
|
|
182
185
|
if (measure(children) <= width) {
|
|
@@ -248,8 +251,8 @@ const PencilTextImplementation = forwardRef(function PencilText({ as: Element =
|
|
|
248
251
|
return JSON.stringify([
|
|
249
252
|
localBox.width, localBox.height, effectiveDpr(2),
|
|
250
253
|
style.fontSize && style.fontFamily
|
|
251
|
-
? document.fonts?.check(
|
|
252
|
-
...['color', 'font-family', 'font-size', 'line-height', 'text-align', 'text-indent',
|
|
254
|
+
? document.fonts?.check(`${style.fontStyle} ${style.fontWeight} ${style.fontSize} ${style.fontFamily}`, text) : false,
|
|
255
|
+
...['color', 'font-family', 'font-size', 'font-weight', 'font-style', 'line-height', 'text-align', 'text-indent',
|
|
253
256
|
'letter-spacing', 'white-space', 'word-break', 'overflow-wrap', 'direction',
|
|
254
257
|
'padding-top', 'padding-left', 'padding-right', 'padding-bottom',
|
|
255
258
|
'--inkra-letter-variation', '--inkra-letter-breaks', '--inkra-letter-outline',
|
|
@@ -260,12 +263,15 @@ const PencilTextImplementation = forwardRef(function PencilText({ as: Element =
|
|
|
260
263
|
pending = null;
|
|
261
264
|
if (!disposed && visible && dirty) {
|
|
262
265
|
try {
|
|
263
|
-
|
|
264
|
-
lastAppearance = appearanceKey();
|
|
266
|
+
paintText(node, source, canvas, text, resolvedSeed, glyphSeed);
|
|
265
267
|
}
|
|
266
268
|
catch {
|
|
267
269
|
delete node.dataset.pencilReady;
|
|
268
270
|
}
|
|
271
|
+
// Remember fallback appearances too. An oversized/unsupported label
|
|
272
|
+
// must not retry expensive measurement for unrelated ancestor styles.
|
|
273
|
+
// Theme/font/size changes and explicit canvas recovery still retry.
|
|
274
|
+
lastAppearance = appearanceKey();
|
|
269
275
|
dirty = false;
|
|
270
276
|
}
|
|
271
277
|
};
|
|
@@ -1,23 +1,49 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import * as AccordionPrimitive from '@radix-ui/react-accordion';
|
|
3
|
-
import { createContext, forwardRef, useContext, useId } from 'react';
|
|
3
|
+
import { createContext, forwardRef, useCallback, useContext, useId, useRef, useState } from 'react';
|
|
4
4
|
import { PencilIcon } from '../PencilIcon.js';
|
|
5
5
|
import { PencilLine } from '../PencilLine.js';
|
|
6
6
|
import { PencilSurface } from '../PencilSurface.js';
|
|
7
7
|
import { PencilText } from '../PencilText.js';
|
|
8
|
+
import { useBrowserLayoutEffect } from '../internal/useBrowserLayoutEffect.js';
|
|
8
9
|
const chevronDownPaths = [
|
|
9
10
|
{ d: 'M6 9L12 15L18 9' },
|
|
10
11
|
];
|
|
11
12
|
const AccordionOrientationContext = createContext('vertical');
|
|
13
|
+
const AccordionOpenValuesContext = createContext(new Set());
|
|
14
|
+
const AccordionItemOpenContext = createContext(false);
|
|
12
15
|
function classes(...values) {
|
|
13
16
|
return values.filter(Boolean).join(' ');
|
|
14
17
|
}
|
|
18
|
+
function assignRef(ref, value) {
|
|
19
|
+
if (typeof ref === 'function')
|
|
20
|
+
return ref(value);
|
|
21
|
+
if (ref)
|
|
22
|
+
ref.current = value;
|
|
23
|
+
}
|
|
24
|
+
function values(value) {
|
|
25
|
+
if (Array.isArray(value))
|
|
26
|
+
return value;
|
|
27
|
+
return value ? [value] : [];
|
|
28
|
+
}
|
|
15
29
|
export const AccordionRoot = forwardRef(function AccordionRoot({ className, orientation = 'vertical', ...props }, ref) {
|
|
16
|
-
|
|
30
|
+
const controlled = props.value !== undefined;
|
|
31
|
+
const [uncontrolledValues, setUncontrolledValues] = useState(() => values(props.defaultValue));
|
|
32
|
+
const currentValues = controlled ? values(props.value) : uncontrolledValues;
|
|
33
|
+
const handleValueChange = (nextValue) => {
|
|
34
|
+
if (!controlled)
|
|
35
|
+
setUncontrolledValues(values(nextValue));
|
|
36
|
+
if (props.type === 'single')
|
|
37
|
+
props.onValueChange?.(nextValue);
|
|
38
|
+
else
|
|
39
|
+
props.onValueChange?.(nextValue);
|
|
40
|
+
};
|
|
41
|
+
return (_jsx(AccordionOrientationContext.Provider, { value: orientation, children: _jsx(AccordionOpenValuesContext.Provider, { value: new Set(currentValues), children: props.type === 'single' ? (_jsx(AccordionPrimitive.Root, { ...props, className: classes('inkra-accordion', className), onValueChange: handleValueChange, orientation: orientation, ref: ref })) : (_jsx(AccordionPrimitive.Root, { ...props, className: classes('inkra-accordion', className), onValueChange: handleValueChange, orientation: orientation, ref: ref })) }) }));
|
|
17
42
|
});
|
|
18
43
|
export const AccordionItem = forwardRef(function AccordionItem({ children, className, dividerSeed, showDivider = true, value, ...props }, ref) {
|
|
19
44
|
const orientation = useContext(AccordionOrientationContext);
|
|
20
|
-
|
|
45
|
+
const open = useContext(AccordionOpenValuesContext).has(value);
|
|
46
|
+
return (_jsx(AccordionItemOpenContext.Provider, { value: open, children: _jsxs(AccordionPrimitive.Item, { ...props, className: classes('inkra-accordion__item', className), ref: ref, value: value, children: [children, showDivider ? (_jsx(PencilLine, { className: "inkra-accordion__divider", orientation: orientation === 'horizontal' ? 'vertical' : 'horizontal', seed: dividerSeed ?? `inkra-accordion-divider-${value}`, weight: 0.8 })) : null] }) }));
|
|
21
47
|
});
|
|
22
48
|
export const AccordionHeader = forwardRef(function AccordionHeader({ className, ...props }, ref) {
|
|
23
49
|
return (_jsx(AccordionPrimitive.Header, { ...props, className: classes('inkra-accordion__header', className), ref: ref }));
|
|
@@ -29,9 +55,43 @@ export const AccordionTrigger = forwardRef(function AccordionTrigger({ children,
|
|
|
29
55
|
return (_jsxs(AccordionPrimitive.Trigger, { ...props, className: classes('inkra-accordion__trigger', className), ref: ref, children: [_jsx(PencilSurface, { "aria-hidden": "true", as: "span", className: "inkra-accordion__focus-ring", radius: 6, seed: `${resolvedSeed}-focus`, variant: "outline" }), _jsx("span", { className: "inkra-accordion__trigger-label", children: typeof children === 'string' ? (_jsx(PencilText, { seed: `${resolvedSeed}-text`, children: children })) : children }), triggerIcon == null ? null : (_jsx("span", { "aria-hidden": "true", className: "inkra-accordion__trigger-icon", children: triggerIcon }))] }));
|
|
30
56
|
});
|
|
31
57
|
export const AccordionContent = forwardRef(function AccordionContent({ children, className, contentClassName, ...props }, ref) {
|
|
32
|
-
|
|
58
|
+
const open = useContext(AccordionItemOpenContext);
|
|
59
|
+
const contentRef = useRef(null);
|
|
60
|
+
const focusWithinRef = useRef(false);
|
|
61
|
+
const callerAriaHidden = props['aria-hidden'];
|
|
62
|
+
const callerInert = props.inert;
|
|
63
|
+
const callerOnBlurCapture = props.onBlurCapture;
|
|
64
|
+
const callerOnFocusCapture = props.onFocusCapture;
|
|
65
|
+
const setContentRef = useCallback((node) => {
|
|
66
|
+
contentRef.current = node;
|
|
67
|
+
const cleanup = assignRef(ref, node);
|
|
68
|
+
return () => {
|
|
69
|
+
contentRef.current = null;
|
|
70
|
+
if (typeof cleanup === 'function')
|
|
71
|
+
cleanup();
|
|
72
|
+
else
|
|
73
|
+
assignRef(ref, null);
|
|
74
|
+
};
|
|
75
|
+
}, [ref]);
|
|
76
|
+
useBrowserLayoutEffect(() => {
|
|
77
|
+
const content = contentRef.current;
|
|
78
|
+
if (open || !content || !focusWithinRef.current)
|
|
79
|
+
return;
|
|
80
|
+
focusWithinRef.current = false;
|
|
81
|
+
const triggerId = content.getAttribute('aria-labelledby');
|
|
82
|
+
if (triggerId)
|
|
83
|
+
content.ownerDocument.getElementById(triggerId)?.focus({ preventScroll: true });
|
|
84
|
+
}, [open]);
|
|
85
|
+
return (_jsx(AccordionPrimitive.Content, { ...props, "aria-hidden": open ? callerAriaHidden : true, className: classes('inkra-accordion__content', className), inert: open ? callerInert : true, onBlurCapture: event => {
|
|
86
|
+
callerOnBlurCapture?.(event);
|
|
87
|
+
if (open && !event.currentTarget.contains(event.relatedTarget))
|
|
88
|
+
focusWithinRef.current = false;
|
|
89
|
+
}, onFocusCapture: event => {
|
|
90
|
+
focusWithinRef.current = true;
|
|
91
|
+
callerOnFocusCapture?.(event);
|
|
92
|
+
}, ref: setContentRef, children: _jsx("div", { className: classes('inkra-accordion__content-body', contentClassName), children: children }) }));
|
|
33
93
|
});
|
|
34
|
-
export const Accordion = Object.freeze({
|
|
94
|
+
export const Accordion = /* @__PURE__ */ Object.freeze({
|
|
35
95
|
Root: AccordionRoot,
|
|
36
96
|
Item: AccordionItem,
|
|
37
97
|
Header: AccordionHeader,
|
|
@@ -11,7 +11,7 @@ export declare function AlertDialogRoot({ defaultOpen, onOpenChange, open, pendi
|
|
|
11
11
|
export type AlertDialogTriggerProps = DialogPrimitive.DialogTriggerProps;
|
|
12
12
|
export declare const AlertDialogTrigger: import("react").ForwardRefExoticComponent<DialogPrimitive.DialogTriggerProps & import("react").RefAttributes<HTMLButtonElement>>;
|
|
13
13
|
export type AlertDialogPortalProps = DialogPrimitive.DialogPortalProps;
|
|
14
|
-
export declare function AlertDialogPortal({ children, ...props }: AlertDialogPortalProps):
|
|
14
|
+
export declare function AlertDialogPortal({ children, ...props }: AlertDialogPortalProps): ReactNode;
|
|
15
15
|
export type AlertDialogBackdropProps = DialogPrimitive.DialogOverlayProps;
|
|
16
16
|
export declare const AlertDialogBackdrop: import("react").ForwardRefExoticComponent<DialogPrimitive.DialogOverlayProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
17
17
|
export interface AlertDialogContentProps extends DialogPrimitive.DialogContentProps {
|