@inkra/react 0.6.0 → 0.9.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.
Files changed (72) hide show
  1. package/README.md +166 -1
  2. package/dist/Button.js +1 -1
  3. package/dist/PencilIcon.d.ts +14 -1
  4. package/dist/PencilIcon.js +88 -52
  5. package/dist/PencilText.js +8 -0
  6. package/dist/form/Textarea.d.ts +4 -0
  7. package/dist/form/Textarea.js +14 -8
  8. package/dist/form/index.d.ts +1 -1
  9. package/dist/form/textareaSizing.d.ts +29 -0
  10. package/dist/form/textareaSizing.js +153 -0
  11. package/dist/guide/index.d.ts +77 -0
  12. package/dist/guide/index.js +379 -0
  13. package/dist/i18n/catalog.d.ts +1 -1
  14. package/dist/i18n/catalog.js +15 -0
  15. package/dist/image-viewer/index.d.ts +2 -0
  16. package/dist/image-viewer/index.js +28 -28
  17. package/dist/index.d.ts +6 -0
  18. package/dist/index.js +5 -0
  19. package/dist/infinite-scroll/gesture.d.ts +19 -0
  20. package/dist/infinite-scroll/gesture.js +218 -0
  21. package/dist/infinite-scroll/index.d.ts +55 -0
  22. package/dist/infinite-scroll/index.js +543 -0
  23. package/dist/infinite-scroll/model.d.ts +33 -0
  24. package/dist/infinite-scroll/model.js +168 -0
  25. package/dist/internal/iconFill.d.ts +16 -0
  26. package/dist/internal/iconFill.js +275 -0
  27. package/dist/inventory-grid/index.d.ts +35 -0
  28. package/dist/inventory-grid/index.js +215 -0
  29. package/dist/inventory-grid/model.d.ts +56 -0
  30. package/dist/inventory-grid/model.js +93 -0
  31. package/dist/inventory-grid/outline.d.ts +9 -0
  32. package/dist/inventory-grid/outline.js +54 -0
  33. package/dist/layout/GridBoard.d.ts +22 -0
  34. package/dist/layout/GridBoard.js +113 -0
  35. package/dist/layout/GridResizable.d.ts +31 -0
  36. package/dist/layout/GridResizable.js +35 -0
  37. package/dist/layout/index.d.ts +19 -4
  38. package/dist/layout/index.js +45 -9
  39. package/dist/layout/swipeNavigation.d.ts +15 -0
  40. package/dist/layout/swipeNavigation.js +121 -0
  41. package/dist/loading/index.d.ts +58 -0
  42. package/dist/loading/index.js +188 -0
  43. package/dist/loading/locales.d.ts +15 -0
  44. package/dist/loading/locales.js +34 -0
  45. package/dist/loading/model.d.ts +6 -0
  46. package/dist/loading/model.js +29 -0
  47. package/dist/masonry/index.d.ts +25 -0
  48. package/dist/masonry/index.js +227 -0
  49. package/dist/masonry/layout.d.ts +35 -0
  50. package/dist/masonry/layout.js +67 -0
  51. package/dist/number-ticker/RollingValue.d.ts +6 -0
  52. package/dist/number-ticker/RollingValue.js +10 -0
  53. package/dist/number-ticker/index.d.ts +4 -0
  54. package/dist/number-ticker/index.js +55 -7
  55. package/dist/number-ticker/rolling.d.ts +35 -0
  56. package/dist/number-ticker/rolling.js +87 -0
  57. package/dist/pagination/index.d.ts +17 -0
  58. package/dist/pagination/index.js +30 -6
  59. package/dist/styles/base.css +9 -1
  60. package/dist/styles/form.css +10 -0
  61. package/dist/styles/guide.css +202 -0
  62. package/dist/styles/image-viewer.css +20 -0
  63. package/dist/styles/infinite-scroll.css +171 -0
  64. package/dist/styles/inventory-grid.css +43 -0
  65. package/dist/styles/layout.css +85 -0
  66. package/dist/styles/loading.css +348 -0
  67. package/dist/styles/masonry.css +68 -0
  68. package/dist/styles/number-ticker.css +46 -0
  69. package/dist/styles/pagination.css +10 -0
  70. package/dist/styles.css +1162 -146
  71. package/dist/toast/manager.d.ts +4 -4
  72. package/package.json +33 -1
