@pkmn/engine 0.1.0-dev.f4ba6ebd → 0.1.0-dev.f6545544

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 (77) hide show
  1. package/README.md +5 -4
  2. package/build/pkg/addon/browser.d.ts +3 -0
  3. package/build/pkg/addon/browser.js +47 -0
  4. package/build/pkg/addon/browser.js.map +1 -0
  5. package/build/pkg/addon/node.d.ts +3 -0
  6. package/build/pkg/addon/node.js +110 -0
  7. package/build/pkg/addon/node.js.map +1 -0
  8. package/build/pkg/addon/wasm.d.ts +2 -0
  9. package/build/pkg/addon/wasm.js +53 -0
  10. package/build/pkg/addon/wasm.js.map +1 -0
  11. package/build/pkg/addon.d.ts +25 -3
  12. package/build/pkg/addon.js +34 -65
  13. package/build/pkg/addon.js.map +1 -1
  14. package/build/pkg/data/layout.json +15 -2
  15. package/build/pkg/data/protocol.json +23 -9
  16. package/build/pkg/data.d.ts +23 -1
  17. package/build/pkg/data.js.map +1 -1
  18. package/build/pkg/gen1.d.ts +15 -18
  19. package/build/pkg/gen1.js +28 -19
  20. package/build/pkg/gen1.js.map +1 -1
  21. package/build/pkg/index.d.ts +39 -89
  22. package/build/pkg/index.js +23 -70
  23. package/build/pkg/index.js.map +1 -1
  24. package/build/pkg/protocol.d.ts +20 -1
  25. package/build/pkg/protocol.js +105 -103
  26. package/build/pkg/protocol.js.map +1 -1
  27. package/build/tools/debug.js +52 -76
  28. package/build/tools/debug.js.map +1 -1
  29. package/build/tools/display/debug.jsx +152 -0
  30. package/build/tools/display/debug.jsx.map +1 -0
  31. package/build/tools/display/index.js +152 -0
  32. package/build/tools/display/index.js.map +1 -0
  33. package/build/tools/display/ui.jsx +359 -0
  34. package/build/tools/display/ui.jsx.map +1 -0
  35. package/build/tools/display/util.js +33 -0
  36. package/build/tools/display/util.js.map +1 -0
  37. package/build.zig +196 -84
  38. package/package.json +51 -135
  39. package/src/bin/install-pkmn-engine +3 -2
  40. package/src/bin/pkmn-debug +1 -1
  41. package/src/lib/bindings/c.zig +255 -0
  42. package/src/lib/bindings/node.zig +121 -0
  43. package/src/lib/bindings/wasm.zig +59 -0
  44. package/src/lib/common/array.zig +1 -1
  45. package/src/lib/common/debug.zig +39 -8
  46. package/src/lib/common/optional.zig +1 -1
  47. package/src/lib/common/options.zig +4 -0
  48. package/src/lib/common/protocol.zig +4 -8
  49. package/src/lib/common/rational.zig +7 -5
  50. package/src/lib/common/rng.zig +1 -3
  51. package/src/lib/common/util.zig +1 -1
  52. package/src/lib/gen1/calc.zig +208 -61
  53. package/src/lib/gen1/chance.zig +40 -22
  54. package/src/lib/gen1/data/moves.zig +2 -4
  55. package/src/lib/gen1/data/species.zig +1 -3
  56. package/src/lib/gen1/data/types.zig +1 -2
  57. package/src/lib/gen1/data.zig +9 -13
  58. package/src/lib/gen1/helpers.zig +5 -21
  59. package/src/lib/gen1/mechanics.zig +107 -82
  60. package/src/lib/gen2/calc.zig +9 -17
  61. package/src/lib/gen2/chance.zig +0 -3
  62. package/src/lib/gen2/data/items.zig +1 -3
  63. package/src/lib/gen2/data/moves.zig +2 -4
  64. package/src/lib/gen2/data/species.zig +1 -3
  65. package/src/lib/gen2/data/types.zig +1 -2
  66. package/src/lib/gen2/data.zig +11 -15
  67. package/src/lib/gen2/generated.zig +18 -23
  68. package/src/lib/gen2/helpers.zig +5 -16
  69. package/src/lib/gen2/mechanics.zig +41 -44
  70. package/src/lib/node.zig +3 -116
  71. package/src/lib/pkmn.zig +7 -2
  72. package/src/lib/wasm.zig +13 -9
  73. package/src/tools/display/dom.ts +95 -0
  74. package/src/tools/display/ui.css +354 -0
  75. package/build/test/display.js +0 -405
  76. package/build/test/display.js.map +0 -1
  77. package/src/test/display/pkmn.html.tmpl +0 -368
