@pkmn/engine 0.1.0-dev.645b4750 → 0.1.0-dev.6b383c80

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 (53) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +22 -61
  3. package/build/pkg/addon/browser.js +2 -2
  4. package/build/pkg/addon/browser.js.map +1 -1
  5. package/build/pkg/addon/node.js +3 -3
  6. package/build/pkg/addon/node.js.map +1 -1
  7. package/build/pkg/addon/wasm.js.map +1 -1
  8. package/build/pkg/addon.d.ts +4 -4
  9. package/build/pkg/addon.js.map +1 -1
  10. package/build/pkg/data.js.map +1 -1
  11. package/build/pkg/gen1.js.map +1 -1
  12. package/build/pkg/protocol.d.ts +11 -2
  13. package/build/pkg/protocol.js.map +1 -1
  14. package/build/tools/debug.js +37 -3
  15. package/build/tools/debug.js.map +1 -1
  16. package/build/tools/display/debug.jsx +1 -87
  17. package/build/tools/display/debug.jsx.map +1 -1
  18. package/build/tools/display/ui.jsx +3 -2
  19. package/build/tools/display/ui.jsx.map +1 -1
  20. package/build/tools/display/util.js +125 -1
  21. package/build/tools/display/util.js.map +1 -1
  22. package/build.zig +224 -350
  23. package/package.json +22 -22
  24. package/src/bin/install-pkmn-engine +23 -22
  25. package/src/lib/bindings/c.zig +28 -82
  26. package/src/lib/bindings/node.zig +7 -11
  27. package/src/lib/bindings/wasm.zig +7 -15
  28. package/src/lib/common/array.zig +9 -16
  29. package/src/lib/common/data.zig +8 -8
  30. package/src/lib/common/debug.zig +46 -61
  31. package/src/lib/common/js.zig +3 -9
  32. package/src/lib/common/optional.zig +20 -25
  33. package/src/lib/common/protocol.zig +224 -189
  34. package/src/lib/common/rational.zig +91 -173
  35. package/src/lib/common/rng.zig +4 -4
  36. package/src/lib/common/util.zig +34 -46
  37. package/src/lib/gen1/calc.zig +128 -115
  38. package/src/lib/gen1/chance.zig +162 -101
  39. package/src/lib/gen1/data/types.zig +2 -2
  40. package/src/lib/gen1/data.zig +27 -24
  41. package/src/lib/gen1/helpers.zig +36 -26
  42. package/src/lib/gen1/mechanics.zig +42 -38
  43. package/src/lib/gen2/calc.zig +3 -6
  44. package/src/lib/gen2/chance.zig +55 -43
  45. package/src/lib/gen2/data/types.zig +6 -3
  46. package/src/lib/gen2/data.zig +20 -16
  47. package/src/lib/gen2/helpers.zig +27 -19
  48. package/src/lib/gen2/mechanics.zig +25 -36
  49. package/src/lib/napi.h +2 -0
  50. package/src/lib/node.zig +1 -5
  51. package/src/lib/pkmn.zig +91 -17
  52. package/src/lib/wasm.zig +2 -6
  53. package/src/tools/display/dom.ts +40 -25
@@ -1,6 +1,5 @@
1
1
  const DEBUG = @import("../common/debug.zig").print;
2
2
 
3
- const builtin = @import("builtin");
4
3
  const common = @import("../common/data.zig");
5
4
  const data = @import("data.zig");
6
5
  const generated = @import("generated.zig");