@@ -0,0 +1,87 @@
1
+ export function sameRollingTokens(from, to) {
2
+ return from.length === to.length && from.every((token, index) => {
3
+ const next = to[index];
4
+ return token.key === next.key && token.text === next.text && token.digit === next.digit;
5
+ });
6
+ }
7
+ export const rollMix = (from, to, progress) => from + (to - from) * progress;
8
+ export function createRollingMotion(from, to, direction, previous) {
9
+ const sign = direction === 'up' ? -1 : 1;
10
+ const snapshot = previous?.columns.map(column => ({
11
+ key: column.key, digit: column.digit,
12
+ width: rollMix(column.fromWidth, column.toWidth, previous.progress),
13
+ faces: column.faces.map(face => ({
14
+ text: face.text,
15
+ offset: rollMix(face.fromOffset, face.toOffset, previous.progress),
16
+ opacity: rollMix(face.fromOpacity, face.toOpacity, previous.progress),
17
+ })).filter(face => Math.abs(face.offset) < 1 && face.opacity > 0.01),
18
+ })) ?? from.map(token => ({ key: token.key, digit: token.digit, width: 1, faces: [{ text: token.text, offset: 0, opacity: 1 }] }));
19
+ const sources = new Map(snapshot.map(column => [column.key, column]));
20
+ const sourceTokens = snapshot.filter(column => column.width > 0 || column.faces.length > 0)
21
+ .map(column => ({ key: column.key, digit: column.digit, text: column.faces[0]?.text ?? '' }));
22
+ return rollingColumns(sourceTokens, to).map(column => {
23
+ const source = sources.get(column.key);
24
+ const faces = [...(source?.faces ?? [])];
25
+ if (column.to !== '' && !faces.some(face => face.text === column.to)) {
26
+ faces.push({ text: column.to, offset: column.digit ? -sign : 0, opacity: column.digit ? 1 : 0 });
27
+ }
28
+ return {
29
+ key: column.key, digit: column.digit, target: column.to,
30
+ fromWidth: source?.width ?? 0, toWidth: column.to === '' ? 0 : 1,
31
+ // Existing faces continue from their actual offset on a rapid retarget.
32
+ faces: faces.map(face => ({ text: face.text, fromOffset: face.offset,
33
+ toOffset: face.text === column.to ? 0 : column.digit ? sign : 0,
34
+ fromOpacity: face.opacity, toOpacity: face.text === column.to || column.digit ? 1 : 0,
35
+ })),
36
+ };
37
+ });
38
+ }
39
+ // Align integer places from the right and fractional places from the decimal.
40
+ // Intl parts keep signs, grouping, currency and compact units out of digit reels.
41
+ export function rollingTokens(parts) {
42
+ const remaining = new Map();
43
+ for (const part of parts) {
44
+ if (part.type === 'integer' || part.type === 'exponentInteger') {
45
+ remaining.set(part.type, (remaining.get(part.type) ?? 0) + Array.from(part.value).length);
46
+ }
47
+ }
48
+ const occurrences = new Map();
49
+ return parts.flatMap((part, index) => {
50
+ if (part.type === 'integer' || part.type === 'exponentInteger') {
51
+ return Array.from(part.value, text => {
52
+ const position = (remaining.get(part.type) ?? 1) - 1;
53
+ remaining.set(part.type, position);
54
+ return { key: `${part.type}-${position}`, text, digit: true };
55
+ });
56
+ }
57
+ if (part.type === 'fraction') {
58
+ return Array.from(part.value, (text, position) => ({ key: `fraction-${position}`, text, digit: true }));
59
+ }
60
+ const identity = part.type === 'literal'
61
+ ? `literal-${part.value}-${parts.slice(index + 1).find(next => next.type !== 'literal')?.type ?? 'end'}`
62
+ : part.type;
63
+ const occurrence = occurrences.get(identity) ?? 0;
64
+ occurrences.set(identity, occurrence + 1);
65
+ const key = part.type === 'group' ? `group-${remaining.get('integer')}` : `${identity}-${occurrence}`;
66
+ return [{ key, text: part.value, digit: false }];
67
+ });
68
+ }
69
+ export function rollingColumns(from, to) {
70
+ const old = new Map(from.map(token => [token.key, token]));
71
+ const next = new Map(to.map(token => [token.key, token]));
72
+ const result = to.map(token => ({
73
+ key: token.key, from: old.get(token.key)?.text ?? '', to: token.text, digit: token.digit,
74
+ }));
75
+ // Departing places retain their old order until their width reaches zero.
76
+ for (let index = from.length - 1; index >= 0; index -= 1) {
77
+ const token = from[index];
78
+ if (next.has(token.key))
79
+ continue;
80
+ const following = from[index + 1];
81
+ const insertion = following ? result.findIndex(column => column.key === following.key) : -1;
82
+ result.splice(insertion < 0 ? result.length : insertion, 0, {
83
+ key: token.key, from: token.text, to: '', digit: token.digit,
84
+ });
85
+ }
86
+ return result;
87
+ }
@@ -1,4 +1,5 @@
1
1
  import type { HTMLAttributes, ReactNode } from 'react';