@@ -1,31 +1,20 @@
1
- const std = @import("std");
2
-
3
1
  const common = @import("../common/data.zig");
2
+ const data = @import("data.zig");
3
+ const gen1 = @import("../gen1/data.zig");
4
4
  const options = @import("../common/options.zig");
5
- const protocol = @import("../common/protocol.zig");
6
5
  const rng = @import("../common/rng.zig");
7
-
8
- const gen1 = @import("../gen1/data.zig");
9
-
10
- const data = @import("data.zig");
6
+ const std = @import("std");
11
7
 
12
8
  const assert = std.debug.assert;
13
-
14
- const expectEqual = std.testing.expectEqual;
15
-
16
9
  const Choice = common.Choice;
17
-
18
- const showdown = options.showdown;
19
-
20
- const PSRNG = rng.PSRNG;
21
-
22
10
  const DVs = data.DVs;
23
11
  const Item = data.Item;
24
12
  const Move = data.Move;
25
13
  const MoveSlot = data.MoveSlot;
14
+ const PSRNG = rng.PSRNG;
15
+ const showdown = options.showdown;
26
16
  const Species = data.Species;
27
17
  const Stats = data.Stats;
28
- const Status = data.Status;
29
18
 
30
19
  const Struct = if (@hasField(std.builtin.Type, "struct")) .@"struct" else .Struct;
31
20
 
@@ -1,50 +1,37 @@
1
- const std = @import("std");
2
-
3
- const pkmn = @import("../pkmn.zig");
4
-
5
- const common = @import("../common/data.zig");
6
1
  const DEBUG = @import("../common/debug.zig").print;
7
- const protocol = @import("../common/protocol.zig");
8
- const rng = @import("../common/rng.zig");
9
- const util = @import("../common/util.zig");
10
2
 
3
+ const builtin = @import("builtin");
4
+ const common = @import("../common/data.zig");
11
5
  const data = @import("data.zig");
12
6
  const generated = @import("generated.zig");
13
-
14
- const assert = std.debug.assert;
15
-
16
- const expectEqual = std.testing.expectEqual;
17
- const expectEqualSlices = std.testing.expectEqualSlices;
18
- const expectEqualStrings = std.testing.expectEqualStrings;
19
-
20
- const showdown = pkmn.options.showdown;
21
-
22
- const Choice = common.Choice;
23
- const ID = common.ID;
24
- const Player = common.Player;
25
- const Result = common.Result;
7
+ const pkmn = @import("../pkmn.zig");
8
+ const protocol = @import("../common/protocol.zig");
9
+ const rng = @import("../common/rng.zig");
10
+ const std = @import("std");
26
11
 
27
12
  const Activate = protocol.Activate;
13
+ const ActivePokemon = data.ActivePokemon;
14
+ const assert = std.debug.assert;
28
15
  const Boost = protocol.Boost;
16
+ const Boosts = data.Boosts;
17
+ const Choice = common.Choice;
29
18
  const Damage = protocol.Damage;
19
+ const Effectiveness = data.Effectiveness;
30
20
  const End = protocol.End;
21
+ const expectEqual = std.testing.expectEqual;
22
+ const expectEqualSlices = std.testing.expectEqualSlices;
23
+ const expectEqualStrings = std.testing.expectEqualStrings;
31
24
  const Fail = protocol.Fail;
32
- const Heal = protocol.Heal;
33
- const Start = protocol.Start;
34
-
35
25
  const Gen12 = rng.Gen12;
