narraleaf-react 0.35.0 → 0.37.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.
@@ -29,4 +29,4 @@ export type { LayeredDefinition, LayerGroupDefinition, LayerResolver, LayerSlot,
29
29
  export type { SentenceMetadata } from "../elements/character/sentence";
30
30
  export type { TextEventAppearance, TextEventConfig, TextEventExpression, } from "../elements/character/textEvent";
31
31
  export type { CharacterPortraitConfig, DialogAvatar, DialogAvatarResolver, DialogAvatarResolverContext, DialogAvatarResolution, DialogAvatarSource, } from "../elements/character/avatar";
32
- export type { WordConfig, WordRenderProps, WordRenderer, } from "../elements/character/word";
32
+ export type { WordConfig, WordEmphasis, WordRenderProps, WordRenderer, } from "../elements/character/word";
@@ -44,12 +44,50 @@ export type WordRenderProps<T = unknown> = {
44
44
  * cannot carry a function. An id that resolves to nothing renders as plain text.
45
45
  */
46
46
  export type WordRenderer<T = any> = string | React.ComponentType<WordRenderProps<T>>;
47
+ /**
48
+ * Emphasis marks: a small glyph drawn beside every character of a word, the typographic way East
49
+ * Asian text stresses a phrase where a Latin one would go italic (傍点 / 圏点 in Japanese, 着重号 in
50
+ * Chinese). The marks follow the characters as they are typed out, so a word that is half revealed
51
+ * carries half its marks.
52
+ */
53
+ export type WordEmphasis = {
54
+ /**
55
+ * The glyph drawn beside each character.
56
+ * @default "dot"
57
+ */
58
+ mark?: "dot" | "circle" | "sesame" | "triangle";
59
+ /**
60
+ * Whether that glyph is solid or hollow.
61
+ * @default "filled"
62
+ */
63
+ fill?: "filled" | "open";
64
+ /**
65
+ * Which side of the text the marks sit on. In horizontal writing `over` is above the line and
66
+ * `under` is below it; in vertical writing both sit to the right of the column, where the
67
+ * convention places them.
68
+ * @default "over"
69
+ */
70
+ position?: "over" | "under";
71
+ };
47
72
  export type WordConfig = {
48
73
  className: string;
49
74
  ruby: string;
50
75
  color: Color;
51
76
  pause: boolean;
52
77
  cps?: number;
78
+ /**
79
+ * Emphasis marks drawn beside this word's characters. See {@link Word.emphasis}.
80
+ */
81
+ emphasis?: WordEmphasis;
82
+ /**
83
+ * This word's size as a share of the line's — `1.25` for a quarter larger, `0.8` for a fifth
84
+ * smaller. Relative rather than absolute, so the word keeps its weight against the rest of the
85
+ * line whatever size the line is set at, including while text scaling brings the line down to
86
+ * fit its box.
87
+ *
88
+ * Ignored when {@link WordConfig.fontSize} is set, which pins the word to an absolute size.
89
+ */
90
+ fontScale?: number;
53
91
  /**
54
92
  * Renders this word's revealed characters. See {@link Word.custom}.
55
93
  */
@@ -82,6 +120,20 @@ export declare class Word<T extends string | DynamicWord | Pausing | TextEvent =
82
120
  * @param text - The text or word to italicize.
83
121
  */
84
122
  static italic(text: string | Word): Word;
123
+ /**
124
+ * Return a word with emphasis marks beside its characters — the East Asian counterpart of
125
+ * italicising a phrase.
126
+ *
127
+ * @param text - The text or word to emphasise.
128
+ * @param emphasis - Which glyph to draw and which side of the text to draw it on. Defaults to a
129
+ * filled dot above the line, the Japanese convention; Chinese text sets `position: "under"`.
130
+ * @example
131
+ * ```ts
132
+ * character.say(["それは", Word.emphasis("わたし"), "が決めることです。"]);
133
+ * character.say(["这是", Word.emphasis("我", {position: "under"}), "的决定。"]);
134
+ * ```
135
+ */
136
+ static emphasis(text: string | Word, emphasis?: WordEmphasis): Word;
85
137
  /**
86
138
  * Render a word with a component of your own — an inline glossary term that opens a definition
87
139
  * popup, a name that links into an in-game encyclopedia, anything the dialogue box cannot say
@@ -9,6 +9,17 @@ export type DarknessOptions = {
9
9
  to: number;
10
10
  /** Duration in milliseconds. */
11
11
  duration: number;
12
+ /**
13
+ * Time held at `from` before the brightness starts moving, in milliseconds, taken out of
14
+ * `duration`. `{from: 1, to: 0, duration: 3000, holdMs: 2000}` is two seconds of black and
15
+ * then a one-second lift out of it.
16
+ *
17
+ * The hold sits at `from` and not at `to` because `from` is where the image swap happens -
18
+ * the incoming frame is already on screen at the very first tick. That makes this the same
19
+ * thing {@link ThroughColor} and {@link Exposure} call a hold: the window the swap hides in.
20
+ * @default 0
21
+ */
22
+ holdMs?: number;
12
23
  easing?: TransformDefinitions.EasingDefinition;
13
24
  };
14
25
  /**
@@ -18,11 +29,24 @@ export type DarknessOptions = {
18
29
  *
19
30
  * This is what backs `image.darken(amount, duration)` — darkening an image in
20
31
  * place is expressed as a transition from its current darkness to the new one.
32
+ *
33
+ * ⚠ The brightness is only worn for as long as the transition runs. What a
34
+ * settled element looks like is the element's own business — an image renders at
35
+ * `Image.state.darkness`, a scene root at nothing at all — so a run ending at a
36
+ * `to` above zero snaps back to full brightness on its last frame unless
37
+ * something else is holding that darkness. Ending dark is what a black
38
+ * background (or {@link ThroughColor} with no uncover) is for; this is a way
39
+ * *through* a darkness, not a way to sit in one.
40
+ *
41
+ * The channel is a linear 0-1 progress and the easing is applied by hand, so
42
+ * that {@link DarknessOptions.holdMs} can be real time rather than a share of an
43
+ * eased curve.
21
44
  */
22
45
  export declare class Darkness extends ImageTransition<AnimationType> {
23
46
  private from;
24
47
  private to;
25
48
  private duration;
49
+ private holdMs;
26
50
  private easing?;
27
51
  constructor(options: DarknessOptions);
28
52
  createTask(): TransitionTask<HTMLImageElement, AnimationType>;
@@ -20,7 +20,20 @@ export type ExposureOptions = {
20
20
  * carries the shadows up without touching the frame at rest. @default 0.04
21
21
  */
22
22
  lift?: number;
23
- /** Fraction (0–1) of the duration spent fully blown out. @default 0 */
23
+ /**
24
+ * Time spent fully blown out, in milliseconds, taken out of `duration` and split evenly off
25
+ * the burn and the cool-down. `{duration: 3000, holdMs: 1500}` burns for 750ms, holds white
26
+ * for a second and a half, and comes back down over the last 750ms.
27
+ *
28
+ * Real time, not a share of an eased curve - see {@link ThroughColor} for why the run is
29
+ * linear. @default 0
30
+ */
31
+ holdMs?: number;
32
+ /**
33
+ * Fraction (0–1) of the duration spent fully blown out.
34
+ * @default 0
35
+ * @deprecated Use {@link holdMs}. Read only when `holdMs` is absent.
36
+ */
24
37
  hold?: number;
25
38
  easing?: TransformDefinitions.EasingDefinition;
26
39
  };
@@ -44,6 +57,7 @@ export declare class Exposure extends ImageTransition<AnimationType> {
44
57
  private ev;
45
58
  private lift;
46
59
  private hold;
60
+ private holdMs;
47
61
  private easing?;
48
62
  constructor(options: ExposureOptions);
49
63
  /**
@@ -18,7 +18,22 @@ export type ThroughColorOptions = {
18
18
  duration: number;
19
19
  /** Hold colour. @default "#000000" */
20
20
  color?: string;
21
- /** Fraction (0–1) of the duration spent fully covered by the colour. @default 0.3 */
21
+ /**
22
+ * Time spent fully covered by the colour, in milliseconds, taken out of `duration` and split
23
+ * evenly off the cover and uncover halves. `{duration: 4000, holdMs: 2000}` is one second in,
24
+ * two seconds of solid colour, one second out.
25
+ *
26
+ * Real time, not a share of an eased curve: see {@link ThroughColor} on why the run is linear.
27
+ * @default 0.3 × duration
28
+ */
29
+ holdMs?: number;
30
+ /**
31
+ * Fraction (0–1) of the duration spent fully covered by the colour.
32
+ * @default 0.3
33
+ * @deprecated Use {@link holdMs}. A fraction cannot say how long the colour is actually held:
34
+ * it is a share of the run, so the seconds it buys move whenever the duration does. Read only
35
+ * when `holdMs` is absent.
36
+ */
22
37
  hold?: number;
23
38
  /**
24
39
  * The coverage geometry the colour covers the frame through. See
@@ -44,14 +59,22 @@ export type ThroughColorOptions = {
44
59
  *
45
60
  * The geometry lives entirely in the `pattern` option (see {@link Mask});
46
61
  * without one, the colour simply fades in and out (fade-to-black/white, or a
47
- * flash with `hold: 0`). {@link Reveal} is the direct-cut counterpart that
62
+ * flash with `holdMs: 0`). {@link Reveal} is the direct-cut counterpart that
48
63
  * takes the same patterns. The `uncover` option picks how the second half
49
64
  * plays: see {@link ThroughColorUncover}.
65
+ *
66
+ * The animation channel is deliberately **linear** and the easing is applied to
67
+ * each moving half by hand. Easing the whole run would make the hold a band of
68
+ * *progress* rather than of time, and every eased curve crosses the middle at
69
+ * its fastest: under the driver's default `easeInOut` a nominal 30% hold plays
70
+ * as 17.8% of the wall clock. `holdMs` can only mean milliseconds because of
71
+ * this - see `heldRunCurve`.
50
72
  */
51
73
  export declare class ThroughColor extends ImageTransition<AnimationType> {
52
74
  private duration;
53
75
  private color;
54
76
  private hold;
77
+ private holdMs;
55
78
  private pattern;
56
79
  private inverted;
57
80
  private uncover;
@@ -17,3 +17,50 @@ export declare function overlayBase(color: string): CSSProps;
17
17
  export declare function wipeGradientDir(direction: TransformDefinitions.WipeDirection): string;
18
18
  /** Gradient axis for blinds slats of a given orientation. */
19
19
  export declare function blindsAxis(orientation: BlindsOrientation): string;
20
+ /**
21
+ * The curve an {@link TransformDefinitions.EasingDefinition} names, as a plain function.
22
+ *
23
+ * A transition that carves a *held* segment out of its run cannot let the animation driver ease the
24
+ * whole 0-1 progress: the hold would then be a band of eased progress rather than of wall-clock
25
+ * time, and an eased curve crosses the middle - exactly where the hold sits - at its fastest. Such
26
+ * a transition asks the driver for a linear channel and eases each moving half itself, which is
27
+ * what this resolves the curve for.
28
+ *
29
+ * The fallback is `easeInOut` and not `linear`, because that is what the driver applies when no
30
+ * ease is given (motion's keyframes generator defaults to it): a transition easing its own halves
31
+ * has to land on the same feel as one that does not.
32
+ */
33
+ export declare function resolveEasing(ease?: TransformDefinitions.EasingDefinition): (t: number) => number;
34
+ /**
35
+ * How far a cover -> hold -> uncover run has travelled at a given point of a LINEAR run: `0` is
36
+ * untouched, `1` is at the extreme (fully covered by the colour, fully blown out). Shared by
37
+ * {@link ThroughColor} and {@link Exposure}, which differ only in what they do with the number.
38
+ *
39
+ * The hold is wall-clock time, carved out of `duration` and split evenly off the two moving halves,
40
+ * so `holdMs: 2000` on a 4s run is two seconds at the extreme with a second either side. That is
41
+ * only true because the run itself is linear and the easing is applied to each half here. Eased as
42
+ * a whole - which is what asking the driver for an eased channel does - the hold is a band of
43
+ * *progress* instead, and the curve crosses it at its fastest: under the default `easeInOut` a
44
+ * nominal 30% hold plays as 17.8% of the wall clock, and 50% as 30.8%.
45
+ *
46
+ * `hold`, the fraction of the duration, is the older spelling and is read only when `holdMs` is
47
+ * absent.
48
+ */
49
+ export declare function heldRunCurve(options: {
50
+ duration: number;
51
+ hold?: number;
52
+ holdMs?: number;
53
+ easing?: TransformDefinitions.EasingDefinition;
54
+ }): (progress: number) => number;
55
+ /**
56
+ * The share of a run spent held at the extreme: `holdMs` measured against the duration, or the
57
+ * legacy `hold` fraction when no absolute time is given.
58
+ *
59
+ * A zero-length run has no share to measure, so any positive hold takes all of it - the transition
60
+ * is then a cut to the extreme and back, which is what a zero duration asks for.
61
+ */
62
+ export declare function holdFraction(options: {
63
+ duration: number;
64
+ hold?: number;
65
+ holdMs?: number;
66
+ }): number;
@@ -0,0 +1,23 @@
1
+ import type React from "react";
2
+ import type { WordConfig, WordEmphasis } from "../../../nlcore/elements/character/word";
3
+ /**
4
+ * The size one word of a line is set at.
5
+ *
6
+ * A word sized in absolute units goes through the line's scale multiplier like every other length
7
+ * in the line. A word sized as a share of the line does not: `em` already resolves against the
8
+ * line's own size, which the multiplier has been applied to, so scaling it a second time would
9
+ * bring the word down twice for every step the line comes down.
10
+ */
11
+ export declare function wordFontSize(config: Partial<WordConfig>, sentenceFontSize: React.CSSProperties["fontSize"]): React.CSSProperties["fontSize"];
12
+ /**
13
+ * The same rule where no scaling is in play — the sample line a settings screen types out has no box
14
+ * to fit itself into.
15
+ */
16
+ export declare function previewWordFontSize(config: Partial<WordConfig>, sentenceFontSize: React.CSSProperties["fontSize"]): React.CSSProperties["fontSize"];
17
+ /**
18
+ * The emphasis marks a word carries, as the two declarations that draw them.
19
+ *
20
+ * The position keyword for vertical writing is always `right`: `over` and `under` are read only in
21
+ * horizontal writing, and both Japanese and Chinese set the marks on the right of a vertical column.
22
+ */
23
+ export declare function emphasisStyle(emphasis: WordEmphasis | undefined): React.CSSProperties;