ice-web-components 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +487 -0
  3. package/dist/index.cjs +1 -0
  4. package/dist/index.mjs +1 -0
  5. package/dist/index.umd.js +1 -0
  6. package/dist/types/components/ICEAlert.d.ts +28 -0
  7. package/dist/types/components/ICEAnchor.d.ts +69 -0
  8. package/dist/types/components/ICEAutoComplete.d.ts +71 -0
  9. package/dist/types/components/ICEAvatar.d.ts +11 -0
  10. package/dist/types/components/ICEAvatarGroup.d.ts +40 -0
  11. package/dist/types/components/ICEBackTop.d.ts +39 -0
  12. package/dist/types/components/ICEBadge.d.ts +17 -0
  13. package/dist/types/components/ICEBreadcrumb.d.ts +82 -0
  14. package/dist/types/components/ICEButton.d.ts +25 -0
  15. package/dist/types/components/ICECalendar.d.ts +84 -0
  16. package/dist/types/components/ICECard.d.ts +22 -0
  17. package/dist/types/components/ICECarousel.d.ts +86 -0
  18. package/dist/types/components/ICECascader.d.ts +84 -0
  19. package/dist/types/components/ICECheckBox.d.ts +21 -0
  20. package/dist/types/components/ICECheckboxGroup.d.ts +75 -0
  21. package/dist/types/components/ICECollapse.d.ts +49 -0
  22. package/dist/types/components/ICEColorPicker.d.ts +52 -0
  23. package/dist/types/components/ICEComment.d.ts +36 -0
  24. package/dist/types/components/ICEDatePicker.d.ts +90 -0
  25. package/dist/types/components/ICEDescriptions.d.ts +40 -0
  26. package/dist/types/components/ICEDrawer.d.ts +59 -0
  27. package/dist/types/components/ICEDropdown.d.ts +63 -0
  28. package/dist/types/components/ICEEmpty.d.ts +28 -0
  29. package/dist/types/components/ICEFloatButton.d.ts +63 -0
  30. package/dist/types/components/ICEForm.d.ts +48 -0
  31. package/dist/types/components/ICEFormItem.d.ts +65 -0
  32. package/dist/types/components/ICEGrid.d.ts +59 -0
  33. package/dist/types/components/ICEIcon.d.ts +9 -0
  34. package/dist/types/components/ICEIconTile.d.ts +53 -0
  35. package/dist/types/components/ICEImagePreview.d.ts +91 -0
  36. package/dist/types/components/ICEImageView.d.ts +62 -0
  37. package/dist/types/components/ICEInputNumber.d.ts +54 -0
  38. package/dist/types/components/ICEKanban.d.ts +85 -0
  39. package/dist/types/components/ICELabel.d.ts +28 -0
  40. package/dist/types/components/ICEList.d.ts +59 -0
  41. package/dist/types/components/ICEMenu.d.ts +48 -0
  42. package/dist/types/components/ICEModal.d.ts +73 -0
  43. package/dist/types/components/ICEPagination.d.ts +62 -0
  44. package/dist/types/components/ICEPanel.d.ts +7 -0
  45. package/dist/types/components/ICEPasswordField.d.ts +18 -0
  46. package/dist/types/components/ICEPopconfirm.d.ts +21 -0
  47. package/dist/types/components/ICEPopover.d.ts +48 -0
  48. package/dist/types/components/ICEProgressBar.d.ts +37 -0
  49. package/dist/types/components/ICERadioButton.d.ts +21 -0
  50. package/dist/types/components/ICERadioGroup.d.ts +71 -0
  51. package/dist/types/components/ICERate.d.ts +43 -0
  52. package/dist/types/components/ICEResult.d.ts +30 -0
  53. package/dist/types/components/ICEScrollPane.d.ts +72 -0
  54. package/dist/types/components/ICESegmented.d.ts +34 -0
  55. package/dist/types/components/ICESelect.d.ts +85 -0
  56. package/dist/types/components/ICESeparator.d.ts +7 -0
  57. package/dist/types/components/ICESkeleton.d.ts +29 -0
  58. package/dist/types/components/ICESlider.d.ts +63 -0
  59. package/dist/types/components/ICESpace.d.ts +51 -0
  60. package/dist/types/components/ICESpin.d.ts +33 -0
  61. package/dist/types/components/ICESplitter.d.ts +85 -0
  62. package/dist/types/components/ICEStatCard.d.ts +14 -0
  63. package/dist/types/components/ICEStatistic.d.ts +78 -0
  64. package/dist/types/components/ICESteps.d.ts +31 -0
  65. package/dist/types/components/ICESvgIcon.d.ts +9 -0
  66. package/dist/types/components/ICESwitch.d.ts +23 -0
  67. package/dist/types/components/ICETable.d.ts +250 -0
  68. package/dist/types/components/ICETabs.d.ts +17 -0
  69. package/dist/types/components/ICETag.d.ts +15 -0
  70. package/dist/types/components/ICETextArea.d.ts +8 -0
  71. package/dist/types/components/ICETextField.d.ts +80 -0
  72. package/dist/types/components/ICETileMap.d.ts +152 -0
  73. package/dist/types/components/ICETimePicker.d.ts +81 -0
  74. package/dist/types/components/ICETimeline.d.ts +33 -0
  75. package/dist/types/components/ICETooltip.d.ts +55 -0
  76. package/dist/types/components/ICETour.d.ts +97 -0
  77. package/dist/types/components/ICETransfer.d.ts +68 -0
  78. package/dist/types/components/ICETree.d.ts +107 -0
  79. package/dist/types/components/ICETreeSelect.d.ts +64 -0
  80. package/dist/types/components/ICETypography.d.ts +67 -0
  81. package/dist/types/components/ICEUpload.d.ts +75 -0
  82. package/dist/types/components/ICEVirtualList.d.ts +100 -0
  83. package/dist/types/components/ICEWatermark.d.ts +49 -0
  84. package/dist/types/components/ICEWindow.d.ts +159 -0
  85. package/dist/types/core/ICEContainer.d.ts +10 -0
  86. package/dist/types/core/ICEFocusManager.d.ts +70 -0
  87. package/dist/types/core/ICEHoverManager.d.ts +26 -0
  88. package/dist/types/core/ICEManager.d.ts +24 -0
  89. package/dist/types/core/ICEMessageManager.d.ts +80 -0
  90. package/dist/types/core/ICEOverlayManager.d.ts +92 -0
  91. package/dist/types/core/ICEPainter.d.ts +12 -0
  92. package/dist/types/core/ICEWidget.d.ts +86 -0
  93. package/dist/types/i18n/ICEI18n.d.ts +35 -0
  94. package/dist/types/index.d.ts +111 -0
  95. package/dist/types/model/ICE2048Model.d.ts +90 -0
  96. package/dist/types/model/ICEBoundedRangeModel.d.ts +20 -0
  97. package/dist/types/model/ICEButtonModel.d.ts +16 -0
  98. package/dist/types/model/ICEFormModel.d.ts +105 -0
  99. package/dist/types/model/ICEHighScoreModel.d.ts +60 -0
  100. package/dist/types/model/ICEMinesweeperModel.d.ts +102 -0
  101. package/dist/types/model/ICESelectionModel.d.ts +31 -0
  102. package/dist/types/model/ICESnakeModel.d.ts +90 -0
  103. package/dist/types/model/ICETetrisModel.d.ts +118 -0
  104. package/dist/types/model/ICEToggleModel.d.ts +14 -0
  105. package/dist/types/theme/ICEArcadeTheme.d.ts +22 -0
  106. package/dist/types/theme/ICETheme.d.ts +120 -0
  107. package/dist/types/util/ICEAccessibilityMirror.d.ts +55 -0
  108. package/dist/types/util/ICEAnimation.d.ts +71 -0
  109. package/dist/types/util/ICEDragReorder.d.ts +102 -0
  110. package/dist/types/util/ICENativeInput.d.ts +65 -0
  111. package/dist/types/util/ICEOverlayPosition.d.ts +52 -0
  112. package/dist/types/util/ICEStyle.d.ts +60 -0
  113. package/dist/types/util/ICEWorldBox.d.ts +14 -0
  114. package/package.json +56 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2022 大漠穷秋
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,487 @@
1
+ # ice-web-components
2
+
3
+ Canvas-native UI components for [`ice-render`](https://github.com/ice-render/ice-render) —
4
+ Swing-style widgets with a Bootstrap-flavoured look, rendered entirely on a single
5
+ `<canvas>`. No DOM widgets, no CSS framework: every pixel (including popups, focus
6
+ rings and shadows) is drawn by the engine.
7
+
8
+ ![ICE Shop admin dashboard](docs/images/admin-dashboard.png)
9
+
10
+ > ⚠️ **Just for fun.** This project is created purely for fun and exploration. It is
11
+ > not intended as a production-ready or battle-tested UI library.
12
+
13
+ ## Highlights
14
+
15
+ - **86 components** — buttons, inputs, selects, tables, trees, menus, modals,
16
+ drawers, notifications, uploads, date/time pickers, cascader, transfer, carousel,
17
+ colour picker… and the small stuff (tags, badges, avatars, skeletons, spins).
18
+ - **One overlay stack for every popup** — Modal / Drawer / Dropdown / Tooltip /
19
+ Popover / Popconfirm / Select / DatePicker / Cascader all go through
20
+ `ICEOverlayManager`: 12 placements, auto flip + clamp to the visible area,
21
+ Esc / outside-click closing, focus trap, enter/exit animation.
22
+ - **Forms with sync + async validation** — `ICEFormModel` (required / min / max /
23
+ length / pattern / custom / **asyncValidator**), `ICEFormItem` shows errors and a
24
+ “validating…” state, `submitAsync()` waits for the async rules.
25
+ - **Keyboard & focus** — Tab / Shift+Tab rotation, Enter/Space activation,
26
+ arrow keys for sliders, menus, tabs and rate; ring drawn above everything.
27
+ - **Bootstrap 5 token theme** (plus a dark theme) — swap with one call.
28
+ - **No name collisions with the engine** — the package’s runtime exports are
29
+ disjoint from `ice-render`’s (there is a regression test for it).
30
+ - **Actually tested** — 705 unit tests (98 suites: form validation, overlay
31
+ positioning, keyboard navigation, sort/hover/focus edge cases, the Minesweeper,
32
+ Tetris and Snake rule models) plus five browser QA suites (`qa:admin`, `qa:gallery`,
33
+ `qa:workbench`, `qa:xp`, `qa:arcade` — 198 assertions) that drive the demo pages with
34
+ real mouse and keyboard events and fail on any console error.
35
+
36
+ ## Quick start
37
+
38
+ > **Install**: `1.0.0` is the first tagged release. The engine (`ice-render`) is on npm;
39
+ > the component library ships as a tarball / git dependency for now — use one of the following:
40
+
41
+ ```bash
42
+ # ① a local path (the usual thing inside a monorepo / workspace)
43
+ npm install /path/to/ice-web-components
44
+
45
+ # ② straight from git
46
+ npm install git+https://github.com/ice-render/ice-web-components.git
47
+
48
+ # ③ pack it, then install the tarball
49
+ (cd /path/to/ice-web-components && npm pack) # produces ice-web-components-0.0.1.tgz
50
+ npm install /path/to/ice-web-components-0.0.1.tgz
51
+ ```
52
+
53
+ All three pull the `ice-render@^1.3.0` dependency from npm. The published tarball
54
+ contains `dist/` only (cjs + esm + umd + type declarations).
55
+
56
+ ```ts
57
+ import { ICE } from 'ice-render';
58
+ import {
59
+ ICEButton,
60
+ ICEHoverManager,
61
+ ICELabel,
62
+ ICEMessage,
63
+ ICEPanel,
64
+ getICEFocusManager,
65
+ } from 'ice-web-components';
66
+
67
+ const ice = new ICE().init('canvas');
68
+ new ICEHoverManager(ice).start(); // canvas has no native hover: opt in
69
+ getICEFocusManager(ice).start(); // Tab / Enter / Esc handling
70
+
71
+ const panel = new ICEPanel({ left: 24, top: 24, width: 372, height: 192 });
72
+ panel.addChild(new ICELabel({ left: 24, top: 20, text: 'Quick start' }));
73
+
74
+ const button = new ICEButton({ left: 24, top: 64, width: 140, text: 'Click me' });
75
+ const hint = new ICELabel({ left: 24, top: 112, text: 'clicked 0 times' });
76
+ let count = 0;
77
+ button.on('click', () => {
78
+ count += 1;
79
+ hint.setText(`clicked ${count} times`);
80
+ ICEMessage.success(ice, `clicked ${count} times`);
81
+ });
82
+
83
+ panel.addChildren([button, hint]);
84
+ ice.addChild(panel);
85
+ ```
86
+
87
+ ![Quick start](docs/images/quick-start.png)
88
+
89
+ ## Documentation
90
+
91
+ Full docs live in [`docs/`](./docs/README.md):
92
+
93
+ | | |
94
+ |---|---|
95
+ | [Architecture](./docs/architecture.md) | Layers, component model, rendering & repaint, events & hover, overlays / focus / forms / theming, plus a “pitfalls” table |
96
+ | [Component cheat sheet](./docs/components.md) | 85 component classes, one line each, grouped, with links into the API |
97
+ | [API reference](./docs/api/README.md) | Constructor props and public methods for every component (**generated from source**, so it cannot drift) |
98
+ | [Examples & scenarios](./docs/guides/examples.md) | What each of the six demo pages shows, which components it uses, and a checklist for building your own |
99
+ | [Theming & colour](./docs/guides/theming.md) | Token groups, status colours, `*TextEmphasis`, custom themes |
100
+ | [Forms & validation](./docs/guides/forms.md) | The three layers, the rule list, async validation, wiring a custom control |
101
+ | [Overlay guide](./docs/guides/overlays.md) | The three ways to use popups, positioning, close policies, content factories |
102
+ | [Canvas layout](./docs/guides/layout.md) | Coordinates & zIndex, cluster + shelf layout, clipping & scrolling, when sizes are ready |
103
+ | [Writing your own component](./docs/guides/custom-components.md) | Three levels of effort, constructor conventions, interaction / form / overlay / theme hooks, type registration and pitfalls |
104
+ | [Testing](./docs/guides/testing.md) | Unit-test recipes (fake ICE + real components) and the browser QA scripts |
105
+ | [Migration](./docs/guides/migration.md) | `UI*` → `ICE*`, Ant → Bootstrap theming, other breaking changes |
106
+
107
+ > The guide pages themselves are written in Chinese for now; this README is English-only.
108
+
109
+ ## Demos
110
+
111
+ All pages under `examples/` are plain HTML — build the package, then open them
112
+ (or serve the folder with any static server).
113
+
114
+ ### `gallery.html` — every component in one page
115
+
116
+ Rendered with a small hand-rolled flow layout (clusters keep their internal
117
+ geometry, clusters wrap like shelves), so adding a demo never requires hunting for
118
+ free coordinates.
119
+
120
+ ![Component gallery](docs/images/gallery.png)
121
+
122
+ ### `admin.html` — a six-page back-office
123
+
124
+ A small “ICE Shop” admin: sidebar with submenus, breadcrumb + page search +
125
+ notifications/user menu in the header, a floating action button, a first-run tour,
126
+ and six pages that switch inside a scroll pane.
127
+
128
+ The business flow is deliberately complete: order filtering (keyword / region /
129
+ amount range / abnormal-only) with a batch toolbar, an order drawer with
130
+ fulfilment steps and a service timeline, inventory warnings with pagination,
131
+ product gallery preview, customer insights with satisfaction scoring, a
132
+ splitter-based fulfilment workbench with anchors, and a settings pane whose
133
+ password form validates across fields.
134
+
135
+ | Dashboard | Orders |
136
+ |---|---|
137
+ | ![Dashboard](docs/images/admin-dashboard.png) | ![Orders](docs/images/admin-orders.png) |
138
+ | ![Fulfilment](docs/images/admin-fulfillment.png) | ![Products](docs/images/admin-products.png) |
139
+ | ![Customers](docs/images/admin-customers.png) | ![Settings](docs/images/admin-settings.png) |
140
+
141
+ Popup layers used by that demo:
142
+
143
+ | Order detail drawer | New-order dialog | Notification dropdown |
144
+ |---|---|---|
145
+ | ![Drawer](docs/images/popup-drawer.png) | ![Modal](docs/images/popup-modal.png) | ![Dropdown](docs/images/popup-dropdown.png) |
146
+
147
+ ### `custom-component.html` — write your own component
148
+
149
+ The same “write a component and plug it into ICE” story as
150
+ [`docs/guides/custom-components.md`](./docs/guides/custom-components.md), but
151
+ runnable: a hand-written `ICEMetric` card that reacts to clicks, hover and
152
+ keyboard, and participates in `ICEForm` validation.
153
+
154
+ ![Custom component](docs/images/custom-component.png)
155
+
156
+ ### `workbench.html` — customer-support workbench
157
+
158
+ A second end-to-end scenario (deliberately *not* a dashboard): a three-pane support
159
+ workbench built with `ICESplitter` — ticket queue with filters and skeleton loading,
160
+ conversation pane with reply composer / quick-reply dropdown / attachment upload /
161
+ ticket tags, and a customer profile pane with satisfaction rating, history timeline
162
+ and knowledge base. Session log: ticket selection drives the profile, sending a
163
+ reply appends a message, the floating button opens a 3-step tour, and the
164
+ back-to-top button appears once the conversation scrolls.
165
+
166
+ ![Support workbench](docs/images/workbench.png)
167
+
168
+ ### `windows-xp.html` — a full-screen Windows XP desktop
169
+
170
+ The fun one: a canvas-only XP desktop that **boots**. Turn it on and you get the black
171
+ boot splash (self-drawn four-colour flag + the running progress blocks), then the blue
172
+ welcome screen: pick a user tile, type anything (or nothing) into the password box and
173
+ press Enter — *any* credentials are accepted, this is a toy. Then the desktop fades in
174
+ with a synthesized startup chime.
175
+
176
+ The sound is generated live with WebAudio (startup / logoff / shutdown / click cues) —
177
+ original tones, no audio files, no Microsoft assets. Hover the tray speaker in the
178
+ taskbar to mute it. Log off from the Start menu and you drop back to the welcome
179
+ screen; shut down and you get the black "it is now safe to turn off your computer"
180
+ screen with a power button that boots the machine all over again.
181
+
182
+ The desktop itself: wallpaper, desktop icons, taskbar with a
183
+ working clock, a Start menu, and draggable windows with minimise / maximise / close.
184
+ Seven tiny apps are wired up (My Computer, My Documents, Notepad, Paint, Minesweeper,
185
+ Internet Explorer, Display Properties), and switching the wallpaper in Display
186
+ Properties repaints the desktop immediately.
187
+
188
+ Looks the part too: it switches to the library's built-in `ICE_XP_THEME` (Luna blue +
189
+ classic grey controls), draws every icon with engine primitives (no bitmap assets, no
190
+ Microsoft artwork), and initialises with `dpr` so text stays crisp on Retina screens.
191
+
192
+ Minesweeper is the full game: beginner / intermediate / expert, first-click-safe mine
193
+ placement, flood fill, right-click flag cycle (🚩 / ❓), chord on double click, LED
194
+ counters, a timer that starts on the first click, and per-difficulty best times. Its
195
+ rules live in a tested pure model (`ICEMinesweeperModel`) — the UI only draws it.
196
+
197
+ Internet Explorer is a **real** browser too: the address bar `fetch()`es the URL,
198
+ `DOMParser` parses the HTML, and the title / headings / paragraphs / links / images are
199
+ drawn with canvas components inside a scroll pane (with back / forward / refresh).
200
+ Same-origin pages always work; other sites obey CORS like any browser, and failures
201
+ land on an XP-style error page. Serve the folder over http (`npx serve .`) — `fetch`
202
+ does not work from `file://`.
203
+ Right-click works because `ICE.init()` no longer stops the `contextmenu` event on its
204
+ way to the dispatcher (ice-render 1.4.1).
205
+
206
+ Two new generic components came out of it: `ICEWindow` (window chrome with an XP Luna
207
+ title bar, drag, resize, maximise/restore, activate event) and `ICEIconTile`
208
+ (selectable icon tile that opens on double click).
209
+
210
+ The eighth app is **ICE Arcade** — the handheld console from `arcade.html`, running
211
+ inside an XP window. It reuses the same two pure models and the same `ICETileMap`
212
+ (so the board is still one node), and the desktop routes the keyboard to it only while
213
+ that window is active; closing the window stops its step timer.
214
+
215
+ ![ICE Arcade inside a Windows XP window](docs/images/xp-arcade.png)
216
+
217
+ | Boot splash | Welcome screen | Password page |
218
+ |---|---|---|
219
+ | ![XP boot](docs/images/xp-boot.png) | ![XP login](docs/images/xp-login.png) | ![XP password](docs/images/xp-login-password.png) |
220
+
221
+ ![Windows XP desktop](docs/images/xp-desktop.png)
222
+
223
+ ### `arcade.html` — ICE Arcade (a handheld console)
224
+
225
+ Not a web page but a **handheld console**: the shell, the screen bezel, the HUD cards,
226
+ the buttons and the sound switch are all ICE components, and there is not a single
227
+ bitmap asset in the picture. Three cartridges are plugged in, and the cartridge row at
228
+ the top switches between them (a fourth slot, Chinese chess, is disabled for now).
229
+
230
+ | | |
231
+ |---|---|
232
+ | ![ICE Arcade · Tetris](docs/images/arcade-tetris.png) | ![ICE Arcade · Snake](docs/images/arcade-snake.png) |
233
+
234
+ **Cartridge 1 — Tetris** (`ICETetrisModel`, 16 unit tests). Modern-standard rules:
235
+ 7-bag fairness, simple wall kicks (0 / ±1 / ±2), a ghost landing preview, soft drop
236
+ +1/cell, hard drop +2/cell, line scores of 100/300/500/800 × level, a level-up every 10
237
+ lines, and a gravity interval that starts at 800 ms and shrinks with the level.
238
+ Keyboard: `←` / `→` move, `↓` soft drop, `Space` hard drop, `↑` / `X` rotate clockwise,
239
+ `Z` rotate counter-clockwise, `P` pause, `R` restart.
240
+
241
+ **Cartridge 2 — Snake** (`ICESnakeModel`, 18 unit tests). Classic rules: the snake grows
242
+ on every meal (+10 points × level), 5 meals per level, an interval that drops from
243
+ 170 ms per cell towards 70 ms, a two-deep turn queue that refuses 180° reversals (and
244
+ lets you survive moving into the tail cell that is about to vacate), and walls that
245
+ kill. Keyboard: arrows or `W` / `A` / `S` / `D` to steer, `P` pause, `R` restart.
246
+ Clicking a cell on the board steers towards it — that is the tile map’s `cellclick`,
247
+ i.e. a real hit test inside a single component.
248
+
249
+ **Cartridge 3 — 2048** (`ICE2048Model`, 19 unit tests). The classic rules: two starting
250
+ tiles, merges score their own value, each tile merges at most once per move (so `2 2 2 2`
251
+ becomes `4 4`, not `8`), a move that changes nothing spawns nothing, and filling the board
252
+ without any merge left is game over. Reaching 2048 wins but lets you keep playing.
253
+ Arrows or `W` / `A` / `S` / `D` slide, `P` pauses, `R` restarts. The numbers are drawn by
254
+ the tile map’s **label layer** — the palette entry for each value carries its font size,
255
+ weight and text colour, so a 4×4 board with 16 numbers is still one node.
256
+
257
+ ![ICE Arcade · 2048](docs/images/arcade-2048.png)
258
+
259
+ Both games are pure models that never touch the canvas; the page only reads the model
260
+ and paints cells. Switching a cartridge tears the old board down, builds the new one
261
+ and re-captions the HUD, so a third game is a registry entry plus a `mount()`.
262
+ Switching away from the tab pauses whatever is running.
263
+
264
+ Under the hood this page is where the engine work happens:
265
+
266
+ - **`ICETileMap`** paints a whole board (10×20 or 20×20 cells) inside **one** node —
267
+ it extends the widget base, draws the grid in `doRender()` with the engine context and
268
+ keeps its own dirty flag, so a 400-cell snake board is 1 node instead of 400 (the QA
269
+ asserts `childNodes.length === 0`). Ghost landing spots go through its highlight layer,
270
+ line clears and meals go through `pulse()`, which fades the overlay with a `tween`.
271
+ - **`registerTheme('arcade', ICE_ARCADE_THEME)`** moves the game palette into tokens:
272
+ pieces and snake colours ship as `ICE_ARCADE_PALETTE`, so the board re-skins with the
273
+ rest of the UI instead of hard-coded hex values in the page.
274
+ - **`ICEHighScoreModel`** keeps a per-cartridge top-5 (sorting, capping, corrupt-storage
275
+ tolerance, injected storage) and the **Leaderboard (L)** button opens an `ICEModal`
276
+ containing an `ICETable` inside an `ICEScrollPane`.
277
+ - `fadeIn` on cartridge switch, `scaleIn` on game over, `pulse` on line clears — all
278
+ from `ICEAnimation`, so the “juice” is library code rather than hand-rolled decay.
279
+ - Toasts are **replaced, not stacked**: a console only needs one status line, and the QA
280
+ caught a stack of three toasts covering the cartridge row (the click never reached the
281
+ button). `ICEMessage` still supports stacking for pages that want it.
282
+
283
+ | Leaderboard (`ICEModal` + `ICETable` + `ICEScrollPane`) |
284
+ |---|
285
+ | ![Leaderboard](docs/images/arcade-leaderboard.png) |
286
+
287
+ > This page deliberately does **not** start `ICEFocusManager` — it activates the
288
+ > focused button with Enter/Space, which collides head-on with “Space = hard drop”.
289
+ > A game page keeps the keyboard for itself; mouse hover still goes through
290
+ > `ICEHoverManager`.
291
+
292
+ ## Components
293
+
294
+ | Group | Components |
295
+ |---|---|
296
+ | Basic | `ICEPanel` `ICEButton` `ICELabel` `ICETypography` `ICEIcon` `ICESvgIcon` `ICESeparator` |
297
+ | Layout | `ICESpace` `ICEGrid` `ICEGridCol` `ICESplitter` `ICEScrollPane` |
298
+ | Data entry | `ICETextField` `ICETextArea` `ICEPasswordField` `ICEInputNumber` `ICESelect` `ICEAutoComplete` `ICECascader` `ICETreeSelect` `ICEDatePicker` `ICETimePicker` `ICECheckBox` `ICECheckboxGroup` `ICERadioButton` `ICERadioGroup` `ICESwitch` `ICESlider` `ICESegmented` `ICERate` `ICEColorPicker` `ICETransfer` `ICEUpload` `ICEForm` `ICEFormItem` |
299
+ | Data display | `ICEVirtualList` `ICEKanban` `ICETable` `ICEList` `ICETree` `ICEStatCard` `ICEStatistic` `ICECard` `ICEComment` `ICEDescriptions` `ICETimeline` `ICEProgressBar` `ICEAvatar` `ICEAvatarGroup` `ICETag` `ICEBadge` `ICEImageView` `ICEImagePreview` `ICECalendar` `ICECarousel` `ICECollapse` `ICEWatermark` |
300
+ | Feedback & status | `ICEAlert` `ICEModal` `ICEDrawer` `ICEMessage` `ICENotification` `ICETooltip` `ICEPopover` `ICEPopconfirm` `ICETour` `ICEFloatButton` `ICEEmpty` `ICESkeleton` `ICESpin` `ICEResult` `ICESteps` `ICEOverlayManager` |
301
+ | Navigation | `ICEMenu` `ICEBreadcrumb` `ICEAnchor` `ICEBackTop` `ICEDropdown` `ICEPagination` `ICETabs` |
302
+ | Layout & core | `ICEWidget` `ICEContainer` `ICEHoverManager` `ICEFocusManager` `ICEMessageManager` `ICEManager` (`ICEPainter` / `ICELayoutManager` are types) |
303
+ | Models | `ICEButtonModel` `ICEToggleModel` `ICEBoundedRangeModel` `ICESelectionModel` `ICEFormModel` |
304
+
305
+ Helper functions: `attachTooltip` `attachPopover` `attachPopconfirm` `attachDropdown`
306
+ `openModal` `openDrawer` `getICEOverlayManager` `getICEFocusManager` `getICEMessageManager`
307
+ `formatStatisticValue` `formatCountdown` `truncateTextLines` `buildMonthGrid` `formatCalendarDate`
308
+ `openImagePreview` `tween` `fadeIn` `fadeOut` `slideIn` `scaleIn` and friends.
309
+
310
+ ## Theme
311
+
312
+ A compact **Bootstrap 5-style** token set (see `ICE_LIGHT_THEME` / `ICE_DARK_THEME`):
313
+
314
+ - semantic colours — `primary` `#0d6efd`, `success` `#198754`, `warning` `#ffc107`,
315
+ `error` `#dc3545`, `info` `#0dcaf0`;
316
+ - subtle pairs for soft surfaces — `primaryBg` / `primaryBorder`, `successBg` /
317
+ `successBorder`, … plus `*TextEmphasis` (Bootstrap’s `*-text-emphasis`) for text
318
+ sitting on those subtle backgrounds;
319
+ - neutrals — `surface`, `elevated`, `background`, `border`, `borderSecondary`;
320
+ - text hierarchy — `text`, `textSecondary`, `textTertiary`, `textDisabled`;
321
+ - spacing / radius / control sizes, Bootstrap’s three shadows (`sm` / `md` / `lg`,
322
+ expressed as explicit `shadowColor` `shadowBlur` `shadowOffset*` numbers), and a
323
+ `focusRing` colour for focused controls.
324
+
325
+ ```ts
326
+ import { iceUIManager } from 'ice-web-components';
327
+
328
+ iceUIManager.setTheme('dark'); // components read tokens when they are created
329
+ ```
330
+
331
+ Status chips default to Bootstrap’s solid `.text-bg-*` look (white text, black text
332
+ on the light `warning` / `info` colours). Pass `variant: 'soft'` for the subtle
333
+ background + emphasis text variant:
334
+
335
+ ```ts
336
+ new ICETag({ text: 'Paid', status: 'success' }); // solid green
337
+ new ICETag({ text: 'Paid', status: 'success', variant: 'soft' }); // #d1e7dd / #0a3622
338
+ ```
339
+
340
+ ## Naming & exports
341
+
342
+ Everything exported by this package uses the **`ICE`** prefix (same convention as
343
+ `ice-render`), and the package’s runtime exports do **not overlap** with the
344
+ engine’s — you can import both namespaces, or name-import from both, without
345
+ ambiguity:
346
+
347
+ | concept | this package | `ice-render` |
348
+ |---|---|---|
349
+ | base class | `ICEWidget` (UI widget base, extends `ICEGroup`) | `ICEComponent` (graphic component base) |
350
+ | image | `ICEImageView` (widget, wraps the primitive) | `ICEImage` (image primitive) |
351
+
352
+ Layout classes are **not** re-exported — they belong to the engine:
353
+
354
+ ```ts
355
+ import { ICEFlowLayout } from 'ice-render';
356
+ import { ICEPanel } from 'ice-web-components';
357
+
358
+ panel.setLayout(new ICEFlowLayout({ gap: 8 }));
359
+ ```
360
+
361
+ ## Interaction notes
362
+
363
+ - **Hover** — ICE deliberately skips full hit-testing on `mousemove`, so canvas
364
+ components have no native `mouseenter` / `mouseleave`. Attach
365
+ `new ICEHoverManager(ice).start()` once and components get lightweight hover
366
+ states (table rows, menu/tree items, buttons, chips…).
367
+ - **Popups** — always open through `getICEOverlayManager(ice)`; overlay content is
368
+ mounted on the engine’s tool layer, so it renders above the scene, follows the
369
+ anchor, and is excluded from scene hit-testing.
370
+ - **Focus** — `getICEFocusManager(ice).start()` gives Tab / Shift+Tab rotation,
371
+ Enter/Space activation, and a ring drawn on the tool layer. The ring follows
372
+ `:focus-visible` semantics: it only appears for **keyboard** focus, so mouse
373
+ clicks and thumb drags stay clean. Text-entry controls (text field, number,
374
+ select, date/time/cascader, colour picker…) opt into `focusRing: 'always'`;
375
+ any component can declare `focusRing: 'keyboard' | 'always' | 'never'` (or
376
+ `setFocusRingMode()`). Overlays that own the keyboard declare `keyboardCaptured`
377
+ so the scene yields Enter/Space.
378
+ - **Rendering pitfalls worth knowing** — the engine sorts by **global zIndex**
379
+ (creation order), so build containers before their children; components that wrap
380
+ caller-provided nodes (carousel slides, card `extra`, modal content) raise those
381
+ subtrees above themselves.
382
+ - **Container hit-testing** — a container that is created *after* its children and
383
+ stays interactive will swallow every click inside it (`ICESplitter` and plain
384
+ layout wrappers therefore ship with `interactive: false`; if you build your own
385
+ wrapper, do the same).
386
+ - **Focus ring vs. hit-testing** — the two rules above bite together: a form item
387
+ wrapper left interactive hides its own input from `ice.hitTest()`, so the focus
388
+ manager can't focus it (no ring, and `getFocused()` returns null while the field
389
+ still accepts typing through its own point-in-box check). `ICEFormItem`,
390
+ `ICEForm`, `ICESpace`, `ICEGrid` and `ICESplitter` are all `interactive: false`.
391
+ - **Accessibility** — the engine hands you an accessibility snapshot
392
+ (`getAccessibilityTree()`: role suggestion, `state.ariaLabel`, screen box, tab order);
393
+ this library wires the other half: controls carry meaningful labels (buttons use their text,
394
+ text fields fall back to the placeholder, checkboxes to their label), and
395
+ `mountICEAccessibilityMirror(ice)` renders that snapshot into invisible-but-real DOM
396
+ (`role` / `aria-label` / `tabindex`, positioned over the canvas). Clicking or focusing a
397
+ mirror element focuses and activates the canvas component, so screen readers and keyboard
398
+ users can drive a canvas UI.
399
+ - **i18n** — built-in strings (table empty state, modal/popconfirm buttons, upload hint,
400
+ transfer panes, tour buttons, form "validating...") come from a locale pack:
401
+ `setICELocale('en-US')` (built-in `zh-CN` + `en-US`), `registerICELocale()` for your own,
402
+ `t('pagination.total', { total: 42 })` for app strings. Components read the text when they
403
+ are built, so switching the locale then rebuilding/relayouting is what shows the change.
404
+ - **Text input & IME** — focusing a text field mounts a **fully transparent native
405
+ `<input>` / `<textarea>`** over it (`ICENativeInput`): the browser and the IME do the
406
+ typing, `input` / `compositionend` write the value back, and `change` / form binding
407
+ keep working. So Chinese / Japanese / Korean input works, paste works, and the caret
408
+ is a real DOM caret — the canvas still draws every pixel (the element is invisible).
409
+ In a runtime without `document` (Node / mini-program) it degrades to the old
410
+ per-key `keydown` path. Pressing Enter emits `submit` on single-line fields.
411
+
412
+ ## Development
413
+
414
+ ```bash
415
+ npm install
416
+ npm run types:check # tsc --noEmit
417
+ npm test # jest (unit tests, node env)
418
+ npm run build # cjs + esm + umd + d.ts
419
+
420
+ # browser QA for examples/admin.html: layout consistency + popup open/close
421
+ # (needs playwright; point PLAYWRIGHT_PATH at an existing install if needed)
422
+ npm run qa:admin
423
+
424
+ # browser QA for examples/gallery.html: zero overlap + real interactions of the
425
+ # newest components (breadcrumb collapse, countdown, radio/checkbox groups,
426
+ # splitter drag, watermark tiling)
427
+ npm run qa:gallery
428
+
429
+ # browser QA for examples/workbench.html: queue → profile, reply composer,
430
+ # quick replies, tags/rating, tour, back-to-top, splitter drag
431
+ npm run qa:workbench
432
+
433
+ # browser QA for examples/windows-xp.html: icons, windows (drag/minimise/restore),
434
+ # boot → login (any password) → desktop, logoff/shutdown/power-on, tray mute,
435
+ # start menu, minesweeper, paint strokes, wallpaper switch, clock
436
+ npm run qa:xp
437
+
438
+ # browser QA for examples/arcade.html: both cartridges played with real key presses,
439
+ # cartridge switching, the self-drawn tile map, tweens, the leaderboard modal, and
440
+ # real clicks on the HUD
441
+ npm run qa:arcade
442
+
443
+ # docs: regenerate the API reference and check relative links
444
+ npm run docs
445
+ ```
446
+
447
+ `npm run qa:admin` drives a real browser: it asserts every page has zero overlapping
448
+ top-level nodes and identical first-element offsets, opens every popup layer and
449
+ asserts it can be closed again, checks that clicking an in-row action button does
450
+ not select the row, then walks the business scenario — breadcrumb follows the page,
451
+ the floating button opens the tour, the back-to-top button returns to the top, the
452
+ inventory table pages, the order queue drives the detail pane, anchors scroll the
453
+ detail, the image preview opens/zooms, and the settings password form revalidates
454
+ across fields. Every popup is screenshotted to `/tmp/qa-*.png`, and any console
455
+ error fails the run.
456
+
457
+ `npm run qa:gallery` does the same for the full gallery page with real mouse
458
+ events (hit-test path): it asserts no two top-level clusters overlap, drives the
459
+ breadcrumb collapse, the radio / checkbox groups, drags the splitter divider and
460
+ checks the watermark tiling, then screenshots to `/tmp/qa-gallery*.png`.
461
+
462
+ `npm run qa:workbench` covers the support workbench (queue → profile, reply composer,
463
+ quick replies, tags, rating, tour, back-to-top, splitter drag) and `npm run qa:xp`
464
+ covers the Windows XP desktop: the boot splash → welcome screen → typing a password and
465
+ pressing Enter (the run asserts the startup cue actually fired), log off back to the
466
+ welcome screen, shut down, power back on, logging in again with the **Log in** button, the
467
+ tray mute toggle, icons, window drag/minimise/restore, Start menu,
468
+ Minesweeper (first-click-safe, flag cycle, difficulty, timer, win), the Paint canvas,
469
+ the wallpaper switch, the clock — and the IE window really fetching pages (plus its
470
+ 404 page, back button, `about:xp` table and bookmarks). `qa:xp` starts a small static
471
+ server itself, because `fetch` does not work from `file://`.
472
+
473
+ `npm run qa:arcade` plays the arcade page with **real key presses**. On the Tetris
474
+ cartridge it moves, rotates, soft-drops, hard-drops, asserts that `P` really stops
475
+ gravity, builds a deterministic board to force a line clear, keeps dropping until game
476
+ over (the best score lands in `localStorage`) and restarts with `R`. Then it clicks the
477
+ Snake cartridge and checks the swap (new board, re-captioned HUD, a fresh model), steers
478
+ with arrow keys, feeds the snake to grow it, drives it into a wall, and switches back —
479
+ plus real clicks on the pause / restart buttons and the sound switch. Layout assertions
480
+ keep both boards inside the screen bezel and the panels from overlapping.
481
+
482
+ See [ROADMAP.md](./ROADMAP.md) for the component backlog and what is still missing
483
+ per component.
484
+
485
+ ## License
486
+
487
+ [MIT](./LICENSE)