36
-
37
- const PointerType = util.PointerType;
38
-
39
- const ActivePokemon = data.ActivePokemon;
40
- const Boosts = data.Boosts;
41
- const Effectiveness = data.Effectiveness;
26
+ const Heal = protocol.Heal;
27
+ const ID = common.ID;
42
28
  const Item = data.Item;
43
29
  const Move = data.Move;
44
- const MoveSlot = data.MoveSlot;
45
- const Pokemon = data.Pokemon;
30
+ const Player = common.Player;
31
+ const Result = common.Result;
32
+ const showdown = pkmn.options.showdown;
46
33
  const Side = data.Side;
47
- const Species = data.Species;
34
+ const Start = protocol.Start;
48
35
  const Stats = data.Stats;
49
36
  const Status = data.Status;
50
37
  const TriAttack = data.TriAttack;
@@ -1206,7 +1193,7 @@ fn updateSideCondition(
1206
1193
  fn endTurn(battle: anytype, options: anytype) @TypeOf(options.log).Error!Result {
1207
1194
  battle.turn += 1;
1208
1195
 
1209
- if (showdown and battle.turn >= 1000) {
1196
+ if (pkmn.options.mod and battle.turn >= 1000) {
1210
1197
  try options.log.tie(.{});
1211
1198
  return Result.Tie;
1212
1199
  } else if (battle.turn >= 65535) {
@@ -1440,7 +1427,8 @@ pub const Effects = struct {
1440
1427
  if (move.type.special() != (last == .MirrorCoat)) return;
1441
1428
  if (state.damage == 0) return;
1442
1429
 
1443
- state.damage *|= 2;
1430
+ // NOTE: llvm/llvm-project#58557
1431
+ state.damage = smul(state.damage, 2);
1444
1432
  state.miss = false;
1445
1433
  }
1446
1434
 
@@ -1535,15 +1523,18 @@ pub const Effects = struct {
1535
1523
 
1536
1524
  pub const double = struct {
1537
1525
  pub fn flying(battle: anytype, player: Player, state: *State, _: anytype) !void {
1538
- if (battle.foe(player).active.volatiles.Flying) state.damage *|= 2;
1526
+ if (!battle.foe(player).active.volatiles.Flying) return;
1527
+ state.damage = smul(state.damage, 2);
1539
1528
  }
1540
1529
 
1541
1530
  pub fn underground(battle: anytype, player: Player, state: *State, _: anytype) !void {
1542
- if (battle.foe(player).active.volatiles.Underground) state.damage *|= 2;
1531
+ if (!battle.foe(player).active.volatiles.Underground) return;
1532
+ state.damage = smul(state.damage, 2);
1543
1533
  }
1544
1534
 
1545
1535
  pub fn minimize(battle: anytype, player: Player, state: *State, _: anytype) !void {
1546
- if (battle.foe(player).active.volatiles.minimized) state.damage *|= 2;
1536
+ if (!battle.foe(player).active.volatiles.minimized) return;
1537
+ state.damage = smul(state.damage, 2);
1547
1538
  }
1548
1539
  };
1549
1540
 
@@ -1712,7 +1703,7 @@ pub const Effects = struct {
1712
1703
  if (!state.proc) return;
1713
1704
  if (foe.conditions.Safeguard) return;
1714
1705
  // Freeze Clause Mod
1715
- if (showdown) for (foe.pokemon) |p| if (Status.is(p.status, .FRZ)) return;
1706
+ if (pkmn.options.mod) for (foe.pokemon) |p| if (Status.is(p.status, .FRZ)) return;
1716
1707
 
1717
1708
  foe_stored.status = Status.init(.FRZ);
1718
1709
  try options.log.status(.{ foe_ident, foe_stored.status, .None });
@@ -1741,7 +1732,7 @@ pub const Effects = struct {
1741
1732
  state.bp *= 2;
1742
1733
  assert(state.bp <= 160);
1743
1734
  } else {
1744
- state.damage *|= 2;
1735
+ state.damage = smul(state.damage, 2);
1745
1736
  }
1746
1737
  }
1747
1738
 
@@ -2212,7 +2203,7 @@ pub const Effects = struct {
2212
2203
 
2213
2204
  pub fn pursuit(battle: anytype, player: Player, state: *State, _: anytype) !void {
2214
2205
  // TODO: remove switching variable and just look at Choice saved in Battle struct?
2215
- if (battle.foe(player).active.volatiles.switching) state.damage *|= 2;
2206
+ if (battle.foe(player).active.volatiles.switching) state.damage = smul(state.damage, 2);
2216
2207
  }
2217
2208
 
2218
2209
  pub const rage = struct {
@@ -2225,7 +2216,7 @@ pub const Effects = struct {
2225
2216
  const side = battle.side(player);
2226
2217
 
2227
2218
  assert(side.active.volatiles.Rage);
2228
- state.damage *|= (side.active.volatiles.rage +| 1);
2219
+ state.damage = smul(state.damage, (side.active.volatiles.rage +| 1)); // TODO: |+
2229
2220
  }
2230
2221
  };
2231
2222
 
@@ -2320,7 +2311,7 @@ pub const Effects = struct {
2320
2311
  side.active.volatiles.attacks += 1;
2321
2312
  side.active.volatiles.Rollout = side.active.volatiles.attacks != 5;
2322
2313
 
2323
- if (side.active.volatiles.DefenseCurl) state.damage *|= 2;
2314
+ if (side.active.volatiles.DefenseCurl) state.damage = smul(state.damage, 2);
2324
2315
  }
2325
2316
  };
2326
2317
 
@@ -2390,7 +2381,7 @@ pub const Effects = struct {
2390
2381
  }
2391
2382
 
2392
2383
  // Sleep Clause Mod
2393
- if (showdown) {
2384
+ if (pkmn.options.mod) {
2394
2385
  for (foe.pokemon) |p| {
2395
2386
  if (Status.is(p.status, .SLP) and !Status.is(p.status, .EXT)) return;
2396
2387
  }
@@ -2848,6 +2839,12 @@ fn convertible(active: ActivePokemon, m: Move) bool {
2848
2839
  return t != .@"???" and !active.types.includes(t);
2849
2840
  }
2850
2841
 
2842
+ // NOTE: llvm/llvm-project#58557
2843
+ fn smul(a: anytype, b: @TypeOf(a)) @TypeOf(a) {
2844
+ if (!comptime builtin.target.isWasm()) return a *| b;
2845
+ return @max(a *% b, std.math.maxInt(@TypeOf(a)));
2846
+ }
2847
+
2851
2848
  pub const Rolls = struct {
2852
2849
  fn speedTie(battle: anytype, options: anytype) !bool {
2853
2850
  const p1 = if (options.calc.overridden(.P1, 0, .speed_tie)) |player|
package/src/lib/node.zig CHANGED
@@ -1,8 +1,6 @@
1
- const std = @import("std");
2
-
3
- const pkmn = @import("pkmn.zig");
4
-
5
1
  const js = @import("common/js.zig");
2
+ const node = @import("bindings/node.zig");
3
+ const std = @import("std");
6
4
 
7
5
  const assert = std.debug.assert;
8
6
 
@@ -13,119 +11,8 @@ const c = @cImport({
13
11
 
14
12
  export fn napi_register_module_v1(env: c.napi_env, exports: c.napi_value) c.napi_value {
15
13
  const properties = [_]c.napi_property_descriptor{
16
- js.Property.init("engine", .{ .value = register(env) }),
14
+ js.Property.init("engine", .{ .value = node.register(env) }),
17
15
  };
18
16
  assert(c.napi_define_properties(env, exports, properties.len, &properties) == c.napi_ok);
19
17
  return exports;
20
18
  }
21
-
22
- pub fn register(env: c.napi_env) c.napi_value {
23
- const object = js.Object.init(env);
24
- const properties = [_]c.napi_property_descriptor{
25
- js.Property.init("options", .{ .value = options(env) }),
26
- js.Property.init("bindings", .{ .value = bindings(env) }),
27
- };
28
- assert(c.napi_define_properties(env, object, properties.len, &properties) == c.napi_ok);
29
- return object;
30
- }
31
-
32
- fn options(env: c.napi_env) c.napi_value {
33
- const object = js.Object.init(env);
34
- const properties = [_]c.napi_property_descriptor{
35
- js.Property.init("showdown", .{ .value = js.Boolean.init(env, pkmn.options.showdown) }),
36
- js.Property.init("log", .{ .value = js.Boolean.init(env, pkmn.options.log) }),
37
- js.Property.init("chance", .{ .value = js.Boolean.init(env, pkmn.options.chance) }),
38
- js.Property.init("calc", .{ .value = js.Boolean.init(env, pkmn.options.calc) }),
39
- };
40
- assert(c.napi_define_properties(env, object, properties.len, &properties) == c.napi_ok);
41
- return object;
42
- }
43
-
44
- fn bindings(env: c.napi_env) c.napi_value {
45
- const array = js.Array.init(env, .{ .length = 1 });
46
- js.Array.set(env, array, 0, bind(env, pkmn.gen1));
47
- return array;
48
- }
49
-
50
- fn bind(env: c.napi_env, gen: anytype) c.napi_value {
51
- const choices_size: u32 = @intCast(gen.CHOICES_SIZE);
52
- const logs_size: u32 = @intCast(gen.LOGS_SIZE);
53
- const object = js.Object.init(env);
54
- const properties = [_]c.napi_property_descriptor{
55
- js.Property.init("CHOICES_SIZE", .{ .value = js.Number.init(env, choices_size) }),
56
- js.Property.init("LOGS_SIZE", .{ .value = js.Number.init(env, logs_size) }),
57
- js.Property.init("update", .{ .method = update(gen) }),
58
- js.Property.init("choices", .{ .method = choices(gen) }),
59
- };
60
- assert(c.napi_define_properties(env, object, properties.len, &properties) == c.napi_ok);
61
- return object;
62
- }
63
-
64
- fn update(gen: anytype) c.napi_callback {
65
- return struct {
66
- fn call(env: c.napi_env, info: c.napi_callback_info) callconv(.C) c.napi_value {
67
- var argc: usize = 4;
68
- var argv: [4]c.napi_value = undefined;
69
- assert(c.napi_get_cb_info(env, info, &argc, &argv, null, null) == c.napi_ok);
70
- assert(argc == 4);
71
-
72
- var data: ?*anyopaque = undefined;
73
- var len: usize = 0;
74
- assert(c.napi_get_arraybuffer_info(env, argv[0], &data, &len) == c.napi_ok);
75
- assert(len == @sizeOf(gen.Battle(gen.PRNG)));
76
-
77
- var battle: *gen.Battle(gen.PRNG) = @alignCast(@ptrCast(data.?));
78
- const c1: pkmn.Choice = @bitCast(js.Number.get(env, argv[1], u8));
79
- const c2: pkmn.Choice = @bitCast(js.Number.get(env, argv[2], u8));
80
-
81
- var vtype: c.napi_valuetype = undefined;
82
- assert(c.napi_typeof(env, argv[3], &vtype) == c.napi_ok);
83
- const result = switch (vtype) {
84
- c.napi_undefined, c.napi_null => battle.update(c1, c2, &gen.NULL),
85
- else => result: {
86
- assert(c.napi_get_arraybuffer_info(env, argv[3], &data, &len) == c.napi_ok);
87
- assert(len == gen.LOGS_SIZE);
88
-
89
- const buf = @as([*]u8, @ptrCast(data.?))[0..gen.LOGS_SIZE];
90
- var stream: pkmn.protocol.ByteStream = .{ .buffer = buf };
91
- // TODO: extract out
92
- var opts = pkmn.battle.options(
93
- pkmn.protocol.FixedLog{ .writer = stream.writer() },
94
- gen.chance.NULL,
95
- gen.calc.NULL,
96
- );
97
- break :result battle.update(c1, c2, &opts);
98
- },
99
- } catch unreachable;
100
-
101
- return js.Number.init(env, @as(u8, @bitCast(result)));
102
- }
103
- }.call;
104
- }
105
-
106
- fn choices(gen: anytype) c.napi_callback {
107
- return struct {
108
- fn call(env: c.napi_env, info: c.napi_callback_info) callconv(.C) c.napi_value {
109
- var argc: usize = 4;
110
- var argv: [4]c.napi_value = undefined;
111
- assert(c.napi_get_cb_info(env, info, &argc, &argv, null, null) == c.napi_ok);
112
- assert(argc == 4);
113
-
114
- var data: ?*anyopaque = undefined;
115
- var len: usize = 0;
116
- assert(c.napi_get_arraybuffer_info(env, argv[0], &data, &len) == c.napi_ok);
117
- assert(len == @sizeOf(gen.Battle(gen.PRNG)));
118
-
119
- var battle: *gen.Battle(gen.PRNG) = @alignCast(@ptrCast(data.?));
120
- const player: pkmn.Player = @enumFromInt(js.Number.get(env, argv[1], u8));
121
- const request: pkmn.Choice.Type = @enumFromInt(js.Number.get(env, argv[2], u8));
122
-
123
- assert(c.napi_get_arraybuffer_info(env, argv[3], &data, &len) == c.napi_ok);
124
- assert(len == gen.CHOICES_SIZE);
125
-
126
- const out = @as([*]pkmn.Choice, @ptrCast(data.?))[0..gen.CHOICES_SIZE];
127
- const n = battle.choices(player, request, out);
128
- return js.Number.init(env, @as(u8, @bitCast(n)));
129
- }
130
- }.call;
131
- }
package/src/lib/pkmn.zig CHANGED
@@ -96,6 +96,10 @@ pub const gen1 = struct {
96
96
  /// though damage calculator support should additionally be turned off
97
97
  /// entirely via `options.calc`.
98
98
  pub const NULL = @import("gen1/calc.zig").NULL;
99
+ /// TODO
100
+ pub const MAX_FRONTIER = @import("gen1/calc.zig").MAX_FRONTIER;
101
+ /// TODO
102
+ pub const Rolls = @import("gen1/calc.zig").Rolls;
99
103
  };
100
104
  /// Provides helpers for initializing Generation I Pokémon battles.
101
105
  pub const helpers = @import("gen1/helpers.zig");
@@ -125,6 +129,7 @@ pub const gen2 = struct {
125
129
  // NOTE: `pub usingnamespace struct { ... }` here results in a (false?) dependency loop
126
130
  pub const js = if (options.internal) @import("common/js.zig") else {};
127
131
  pub const bindings = if (options.internal) struct {
128
- pub const node = @import("node.zig");
129
- pub const wasm = @import("wasm.zig");
132
+ pub const c = @import("bindings/c.zig");
133
+ pub const node = @import("bindings/node.zig");
134
+ pub const wasm = @import("bindings/wasm.zig");
130
135
  } else {};
package/src/lib/wasm.zig CHANGED
@@ -1,13 +1,17 @@
1
1
  const std = @import("std");
2
+ const wasm = @import("bindings/wasm.zig");
2
3
 
3
- const pkmn = @import("pkmn.zig");
4
+ export const SHOWDOWN = wasm.options.showdown;
5
+ export const LOG = wasm.options.log;
6
+ export const CHANCE = wasm.options.chance;
7
+ export const CALC = wasm.options.calc;
4
8
 
5
- export const SHOWDOWN = pkmn.options.showdown;
6
- export const LOG = pkmn.options.log;
7
- export const CHANCE = pkmn.options.chance;
8
- export const CALC = pkmn.options.calc;
9
+ export const GEN1_CHOICES_SIZE = wasm.gen(1).CHOICES_SIZE;
10
+ export const GEN1_LOGS_SIZE = wasm.gen(1).LOGS_SIZE;
9
11
 
10
- export const GEN1_CHOICES_SIZE =
11
- std.math.ceilPowerOfTwo(u32, @as(u32, @intCast(pkmn.gen1.CHOICES_SIZE))) catch unreachable;
12
- export const GEN1_LOGS_SIZE =
13
- std.math.ceilPowerOfTwo(u32, @as(u32, @intCast(pkmn.gen1.LOGS_SIZE))) catch unreachable;
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();
@@ -0,0 +1,95 @@
1
+ /** @license MIT modified from Vadim Demedes's https://github.com/vadimdemedes/dom-chef */
2
+ type Attributes = JSX.IntrinsicElements['div'];
3
+ type DocumentFragmentConstructor = typeof DocumentFragment;
4
+ type ElementFunction = (props?: any) => HTMLElement;
5
+
6
+ declare global {
7
+ namespace JSX {
8
+ interface Element extends HTMLElement, DocumentFragment {
9
+ addEventListener: HTMLElement['addEventListener'];
10
+ removeEventListener: HTMLElement['removeEventListener'];
11
+ className: HTMLElement['className'];
12
+ }
13
+ }
14
+ }
15
+
16
+ interface JSXElementClassDocumentFragment extends DocumentFragment, JSX.ElementClass { }
17
+ interface Fragment {
18
+ prototype: JSXElementClassDocumentFragment;
19
+ new(): JSXElementClassDocumentFragment;
20
+ }
21
+
22
+ // https://github.com/preactjs/preact/blob/1bbd687c/src/constants.js#L3
23
+ const IS_NON_DIMENSIONAL = /acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i;
24
+
25
+ const isFragment = (type: DocumentFragmentConstructor | ElementFunction):
26
+ type is DocumentFragmentConstructor => type === DocumentFragment;
27
+
28
+ const setCSSProps = (element: HTMLElement, style: CSSStyleDeclaration) => {
29
+ for (const [name, value] of Object.entries(style)) {
30
+ if (name.startsWith('-')) {
31
+ element.style.setProperty(name, value);
32
+ } else if (typeof value === 'number' && !IS_NON_DIMENSIONAL.test(name)) {
33
+ element.style[name as any] = `${value as string}px`;
34
+ } else {
35
+ element.style[name as any] = value;
36
+ }
37
+ }
38
+ };
39
+
40
+ const setAttribute = (element: HTMLElement, name: string, value: string) => {
41
+ if (value !== undefined && value !== null) element.setAttribute(name, value);
42
+ };
43
+
44
+ const addChildren = (parent: Element | DocumentFragment, children: Node[]) => {
45
+ for (const child of children) {
46
+ if (child instanceof Node) {
47
+ parent.appendChild(child);
48
+ } else if (Array.isArray(child)) {
49
+ addChildren(parent, child);
50
+ } else if (typeof child !== 'boolean' && typeof child !== 'undefined' && child !== null) {
51
+ parent.appendChild(document.createTextNode(child));
52
+ }
53
+ }
54
+ };
55
+
56
+ // https://github.com/facebook/react/blob/3f899089/packages/react-dom/src/shared/DOMProperty.js#L288-L322
57
+ const FALSIFIABLE_ATTRIBUTES = ['contentEditable', 'draggable', 'spellCheck', 'value'];
58
+
59
+ export const h = (
60
+ type: DocumentFragmentConstructor | ElementFunction | string,
61
+ attributes?: Attributes,
62
+ ...children: Node[]
63
+ ) => {
64
+ if (typeof type !== 'string') {
65
+ const element = isFragment(type) ? document.createDocumentFragment() : type(attributes);
66
+ addChildren(element, children);
67
+ return element;
68
+ }
69
+
70
+ const element = document.createElement(type);
71
+ addChildren(element, children);
72
+ if (!attributes) return element;
73
+
74
+ for (let [name, value] of Object.entries(attributes)) {
75
+ if (name === 'htmlFor') name = 'for';
76
+
77
+ if (name === 'class' || name === 'className') {
78
+ const existingClassname = element.getAttribute('class') ?? '';
79
+ setAttribute(element, 'class', (existingClassname + ' ' + String(value)).trim());
80
+ } else if (name === 'style') {
81
+ setCSSProps(element, value);
82
+ } else if (name.startsWith('on')) {
83
+ const eventName = name.slice(2).toLowerCase().replace(/^-/, '');
84
+ element.addEventListener(eventName, value);
85
+ } else if (name === 'dangerouslySetInnerHTML' && '__html' in value) {
86
+ element.innerHTML = value.__html;
87
+ } else if (name !== 'key' && (FALSIFIABLE_ATTRIBUTES.includes(name) || value !== false)) {
88
+ setAttribute(element, name, value === true ? '' : value);
89
+ }
90
+ }
91
+
92
+ return element;
93
+ };
94
+
95
+ export const Fragment = (props: {children: any}) => props.children as Fragment;