@@ -130,11 +129,11 @@ fn start(battle: anytype, options: anytype) !Result {
130
129
 
131
130
  const p1_slot = findFirstAlive(p1);
132
131
  assert(!showdown or p1_slot == 1);
133
- if (p1_slot == 0) return if (findFirstAlive(p2) == 0) Result.Tie else Result.Lose;
132
+ if (p1_slot == 0) return if (findFirstAlive(p2) == 0) .Tie else .Lose;
134
133
 
135
134
  const p2_slot = findFirstAlive(p2);
136
135
  assert(!showdown or p2_slot == 1);
137
- if (p2_slot == 0) return Result.Win;
136
+ if (p2_slot == 0) return .Win;
138
137
 
139
138
  try switchIn(battle, .P1, p1_slot, .Initial, options);
140
139
  try switchIn(battle, .P2, p2_slot, .Initial, options);
@@ -682,7 +681,7 @@ pub fn adjustDamage(battle: anytype, player: Player, state: *State, _: anytype)
682
681
 
683
682
  // Type effectiveness matchup precedence only matters with (NVE, SE)
684
683
  if (!showdown and (eff1 + eff2) == Effectiveness.mismatch and
685
- types.type1.precedence() > types.type2.precedence())
684
+ Type.precedence(state.type, types.type1) > Type.precedence(state.type, types.type2))
686
685
  {
687
686
  assert(eff2 != neutral);
688
687
  d = d *% eff2 / 10;
@@ -819,13 +818,13 @@ fn checkFaint(
819
818
 
820
819
  if (player_out and foe_out) {
821
820
  try options.log.tie(.{});
822
- return Result.Tie;
821
+ return .Tie;
823
822
  } else if (player_out) {
824
823
  try options.log.win(.{player.foe()});
825
- return if (player == .P1) Result.Lose else Result.Win;
824
+ return if (player == .P1) .Lose else .Win;
826
825
  } else if (foe_out) {
827
826
  try options.log.win(.{player});
828
- return if (player == .P1) Result.Win else Result.Lose;
827
+ return if (player == .P1) .Win else .Lose;
829
828
  }
830
829
 
831
830
  const foe_choice: Choice.Type = if (foe_fainted) .Switch else .Pass;
@@ -1195,14 +1194,14 @@ fn endTurn(battle: anytype, options: anytype) @TypeOf(options.log).Error!Result
1195
1194
 
1196
1195
  if (pkmn.options.mod and battle.turn >= 1000) {
1197
1196
  try options.log.tie(.{});
1198
- return Result.Tie;
1197
+ return .Tie;
1199
1198
  } else if (battle.turn >= 65535) {
1200
- return Result.Error;
1199
+ return .Error;
1201
1200
  }
1202
1201
 
1203
1202
  try options.log.turn(.{battle.turn});
1204
1203
 
1205
- return Result.Default;
1204
+ return .Default;
1206
1205
  }
1207
1206
 
1208
1207
  pub fn buildRage(battle: anytype, player: Player, state: *State, options: anytype) !void {
@@ -1427,8 +1426,7 @@ pub const Effects = struct {
1427
1426
  if (move.type.special() != (last == .MirrorCoat)) return;
1428
1427
  if (state.damage == 0) return;
1429
1428
 
1430
- // NOTE: llvm/llvm-project#58557
1431
- state.damage = smul(state.damage, 2);
1429
+ state.damage *|= 2;
1432
1430
  state.miss = false;
1433
1431
  }
1434
1432
 
@@ -1523,18 +1521,15 @@ pub const Effects = struct {
1523
1521
 
1524
1522
  pub const double = struct {
1525
1523
  pub fn flying(battle: anytype, player: Player, state: *State, _: anytype) !void {
1526
- if (!battle.foe(player).active.volatiles.Flying) return;
1527
- state.damage = smul(state.damage, 2);
1524
+ if (battle.foe(player).active.volatiles.Flying) state.damage *|= 2;
1528
1525
  }
1529
1526
 
1530
1527
  pub fn underground(battle: anytype, player: Player, state: *State, _: anytype) !void {
1531
- if (!battle.foe(player).active.volatiles.Underground) return;
1532
- state.damage = smul(state.damage, 2);
1528
+ if (battle.foe(player).active.volatiles.Underground) state.damage *|= 2;
1533
1529
  }
1534
1530
 
1535
1531
  pub fn minimize(battle: anytype, player: Player, state: *State, _: anytype) !void {
1536
- if (!battle.foe(player).active.volatiles.minimized) return;
1537
- state.damage = smul(state.damage, 2);
1532
+ if (battle.foe(player).active.volatiles.minimized) state.damage *|= 2;
1538
1533
  }
1539
1534
  };
1540
1535
 
@@ -1558,7 +1553,7 @@ pub const Effects = struct {
1558
1553
  const last = battle.foe(player).lastMove(true);
1559
1554
  const failed = volatiles.Encore or
1560
1555
  (last == .None or last == .Struggle or
1561
- last == .Encore or .last == .MirrorMove);
1556
+ last == .Encore or .last == .MirrorMove);
1562
1557
  if (failed) return options.log.fail(.{ foe_ident, .None });
1563
1558
 
1564
1559
  var slot: u3 = 0;
@@ -1732,7 +1727,7 @@ pub const Effects = struct {
1732
1727
  state.bp *= 2;
1733
1728
  assert(state.bp <= 160);
1734
1729
  } else {
1735
- state.damage = smul(state.damage, 2);
1730
+ state.damage *|= 2;
1736
1731
  }
1737
1732
  }
1738
1733
 
@@ -2203,7 +2198,7 @@ pub const Effects = struct {
2203
2198
 
2204
2199
  pub fn pursuit(battle: anytype, player: Player, state: *State, _: anytype) !void {
2205
2200
  // TODO: remove switching variable and just look at Choice saved in Battle struct?
2206
- if (battle.foe(player).active.volatiles.switching) state.damage = smul(state.damage, 2);
2201
+ if (battle.foe(player).active.volatiles.switching) state.damage *|= 2;
2207
2202
  }
2208
2203
 
2209
2204
  pub const rage = struct {
@@ -2216,7 +2211,7 @@ pub const Effects = struct {
2216
2211
  const side = battle.side(player);
2217
2212
 
2218
2213
  assert(side.active.volatiles.Rage);
2219
- state.damage = smul(state.damage, (side.active.volatiles.rage +| 1)); // TODO: |+
2214
+ state.damage *|= (side.active.volatiles.rage +| 1);
2220
2215
  }
2221
2216
  };
2222
2217
 
@@ -2311,7 +2306,7 @@ pub const Effects = struct {
2311
2306
  side.active.volatiles.attacks += 1;
2312
2307
  side.active.volatiles.Rollout = side.active.volatiles.attacks != 5;
2313
2308
 
2314
- if (side.active.volatiles.DefenseCurl) state.damage = smul(state.damage, 2);
2309
+ if (side.active.volatiles.DefenseCurl) state.damage *|= 2;
2315
2310
  }
2316
2311
  };
2317
2312
 
@@ -2839,12 +2834,6 @@ fn convertible(active: ActivePokemon, m: Move) bool {
2839
2834
  return t != .@"???" and !active.types.includes(t);
2840
2835
  }
2841
2836
 
2842
- // NOTE: llvm/llvm-project#58557
2843
- fn smul(a: anytype, b: @TypeOf(a)) @TypeOf(a) {
2844
- if (!comptime builtin.target.cpu.arch.isWasm()) return a *| b;
2845
- return @max(a *% b, std.math.maxInt(@TypeOf(a)));
2846
- }
2847
-
2848
2837
  pub const Rolls = struct {
2849
2838
  fn speedTie(battle: anytype, options: anytype) !bool {
2850
2839
  const p1 = if (options.calc.overridden(.P1, 0, .speed_tie)) |player|
@@ -3081,7 +3070,7 @@ pub const Rolls = struct {
3081
3070
  }
3082
3071
 
3083
3072
  // Conversion 2 can at most choose between 7 types (vs. a Grass-type attack)
3084
- var CONVERSION_2: [7]Type = [_]Type{.@"???"} ** 7;
3073
+ var CONVERSION_2: [7]Type = @splat(.@"???");
3085
3074
 
3086
3075
  fn conversion2(battle: anytype, player: Player, mtype: Type, options: anytype) !Type {
3087
3076
  var i: u8 = 0;
@@ -3506,11 +3495,11 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
3506
3495
  },
3507
3496
  .Switch => {
3508
3497
  const side = battle.side(player);
3509
- var slot: u4 = 2;
3498
+ var slot: usize = 2;
3510
3499
  while (slot <= 6) : (slot += 1) {
3511
3500
  const id = side.order[slot - 1];
3512
3501
  if (id == 0 or side.pokemon[id - 1].hp == 0) continue;
3513
- out[n] = .{ .type = .Switch, .data = slot };
3502
+ out[n] = .{ .type = .Switch, .data = @intCast(slot) };
3514
3503
  n += 1;
3515
3504
  }
3516
3505
  if (n == 0) {
@@ -3540,11 +3529,11 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
3540
3529
  return n;
3541
3530
  }
3542
3531
 
3543
- var slot: u4 = 2;
3532
+ var slot: usize = 2;
3544
3533
  while (!active.volatiles.Trapped and slot <= 6) : (slot += 1) {
3545
3534
  const id = side.order[slot - 1];
3546
3535
  if (id == 0 or side.pokemon[id - 1].hp == 0) continue;
3547
- out[n] = .{ .type = .Switch, .data = slot };
3536
+ out[n] = .{ .type = .Switch, .data = @intCast(slot) };
3548
3537
  n += 1;
3549
3538
  }
3550
3539
 
@@ -3577,7 +3566,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
3577
3566
  const struggle =
3578
3567
  m.id == .Bide and (m.pp == 0 or active.volatiles.disable.move == slot);
3579
3568
  const s = if (struggle) 0 else slot;
3580
- out[n] = .{ .type = .Move, .data = s };
3569
+ out[n] = .{ .type = .Move, .data = @intCast(s) };
3581
3570
  n += 1;
3582
3571
  return n;
3583
3572
  }
@@ -3591,7 +3580,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
3591
3580
  if (m.id == .None) break;
3592
3581
  if (m.pp == 0) continue;
3593
3582
  if (active.volatiles.disable.move == slot) continue;
3594
- out[n] = .{ .type = .Move, .data = slot };
3583
+ out[n] = .{ .type = .Move, .data = @intCast(slot) };
3595
3584
  n += 1;
3596
3585
  }
3597
3586
  // Struggle (Pokémon Showdown would use 'move 1' here)
package/src/lib/napi.h ADDED
@@ -0,0 +1,2 @@
1
+ #define NAPI_VERSION 8
2
+ #include <node_api.h>
package/src/lib/node.zig CHANGED
@@ -1,14 +1,10 @@
1
+ const c = @import("napi");
1
2
  const js = @import("common/js.zig");
2
3
  const node = @import("bindings/node.zig");
3
4
  const std = @import("std");
4
5
 
5
6
  const assert = std.debug.assert;
6
7
 
7
- const c = @cImport({
8
- @cDefine("NAPI_VERSION", "8");
9
- @cInclude("node_api.h");
10
- });
11
-
12
8
  export fn napi_register_module_v1(env: c.napi_env, exports: c.napi_value) c.napi_value {
13
9
  const properties = [_]c.napi_property_descriptor{
14
10
  js.Property.init("engine", .{ .value = node.register(env) }),
package/src/lib/pkmn.zig CHANGED
@@ -22,9 +22,9 @@ pub const Choice = @import("common/data.zig").Choice;
22
22
  /// considered terminal.
23
23
  pub const Result = @import("common/data.zig").Result;
24
24
 
25
- // TODO
25
+ /// Helpers for working with bit-packed arrays of non-powers-of-2 types.
26
26
  pub const Array = @import("common/array.zig").Array;
27
- // TODO
27
+ /// Optimized optional representation which stores the empty None value as a sentinel.
28
28
  pub const Optional = @import("common/optional.zig").Optional;
29
29
 
30
30
  /// Pokémon Showdown's RNG (backed by a Generation V & VI RNG).
@@ -32,31 +32,78 @@ pub const PSRNG = @import("common/rng.zig").PSRNG;
32
32
 
33
33
  /// Namespace for helpers related to protocol message logging.
34
34
  pub const protocol = if (options.internal) @import("common/protocol.zig") else struct {
35
- /// Logs protocol information to its `Writer` during a battle update when
35
+ /// Logs protocol information to its `WriterT` during a battle update when
36
36
  /// `options.log` is enabled.
37
37
  pub const Log = @import("common/protocol.zig").Log;
38
- /// Stripped down version of `std.io.FixedBufferStream` optimized for
39
- /// efficiently writing the individual protocol bytes. Note that the
40
- /// `ByteStream.Writer` is **not** a `std.io.Writer` and should not be
41
- /// used for general purpose writing.
42
- pub const ByteStream = @import("common/protocol.zig").ByteStream;
43
- /// `Log` type backed by the optimized `ByteStream.Writer`. Intended to be
44
- /// intialized with a `LOGS_SIZE`-sized buffer.
38
+ /// `Log` type backed by the optimized `Writer`. Intended to be intialized with
39
+ /// a `LOGS_SIZE`-sized buffer.
45
40
  pub const FixedLog = @import("common/protocol.zig").FixedLog;
46
- /// Null object pattern implementation of `Log` backed by a
47
- /// `std.io.null_writer`. Ignores anything sent to it, though protocol
48
- /// logging should additionally be turned off entirely with `options.log`.
41
+ /// Minimal logging helper optimized for efficiently writing the individual
42
+ /// protocol bytes into a fixed buffer. Note that this is **not** a
43
+ /// `std.Io.Writer` and should not be used for general purpose writing.
44
+ pub const Writer = @import("common/protocol.zig").Writer;
45
+ /// Null object pattern implementation of `Log` backed by a null writer.
46
+ /// Ignores anything sent to it, though protocol logging should additionally
47
+ /// be turned off entirely with `options.log`.
49
48
  pub const NULL = @import("common/protocol.zig").NULL;
50
49
  };
51
50
 
52
- pub usingnamespace @import("common/rational.zig");
51
+ /// Specialization of a rational number used by the engine to compute probabilties.
52
+ pub const Rational = @import("common/rational.zig").Rational;
53
53
 
54
54
  /// Namespace for cross-generation battle-related types.
55
55
  pub const battle = @import("common/battle.zig");
56
56
 
57
57
  /// Namespace for Generation I Pokémon.
58
58
  pub const gen1 = struct {
59
- pub usingnamespace @import("gen1/data.zig");
59
+ const data = @import("gen1/data.zig");
60
+ /// The minimum size in bytes required to hold all Generation I choice options.
61
+ pub const MAX_CHOICES = data.MAX_CHOICES;
62
+ /// The maximum number of bytes possibly logged by a single Generation I update.
63
+ pub const MAX_LOGS = data.MAX_LOGS;
64
+ /// The optimal size in bytes required to hold all Generation I choice options.
65
+ /// At least as large as MAX_CHOICES.
66
+ pub const CHOICES_SIZE = data.CHOICES_SIZE;
67
+ /// The optimal size in bytes required to hold the largest amount of log data possible from a
68
+ /// single Generation I update. At least as large as MAX_LOGS.
69
+ pub const LOGS_SIZE = data.LOGS_SIZE;
70
+ /// Null object pattern implementation of pkmn.battle.Options for Generation I.
71
+ pub const NULL = data.NULL;
72
+ /// The pseudo random number generator used by Generation I.
73
+ pub const PRNG = data.PRNG;
74
+ /// Representation of a Generation I battle.
75
+ pub const Battle = data.Battle;
76
+ /// Representation of one side of a Generation I Pokémon battle.
77
+ pub const Side = data.Side;
78
+ /// Representation of the state for single Generation I Pokémon while active in battle.
79
+ pub const ActivePokemon = data.ActivePokemon;
80
+ /// Representation of the state for single Generation I Pokémon while inactive in the party.
81
+ pub const Pokemon = data.Pokemon;
82
+ /// Representation of a Generation I Pokémon's move slot in a battle.
83
+ pub const MoveSlot = data.MoveSlot;
84
+ /// Details required to detect desyncs based on the move last selected/executed by players.
85
+ pub const MoveDetails = data.MoveDetails;
86
+ /// Bitfield representation of a Generation I & II Pokémon's major status condition.
87
+ pub const Status = data.Status;
88
+ /// Bitfield representation of volatile statuses and associated data in Generation I.
89
+ pub const Volatiles = data.Volatiles;
90
+ /// Representation of a Pokémon's stats in Generation I.
91
+ pub const Stats = data.Stats;
92
+ /// Representation of a Pokémon's boosts in Generation I.
93
+ pub const Boosts = data.Boosts;
94
+ /// Representation of a Generation I Pokémon move.
95
+ pub const Move = data.Move;
96
+ /// Representation of a Generation I Pokémon species.
97
+ pub const Species = data.Species;
98
+ /// Representation of a Generation I type in Pokémon.
99
+ pub const Type = data.Type;
100
+ /// Representation of a Generation I Pokémon's typing.
101
+ pub const Types = data.Types;
102
+ /// Modifiers for the effectiveness of a type vs. another type in Pokémon.
103
+ pub const Effectiveness = data.Effectiveness;
104
+ /// Representation of a Generation I Pokémon's determinant values.
105
+ pub const DVs = data.DVs;
106
+
60
107
  /// Tracks chance actions and their associated probability during a
61
108
  /// Generation I battle update when `options.chance` is enabled.
62
109
  pub const Chance = @import("gen1/chance.zig").Chance;
@@ -107,7 +154,34 @@ pub const gen1 = struct {
107
154
 
108
155
  /// TODO
109
156
  pub const gen2 = struct {
110
- pub usingnamespace @import("gen2/data.zig");
157
+ const data = @import("gen2/data.zig");
158
+ pub const MAX_CHOICES = data.MAX_CHOICES;
159
+ pub const MAX_LOGS = data.MAX_LOGS;
160
+ pub const CHOICES_SIZE = data.CHOICES_SIZE;
161
+ pub const LOGS_SIZE = data.LOGS_SIZE;
162
+ pub const NULL = data.NULL;
163
+ pub const PRNG = data.PRNG;
164
+ pub const Battle = data.Battle;
165
+ pub const Field = data.Field;
166
+ pub const Weather = data.Weather;
167
+ pub const Side = data.Side;
168
+ pub const ActivePokemon = data.ActivePokemon;
169
+ pub const Pokemon = data.Pokemon;
170
+ pub const Gender = data.Gender;
171
+ pub const DVs = data.DVs;
172
+ pub const MoveSlot = data.MoveSlot;
173
+ pub const Status = data.Status;
174
+ pub const TriAttack = data.TriAttack;
175
+ pub const Volatiles = data.Volatiles;
176
+ pub const Stats = data.Stats;
177
+ pub const Boosts = data.Boosts;
178
+ pub const Item = data.Item;
179
+ pub const Move = data.Move;
180
+ pub const Species = data.Species;
181
+ pub const Type = data.Type;
182
+ pub const Types = data.Types;
183
+ pub const Effectiveness = data.Effectiveness;
184
+
111
185
  pub const Chance = @import("gen2/chance.zig").Chance;
112
186
  pub const chance = struct {
113
187
  pub const Actions = @import("gen2/chance.zig").Actions;
@@ -126,8 +200,8 @@ pub const gen2 = struct {
126
200
  };
127
201
 
128
202
  // Internal APIs used by other pkmn libraries, not actually part of the public API.
129
- // NOTE: `pub usingnamespace struct { ... }` here results in a (false?) dependency loop
130
203
  pub const js = if (options.internal) @import("common/js.zig") else {};
204
+ pub const util = if (options.internal) @import("common/util.zig") else {};
131
205
  pub const bindings = if (options.internal) struct {
132
206
  pub const c = @import("bindings/c.zig");
133
207
  pub const node = @import("bindings/node.zig");
package/src/lib/wasm.zig CHANGED
@@ -9,9 +9,5 @@ export const CALC = wasm.options.calc;
9
9
  export const GEN1_CHOICES_SIZE = wasm.gen(1).CHOICES_SIZE;
10
10
  export const GEN1_LOGS_SIZE = wasm.gen(1).LOGS_SIZE;
11
11
 
12
- const exportable = @hasDecl(std.zig, "Zir") and !@hasField(std.zig.Zir.Inst.Tag, "export_value");
13
-
14
- usingnamespace if (exportable) struct {
15
- export const GEN1_update = wasm.gen(1).update;
16
- export const GEN1_choices = wasm.gen(1).choices;
17
- } else wasm.exports();
12
+ export const GEN1_update = wasm.gen(1).update;
13
+ export const GEN1_choices = wasm.gen(1).choices;
@@ -1,23 +1,18 @@
1
1
  /** @license MIT modified from Vadim Demedes's https://github.com/vadimdemedes/dom-chef */
2
- type Attributes = JSX.IntrinsicElements['div'];
3
- type DocumentFragmentFunction = (props?: any) => DocumentFragment;
4
- type ElementFunction = (props?: any) => HTMLElement;
5
2
 
6
3
  declare global {
7
4
  namespace JSX {
8
- interface Element extends HTMLElement, DocumentFragment {
9
- addEventListener: HTMLElement['addEventListener'];
10
- removeEventListener: HTMLElement['removeEventListener'];
11
- className: HTMLElement['className'];
5
+ type Element = HTMLElement | DocumentFragment;
6
+ interface IntrinsicElements {
7
+ [elemName: string]: Record<string, unknown>;
12
8
  }
13
9
  }
14
10
  }
15
11
 
16
- interface JSXElementClassDocumentFragment extends DocumentFragment, JSX.ElementClass { }
17
- interface Fragment {
18
- prototype: JSXElementClassDocumentFragment;
19
- new(): JSXElementClassDocumentFragment;
20
- }
12
+ type Child = Node | string | number | boolean | null | undefined | Child[];
13
+
14
+ type DocumentFragmentFunction = (props?: any) => DocumentFragment;
15
+ type ElementFunction = (props?: any) => HTMLElement;
21
16
 
22
17
  // https://github.com/preactjs/preact/blob/1bbd687c/src/constants.js#L3
23
18
  const IS_NON_DIMENSIONAL = /acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i;
@@ -38,14 +33,14 @@ const setAttribute = (element: HTMLElement, name: string, value: string) => {
38
33
  if (value !== undefined && value !== null) element.setAttribute(name, value);
39
34
  };
40
35
 
41
- const addChildren = (parent: Element | DocumentFragment, children: Node[]) => {
36
+ const addChildren = (parent: Node, children: Child[]) => {
42
37
  for (const child of children) {
43
38
  if (child instanceof Node) {
44
39
  parent.appendChild(child);
45
40
  } else if (Array.isArray(child)) {
46
41
  addChildren(parent, child);
47
42
  } else if (typeof child !== 'boolean' && typeof child !== 'undefined' && child !== null) {
48
- parent.appendChild(document.createTextNode(child));
43
+ parent.appendChild(document.createTextNode(String(child)));
49
44
  }
50
45
  }
51
46
  };
@@ -53,11 +48,31 @@ const addChildren = (parent: Element | DocumentFragment, children: Node[]) => {
53
48
  // https://github.com/facebook/react/blob/3f899089/packages/react-dom/src/shared/DOMProperty.js#L288-L322
54
49
  const FALSIFIABLE_ATTRIBUTES = ['contentEditable', 'draggable', 'spellCheck', 'value'];
55
50
 
56
- export const h = (
51
+ export function h<K extends keyof HTMLElementTagNameMap>(
52
+ type: K,
53
+ attributes?: any,
54
+ ...children: Child[]
55
+ ): HTMLElementTagNameMap[K];
56
+ export function h(
57
+ type: DocumentFragmentFunction,
58
+ attributes?: any,
59
+ ...children: Child[]
60
+ ): DocumentFragment;
61
+ export function h<T extends HTMLElement>(
62
+ type: (props: any) => T,
63
+ attributes?: any,
64
+ ...children: Child[]
65
+ ): T;
66
+ export function h(
67
+ type: string,
68
+ attributes?: any,
69
+ ...children: Child[]
70
+ ): HTMLElement;
71
+ export function h(
57
72
  type: DocumentFragmentFunction | ElementFunction | string,
58
- attributes?: Attributes,
59
- ...children: Node[]
60
- ) => {
73
+ attributes?: any,
74
+ ...children: Child[]
75
+ ): JSX.Element {
61
76
  if (typeof type !== 'string') {
62
77
  const element = type(attributes);
63
78
  addChildren(element, children);
@@ -75,19 +90,19 @@ export const h = (
75
90
  const existingClassname = element.getAttribute('class') ?? '';
76
91
  setAttribute(element, 'class', (existingClassname + ' ' + String(value)).trim());
77
92
  } else if (name === 'style') {
78
- setCSSProps(element, value);
93
+ setCSSProps(element, value as CSSStyleDeclaration);
79
94
  } else if (name.startsWith('on')) {
80
95
  const eventName = name.slice(2).toLowerCase().replace(/^-/, '');
81
- element.addEventListener(eventName, value);
82
- } else if (name === 'dangerouslySetInnerHTML' && '__html' in value) {
83
- element.innerHTML = value.__html;
96
+ element.addEventListener(eventName, value as EventListenerOrEventListenerObject);
97
+ } else if (name === 'dangerouslySetInnerHTML' && value && (value as any).__html) {
98
+ element.innerHTML = (value as any).__html;
84
99
  } else if (name !== 'key' && (FALSIFIABLE_ATTRIBUTES.includes(name) || value !== false)) {
85
- setAttribute(element, name, value === true ? '' : value);
100
+ setAttribute(element, name, value === true ? '' : String(value));
86
101
  }
87
102
  }
88
103
 
89
104
  return element;
90
- };
105
+ }
91
106
 
92
107
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
93
- export const Fragment = (unused: any) => document.createDocumentFragment();
108
+ export const Fragment = (unused?: any) => document.createDocumentFragment();