@ntmats/hc-ui 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 +30 -10
- package/dist/index.d.ts +974 -10
- package/dist/index.js +1859 -6
- package/dist/styles.css +1277 -55
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ButtonHTMLAttributes, type HTMLAttributes, type InputHTMLAttributes, type KeyboardEvent, type MouseEvent, type ReactElement, type ReactNode, type Ref } from 'react';
|
|
1
|
+
import { type ButtonHTMLAttributes, type Dispatch, type HTMLAttributes, type InputHTMLAttributes, type KeyboardEvent, type MouseEvent, type ReactElement, type ReactNode, type Ref, type SetStateAction } from 'react';
|
|
2
2
|
export type SurfaceTone = 'page' | 'main' | 'content' | 'elevated' | 'overlay' | 'card';
|
|
3
3
|
export interface SurfaceProps extends HTMLAttributes<HTMLDivElement> {
|
|
4
4
|
tone?: SurfaceTone;
|
|
@@ -46,6 +46,11 @@ export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
|
|
46
46
|
fullWidth?: boolean;
|
|
47
47
|
/** Busy state: keeps focus and label, shows a spinner and ignores clicks. */
|
|
48
48
|
loading?: boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Opt-in: a long label wraps onto more lines (the button grows taller) instead of
|
|
51
|
+
* being cut with an ellipsis, which stays the default.
|
|
52
|
+
*/
|
|
53
|
+
wrap?: boolean;
|
|
49
54
|
}
|
|
50
55
|
/** Pill button. The caller supplies the label text and handles the action. */
|
|
51
56
|
export declare const Button: import("react").ForwardRefExoticComponent<ButtonProps & import("react").RefAttributes<HTMLButtonElement>>;
|
|
@@ -298,15 +303,12 @@ interface SheetChoiceProps<T extends string> extends Omit<HTMLAttributes<HTMLDiv
|
|
|
298
303
|
}
|
|
299
304
|
export interface SheetOptionsProps<T extends string> extends SheetChoiceProps<T> {
|
|
300
305
|
options: readonly SheetOption<T>[];
|
|
301
|
-
/**
|
|
302
|
-
* `card` (default): rows on a `bg-muted-2` card, as HCUI's SheetOptions. `list`: plain
|
|
303
|
-
* 48px rows divided by a line, the chosen one bolder with a 20px check in the text color,
|
|
304
|
-
* as the list of HCUI's mobile sort sheet (`SortButton`).
|
|
305
|
-
*/
|
|
306
|
-
variant?: 'card' | 'list';
|
|
307
306
|
}
|
|
308
|
-
/**
|
|
309
|
-
|
|
307
|
+
/**
|
|
308
|
+
* Single-choice list of rows with a check on a `bg-muted-2` card (HCUI's SheetOptions),
|
|
309
|
+
* for inside a sheet. It is also the phone's sort list (decision of 2026-11-07).
|
|
310
|
+
*/
|
|
311
|
+
export declare function SheetOptions<T extends string>({ label, options, value, onChange, className, ...props }: SheetOptionsProps<T>): import("react/jsx-runtime").JSX.Element;
|
|
310
312
|
export interface SheetChipsProps<T extends string> extends SheetChoiceProps<T> {
|
|
311
313
|
options: readonly {
|
|
312
314
|
value: T;
|
|
@@ -442,7 +444,7 @@ export interface SortMenuProps<T extends string> extends Omit<MenuProps, 'label'
|
|
|
442
444
|
/**
|
|
443
445
|
* Sort control: a `Chip` showing the current choice that opens a `Menu` with a
|
|
444
446
|
* `MenuRadioGroup` (HCUI's `DesktopSortMenu`). Options and value come from the caller.
|
|
445
|
-
* On phones, compose `Sheet` with `SheetOptions` behind an `IconButton` instead.
|
|
447
|
+
* On phones, compose `Sheet` with `SheetOptions` (its card look) behind an `IconButton` instead.
|
|
446
448
|
*/
|
|
447
449
|
export declare function SortMenu<T extends string>({ label, value, options, onChange, applied, size, triggerClassName, ...props }: SortMenuProps<T>): import("react/jsx-runtime").JSX.Element;
|
|
448
450
|
export interface PopoverProps extends PopupBaseProps, Omit<HTMLAttributes<HTMLDivElement>, 'role' | 'children'> {
|
|
@@ -639,4 +641,966 @@ export interface CommandPaletteProps extends Omit<HTMLAttributes<HTMLDialogEleme
|
|
|
639
641
|
* item under it. Texts, results and actions come from the caller.
|
|
640
642
|
*/
|
|
641
643
|
export declare const CommandPalette: import("react").ForwardRefExoticComponent<CommandPaletteProps & import("react").RefAttributes<HTMLDialogElement>>;
|
|
644
|
+
/**
|
|
645
|
+
* Walks a list of image addresses: starts at the first and moves to the next on
|
|
646
|
+
* each load error. Starts over when the list changes. `src` is `undefined` once
|
|
647
|
+
* every address failed.
|
|
648
|
+
*/
|
|
649
|
+
export declare function useImageChain(sources: readonly string[]): {
|
|
650
|
+
src: string | undefined;
|
|
651
|
+
index: number;
|
|
652
|
+
/** Call from `onError`: moves to the next address. */
|
|
653
|
+
next: () => void;
|
|
654
|
+
};
|
|
655
|
+
/** Rows of the mini dial, top to bottom. */
|
|
656
|
+
export declare const MINI_DIAL_STATS: readonly ["speed", "attack", "defense", "damage"];
|
|
657
|
+
export type MiniDialStat = (typeof MINI_DIAL_STATS)[number];
|
|
658
|
+
/** One resolved cell: the power color (or none), whether it is round and whether it is a special power. */
|
|
659
|
+
export interface MiniDialCell {
|
|
660
|
+
/** Standard power color. Without it the cell is the dial paper (no power). */
|
|
661
|
+
color?: string;
|
|
662
|
+
/** Round power (e.g. WWE). */
|
|
663
|
+
round?: boolean;
|
|
664
|
+
/** Special power: paper with a dark outline. */
|
|
665
|
+
special?: boolean;
|
|
666
|
+
}
|
|
667
|
+
/** One click of the dial, with its 4 cells. */
|
|
668
|
+
export interface MiniDialColumn {
|
|
669
|
+
/** Click number; also decides what has passed relative to `currentClick`. */
|
|
670
|
+
click: number;
|
|
671
|
+
cells: Record<MiniDialStat, MiniDialCell>;
|
|
672
|
+
}
|
|
673
|
+
export interface MiniDialViewProps {
|
|
674
|
+
columns: readonly MiniDialColumn[];
|
|
675
|
+
/** In play: the current click gets an outline and earlier ones fade. */
|
|
676
|
+
currentClick?: number;
|
|
677
|
+
/** Accessible label, already localized (e.g. "Dial: 6 clicks"). */
|
|
678
|
+
label: string;
|
|
679
|
+
className?: string;
|
|
680
|
+
}
|
|
681
|
+
/**
|
|
682
|
+
* The dial summarized in small squares: one column per click, one row per value
|
|
683
|
+
* (speed, attack, defense, damage). The cell size is `--hc-mini-dial-cell` (7px).
|
|
684
|
+
*/
|
|
685
|
+
export declare const MiniDialView: import("react").ForwardRefExoticComponent<MiniDialViewProps & import("react").RefAttributes<HTMLSpanElement>>;
|
|
686
|
+
export interface PowerIconViewProps {
|
|
687
|
+
/** Power color (the circle's fill). */
|
|
688
|
+
color: string;
|
|
689
|
+
/** Address of the value symbol (boot, fist, shield, starburst), already built. */
|
|
690
|
+
glyphSrc: string;
|
|
691
|
+
/** The symbol is black; `true` turns it white, for contrast on dark colors. */
|
|
692
|
+
invertGlyph?: boolean;
|
|
693
|
+
size?: number;
|
|
694
|
+
}
|
|
695
|
+
/** Power icon: a circle in the power color with the value symbol on top. */
|
|
696
|
+
export declare const PowerIconView: import("react").ForwardRefExoticComponent<PowerIconViewProps & import("react").RefAttributes<HTMLSpanElement>>;
|
|
697
|
+
export interface StatGlyphViewProps {
|
|
698
|
+
/** Address of the symbol, already built. */
|
|
699
|
+
src: string;
|
|
700
|
+
/** Alternative text, as the caller wants it read. */
|
|
701
|
+
alt: string;
|
|
702
|
+
size?: number;
|
|
703
|
+
}
|
|
704
|
+
/** The value symbol alone (no circle), in neutral gray. Used in the dial legend. */
|
|
705
|
+
export declare const StatGlyphView: import("react").ForwardRefExoticComponent<StatGlyphViewProps & import("react").RefAttributes<HTMLImageElement>>;
|
|
706
|
+
export interface SpecialIconViewProps {
|
|
707
|
+
/** Round white background (the image server's `sp/white.svg`), already built. */
|
|
708
|
+
backgroundSrc: string;
|
|
709
|
+
/** Symbol over the background (the trait star or a value symbol), already built. */
|
|
710
|
+
glyphSrc: string;
|
|
711
|
+
size?: number;
|
|
712
|
+
}
|
|
713
|
+
/** Special power and trait icon: a white circle with a symbol in the middle. */
|
|
714
|
+
export declare const SpecialIconView: import("react").ForwardRefExoticComponent<SpecialIconViewProps & import("react").RefAttributes<HTMLSpanElement>>;
|
|
715
|
+
export interface EquipmentIconProps {
|
|
716
|
+
size?: number;
|
|
717
|
+
}
|
|
718
|
+
/** Equipment icon: a white diamond on a purple circle. Decorative. */
|
|
719
|
+
export declare function EquipmentIcon({ size }: EquipmentIconProps): import("react/jsx-runtime").JSX.Element;
|
|
720
|
+
export type StatIconType = 'speed' | 'attack' | 'defense' | 'damage';
|
|
721
|
+
export interface StatIconProps {
|
|
722
|
+
type: StatIconType;
|
|
723
|
+
size?: number;
|
|
724
|
+
className?: string;
|
|
725
|
+
}
|
|
726
|
+
/** Arrow, shield or diamond drawn in SVG in the text color (`currentColor`); not the official symbols. */
|
|
727
|
+
export declare function StatIcon({ type, size, className }: StatIconProps): import("react/jsx-runtime").JSX.Element;
|
|
728
|
+
/**
|
|
729
|
+
* Avatar shape:
|
|
730
|
+
* - `round`: the default (the caller's class crops it);
|
|
731
|
+
* - `card`: one-shot, a landscape card (width ÷ 1.42, proportional corners);
|
|
732
|
+
* - `square`: maps, a square with slightly rounded corners;
|
|
733
|
+
* - `square-fit`: terrain marker, a square with the whole image fitted in.
|
|
734
|
+
*/
|
|
735
|
+
export type AvatarShape = 'round' | 'card' | 'square' | 'square-fit';
|
|
736
|
+
export interface AvatarViewProps {
|
|
737
|
+
/** Image addresses, in the order to try (e.g. a thumbnail, then the original). */
|
|
738
|
+
sources: readonly string[];
|
|
739
|
+
/** Image drawn when there is no address or all of them failed. */
|
|
740
|
+
fallbackSrc: string;
|
|
741
|
+
/** Alternative text, in the app's language. */
|
|
742
|
+
alt: string;
|
|
743
|
+
shape?: AvatarShape;
|
|
744
|
+
className?: string;
|
|
745
|
+
/** Reports that every attempt failed and the fallback is showing. */
|
|
746
|
+
onFallback?: () => void;
|
|
747
|
+
/** Width and height in px (the height follows the shape for `card`). Without it the caller's class sizes it. */
|
|
748
|
+
size?: number;
|
|
749
|
+
}
|
|
750
|
+
/**
|
|
751
|
+
* The piece's portrait. Tries each address in order and, when none loads, shows
|
|
752
|
+
* `fallbackSrc` (with `data-fallback` and the muted fill). The image cannot be
|
|
753
|
+
* dragged, selected or opened in a context menu.
|
|
754
|
+
*/
|
|
755
|
+
export declare const AvatarView: import("react").ForwardRefExoticComponent<AvatarViewProps & import("react").RefAttributes<HTMLImageElement>>;
|
|
756
|
+
export interface SetIconViewProps {
|
|
757
|
+
/** Addresses of the set icon, in the order to try. Empty: renders nothing. */
|
|
758
|
+
sources: readonly string[];
|
|
759
|
+
className?: string;
|
|
760
|
+
/** Tooltip (e.g. the set name), in the app's language. */
|
|
761
|
+
title?: string;
|
|
762
|
+
}
|
|
763
|
+
/** Set icon. Tries each address in order and disappears when none loads. Decorative: the set name is next to it. */
|
|
764
|
+
export declare const SetIconView: import("react").ForwardRefExoticComponent<SetIconViewProps & import("react").RefAttributes<HTMLImageElement>>;
|
|
765
|
+
/** Value rows of the dial, top to bottom (the top row is the click number). */
|
|
766
|
+
export declare const COMBAT_DIAL_STATS: readonly ["speed", "attack", "defense", "damage"];
|
|
767
|
+
export type CombatDialStat = (typeof COMBAT_DIAL_STATS)[number];
|
|
768
|
+
/** Content of a power's tip, already in the app's language. */
|
|
769
|
+
export interface CombatDialCellTip {
|
|
770
|
+
title: string;
|
|
771
|
+
description: string;
|
|
772
|
+
/** Standard power color; none for a special power. */
|
|
773
|
+
accent?: string;
|
|
774
|
+
/** The color mark is a circle (round power, e.g. WWE). */
|
|
775
|
+
accentRound?: boolean;
|
|
776
|
+
}
|
|
777
|
+
/** One resolved value cell. */
|
|
778
|
+
export interface CombatDialCell {
|
|
779
|
+
value: number;
|
|
780
|
+
/** Accessible label, already localized (e.g. "Click 1: Speed 8 — Charge"). */
|
|
781
|
+
label: string;
|
|
782
|
+
/** Cell fill: the power color, or the dial paper (`var(--hc-color-dial-paper)`) for no power. */
|
|
783
|
+
background: string;
|
|
784
|
+
/** Number color, chosen for contrast with the fill. */
|
|
785
|
+
color: string;
|
|
786
|
+
/** Round power (e.g. WWE). */
|
|
787
|
+
round?: boolean;
|
|
788
|
+
/** Special power: dark outline. */
|
|
789
|
+
outlined?: boolean;
|
|
790
|
+
/** Without a tip the cell opens nothing; on a static dial it is not even a button. */
|
|
791
|
+
tip?: CombatDialCellTip;
|
|
792
|
+
}
|
|
793
|
+
/** One click of the dial. */
|
|
794
|
+
export interface CombatDialColumn {
|
|
795
|
+
click: number;
|
|
796
|
+
/** Group label (e.g. "Click 3"). */
|
|
797
|
+
label: string;
|
|
798
|
+
/** Label of the number button when the dial changes the click (e.g. "Select click 3"). */
|
|
799
|
+
selectLabel: string;
|
|
800
|
+
/** Outside the selected point value: faded. */
|
|
801
|
+
dimmed?: boolean;
|
|
802
|
+
/** Starting line of a point value: color of the bar left of the column. */
|
|
803
|
+
startColor?: string;
|
|
804
|
+
cells: Record<CombatDialStat, CombatDialCell>;
|
|
805
|
+
}
|
|
806
|
+
/** One legend symbol, with its address already built. */
|
|
807
|
+
export interface CombatDialLegendIcon {
|
|
808
|
+
src: string;
|
|
809
|
+
alt: string;
|
|
810
|
+
title?: string;
|
|
811
|
+
/** `true`: the neutral value symbol (`StatGlyphView`); otherwise the piece's combat symbol. */
|
|
812
|
+
glyph?: boolean;
|
|
813
|
+
}
|
|
814
|
+
export interface CombatDialLegend {
|
|
815
|
+
range: number;
|
|
816
|
+
targets: number;
|
|
817
|
+
/** Address of the target bolt. */
|
|
818
|
+
boltSrc: string;
|
|
819
|
+
icons: Record<CombatDialStat, CombatDialLegendIcon>;
|
|
820
|
+
}
|
|
821
|
+
/** One point value, with the chip texts ready. */
|
|
822
|
+
export interface CombatDialPoint {
|
|
823
|
+
index: number;
|
|
824
|
+
startClick: number;
|
|
825
|
+
color: string;
|
|
826
|
+
/** E.g. "100 pts". Without it the chip shows only the clicks; with none at all the row is hidden. */
|
|
827
|
+
valueLabel?: ReactNode;
|
|
828
|
+
/** E.g. "6 clicks". */
|
|
829
|
+
clicksLabel: ReactNode;
|
|
830
|
+
/** Native tooltip of the chip, shown only when there is more than one point value. */
|
|
831
|
+
title?: string;
|
|
832
|
+
}
|
|
833
|
+
export interface CombatDialLabels {
|
|
834
|
+
/** Header title (e.g. "Dial"). */
|
|
835
|
+
heading: string;
|
|
836
|
+
/** KO mark in the header (e.g. "K.O"). */
|
|
837
|
+
koMark: string;
|
|
838
|
+
/** What follows the KO mark (e.g. "at 7"), already localized. A node, so the number can stay in its own text node. */
|
|
839
|
+
koAt: ReactNode;
|
|
840
|
+
/** Text of the KO column cells (e.g. "KO"). */
|
|
841
|
+
koCell: string;
|
|
842
|
+
}
|
|
843
|
+
/** The open tip: which cell, how (passive hover or the touch dialog), where and its content. */
|
|
844
|
+
export interface CombatDialTipState {
|
|
845
|
+
key: string;
|
|
846
|
+
click: number;
|
|
847
|
+
stat: CombatDialStat;
|
|
848
|
+
content: CombatDialCellTip;
|
|
849
|
+
mode: 'hover' | 'dialog';
|
|
850
|
+
anchor: {
|
|
851
|
+
x: number;
|
|
852
|
+
y: number;
|
|
853
|
+
preferAbove?: boolean;
|
|
854
|
+
};
|
|
855
|
+
trigger: HTMLElement;
|
|
856
|
+
}
|
|
857
|
+
export interface CombatDialViewProps {
|
|
858
|
+
columns: readonly CombatDialColumn[];
|
|
859
|
+
legend: CombatDialLegend;
|
|
860
|
+
points: readonly CombatDialPoint[];
|
|
861
|
+
/** `index` of the selected point value; -1 for none. */
|
|
862
|
+
selectedPointIndex: number;
|
|
863
|
+
/** Click centered in the track when the point value changes (its starting line). */
|
|
864
|
+
focusClick?: number;
|
|
865
|
+
currentClick?: number;
|
|
866
|
+
labels: CombatDialLabels;
|
|
867
|
+
/** `printed`: the physical card's dial, without the header, in rows of 12 clicks. */
|
|
868
|
+
presentation?: 'default' | 'printed';
|
|
869
|
+
/**
|
|
870
|
+
* `comfortable` (default): 40px cells. `compact`: 34px cells, 6px gaps, 13px numbers and
|
|
871
|
+
* tighter point chips, for dense lists (HCUI's compact dial of the unit view on phones).
|
|
872
|
+
* The printed presentation keeps its own measures.
|
|
873
|
+
*/
|
|
874
|
+
density?: 'comfortable' | 'compact';
|
|
875
|
+
/** Without it the dial is static: it neither marks nor changes the click; only cells with a tip react. */
|
|
876
|
+
onClickChange?: (click: number) => void;
|
|
877
|
+
onSelectPoint: (point: CombatDialPoint) => void;
|
|
878
|
+
/** The open tip, controlled by the caller (which closes it when the piece changes). */
|
|
879
|
+
tip: CombatDialTipState | null;
|
|
880
|
+
onTipChange: Dispatch<SetStateAction<CombatDialTipState | null>>;
|
|
881
|
+
/** `id` of the tip element, for the hover's `aria-describedby`. */
|
|
882
|
+
tipId: string;
|
|
883
|
+
/** The tip drawn by the caller (callout, dialog), shown while `tip` is set. */
|
|
884
|
+
tipSlot?: ReactNode;
|
|
885
|
+
}
|
|
886
|
+
/**
|
|
887
|
+
* Combat dial: the legend, one column per click, the KO column and, outside the
|
|
888
|
+
* printed card, the header with the point values. Hover, touch and keyboard on
|
|
889
|
+
* the cells open the tip through `onTipChange`; the caller draws the tip.
|
|
890
|
+
*/
|
|
891
|
+
export declare const CombatDialView: import("react").ForwardRefExoticComponent<CombatDialViewProps & import("react").RefAttributes<HTMLElement>>;
|
|
892
|
+
export declare const PHYSICAL_CARD_WIDTH = 375;
|
|
893
|
+
export declare const PHYSICAL_CARD_HEIGHT = 525;
|
|
894
|
+
export declare const PHYSICAL_CARD_BACK_TABLE_HEIGHT = 440;
|
|
895
|
+
/** Position of each value of the bystander's dial: every row goes 24px down and 7px right. */
|
|
896
|
+
export declare const PHYSICAL_CARD_BYSTANDER_DIAL_STEP: {
|
|
897
|
+
x: number;
|
|
898
|
+
y: number;
|
|
899
|
+
};
|
|
900
|
+
/**
|
|
901
|
+
* Splits the text blocks between the front (table 0) and the backs (1, 2, …), as
|
|
902
|
+
* HCUnits: a block that does not fit moves to the next column. The front may end
|
|
903
|
+
* up empty (the portrait and keywords already take room); a back column always
|
|
904
|
+
* keeps at least one block, even if it overflows.
|
|
905
|
+
*
|
|
906
|
+
* `overflowing(table)` tells whether that column overflows with the current split.
|
|
907
|
+
* Returns the next split, or `null` once it is stable.
|
|
908
|
+
*/
|
|
909
|
+
export declare function nextPhysicalCardDistribution(pages: number[], overflowing: (table: number) => boolean): number[] | null;
|
|
910
|
+
/** How many backs the split needs (each back has two columns). There is always one. */
|
|
911
|
+
export declare function physicalCardBackCount(pages: number[]): number;
|
|
912
|
+
/** Cursor position on the card (0 to 1) → tilt, parallax (-1 to 1) and shine (%). */
|
|
913
|
+
export declare function physicalCardTiltAt(x: number, y: number, damping?: number): {
|
|
914
|
+
rotateX: number;
|
|
915
|
+
rotateY: number;
|
|
916
|
+
parallaxX: number;
|
|
917
|
+
parallaxY: number;
|
|
918
|
+
shineX: number;
|
|
919
|
+
shineY: number;
|
|
920
|
+
};
|
|
921
|
+
type CardTilt = ReturnType<typeof physicalCardTiltAt>;
|
|
922
|
+
/** The finished values each moving part of the card takes, ready for `style`. */
|
|
923
|
+
export declare function physicalCardTiltStyles(tilt: CardTilt): {
|
|
924
|
+
card: string;
|
|
925
|
+
art: string;
|
|
926
|
+
header: string;
|
|
927
|
+
shine: string;
|
|
928
|
+
};
|
|
929
|
+
/** Sensor reading relative to the reference pose → position on the card (0 to 1). */
|
|
930
|
+
export declare function physicalCardDevicePosition(beta: number, gamma: number, baseline: {
|
|
931
|
+
beta: number;
|
|
932
|
+
gamma: number;
|
|
933
|
+
}, screenAngle?: number): {
|
|
934
|
+
x: number;
|
|
935
|
+
y: number;
|
|
936
|
+
};
|
|
937
|
+
/**
|
|
938
|
+
* The iPhone only releases the motion sensor after a touch. Called at the end of
|
|
939
|
+
* the touches while the focus view is open, starting with the finger that opened
|
|
940
|
+
* it; outside iOS it does nothing. A touch the browser refuses leaves the request
|
|
941
|
+
* for the next one.
|
|
942
|
+
*/
|
|
943
|
+
export declare function requestPhysicalCardMotionAccess(): void;
|
|
944
|
+
/** Value rows of the dial, top to bottom. */
|
|
945
|
+
export declare const PHYSICAL_CARD_STATS: readonly ["speed", "attack", "defense", "damage"];
|
|
946
|
+
export type PhysicalCardStat = (typeof PHYSICAL_CARD_STATS)[number];
|
|
947
|
+
/** A tip's content (team ability, improved ability, bystander power), in the app's language. */
|
|
948
|
+
export interface PhysicalCardTip {
|
|
949
|
+
title: string;
|
|
950
|
+
/** Original description; the app may translate it before drawing the tip. */
|
|
951
|
+
description: string;
|
|
952
|
+
iconSrc?: string;
|
|
953
|
+
/** Standard power color; without one it is a special power. */
|
|
954
|
+
accent?: string;
|
|
955
|
+
badgeType?: PhysicalCardStat;
|
|
956
|
+
}
|
|
957
|
+
/** An open tip: which control, how (passive hover or the touch dialog), where, and the content. */
|
|
958
|
+
export interface PhysicalCardTipState extends PhysicalCardTip {
|
|
959
|
+
/** The control's key (e.g. `ta:<id>`, `imp:<type>:<id>`, `dial:<value>`). */
|
|
960
|
+
key: string;
|
|
961
|
+
mode: 'hover' | 'dialog';
|
|
962
|
+
anchor: {
|
|
963
|
+
x: number;
|
|
964
|
+
y: number;
|
|
965
|
+
preferAbove?: boolean;
|
|
966
|
+
};
|
|
967
|
+
trigger: HTMLElement;
|
|
968
|
+
}
|
|
969
|
+
/** A control that opens a tip: its key and content. */
|
|
970
|
+
export interface PhysicalCardTipTarget {
|
|
971
|
+
key: string;
|
|
972
|
+
tip: PhysicalCardTip;
|
|
973
|
+
}
|
|
974
|
+
/** Team ability button in the title (or on the bystander token's stripe). */
|
|
975
|
+
export interface PhysicalCardTeamAbility extends PhysicalCardTipTarget {
|
|
976
|
+
/** Accessible label (e.g. "Team ability: Avengers"). */
|
|
977
|
+
label: string;
|
|
978
|
+
iconSrc: string;
|
|
979
|
+
}
|
|
980
|
+
/** A group of improved abilities (movement or targeting) on the strip above the dial. */
|
|
981
|
+
export interface PhysicalCardImprovedGroup {
|
|
982
|
+
kind: 'movement' | 'targeting';
|
|
983
|
+
kindIconSrc: string;
|
|
984
|
+
kindLabel: string;
|
|
985
|
+
abilities: (PhysicalCardTipTarget & {
|
|
986
|
+
label: string;
|
|
987
|
+
iconSrc: string;
|
|
988
|
+
})[];
|
|
989
|
+
}
|
|
990
|
+
/** A text block (special power, trait, effect), translated and assembled. */
|
|
991
|
+
export interface PhysicalCardRule {
|
|
992
|
+
name?: string;
|
|
993
|
+
/** Drawn text (the game symbols already swapped in by the app). */
|
|
994
|
+
text: ReactNode;
|
|
995
|
+
/** Text copied when the paragraph is tapped. */
|
|
996
|
+
copyText: string;
|
|
997
|
+
icon?: ReactNode;
|
|
998
|
+
}
|
|
999
|
+
/** The front's portrait: the character's art, the terrain's art or an object token. */
|
|
1000
|
+
export type PhysicalCardToken =
|
|
1001
|
+
/** The character's portrait, drawn by the app with the class the card asks for. */
|
|
1002
|
+
{
|
|
1003
|
+
kind: 'portrait';
|
|
1004
|
+
render: (className: string) => ReactNode;
|
|
1005
|
+
} | {
|
|
1006
|
+
kind: 'terrain';
|
|
1007
|
+
src: string;
|
|
1008
|
+
alt: string;
|
|
1009
|
+
} | {
|
|
1010
|
+
kind: 'object';
|
|
1011
|
+
src?: string;
|
|
1012
|
+
alt: string;
|
|
1013
|
+
color?: string | null;
|
|
1014
|
+
};
|
|
1015
|
+
/** A cell of the bystander's dial (a single click). */
|
|
1016
|
+
export interface PhysicalCardBystanderCell {
|
|
1017
|
+
stat: PhysicalCardStat;
|
|
1018
|
+
value: number;
|
|
1019
|
+
label: string;
|
|
1020
|
+
background?: string;
|
|
1021
|
+
color?: string;
|
|
1022
|
+
special: boolean;
|
|
1023
|
+
/** Without a tip the cell is not a button. */
|
|
1024
|
+
tip?: PhysicalCardTipTarget;
|
|
1025
|
+
}
|
|
1026
|
+
/** Replica of the bystander's physical token (HCUnits' `BystanderView`). */
|
|
1027
|
+
export interface PhysicalCardBystander {
|
|
1028
|
+
art?: {
|
|
1029
|
+
src: string;
|
|
1030
|
+
alt: string;
|
|
1031
|
+
};
|
|
1032
|
+
range?: number | null;
|
|
1033
|
+
rangeLabel: string;
|
|
1034
|
+
targets: number;
|
|
1035
|
+
boltSrc: string;
|
|
1036
|
+
symbols: Partial<Record<PhysicalCardStat, string>>;
|
|
1037
|
+
/** In an arc on the left: the kind's icon (without `ability`) and each ability. */
|
|
1038
|
+
improved: {
|
|
1039
|
+
kind: 'movement' | 'targeting';
|
|
1040
|
+
iconSrc: string;
|
|
1041
|
+
label: string;
|
|
1042
|
+
ability?: PhysicalCardTipTarget & {
|
|
1043
|
+
label: string;
|
|
1044
|
+
iconSrc: string;
|
|
1045
|
+
};
|
|
1046
|
+
}[];
|
|
1047
|
+
trait?: {
|
|
1048
|
+
src: string;
|
|
1049
|
+
alt: string;
|
|
1050
|
+
};
|
|
1051
|
+
extra?: {
|
|
1052
|
+
kind: 'construct' | 'stack' | 'passengers';
|
|
1053
|
+
src: string;
|
|
1054
|
+
alt: string;
|
|
1055
|
+
text?: ReactNode;
|
|
1056
|
+
};
|
|
1057
|
+
name: string;
|
|
1058
|
+
dial?: {
|
|
1059
|
+
label: string;
|
|
1060
|
+
cells: PhysicalCardBystanderCell[];
|
|
1061
|
+
};
|
|
1062
|
+
}
|
|
1063
|
+
export interface PhysicalCardPoint {
|
|
1064
|
+
index: number;
|
|
1065
|
+
value: number;
|
|
1066
|
+
color: string;
|
|
1067
|
+
/** Accessible label (e.g. "100 pts, starts at click 1"). */
|
|
1068
|
+
label: string;
|
|
1069
|
+
}
|
|
1070
|
+
/** Measures, backgrounds and seals of the front (the app's layout rules, already applied). */
|
|
1071
|
+
export interface PhysicalCardFrontLayout {
|
|
1072
|
+
frontTable: 'normal' | 'long' | 'short' | 'none';
|
|
1073
|
+
frontTableHeight: number;
|
|
1074
|
+
rarityImage?: string | null;
|
|
1075
|
+
dialImage?: string | null;
|
|
1076
|
+
teamUp: boolean;
|
|
1077
|
+
/** Smaller portrait when the dial takes two rows. */
|
|
1078
|
+
smallToken: boolean;
|
|
1079
|
+
improvedTop: number;
|
|
1080
|
+
}
|
|
1081
|
+
export interface PhysicalCardViewProps {
|
|
1082
|
+
/** The card's backgrounds (front, back and the legacy overlay, when there is one), as addresses the app serves. */
|
|
1083
|
+
images: {
|
|
1084
|
+
front: string;
|
|
1085
|
+
back: string;
|
|
1086
|
+
legacy?: string | null;
|
|
1087
|
+
};
|
|
1088
|
+
layout: PhysicalCardFrontLayout;
|
|
1089
|
+
/** Name in capitals; the type shrinks until it fits the title line. */
|
|
1090
|
+
title: string;
|
|
1091
|
+
gearSrc?: string;
|
|
1092
|
+
/** Lines under the title, in order (object or bystander type, keywords). */
|
|
1093
|
+
headerLines: {
|
|
1094
|
+
kind: 'type' | 'keywords';
|
|
1095
|
+
text: string;
|
|
1096
|
+
}[];
|
|
1097
|
+
teamAbilities: PhysicalCardTeamAbility[];
|
|
1098
|
+
/** Shield drawn in every team ability button. */
|
|
1099
|
+
teamAbilityIcon?: ReactNode;
|
|
1100
|
+
collection: {
|
|
1101
|
+
number: ReactNode;
|
|
1102
|
+
setSources: readonly string[];
|
|
1103
|
+
setTitle?: string;
|
|
1104
|
+
};
|
|
1105
|
+
dimensions?: {
|
|
1106
|
+
text: string;
|
|
1107
|
+
oval: boolean;
|
|
1108
|
+
};
|
|
1109
|
+
token?: PhysicalCardToken;
|
|
1110
|
+
bystander?: PhysicalCardBystander;
|
|
1111
|
+
/** Keyphrase lines of the object, in order, before the effects. */
|
|
1112
|
+
keyphrases?: {
|
|
1113
|
+
key: string;
|
|
1114
|
+
content: ReactNode;
|
|
1115
|
+
}[];
|
|
1116
|
+
relic?: {
|
|
1117
|
+
label: string;
|
|
1118
|
+
value: string;
|
|
1119
|
+
};
|
|
1120
|
+
rules: PhysicalCardRule[];
|
|
1121
|
+
/**
|
|
1122
|
+
* Restarts the split of the text between front and backs when it changes
|
|
1123
|
+
* (piece, language and translated texts).
|
|
1124
|
+
*/
|
|
1125
|
+
contentKey: string;
|
|
1126
|
+
improved: PhysicalCardImprovedGroup[];
|
|
1127
|
+
/** The printed dial (a `CombatDialView` with `presentation="printed"`, built by the app) and how many rows it takes. */
|
|
1128
|
+
dial?: {
|
|
1129
|
+
tables: 1 | 2;
|
|
1130
|
+
node: ReactNode;
|
|
1131
|
+
};
|
|
1132
|
+
terrainDial?: {
|
|
1133
|
+
icons: {
|
|
1134
|
+
range: {
|
|
1135
|
+
src: string;
|
|
1136
|
+
alt: string;
|
|
1137
|
+
};
|
|
1138
|
+
giantReach: {
|
|
1139
|
+
src: string;
|
|
1140
|
+
alt: string;
|
|
1141
|
+
};
|
|
1142
|
+
damage: {
|
|
1143
|
+
src: string;
|
|
1144
|
+
alt: string;
|
|
1145
|
+
};
|
|
1146
|
+
destroy: {
|
|
1147
|
+
src: string;
|
|
1148
|
+
alt: string;
|
|
1149
|
+
};
|
|
1150
|
+
};
|
|
1151
|
+
values: [ReactNode, ReactNode, ReactNode];
|
|
1152
|
+
};
|
|
1153
|
+
/** Points on the front's bar: the point value buttons, an object's line, or nothing. */
|
|
1154
|
+
points: {
|
|
1155
|
+
kind: 'none';
|
|
1156
|
+
} | {
|
|
1157
|
+
kind: 'object';
|
|
1158
|
+
values: string;
|
|
1159
|
+
suffix: string;
|
|
1160
|
+
} | {
|
|
1161
|
+
kind: 'normal' | 'long' | 'short';
|
|
1162
|
+
label: string;
|
|
1163
|
+
items: PhysicalCardPoint[];
|
|
1164
|
+
selectedIndex: number;
|
|
1165
|
+
};
|
|
1166
|
+
onSelectPoint: (point: PhysicalCardPoint) => void;
|
|
1167
|
+
/** The last back's black bar: real name and source, already in capitals. */
|
|
1168
|
+
backBar: {
|
|
1169
|
+
realName?: {
|
|
1170
|
+
label: string;
|
|
1171
|
+
value: string;
|
|
1172
|
+
};
|
|
1173
|
+
source?: {
|
|
1174
|
+
label: string;
|
|
1175
|
+
value: string;
|
|
1176
|
+
};
|
|
1177
|
+
};
|
|
1178
|
+
labels: {
|
|
1179
|
+
/** The front's label (e.g. "Batman · card front"). */
|
|
1180
|
+
front: string;
|
|
1181
|
+
/** Each back's label (`index` from 0). */
|
|
1182
|
+
back: (index: number, count: number) => string;
|
|
1183
|
+
};
|
|
1184
|
+
onCopy: (text: string) => void;
|
|
1185
|
+
/** Desktop: the front fits whole in the visible height of the scrolling area (`fitHeightWithin`). */
|
|
1186
|
+
fitHeight?: boolean;
|
|
1187
|
+
/** Selector of the scrolling area, looked up from the card, for `fitHeight`. */
|
|
1188
|
+
fitHeightWithin?: string;
|
|
1189
|
+
/** Focus view: one side at a time, with the `focusActions` buttons below. */
|
|
1190
|
+
focus?: boolean;
|
|
1191
|
+
/** Holding the card with a finger (outside the focus view) asks for the focus view. */
|
|
1192
|
+
onRequestFocus?: () => void;
|
|
1193
|
+
/** The focus view's buttons; `flip` turns to the next side (`nextSide`: 0 is the front). */
|
|
1194
|
+
focusActions?: (state: {
|
|
1195
|
+
nextSide: number;
|
|
1196
|
+
backs: number;
|
|
1197
|
+
flip: () => void;
|
|
1198
|
+
}) => ReactNode;
|
|
1199
|
+
/** The open tip, controlled by the caller. */
|
|
1200
|
+
tip: PhysicalCardTipState | null;
|
|
1201
|
+
onTipChange: Dispatch<SetStateAction<PhysicalCardTipState | null>>;
|
|
1202
|
+
/** `id` of the tip element, for the hover's `aria-describedby`. */
|
|
1203
|
+
tipId: string;
|
|
1204
|
+
/** The tip drawn by the app, while `tip` is set. */
|
|
1205
|
+
tipSlot?: ReactNode;
|
|
1206
|
+
}
|
|
1207
|
+
export interface PhysicalCardFocusViewProps {
|
|
1208
|
+
/** The dialog's accessible name (the piece's name). */
|
|
1209
|
+
label: string;
|
|
1210
|
+
onClose: () => void;
|
|
1211
|
+
/** A second card with `focus`, usually the app's adapter again. */
|
|
1212
|
+
children: ReactNode;
|
|
1213
|
+
}
|
|
1214
|
+
/**
|
|
1215
|
+
* The card's focus view: a full-screen modal dialog over a dimmed, blurred
|
|
1216
|
+
* background. Inside goes a second card with `focus`; tapping outside it (after
|
|
1217
|
+
* the finger that opened it is lifted) or Esc calls `onClose`.
|
|
1218
|
+
*/
|
|
1219
|
+
export declare function PhysicalCardFocusView({ label, onClose, children }: PhysicalCardFocusViewProps): import("react/jsx-runtime").JSX.Element;
|
|
1220
|
+
/**
|
|
1221
|
+
* The physical card (front and backs in HCUnits' format), presentation only. It
|
|
1222
|
+
* gets everything resolved: backgrounds, seals, texts already translated and
|
|
1223
|
+
* assembled, symbol addresses, the printed dial ready and the points. It handles
|
|
1224
|
+
* the scale, the split of the text between front and backs, the tilt and shine,
|
|
1225
|
+
* holding to open the focus view and flipping in it, and opens the tips through
|
|
1226
|
+
* `onTipChange`; drawing the tip, translating and copying are the caller's.
|
|
1227
|
+
*/
|
|
1228
|
+
export declare const PhysicalCardView: import("react").ForwardRefExoticComponent<PhysicalCardViewProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
1229
|
+
/**
|
|
1230
|
+
* A faithful port of hcmaps.net's map drawing engine (map.js / tile.js / edge.js /
|
|
1231
|
+
* wall.js), the same engine HCUnits uses to draw the map's canvas. It reads the
|
|
1232
|
+
* same JSON (the piece's map) and draws it 1:1 locally, with no runtime
|
|
1233
|
+
* dependency on their scripts. Credit: hcmaps.net (original engine in JS).
|
|
1234
|
+
*/
|
|
1235
|
+
export declare const MAP_TILE_SIZE = 64;
|
|
1236
|
+
export declare const MAP_ELEVATION_TILE_COLORS: string[];
|
|
1237
|
+
export declare const MAP_TERRAIN_TILE_COLORS: Record<string, string>;
|
|
1238
|
+
export declare const MAP_TERRAIN_EDGE_COLORS: Record<string, string>;
|
|
1239
|
+
export declare const MAP_ELEVATION_EDGE_COLOR = "#FF0000";
|
|
1240
|
+
export declare const MAP_INDOOR_EDGE_COLOR = "#FFFF00";
|
|
1241
|
+
export declare const MAP_WALL_COLORS: Record<string, string>;
|
|
1242
|
+
interface JsonLabel {
|
|
1243
|
+
text: string;
|
|
1244
|
+
color?: string;
|
|
1245
|
+
rotate?: number;
|
|
1246
|
+
}
|
|
1247
|
+
interface JsonTile {
|
|
1248
|
+
terrain?: string;
|
|
1249
|
+
type?: string;
|
|
1250
|
+
elevation?: number | string;
|
|
1251
|
+
isStartingZone?: boolean;
|
|
1252
|
+
isStartingZone4p?: boolean;
|
|
1253
|
+
group?: number;
|
|
1254
|
+
label?: JsonLabel;
|
|
1255
|
+
}
|
|
1256
|
+
interface JsonWall {
|
|
1257
|
+
x0: number;
|
|
1258
|
+
y0: number;
|
|
1259
|
+
x1: number;
|
|
1260
|
+
y1: number;
|
|
1261
|
+
type?: string;
|
|
1262
|
+
}
|
|
1263
|
+
interface JsonRamp {
|
|
1264
|
+
x0: number;
|
|
1265
|
+
y0: number;
|
|
1266
|
+
x1: number;
|
|
1267
|
+
y1: number;
|
|
1268
|
+
}
|
|
1269
|
+
export interface HCMapJson {
|
|
1270
|
+
name?: string;
|
|
1271
|
+
width: number;
|
|
1272
|
+
height: number;
|
|
1273
|
+
type?: string;
|
|
1274
|
+
defaultTerrain?: string;
|
|
1275
|
+
defaultElevation?: number;
|
|
1276
|
+
defaultType?: string;
|
|
1277
|
+
defaultWallType?: string;
|
|
1278
|
+
hideLabels?: boolean;
|
|
1279
|
+
rows: {
|
|
1280
|
+
tiles: JsonTile[];
|
|
1281
|
+
}[];
|
|
1282
|
+
walls?: JsonWall[];
|
|
1283
|
+
ramps?: JsonRamp[];
|
|
1284
|
+
ziplines?: JsonWall[];
|
|
1285
|
+
}
|
|
1286
|
+
export declare class HCMap {
|
|
1287
|
+
width: number;
|
|
1288
|
+
height: number;
|
|
1289
|
+
type: string;
|
|
1290
|
+
private tiles;
|
|
1291
|
+
private walls;
|
|
1292
|
+
private ziplines;
|
|
1293
|
+
constructor(json: HCMapJson);
|
|
1294
|
+
private getTile;
|
|
1295
|
+
private tryAddEdge;
|
|
1296
|
+
draw(ctx: CanvasRenderingContext2D): void;
|
|
1297
|
+
private drawWall;
|
|
1298
|
+
}
|
|
1299
|
+
export declare function mapSquare(map: HCMapJson, x: number, y: number): {
|
|
1300
|
+
x: number;
|
|
1301
|
+
y: number;
|
|
1302
|
+
terrain: string;
|
|
1303
|
+
elevation: string | number;
|
|
1304
|
+
environment: string;
|
|
1305
|
+
starting: boolean;
|
|
1306
|
+
label: string | undefined;
|
|
1307
|
+
};
|
|
1308
|
+
export type MapSquare = ReturnType<typeof mapSquare>;
|
|
1309
|
+
/** A text elevation exists in the hcmaps format; the 2D drawing treats it as an elevated level. */
|
|
1310
|
+
export declare function mapElevationLevel(elevation: number | string): number;
|
|
1311
|
+
export declare function mapMaxLevel(map: HCMapJson): number;
|
|
1312
|
+
export interface MapInspection {
|
|
1313
|
+
square: MapSquare;
|
|
1314
|
+
adjacent?: MapSquare;
|
|
1315
|
+
line?: {
|
|
1316
|
+
x0: number;
|
|
1317
|
+
y0: number;
|
|
1318
|
+
x1: number;
|
|
1319
|
+
y1: number;
|
|
1320
|
+
};
|
|
1321
|
+
wall?: string;
|
|
1322
|
+
}
|
|
1323
|
+
/** Coordinates are in squares; wall tolerance is based on screen pixels by the caller. */
|
|
1324
|
+
export declare function inspectMap(map: HCMapJson, x: number, y: number, tolerance?: number): MapInspection | null;
|
|
1325
|
+
export declare function mapSquareCoordinate(square: Pick<MapSquare, 'x' | 'y'>): string;
|
|
1326
|
+
/**
|
|
1327
|
+
* The 3D model's scene, with no drawing. It uses only what the map JSON has:
|
|
1328
|
+
* terrain, elevation, environment, walls, ramps and ziplines. Blocking follows
|
|
1329
|
+
* the game's rule: indoor goes up to the map's highest elevation, outdoor takes
|
|
1330
|
+
* one elevation. Walls, water and obscuring have no height in the data: those
|
|
1331
|
+
* are conventions.
|
|
1332
|
+
*/
|
|
1333
|
+
export declare const MAP_SCENE_LEVEL_HEIGHT = 0.62;
|
|
1334
|
+
export declare const MAP_SCENE_BASE_Z = -0.34;
|
|
1335
|
+
export interface MapSceneCell extends MapSquare {
|
|
1336
|
+
level: number;
|
|
1337
|
+
/** The square's floor, without the terrain's volume. */
|
|
1338
|
+
floor: number;
|
|
1339
|
+
top: number;
|
|
1340
|
+
}
|
|
1341
|
+
export interface MapSceneWall {
|
|
1342
|
+
x0: number;
|
|
1343
|
+
y0: number;
|
|
1344
|
+
x1: number;
|
|
1345
|
+
y1: number;
|
|
1346
|
+
type: string;
|
|
1347
|
+
z0: number;
|
|
1348
|
+
z1: number;
|
|
1349
|
+
}
|
|
1350
|
+
export interface MapSceneRamp {
|
|
1351
|
+
x: number;
|
|
1352
|
+
y: number;
|
|
1353
|
+
dx: number;
|
|
1354
|
+
dy: number;
|
|
1355
|
+
from: number;
|
|
1356
|
+
to: number;
|
|
1357
|
+
}
|
|
1358
|
+
export interface MapSceneLine {
|
|
1359
|
+
x0: number;
|
|
1360
|
+
y0: number;
|
|
1361
|
+
x1: number;
|
|
1362
|
+
y1: number;
|
|
1363
|
+
z: number;
|
|
1364
|
+
}
|
|
1365
|
+
export interface MapScene {
|
|
1366
|
+
width: number;
|
|
1367
|
+
height: number;
|
|
1368
|
+
cells: MapSceneCell[];
|
|
1369
|
+
walls: MapSceneWall[];
|
|
1370
|
+
ramps: MapSceneRamp[];
|
|
1371
|
+
ziplines: MapSceneLine[];
|
|
1372
|
+
maxTop: number;
|
|
1373
|
+
}
|
|
1374
|
+
export declare function mapSceneCell(scene: Pick<MapScene, 'width' | 'height' | 'cells'>, x: number, y: number): MapSceneCell | null;
|
|
1375
|
+
export declare function buildMapScene(map: HCMapJson): MapScene;
|
|
1376
|
+
export interface MapCamera {
|
|
1377
|
+
azimuth: number;
|
|
1378
|
+
tilt: number;
|
|
1379
|
+
zoom: number;
|
|
1380
|
+
panX: number;
|
|
1381
|
+
panY: number;
|
|
1382
|
+
}
|
|
1383
|
+
export interface MapViewport {
|
|
1384
|
+
width: number;
|
|
1385
|
+
height: number;
|
|
1386
|
+
}
|
|
1387
|
+
export declare const MAP_DEFAULT_CAMERA: MapCamera;
|
|
1388
|
+
/** 90 is the flat view from above. Below the minimum the board becomes a line. */
|
|
1389
|
+
export declare const MAP_TILT_RANGE: readonly [6, 90];
|
|
1390
|
+
export declare const MAP_ZOOM_RANGE: readonly [1, 6];
|
|
1391
|
+
export declare const clampMapValue: (value: number, min: number, max: number) => number;
|
|
1392
|
+
export interface MapProjection {
|
|
1393
|
+
scale: number;
|
|
1394
|
+
sin: number;
|
|
1395
|
+
cos: number;
|
|
1396
|
+
point(x: number, y: number, z: number): [number, number];
|
|
1397
|
+
/** Larger = closer to the viewer. */
|
|
1398
|
+
depth(x: number, y: number): number;
|
|
1399
|
+
/** From the screen to the map, on a plane at height `z`. */
|
|
1400
|
+
ground(px: number, py: number, z: number): [number, number];
|
|
1401
|
+
}
|
|
1402
|
+
export declare function makeMapProjection(scene: MapScene, camera: MapCamera, viewport: MapViewport): MapProjection;
|
|
1403
|
+
/** The map's center may go as far as any of its corners reaches the middle of the screen. */
|
|
1404
|
+
export declare function clampMapPan(scene: MapScene, camera: MapCamera, viewport: MapViewport): MapCamera;
|
|
1405
|
+
/** Zooms keeping the point at `anchor` (measured from the screen's center) still. */
|
|
1406
|
+
export declare function zoomMapCamera(scene: MapScene, camera: MapCamera, viewport: MapViewport, factor: number, anchor?: {
|
|
1407
|
+
x: number;
|
|
1408
|
+
y: number;
|
|
1409
|
+
}): MapCamera;
|
|
1410
|
+
/** A square's four sides: the edge's points, the neighbor and the normal. */
|
|
1411
|
+
export declare const MAP_CELL_SIDES: readonly [{
|
|
1412
|
+
readonly ax: 0;
|
|
1413
|
+
readonly ay: 1;
|
|
1414
|
+
readonly bx: 0;
|
|
1415
|
+
readonly by: 0;
|
|
1416
|
+
readonly dx: -1;
|
|
1417
|
+
readonly dy: 0;
|
|
1418
|
+
}, {
|
|
1419
|
+
readonly ax: 1;
|
|
1420
|
+
readonly ay: 0;
|
|
1421
|
+
readonly bx: 1;
|
|
1422
|
+
readonly by: 1;
|
|
1423
|
+
readonly dx: 1;
|
|
1424
|
+
readonly dy: 0;
|
|
1425
|
+
}, {
|
|
1426
|
+
readonly ax: 0;
|
|
1427
|
+
readonly ay: 0;
|
|
1428
|
+
readonly bx: 1;
|
|
1429
|
+
readonly by: 0;
|
|
1430
|
+
readonly dx: 0;
|
|
1431
|
+
readonly dy: -1;
|
|
1432
|
+
}, {
|
|
1433
|
+
readonly ax: 1;
|
|
1434
|
+
readonly ay: 1;
|
|
1435
|
+
readonly bx: 0;
|
|
1436
|
+
readonly by: 1;
|
|
1437
|
+
readonly dx: 0;
|
|
1438
|
+
readonly dy: 1;
|
|
1439
|
+
}];
|
|
1440
|
+
/**
|
|
1441
|
+
* From the screen to map coordinates, testing from front to back. Returns the
|
|
1442
|
+
* point in `inspectMap`'s terms, so the tooltip and selection match the 2D board.
|
|
1443
|
+
*/
|
|
1444
|
+
export declare function pickMapScene(scene: MapScene, view: MapProjection, px: number, py: number): {
|
|
1445
|
+
x: number;
|
|
1446
|
+
y: number;
|
|
1447
|
+
} | null;
|
|
1448
|
+
type Point = readonly [number, number];
|
|
1449
|
+
/**
|
|
1450
|
+
* The target's squares inside the board and their outer outline, in map coordinates.
|
|
1451
|
+
* Corners are where the outline turns or ends: the volume's edges rise there.
|
|
1452
|
+
*/
|
|
1453
|
+
export declare function mapFootprintOutline({ x, y, width, height, cells }: {
|
|
1454
|
+
x: number;
|
|
1455
|
+
y: number;
|
|
1456
|
+
width: number;
|
|
1457
|
+
height: number;
|
|
1458
|
+
cells?: readonly (readonly boolean[])[] | null;
|
|
1459
|
+
}, boardWidth: number, boardHeight: number): {
|
|
1460
|
+
squares: Point[];
|
|
1461
|
+
edges: [Point, Point][];
|
|
1462
|
+
corners: Point[];
|
|
1463
|
+
};
|
|
1464
|
+
export interface MapSceneFootprint {
|
|
1465
|
+
x: number;
|
|
1466
|
+
y: number;
|
|
1467
|
+
width: number;
|
|
1468
|
+
height: number;
|
|
1469
|
+
top: number;
|
|
1470
|
+
valid: boolean;
|
|
1471
|
+
/** Occupied squares (`cells[y][x]`, from the corner); without it, the whole rectangle. */
|
|
1472
|
+
cells?: readonly (readonly boolean[])[] | null;
|
|
1473
|
+
}
|
|
1474
|
+
export interface MapSceneHighlight {
|
|
1475
|
+
square?: {
|
|
1476
|
+
x: number;
|
|
1477
|
+
y: number;
|
|
1478
|
+
};
|
|
1479
|
+
line?: {
|
|
1480
|
+
x0: number;
|
|
1481
|
+
y0: number;
|
|
1482
|
+
x1: number;
|
|
1483
|
+
y1: number;
|
|
1484
|
+
};
|
|
1485
|
+
/** The target of a piece being moved: floor and volume, cut to the board. */
|
|
1486
|
+
footprint?: MapSceneFootprint;
|
|
1487
|
+
}
|
|
1488
|
+
export declare function drawMapScene(ctx: CanvasRenderingContext2D, scene: MapScene, view: MapProjection, texture: HTMLCanvasElement, highlight?: MapSceneHighlight | null, { tufts }?: {
|
|
1489
|
+
tufts?: boolean;
|
|
1490
|
+
}): void;
|
|
1491
|
+
/** The thumbnail's default height, in pixels. */
|
|
1492
|
+
export declare const MAP_THUMBNAIL_HEIGHT = 192;
|
|
1493
|
+
/**
|
|
1494
|
+
* The map's thumbnail as a PNG data URL: draws with the engine at full resolution
|
|
1495
|
+
* and scales down to `height` pixels. Gets the JSON already fetched; `null` when it
|
|
1496
|
+
* has no dimensions or the browser gives no 2D context.
|
|
1497
|
+
*/
|
|
1498
|
+
export declare function renderMapThumbnail(json: HCMapJson, height?: number): string | null;
|
|
1499
|
+
export interface MapBoardViewProps extends Omit<HTMLAttributes<HTMLDivElement>, 'children' | 'onPointerDown' | 'onPointerMove' | 'onPointerLeave'> {
|
|
1500
|
+
/** The hcmaps JSON, already fetched and checked. */
|
|
1501
|
+
map: HCMapJson;
|
|
1502
|
+
/** The drawing's accessible name, in the app's language (the map's name or a generic one). */
|
|
1503
|
+
canvasLabel: string;
|
|
1504
|
+
/** What is inspected: the square is shaded; an edge becomes a line. */
|
|
1505
|
+
selection?: Pick<MapInspection, 'square' | 'line'> | null;
|
|
1506
|
+
/** Pointer over the map, in squares (0..width, 0..height), and the screen point. Touch counts only on press; mouse and pen also while passing over. */
|
|
1507
|
+
onInspect?(x: number, y: number, clientX: number, clientY: number): void;
|
|
1508
|
+
/** The mouse or pen left the map. */
|
|
1509
|
+
onLeave?(): void;
|
|
1510
|
+
/** Drawing failed (a JSON outside the expected format). */
|
|
1511
|
+
onDrawError?(): void;
|
|
1512
|
+
}
|
|
1513
|
+
/**
|
|
1514
|
+
* The map's 2D board: the hcmaps drawing in a canvas and, over it, the inspection's
|
|
1515
|
+
* highlight. Gets the map already fetched; fetching, translating and deciding what
|
|
1516
|
+
* the tooltip shows are the caller's. Other attributes (role, focus, keyboard,
|
|
1517
|
+
* `aria-*`, `data-*`) go to the container.
|
|
1518
|
+
*/
|
|
1519
|
+
export declare const MapBoardView: import("react").ForwardRefExoticComponent<MapBoardViewProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
1520
|
+
export interface MapBoardPlaceholderProps {
|
|
1521
|
+
/** Read by screen readers while the map loads (e.g. "Loading map"). */
|
|
1522
|
+
label: string;
|
|
1523
|
+
}
|
|
1524
|
+
/** The map's place while it loads: the empty grid in a map's proportion. The label is visually hidden. */
|
|
1525
|
+
export declare function MapBoardPlaceholder({ label }: MapBoardPlaceholderProps): import("react/jsx-runtime").JSX.Element;
|
|
1526
|
+
export interface MapSwatchProps {
|
|
1527
|
+
/** The square's fill, as on the board. */
|
|
1528
|
+
fill: string;
|
|
1529
|
+
/** The square's border (the terrain's or the elevation's color). */
|
|
1530
|
+
edge: string;
|
|
1531
|
+
/** Number inside the square (the elevation's level). */
|
|
1532
|
+
children?: number;
|
|
1533
|
+
/** White text, for dark fills. */
|
|
1534
|
+
lightText?: boolean;
|
|
1535
|
+
}
|
|
1536
|
+
/** A legend square with a map square's fill and border. Decorative. */
|
|
1537
|
+
export declare function MapSwatch({ fill, edge, children, lightText }: MapSwatchProps): import("react/jsx-runtime").JSX.Element;
|
|
1538
|
+
/** One inspected square, resolved: texts in the app's language and the board's colors. */
|
|
1539
|
+
export interface MapTooltipSquare {
|
|
1540
|
+
/** Coordinate (e.g. "H8"); shown only when the title is not the coordinate itself. */
|
|
1541
|
+
coordinate?: string;
|
|
1542
|
+
terrain: {
|
|
1543
|
+
label: string;
|
|
1544
|
+
fill: string;
|
|
1545
|
+
edge: string;
|
|
1546
|
+
};
|
|
1547
|
+
elevation: {
|
|
1548
|
+
level: number;
|
|
1549
|
+
label: string;
|
|
1550
|
+
fill: string;
|
|
1551
|
+
edge: string;
|
|
1552
|
+
lightText?: boolean;
|
|
1553
|
+
};
|
|
1554
|
+
/** The square's environment, e.g. a `MapEnvironmentView`. */
|
|
1555
|
+
environment?: ReactNode;
|
|
1556
|
+
/** Text in place of the environment, when the map does not say (e.g. "Environment unspecified"). */
|
|
1557
|
+
environmentNote?: string;
|
|
1558
|
+
/** Notes, in order (what fills a blocking square, starting area, the square's label). */
|
|
1559
|
+
notes?: readonly string[];
|
|
1560
|
+
}
|
|
1561
|
+
export interface MapTooltipViewProps {
|
|
1562
|
+
id?: string;
|
|
1563
|
+
/** Screen point (viewport coordinates) the tooltip opens next to. A new object moves it. */
|
|
1564
|
+
anchor: {
|
|
1565
|
+
x: number;
|
|
1566
|
+
y: number;
|
|
1567
|
+
};
|
|
1568
|
+
title: string;
|
|
1569
|
+
/** The wall's color, when the inspected point is a wall. */
|
|
1570
|
+
wallColor?: string;
|
|
1571
|
+
/** One square, or two on an edge between squares. */
|
|
1572
|
+
squares: readonly MapTooltipSquare[];
|
|
1573
|
+
}
|
|
1574
|
+
/**
|
|
1575
|
+
* The map inspection's tooltip: the title (wall, edge or coordinate) and what is in
|
|
1576
|
+
* each square. It is `position: fixed`, next to `anchor`, without leaving the
|
|
1577
|
+
* screen; to take it out of a container with overflow, the caller portals it.
|
|
1578
|
+
*/
|
|
1579
|
+
export declare function MapTooltipView({ id, anchor, title, wallColor, squares }: MapTooltipViewProps): import("react/jsx-runtime").JSX.Element;
|
|
1580
|
+
export interface MapLegendItem {
|
|
1581
|
+
fill: string;
|
|
1582
|
+
edge: string;
|
|
1583
|
+
/** Visible name, in the app's language; also the item's key. */
|
|
1584
|
+
label: string;
|
|
1585
|
+
}
|
|
1586
|
+
export interface MapLegendViewProps {
|
|
1587
|
+
items: readonly MapLegendItem[];
|
|
1588
|
+
/** After the items, e.g. the map's environments (`MapEnvironmentView`). */
|
|
1589
|
+
children?: ReactNode;
|
|
1590
|
+
}
|
|
1591
|
+
/** The 3D model's legend: a small square and the name of each terrain on the map. */
|
|
1592
|
+
export declare function MapLegendView({ items, children }: MapLegendViewProps): import("react/jsx-runtime").JSX.Element;
|
|
1593
|
+
export type MapEnvironmentKind = 'indoor' | 'outdoor';
|
|
1594
|
+
/** The map's types (`indoor`, `outdoor`, `indoorOutdoor`, in any spelling) as environments, without repeats. */
|
|
1595
|
+
export declare function mapEnvironments(types: readonly string[]): MapEnvironmentKind[];
|
|
1596
|
+
export interface MapEnvironmentViewProps {
|
|
1597
|
+
types: readonly string[];
|
|
1598
|
+
/** Each environment's icon address (black line icons; the dark theme turns them light). */
|
|
1599
|
+
icons: Record<MapEnvironmentKind, string>;
|
|
1600
|
+
/** Each environment's visible name. A game term: HCUI shows it in English in all three languages. */
|
|
1601
|
+
labels: Record<MapEnvironmentKind, string>;
|
|
1602
|
+
className?: string;
|
|
1603
|
+
}
|
|
1604
|
+
/** The map's environments (indoor / outdoor), each with its icon and name. With no known environment it draws nothing. */
|
|
1605
|
+
export declare function MapEnvironmentView({ types, icons, labels, className }: MapEnvironmentViewProps): import("react/jsx-runtime").JSX.Element | null;
|
|
642
1606
|
export {};
|