@ntmats/hc-ui 0.6.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -310,6 +310,7 @@ export interface DialogProps extends Omit<HTMLAttributes<HTMLDialogElement>, 'ti
310
310
  */
311
311
  export declare const Dialog: ForwardRefExoticComponent<DialogProps & RefAttributes<HTMLDialogElement>>;
312
312
  export type SheetPresentation = 'bottom' | 'center' | 'auto';
313
+ export type SheetHeight = 'content' | 'half';
313
314
  interface SheetBaseProps extends Omit<DialogProps, 'size' | 'actions' | 'description' | 'closeLabel' | 'title'> {
314
315
  /** Slot under the scrolling body, for the buttons (Clear | Done). */
315
316
  footer?: ReactNode;
@@ -321,6 +322,13 @@ interface SheetBaseProps extends Omit<DialogProps, 'size' | 'actions' | 'descrip
321
322
  presentation?: SheetPresentation;
322
323
  /** Maximum width of the centered panel, in px. Default 440. */
323
324
  width?: number;
325
+ /**
326
+ * Resting height at the bottom. `content` (default) grows with the content up to almost the
327
+ * whole screen. `half` rests at half the screen, so what is behind stays in sight (dimmed),
328
+ * and the content scrolls inside; a drag up from the grabber or header still goes to full
329
+ * height, and a drag down from there comes back to half. No effect on the centered panel.
330
+ */
331
+ height?: SheetHeight;
324
332
  }
325
333
  /**
326
334
  * `Sheet` props. By default the sheet has a header with the title (its accessible name) and a
@@ -346,7 +354,8 @@ export type SheetProps = SheetBaseProps & ({
346
354
  * Tab trap, Esc, focus return, scroll lock, stacking and controlled `open`/`onClose`.
347
355
  * At the bottom it has a grabber, drag to close (past 20% of its height or a quick
348
356
  * flick) and drag up from the grabber or header to full height; content that has
349
- * scrolled down scrolls back before the sheet moves.
357
+ * scrolled down scrolls back before the sheet moves. `height="half"` rests at half the
358
+ * screen instead of growing with the content.
350
359
  */
351
360
  export declare const Sheet: ForwardRefExoticComponent<SheetProps & RefAttributes<HTMLDialogElement>>;