2
+ import type { ButtonVariant } from '../Button.js';
2
3
  export interface PaginationLabels {
3
4
  nav: string;
4
5
  first: string;
@@ -7,6 +8,8 @@ export interface PaginationLabels {
7
8
  last: string;
8
9
  page: (page: number) => string;
9
10
  ellipsis: string;
11
+ pageSize?: string;
12
+ pageSizeOption?: (pageSize: number) => string;
10
13
  }
11
14
  export interface PaginationProps extends Omit<HTMLAttributes<HTMLElement>, 'children' | 'onChange'> {
12
15
  totalPages: number;
@@ -28,5 +31,19 @@ export interface PaginationProps extends Omit<HTMLAttributes<HTMLElement>, 'chil
28
31
  disabled?: boolean;
29
32
  size?: 'sm' | 'md' | 'lg';
30
33
  labels?: Partial<PaginationLabels>;
34
+ /** Appearance of numbered and boundary buttons; defaults to ghost. */
35
+ variant?: ButtonVariant;
36
+ /** Logical alignment of the size selector and page controls. */
37
+ align?: 'start' | 'center' | 'end';
38
+ /** Wrap page controls instead of scrolling them horizontally. */
39
+ wrap?: boolean;
40
+ /** Controlled positive safe-integer page size. Does not paginate caller data. */
41
+ pageSize?: number;
42
+ /** Initial uncontrolled page size; defaults to 10. */
43
+ defaultPageSize?: number;
44
+ /** Enable the size selector with positive safe integers; invalid entries are ignored. */
45
+ pageSizeOptions?: readonly number[];
46
+ /** Requests a page size, then requests page 1 when the current page is above 1. */
47
+ onPageSizeChange?: (pageSize: number) => void;
31
48
  }
32
49
  export declare const Pagination: import("react").ForwardRefExoticComponent<PaginationProps & import("react").RefAttributes<HTMLElement>>;
@@ -2,6 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useInkraLocale } from '../i18n/index.js';
3
3
  import { forwardRef, useEffect, useState } from 'react';
4
4
  import { Button } from '../Button.js';
5
+ import { Select } from '../select/index.js';
5
6
  import { PencilIcon } from '../PencilIcon.js';
6
7
  import { PencilSurface } from '../PencilSurface.js';
7
8
  import { PencilText } from '../PencilText.js';
@@ -9,12 +10,20 @@ import { clampPage, getPaginationEntries, normalizeTotal } from './model.js';
9
10
  const defaultLabels = (t) => ({
10
11
  nav: t('pagination.nav'), first: t('pagination.first'), previous: t('common.previousPage'), next: t('common.nextPage'), last: t('pagination.last'),
11
12
  page: page => t('pagination.page', { page }), ellipsis: t('pagination.more'),
13
+ pageSize: t('pagination.pageSize'), pageSizeOption: pageSize => t('pagination.pageSizeOption', { pageSize }),
12
14
  });
13
- export const Pagination = forwardRef(function Pagination({ totalPages, page, defaultPage = 1, onPageChange, siblingCount = 1, boundaryCount = 1, showFirstLast = false, showPreviousNext = true, hideOnSinglePage = false, simple = false, renderPage, renderSummary, disabled = false, size = 'md', labels, className, ...props }, ref) {
15
+ export const Pagination = forwardRef(function Pagination({ totalPages, page, defaultPage = 1, onPageChange, siblingCount = 1, boundaryCount = 1, showFirstLast = false, showPreviousNext = true, hideOnSinglePage = false, simple = false, renderPage, renderSummary, disabled = false, size = 'md', labels, className, variant = 'ghost', align = 'start', wrap = false, pageSize, defaultPageSize = 10, pageSizeOptions, onPageSizeChange, ...props }, ref) {
14
16
  const { t } = useInkraLocale();
15
17
  const total = normalizeTotal(totalPages);
16
18
  const [internalPage, setInternalPage] = useState(() => clampPage(defaultPage, total));
17
19
  const current = clampPage(page ?? internalPage, total);
20
+ const [internalPageSize, setInternalPageSize] = useState(() => normalizePageSize(defaultPageSize) ?? 10);
21
+ const currentPageSize = normalizePageSize(pageSize ?? internalPageSize) ?? 10;
22
+ const sizeOptions = [...new Set((pageSizeOptions ?? []).filter(value => normalizePageSize(value) !== undefined))];
23
+ if (sizeOptions.length && !sizeOptions.includes(currentPageSize))
24
+ sizeOptions.push(currentPageSize);
25
+ sizeOptions.sort((left, right) => left - right);
26
+ const showPages = !hideOnSinglePage || total > 1;
18
27
  const text = { ...defaultLabels(t), ...labels };
19
28
  useEffect(() => {
20
29
  if (page === undefined)
@@ -28,6 +37,18 @@ export const Pagination = forwardRef(function Pagination({ totalPages, page, def
28
37
  setInternalPage(target);
29
38
  onPageChange?.(target);
30
39
  }
40
+ function selectPageSize(value) {
41
+ const next = normalizePageSize(Number(value));
42
+ if (disabled || next === undefined || !sizeOptions.includes(next) || next === currentPageSize)
43
+ return;
44
+ if (pageSize === undefined)
45
+ setInternalPageSize(next);
46
+ if (page === undefined)
47
+ setInternalPage(clampPage(1, total));
48
+ onPageSizeChange?.(next);
49
+ if (current > 1)
50
+ onPageChange?.(1);
51
+ }
31
52
  const arrow = (kind, target, unavailable) => {
32
53
  const backwards = kind === 'first' || kind === 'previous';
33
54
  const paths = [{ d: backwards ? 'm15 18-6-6 6-6' : 'm9 18 6-6-6-6' }];
@@ -35,11 +56,14 @@ export const Pagination = forwardRef(function Pagination({ totalPages, page, def
35
56
  paths.push({ d: 'M5 6v12' });
36
57
  if (kind === 'last')
37
58
  paths.push({ d: 'M19 6v12' });
38
- return _jsx("li", { children: _jsx(Button, { variant: "ghost", size: size === 'md' ? 'icon' : `icon-${size}`, "aria-label": text[kind], title: text[kind], disabled: disabled || unavailable, onClick: () => select(target), children: _jsx(PencilIcon, { className: "inkra-pagination__arrow", paths: paths, size: 20, sourceSize: 24 }) }) }, kind);
59
+ return _jsx("li", { children: _jsx(Button, { variant: variant, size: size === 'md' ? 'icon' : `icon-${size}`, "aria-label": text[kind], title: text[kind], disabled: disabled || unavailable, onClick: () => select(target), children: _jsx(PencilIcon, { className: "inkra-pagination__arrow", paths: paths, size: 20, sourceSize: 24 }) }) }, kind);
39
60
  };
40
- if (hideOnSinglePage && total <= 1)
61
+ if (!showPages && !sizeOptions.length)
41
62
  return null;
42
- return _jsx("nav", { "aria-label": text.nav, ...props, ref: ref, className: ['inkra-pagination', className].filter(Boolean).join(' '), "data-empty": !total || undefined, "data-simple": simple || undefined, children: _jsxs("ul", { className: "inkra-pagination__list", children: [showFirstLast && arrow('first', 1, current <= 1), showPreviousNext && arrow('previous', current - 1, current <= 1), simple ? _jsx("li", { className: "inkra-pagination__summary", "aria-live": "polite", "aria-atomic": "true", children: renderSummary ? renderSummary(current, total) : _jsx(PencilText, { children: `${current} / ${total}` }) }) : getPaginationEntries(total, current, siblingCount, boundaryCount).map(entry => typeof entry === 'number'
43
- ? _jsx("li", { className: "inkra-pagination__page-item", children: _jsxs(Button, { size: size, variant: "ghost", className: "inkra-pagination__page", "aria-label": text.page(entry), "aria-current": entry === current ? 'page' : undefined, disabled: disabled, onClick: () => select(entry), children: [entry === current && _jsx(PencilSurface, { as: "span", "aria-hidden": "true", variant: "selection", className: "inkra-pagination__selection", seed: `page-${entry}` }), renderPage ? renderPage(entry, { isCurrent: entry === current }) : _jsx(PencilText, { children: String(entry) })] }) }, entry)
44
- : _jsxs("li", { className: "inkra-pagination__ellipsis", children: [_jsx("span", { className: "inkra-pagination__sr-only", children: text.ellipsis }), _jsx(PencilText, { "aria-hidden": "true", children: "..." })] }, entry)), showPreviousNext && arrow('next', current + 1, !total || current >= total), showFirstLast && arrow('last', total, !total || current >= total)] }) });
63
+ return _jsxs("nav", { "aria-label": text.nav, ...props, ref: ref, className: ['inkra-pagination', className].filter(Boolean).join(' '), "data-empty": !total || undefined, "data-simple": simple || undefined, "data-align": align, "data-wrap": wrap || undefined, children: [sizeOptions.length > 0 && _jsx("div", { className: "inkra-pagination__size", children: _jsxs(Select.Root, { dir: props.dir === 'rtl' ? 'rtl' : props.dir === 'ltr' ? 'ltr' : undefined, value: String(currentPageSize), onValueChange: selectPageSize, disabled: disabled, children: [_jsx(Select.Trigger, { "aria-label": text.pageSize, size: size, fullWidth: false, children: _jsx(Select.Value, { children: _jsx(PencilText, { children: text.pageSizeOption(currentPageSize) }) }) }), _jsx(Select.Portal, { children: _jsx(Select.Content, { children: _jsx(Select.Viewport, { children: sizeOptions.map(value => _jsxs(Select.Item, { value: String(value), children: [_jsx(Select.ItemText, { children: text.pageSizeOption(value) }), _jsx(Select.ItemIndicator, {})] }, value)) }) }) })] }) }), showPages && _jsxs("ul", { className: "inkra-pagination__list", children: [showFirstLast && arrow('first', 1, current <= 1), showPreviousNext && arrow('previous', current - 1, current <= 1), simple ? _jsx("li", { className: "inkra-pagination__summary", "aria-live": "polite", "aria-atomic": "true", children: renderSummary ? renderSummary(current, total) : _jsx(PencilText, { children: `${current} / ${total}` }) }) : getPaginationEntries(total, current, siblingCount, boundaryCount).map(entry => typeof entry === 'number'
64
+ ? _jsx("li", { className: "inkra-pagination__page-item", children: _jsxs(Button, { size: size, variant: variant, className: "inkra-pagination__page", "aria-label": text.page(entry), "aria-current": entry === current ? 'page' : undefined, disabled: disabled, onClick: () => select(entry), children: [entry === current && _jsx(PencilSurface, { as: "span", "aria-hidden": "true", variant: "selection", className: "inkra-pagination__selection", seed: `page-${entry}` }), renderPage ? renderPage(entry, { isCurrent: entry === current }) : _jsx(PencilText, { children: String(entry) })] }) }, entry)
65
+ : _jsxs("li", { className: "inkra-pagination__ellipsis", children: [_jsx("span", { className: "inkra-pagination__sr-only", children: text.ellipsis }), _jsx(PencilText, { "aria-hidden": "true", children: "..." })] }, entry)), showPreviousNext && arrow('next', current + 1, !total || current >= total), showFirstLast && arrow('last', total, !total || current >= total)] })] });
45
66
  });
67
+ function normalizePageSize(value) {
68
+ return Number.isSafeInteger(value) && value > 0 ? value : undefined;
69
+ }
@@ -373,6 +373,14 @@
373
373
  opacity: 0;
374
374
  }
375
375
 
376
+ .inkra-button[data-variant="ghost"] > .inkra-button__surface > .inkra-pencil-surface__border {
377
+ display: none;
378
+ }
379
+
380
+ .inkra-button[data-variant="ghost"] > .inkra-button__surface > .inkra-pencil-surface__content {
381
+ clip-path: none;
382
+ }
383
+
376
384
  .inkra-button[data-variant="ghost"]:hover:not(:disabled):not([aria-disabled="true"])
377
385
  .inkra-button__surface > .inkra-pencil-surface__ink,
378
386
  .inkra-button[data-variant="ghost"]:focus-visible
@@ -391,7 +399,7 @@
391
399
  .inkra-button[data-variant="ghost"]:focus-visible
392
400
  .inkra-button__surface[data-pencil-fallback="true"] {
393
401
  background: var(--inkra-surface-muted);
394
- border-color: var(--inkra-border);
402
+ border-color: transparent;
395
403
  }
396
404
 
397
405
  .inkra-button[data-variant="link"] .inkra-button__label {
@@ -228,6 +228,16 @@
228
228
  resize: none;
229
229
  }
230
230
 
231
+ /* Native rows establish the initial editor height; counters remain a separate row. */
232
+ .inkra-field-control-hit--textarea[data-row-sizing="true"] .inkra-field-control--textarea {
233
+ min-height: 0;
234
+ }
235
+
236
+ .inkra-field-control-hit--textarea[data-row-sizing="true"] .inkra-textarea {
237
+ flex: none;
238
+ min-height: 0;
239
+ }
240
+
231
241
  .inkra-textarea__footer {
232
242
  display: flex;
233
243
  align-items: center;
@@ -0,0 +1,202 @@
1
+ .inkra-guide__spotlight,
2
+ .inkra-guide__backdrop {
3
+ inset: 0;
4
+ pointer-events: none;
5
+ position: fixed;
6
+ z-index: calc(var(--inkra-z-modal, 30) + var(--inkra-floating-layer-offset, 0));
7
+ }
8
+
9
+ .inkra-guide__backdrop {
10
+ background: var(--inkra-overlay);
11
+ }
12
+
13
+ .inkra-guide__spotlight {
14
+ box-shadow: 0 0 0 100vmax var(--inkra-overlay);
15
+ overflow: visible;
16
+ }
17
+
18
+ .inkra-guide__spotlight > .inkra-pencil-surface__ink,
19
+ .inkra-guide__spotlight > .inkra-pencil-surface__border {
20
+ border-radius: var(--inkra-guide-highlight-radius, 8px);
21
+ }
22
+
23
+ .inkra-guide__content {
24
+ max-height: min(70dvh, var(--radix-popover-content-available-height, 70dvh));
25
+ max-width: min(24rem, calc(100dvw - var(--inkra-space-6)));
26
+ outline: 0;
27
+ transform-origin: var(--radix-popover-content-transform-origin);
28
+ width: min(22rem, calc(100dvw - var(--inkra-space-6)));
29
+ z-index: calc(var(--inkra-z-modal, 30) + var(--inkra-floating-layer-offset, 0) + 1);
30
+ }
31
+
32
+ .inkra-guide__content:focus-visible {
33
+ outline: var(--inkra-focus-width) solid var(--inkra-focus);
34
+ outline-offset: var(--inkra-focus-offset);
35
+ }
36
+
37
+ .inkra-guide__skip-layer {
38
+ inset: 0;
39
+ pointer-events: none;
40
+ position: fixed;
41
+ z-index: calc(var(--inkra-z-modal, 30) + var(--inkra-floating-layer-offset, 0) + 2);
42
+ }
43
+
44
+ .inkra-guide__skip {
45
+ color: var(--inkra-ink-muted);
46
+ opacity: .72;
47
+ pointer-events: auto;
48
+ position: absolute;
49
+ }
50
+
51
+ .inkra-guide__skip:is(:hover, :focus-visible) {
52
+ color: var(--inkra-ink);
53
+ opacity: 1;
54
+ }
55
+
56
+ .inkra-guide__skip[data-placement^="top"] {
57
+ inset-block-start: max(var(--inkra-space-4), env(safe-area-inset-top));
58
+ }
59
+
60
+ .inkra-guide__skip[data-placement^="bottom"] {
61
+ inset-block-end: max(var(--inkra-space-4), env(safe-area-inset-bottom));
62
+ }
63
+
64
+ .inkra-guide__skip[data-placement$="start"] {
65
+ inset-inline-start: max(var(--inkra-space-4), env(safe-area-inset-left));
66
+ }
67
+
68
+ .inkra-guide__skip[data-placement$="end"] {
69
+ inset-inline-end: max(var(--inkra-space-4), env(safe-area-inset-right));
70
+ }
71
+
72
+ .inkra-guide__surface {
73
+ color: var(--inkra-ink);
74
+ display: block;
75
+ max-height: inherit;
76
+ width: 100%;
77
+ }
78
+
79
+ .inkra-guide__panel {
80
+ box-sizing: border-box;
81
+ display: grid;
82
+ gap: var(--inkra-space-4);
83
+ max-height: inherit;
84
+ overflow: auto;
85
+ overflow-wrap: anywhere;
86
+ overscroll-behavior: contain;
87
+ padding: var(--inkra-space-5);
88
+ }
89
+
90
+ .inkra-guide__header,
91
+ .inkra-guide__footer,
92
+ .inkra-guide__navigation {
93
+ align-items: center;
94
+ display: flex;
95
+ gap: var(--inkra-space-2);
96
+ }
97
+
98
+ .inkra-guide__header {
99
+ align-items: flex-start;
100
+ }
101
+
102
+ .inkra-guide__heading {
103
+ display: grid;
104
+ flex: 1;
105
+ gap: var(--inkra-space-1);
106
+ min-width: 0;
107
+ }
108
+
109
+ .inkra-guide__progress {
110
+ color: var(--inkra-ink-subtle);
111
+ font-family: var(--inkra-font-body);
112
+ font-size: var(--inkra-text-sm);
113
+ line-height: var(--inkra-line-ui);
114
+ }
115
+
116
+ .inkra-guide__title {
117
+ font-family: var(--inkra-font-display);
118
+ font-size: var(--inkra-text-xl);
119
+ font-weight: var(--inkra-weight-regular);
120
+ line-height: var(--inkra-line-ui);
121
+ margin: 0;
122
+ }
123
+
124
+ .inkra-guide__description {
125
+ color: var(--inkra-ink-muted);
126
+ font-family: var(--inkra-font-body);
127
+ font-size: var(--inkra-text-md);
128
+ line-height: var(--inkra-line-body);
129
+ }
130
+
131
+ .inkra-guide__close {
132
+ flex: none;
133
+ }
134
+
135
+ .inkra-guide__footer {
136
+ flex-wrap: wrap;
137
+ justify-content: space-between;
138
+ }
139
+
140
+ .inkra-guide__footer > :empty {
141
+ display: none;
142
+ }
143
+
144
+ .inkra-guide__navigation {
145
+ flex-wrap: wrap;
146
+ justify-content: flex-end;
147
+ margin-inline-start: auto;
148
+ }
149
+
150
+ .inkra-guide__footer .inkra-button {
151
+ max-width: 100%;
152
+ }
153
+
154
+ @media (max-width: 30rem) {
155
+ .inkra-guide__content {
156
+ max-height: calc(100dvh - var(--inkra-space-4));
157
+ max-width: calc(100dvw - var(--inkra-space-4));
158
+ width: calc(100dvw - var(--inkra-space-4));
159
+ }
160
+
161
+ .inkra-guide__panel {
162
+ padding: var(--inkra-space-4);
163
+ }
164
+
165
+ .inkra-guide__footer,
166
+ .inkra-guide__navigation {
167
+ width: 100%;
168
+ }
169
+
170
+ .inkra-guide__footer > .inkra-button,
171
+ .inkra-guide__navigation > .inkra-button {
172
+ flex: 1 1 auto;
173
+ }
174
+ }
175
+
176
+ @media (prefers-reduced-motion: reduce) {
177
+ .inkra-guide__content {
178
+ animation: none !important;
179
+ }
180
+ }
181
+
182
+ @media (forced-colors: active) {
183
+ .inkra-guide__backdrop {
184
+ background: CanvasText;
185
+ opacity: 0.25;
186
+ }
187
+
188
+ .inkra-guide__spotlight {
189
+ box-shadow: 0 0 0 100vmax color-mix(in srgb, CanvasText 25%, transparent);
190
+ outline: 2px solid Highlight;
191
+ }
192
+
193
+ .inkra-guide__surface {
194
+ background: Canvas;
195
+ border: 1px solid CanvasText;
196
+ }
197
+
198
+ .inkra-guide__skip {
199
+ color: ButtonText;
200
+ opacity: 1;
201
+ }
202
+ }
@@ -79,6 +79,7 @@
79
79
  }
80
80
 
81
81
  .inkra-image-viewer__body {
82
+ position: relative;
82
83
  display: grid;
83
84
  flex: 1 1 auto;
84
85
  min-block-size: var(--inkra-touch-target);
@@ -226,6 +227,24 @@
226
227
  padding: var(--inkra-focus-offset);
227
228
  position: relative;
228
229
  scrollbar-gutter: auto;
230
+ pointer-events: auto;
231
+ }
232
+
233
+ .inkra-image-viewer__floating-tools {
234
+ position: absolute;
235
+ inset-inline: max(var(--inkra-space-3), env(safe-area-inset-left)) max(var(--inkra-space-3), env(safe-area-inset-right));
236
+ inset-block-start: max(var(--inkra-space-3), env(safe-area-inset-top));
237
+ display: flex;
238
+ justify-content: center;
239
+ padding-inline: var(--inkra-touch-target);
240
+ pointer-events: none;
241
+ z-index: 6;
242
+ }
243
+
244
+ .inkra-image-viewer__floating-tools[data-position="bottom"] {
245
+ inset-block-start: auto;
246
+ inset-block-end: max(var(--inkra-space-3), env(safe-area-inset-bottom));
247
+ padding-inline: 0;
229
248
  }
230
249
 
231
250
  .inkra-image-viewer__video-navigation {
@@ -345,6 +364,7 @@
345
364
  @media print {
346
365
  .inkra-image-viewer__close-control,
347
366
  .inkra-image-viewer__navigation,
367
+ .inkra-image-viewer__floating-tools,
348
368
  .inkra-image-viewer__bottom { display: none; }
349
369
  }
350
370
 
@@ -0,0 +1,171 @@
1
+ .inkra-infinite-scroll {
2
+ --inkra-infinite-scroll-pull-distance: 0px;
3
+ --inkra-infinite-scroll-indicator-inline-offset: 0px;
4
+ --inkra-infinite-scroll-indicator-block-offset: 0px;
5
+ --inkra-infinite-scroll-indicator-viewport-inline-size: 100%;
6
+ --inkra-infinite-scroll-indicator-viewport-block-size: 100%;
7
+ position: relative;
8
+ display: grid;
9
+ grid-template-rows: auto minmax(min-content, 1fr) auto;
10
+ max-inline-size: 100%;
11
+ min-block-size: 0;
12
+ min-inline-size: 0;
13
+ overflow: auto;
14
+ overscroll-behavior: contain;
15
+ color: var(--inkra-ink);
16
+ }
17
+
18
+ .inkra-infinite-scroll__body {
19
+ display: grid;
20
+ grid-template-columns: auto minmax(min-content, 1fr) auto;
21
+ grid-row: 2;
22
+ min-block-size: 0;
23
+ min-inline-size: 0;
24
+ transform: translate3d(0, 0, 0);
25
+ transition: transform var(--inkra-motion-slow) cubic-bezier(0.22, 1.45, 0.36, 1);
26
+ }
27
+
28
+ .inkra-infinite-scroll[data-pull-direction] > .inkra-infinite-scroll__body {
29
+ transition: none;
30
+ }
31
+
32
+ .inkra-infinite-scroll__content {
33
+ grid-column: 2;
34
+ grid-row: 1;
35
+ min-block-size: 100%;
36
+ min-inline-size: 100%;
37
+ }
38
+
39
+ .inkra-infinite-scroll[data-pull-direction="top"] > .inkra-infinite-scroll__body {
40
+ transform: translate3d(0, var(--inkra-infinite-scroll-pull-distance), 0);
41
+ }
42
+ .inkra-infinite-scroll[data-pull-direction="bottom"] > .inkra-infinite-scroll__body {
43
+ transform: translate3d(0, calc(-1 * var(--inkra-infinite-scroll-pull-distance)), 0);
44
+ }
45
+ .inkra-infinite-scroll[data-pull-direction="left"] > .inkra-infinite-scroll__body {
46
+ transform: translate3d(var(--inkra-infinite-scroll-pull-distance), 0, 0);
47
+ }
48
+ .inkra-infinite-scroll[data-pull-direction="right"] > .inkra-infinite-scroll__body {
49
+ transform: translate3d(calc(-1 * var(--inkra-infinite-scroll-pull-distance)), 0, 0);
50
+ }
51
+
52
+ .inkra-infinite-scroll__indicator {
53
+ position: relative;
54
+ display: none;
55
+ min-block-size: 0;
56
+ min-inline-size: 0;
57
+ z-index: 1;
58
+ }
59
+ .inkra-infinite-scroll__indicator[data-active] { display: flex; }
60
+ .inkra-infinite-scroll__indicator[data-direction="top"],
61
+ .inkra-infinite-scroll__indicator[data-direction="bottom"] {
62
+ inline-size: 100%;
63
+ }
64
+ .inkra-infinite-scroll__indicator[data-visible][data-direction="top"],
65
+ .inkra-infinite-scroll__indicator[data-visible][data-direction="bottom"] {
66
+ min-block-size: 52px;
67
+ }
68
+ .inkra-infinite-scroll__indicator[data-direction="left"],
69
+ .inkra-infinite-scroll__indicator[data-direction="right"] {
70
+ align-self: stretch;
71
+ grid-row: 1;
72
+ }
73
+ .inkra-infinite-scroll__indicator[data-visible][data-direction="left"],
74
+ .inkra-infinite-scroll__indicator[data-visible][data-direction="right"] {
75
+ min-inline-size: 112px;
76
+ }
77
+ .inkra-infinite-scroll__indicator[data-direction="top"] { grid-row: 1; }
78
+ .inkra-infinite-scroll__indicator[data-direction="bottom"] { grid-row: 3; }
79
+ .inkra-infinite-scroll__indicator[data-direction="left"] { grid-column: 1; }
80
+ .inkra-infinite-scroll__indicator[data-direction="right"] { grid-column: 3; }
81
+
82
+ /* Pull feedback reveals inside the elastic gap, without changing scroll layout. */
83
+ .inkra-infinite-scroll__indicator[data-pulling] {
84
+ position: absolute;
85
+ grid-area: auto;
86
+ left: var(--inkra-pull-left, 0px);
87
+ top: var(--inkra-pull-top, 0px);
88
+ inline-size: var(--inkra-pull-width, 0px);
89
+ block-size: var(--inkra-pull-height, 0px);
90
+ min-inline-size: 0 !important;
91
+ min-block-size: 0 !important;
92
+ overflow: clip;
93
+ pointer-events: none;
94
+ }
95
+ .inkra-infinite-scroll__indicator[data-pulling] .inkra-infinite-scroll__indicator-surface {
96
+ padding: 0;
97
+ max-inline-size: none;
98
+ min-block-size: 0;
99
+ }
100
+ .inkra-infinite-scroll__indicator[data-pulling][data-direction="left"] .inkra-infinite-scroll__loading-label,
101
+ .inkra-infinite-scroll__indicator[data-pulling][data-direction="right"] .inkra-infinite-scroll__loading-label {
102
+ display: none;
103
+ }
104
+
105
+ .inkra-infinite-scroll__indicator-surface {
106
+ display: flex;
107
+ align-items: center;
108
+ justify-content: center;
109
+ inline-size: 100%;
110
+ min-inline-size: 0;
111
+ padding: var(--inkra-space-1) var(--inkra-space-2);
112
+ }
113
+ .inkra-infinite-scroll__indicator[data-direction="left"] .inkra-infinite-scroll__indicator-surface,
114
+ .inkra-infinite-scroll__indicator[data-direction="right"] .inkra-infinite-scroll__indicator-surface {
115
+ min-block-size: 100%;
116
+ max-inline-size: 144px;
117
+ }
118
+ .inkra-infinite-scroll__indicator[data-status="loading"][data-direction="top"] .inkra-infinite-scroll__indicator-surface {
119
+ inline-size: var(--inkra-infinite-scroll-indicator-viewport-inline-size);
120
+ transform: translate3d(var(--inkra-infinite-scroll-indicator-inline-offset), 0, 0);
121
+ }
122
+ .inkra-infinite-scroll__indicator[data-status="loading"][data-direction="bottom"] .inkra-infinite-scroll__indicator-surface {
123
+ inline-size: var(--inkra-infinite-scroll-indicator-viewport-inline-size);
124
+ transform: translate3d(var(--inkra-infinite-scroll-indicator-inline-offset), 0, 0);
125
+ }
126
+ .inkra-infinite-scroll__indicator[data-status="loading"][data-direction="left"] .inkra-infinite-scroll__indicator-surface {
127
+ block-size: var(--inkra-infinite-scroll-indicator-viewport-block-size);
128
+ min-block-size: 0;
129
+ transform: translate3d(0, var(--inkra-infinite-scroll-indicator-block-offset), 0);
130
+ }
131
+ .inkra-infinite-scroll__indicator[data-status="loading"][data-direction="right"] .inkra-infinite-scroll__indicator-surface {
132
+ block-size: var(--inkra-infinite-scroll-indicator-viewport-block-size);
133
+ min-block-size: 0;
134
+ transform: translate3d(0, var(--inkra-infinite-scroll-indicator-block-offset), 0);
135
+ }
136
+ .inkra-infinite-scroll__indicator-content {
137
+ display: flex;
138
+ align-items: center;
139
+ justify-content: center;
140
+ flex-wrap: wrap;
141
+ gap: var(--inkra-space-2);
142
+ inline-size: 100%;
143
+ min-inline-size: 0;
144
+ overflow-wrap: anywhere;
145
+ text-align: center;
146
+ }
147
+ .inkra-infinite-scroll__indicator-content > .inkra-progress { max-inline-size: 240px; }
148
+ .inkra-infinite-scroll__indicator[data-direction="left"] .inkra-infinite-scroll__indicator-content > .inkra-progress,
149
+ .inkra-infinite-scroll__indicator[data-direction="right"] .inkra-infinite-scroll__indicator-content > .inkra-progress { min-inline-size: 96px; }
150
+ .inkra-infinite-scroll__loading {
151
+ display: flex;
152
+ align-items: center;
153
+ justify-content: center;
154
+ gap: var(--inkra-space-2);
155
+ min-inline-size: 0;
156
+ }
157
+ .inkra-infinite-scroll__loading > .inkra-progress { flex: 0 0 auto; }
158
+ .inkra-infinite-scroll__loading-label { min-inline-size: 0; white-space: nowrap; }
159
+ .inkra-infinite-scroll__indicator[data-direction="left"] .inkra-infinite-scroll__loading,
160
+ .inkra-infinite-scroll__indicator[data-direction="right"] .inkra-infinite-scroll__loading { flex-wrap: wrap; }
161
+ .inkra-infinite-scroll__indicator[data-direction="left"] .inkra-infinite-scroll__loading-label,
162
+ .inkra-infinite-scroll__indicator[data-direction="right"] .inkra-infinite-scroll__loading-label { white-space: normal; }
163
+
164
+ @media (prefers-reduced-motion: reduce) {
165
+ .inkra-infinite-scroll__body { transition-duration: 0ms; }
166
+ }
167
+
168
+ @media print {
169
+ .inkra-infinite-scroll { overflow: visible; }
170
+ .inkra-infinite-scroll__body { transform: none !important; }
171
+ }