352
361
  export interface SheetOption<T extends string> {
@@ -1358,6 +1367,21 @@ export interface HCMapJson {
1358
1367
  ramps?: JsonRamp[];
1359
1368
  ziplines?: JsonWall[];
1360
1369
  }
1370
+ /** The grid's line between squares. */
1371
+ export declare const MAP_GRID_COLOR = "#222222";
1372
+ /** `soft` grid: the line between two plain squares (clear, at floor level). */
1373
+ export declare const MAP_GRID_SOFT_COLOR = "#D4D2CC";
1374
+ /** `soft` grid: between two squares of the same terrain, the fill darkened by this fraction. */
1375
+ export declare const MAP_GRID_TERRAIN_SHADE = 0.22;
1376
+ export interface MapDrawOptions {
1377
+ /**
1378
+ * `dark` (default): every square outlined in the dark grid, as printed. `soft`: the line between
1379
+ * two plain squares (clear terrain at floor level) is light gray; between two squares of the same
1380
+ * terrain, a darker tone of that terrain's color; between different kinds and on the map's edge,
1381
+ * the dark grid.
1382
+ */
1383
+ grid?: 'dark' | 'soft';
1384
+ }
1361
1385
  export declare class HCMap {
1362
1386
  width: number;
1363
1387
  height: number;
@@ -1368,7 +1392,13 @@ export declare class HCMap {
1368
1392
  constructor(json: HCMapJson);
1369
1393
  private getTile;
1370
1394
  private tryAddEdge;
1371
- draw(ctx: CanvasRenderingContext2D): void;
1395
+ /**
1396
+ * The `soft` grid's color for the line between two squares: light gray between plain squares
1397
+ * (clear, floor level, no starting zone), a tone of the terrain's own color between two squares
1398
+ * of the same kind, and the dark grid between different kinds or on the map's edge.
1399
+ */
1400
+ private softGridColor;
1401
+ draw(ctx: CanvasRenderingContext2D, { grid }?: MapDrawOptions): void;
1372
1402
  private drawWall;
1373
1403
  }
1374
1404
  export declare function mapSquare(map: HCMapJson, x: number, y: number): {
@@ -1584,6 +1614,8 @@ export interface MapBoardViewProps extends Omit<HTMLAttributes<HTMLDivElement>,
1584
1614
  onLeave?(): void;
1585
1615
  /** Drawing failed (a JSON outside the expected format). */
1586
1616
  onDrawError?(): void;
1617
+ /** The grid between squares: `dark` (default) or `soft` (light between plain squares). */
1618
+ grid?: MapDrawOptions['grid'];
1587
1619
  }
1588
1620
  /**
1589
1621
  * The map's 2D board: the hcmaps drawing in a canvas and, over it, the inspection's
package/dist/index.js CHANGED
@@ -602,7 +602,7 @@ function useSheetDrag(sheet, latest) {
602
602
  };
603
603
  }, [sheet, latest]);
604
604
  }
605
- var Sheet = forwardRef(function Sheet2({ presentation = "auto", width = 440, footer, dismissible = true, header = true, onClose, className, style, ...props }, ref) {
605
+ var Sheet = forwardRef(function Sheet2({ presentation = "auto", width = 440, height = "content", footer, dismissible = true, header = true, onClose, className, style, ...props }, ref) {
606
606
  const [node, setNode] = useState(null);
607
607
  const setRef = useCallback((element) => {
608
608
  setNode(element);
@@ -626,7 +626,8 @@ var Sheet = forwardRef(function Sheet2({ presentation = "auto", width = 440, foo
626
626
  headerless: !header,
627
627
  className: classes("hc-ui-sheet", className),
628
628
  style: sized,
629
- "data-presentation": presentation
629
+ "data-presentation": presentation,
630
+ "data-height": height === "half" ? "half" : void 0
630
631
  }
631
632
  );
632
633
  });
@@ -2468,6 +2469,16 @@ var Tile = class {
2468
2469
  ctx.restore();
2469
2470
  }
2470
2471
  };
2472
+ var MAP_GRID_COLOR = "#222222";
2473
+ var MAP_GRID_SOFT_COLOR = "#D4D2CC";
2474
+ var MAP_GRID_TERRAIN_SHADE = 0.22;
2475
+ function shadeHex(color, amount) {
2476
+ const m = /^#([0-9a-f]{6})$/i.exec(color);
2477
+ if (!m) return null;
2478
+ const n = parseInt(m[1], 16);
2479
+ const ch = (shift) => Math.round((n >> shift & 255) * (1 - amount)).toString(16).padStart(2, "0");
2480
+ return `#${ch(16)}${ch(8)}${ch(0)}`;
2481
+ }
2471
2482
  var HCMap = class {
2472
2483
  constructor(json) {
2473
2484
  __publicField(this, "width");
@@ -2537,7 +2548,19 @@ var HCMap = class {
2537
2548
  t0.addEdge(edgeType, MAP_TERRAIN_EDGE_COLORS[t0.terrain] ?? "#FFFFFF", dashed);
2538
2549
  }
2539
2550
  }
2540
- draw(ctx) {
2551
+ /**
2552
+ * The `soft` grid's color for the line between two squares: light gray between plain squares
2553
+ * (clear, floor level, no starting zone), a tone of the terrain's own color between two squares
2554
+ * of the same kind, and the dark grid between different kinds or on the map's edge.
2555
+ */
2556
+ softGridColor(a, b) {
2557
+ if (!a || !b) return MAP_GRID_COLOR;
2558
+ const same = a.terrain === b.terrain && a.type === b.type && String(a.elevation) === String(b.elevation) && !!a.isStartingZone === !!b.isStartingZone && !!a.isStartingZone4p === !!b.isStartingZone4p && a.fillStyle === b.fillStyle;
2559
+ if (!same) return MAP_GRID_COLOR;
2560
+ if (a.terrain === "clear" && Number(a.elevation) === 1 && !a.isStartingZone && !a.isStartingZone4p) return MAP_GRID_SOFT_COLOR;
2561
+ return shadeHex(a.fillStyle, MAP_GRID_TERRAIN_SHADE) ?? MAP_GRID_COLOR;
2562
+ }
2563
+ draw(ctx, { grid = "dark" } = {}) {
2541
2564
  ctx.save();
2542
2565
  for (const tile of this.tiles) tile.draw(ctx);
2543
2566
  ctx.restore();
@@ -2545,11 +2568,29 @@ var HCMap = class {
2545
2568
  for (const tile of this.tiles) tile.drawEdges(ctx);
2546
2569
  ctx.restore();
2547
2570
  ctx.save();
2548
- ctx.strokeStyle = "#222222";
2571
+ ctx.strokeStyle = MAP_GRID_COLOR;
2549
2572
  ctx.lineWidth = 1;
2550
- for (let y = 0; y < this.height; y++) {
2551
- for (let x = 0; x < this.width; x++) {
2552
- ctx.strokeRect(x * MAP_TILE_SIZE, y * MAP_TILE_SIZE, MAP_TILE_SIZE, MAP_TILE_SIZE);
2573
+ if (grid === "dark") {
2574
+ for (let y = 0; y < this.height; y++) {
2575
+ for (let x = 0; x < this.width; x++) {
2576
+ ctx.strokeRect(x * MAP_TILE_SIZE, y * MAP_TILE_SIZE, MAP_TILE_SIZE, MAP_TILE_SIZE);
2577
+ }
2578
+ }
2579
+ } else {
2580
+ const lines = [];
2581
+ for (let y = 0; y <= this.height; y++) {
2582
+ for (let x = 0; x < this.width; x++) lines.push({ x0: x, y0: y, x1: x + 1, y1: y, color: this.softGridColor(this.getTile(x, y - 1), this.getTile(x, y)) });
2583
+ }
2584
+ for (let x = 0; x <= this.width; x++) {
2585
+ for (let y = 0; y < this.height; y++) lines.push({ x0: x, y0: y, x1: x, y1: y + 1, color: this.softGridColor(this.getTile(x - 1, y), this.getTile(x, y)) });
2586
+ }
2587
+ lines.sort((a, b) => Number(a.color === MAP_GRID_COLOR) - Number(b.color === MAP_GRID_COLOR));
2588
+ for (const l of lines) {
2589
+ ctx.strokeStyle = l.color;
2590
+ ctx.beginPath();
2591
+ ctx.moveTo(l.x0 * MAP_TILE_SIZE, l.y0 * MAP_TILE_SIZE);
2592
+ ctx.lineTo(l.x1 * MAP_TILE_SIZE, l.y1 * MAP_TILE_SIZE);
2593
+ ctx.stroke();
2553
2594
  }
2554
2595
  }
2555
2596
  ctx.restore();
@@ -3140,7 +3181,7 @@ function renderMapThumbnail(json, height = MAP_THUMBNAIL_HEIGHT) {
3140
3181
  tctx.drawImage(full, 0, 0, thumb.width, thumb.height);
3141
3182
  return thumb.toDataURL("image/png");
3142
3183
  }
3143
- var MapBoardView = forwardRef(function MapBoardView2({ map, canvasLabel, selection, onInspect, onLeave, onDrawError, className, style, ...rest }, ref) {
3184
+ var MapBoardView = forwardRef(function MapBoardView2({ map, canvasLabel, selection, onInspect, onLeave, onDrawError, grid = "dark", className, style, ...rest }, ref) {
3144
3185
  const canvasRef = useRef(null);
3145
3186
  const failed = useRef(onDrawError);
3146
3187
  failed.current = onDrawError;
@@ -3151,11 +3192,11 @@ var MapBoardView = forwardRef(function MapBoardView2({ map, canvasLabel, selecti
3151
3192
  if (!ctx) return;
3152
3193
  try {
3153
3194
  ctx.clearRect(0, 0, canvas.width, canvas.height);
3154
- new HCMap(map).draw(ctx);
3195
+ new HCMap(map).draw(ctx, { grid });
3155
3196
  } catch {
3156
3197
  failed.current?.();
3157
3198
  }
3158
- }, [map]);
3199
+ }, [map, grid]);
3159
3200
  function pointer(event) {
3160
3201
  const bounds = event.currentTarget.getBoundingClientRect();
3161
3202
  onInspect?.((event.clientX - bounds.left) / bounds.width * map.width, (event.clientY - bounds.top) / bounds.height * map.height, event.clientX, event.clientY);
@@ -3261,6 +3302,9 @@ export {
3261
3302
  MAP_DEFAULT_CAMERA,
3262
3303
  MAP_ELEVATION_EDGE_COLOR,
3263
3304
  MAP_ELEVATION_TILE_COLORS,
3305
+ MAP_GRID_COLOR,
3306
+ MAP_GRID_SOFT_COLOR,
3307
+ MAP_GRID_TERRAIN_SHADE,
3264
3308
  MAP_INDOOR_EDGE_COLOR,
3265
3309
  MAP_SCENE_BASE_Z,
3266
3310
  MAP_SCENE_LEVEL_HEIGHT,
package/dist/styles.css CHANGED
@@ -854,6 +854,11 @@
854
854
  border-radius: 20px 20px 0 0;
855
855
  }
856
856
  [data-hc-ds] .hc-ui-sheet[data-presentation='bottom'][data-closing] { animation: hc-ui-sheet-down var(--hc-dur-medium) var(--hc-ease-emphasized-accel) forwards; }
857
+ /* height="half": rests at half the screen; the body scrolls. Expanded still wins (full height). */
858
+ [data-hc-ds] .hc-ui-sheet[data-presentation='bottom'][data-height='half']:not([data-expanded]) {
859
+ height: 50dvh;
860
+ max-height: 50dvh;
861
+ }
857
862
 
858
863
  @media (max-width: 959.98px) {
859
864
  [data-hc-ds] .hc-ui-sheet[data-presentation='auto'] {
@@ -888,6 +893,10 @@
888
893
  border-radius: 20px 20px 0 0;
889
894
  }
890
895
  [data-hc-ds] .hc-ui-sheet[data-presentation='auto'][data-closing] { animation: hc-ui-sheet-down var(--hc-dur-medium) var(--hc-ease-emphasized-accel) forwards; }
896
+ [data-hc-ds] .hc-ui-sheet[data-presentation='auto'][data-height='half']:not([data-expanded]) {
897
+ height: 50dvh;
898
+ max-height: 50dvh;
899
+ }
891
900
  }
892
901
 
893
902
  /* Closing by drag: the backdrop fades with the sheet instead of waiting for the exit. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ntmats/hc-ui",
3
- "version": "0.6.0",
3
+ "version": "0.8.0",
4
4
  "type": "module",
5
5
  "description": "Shared HCUI/Arena React presentation primitives.",
6
6
  "license": "UNLICENSED",