panelui-native 0.54.0 → 0.57.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 (47) hide show
  1. package/README.md +18 -9
  2. package/lib/module/components/combobox/index.js +61 -7
  3. package/lib/module/components/combobox/index.js.map +1 -1
  4. package/lib/module/components/hex-chart/index.js +765 -0
  5. package/lib/module/components/hex-chart/index.js.map +1 -0
  6. package/lib/module/components/qr-code/index.js +641 -0
  7. package/lib/module/components/qr-code/index.js.map +1 -0
  8. package/lib/module/components/qr-code/qr-encode.js +475 -0
  9. package/lib/module/components/qr-code/qr-encode.js.map +1 -0
  10. package/lib/module/components/sortable/index.js +74 -12
  11. package/lib/module/components/sortable/index.js.map +1 -1
  12. package/lib/module/components/steps/index.js +95 -12
  13. package/lib/module/components/steps/index.js.map +1 -1
  14. package/lib/module/components/tag-input/index.js +435 -0
  15. package/lib/module/components/tag-input/index.js.map +1 -0
  16. package/lib/module/components/tour/index.js +661 -0
  17. package/lib/module/components/tour/index.js.map +1 -0
  18. package/lib/module/index.js +4 -0
  19. package/lib/module/index.js.map +1 -1
  20. package/lib/module/utils/chart.js +209 -0
  21. package/lib/module/utils/chart.js.map +1 -1
  22. package/lib/typescript/src/components/combobox/index.d.ts.map +1 -1
  23. package/lib/typescript/src/components/hex-chart/index.d.ts +277 -0
  24. package/lib/typescript/src/components/hex-chart/index.d.ts.map +1 -0
  25. package/lib/typescript/src/components/qr-code/index.d.ts +221 -0
  26. package/lib/typescript/src/components/qr-code/index.d.ts.map +1 -0
  27. package/lib/typescript/src/components/qr-code/qr-encode.d.ts +39 -0
  28. package/lib/typescript/src/components/qr-code/qr-encode.d.ts.map +1 -0
  29. package/lib/typescript/src/components/sortable/index.d.ts.map +1 -1
  30. package/lib/typescript/src/components/steps/index.d.ts +19 -0
  31. package/lib/typescript/src/components/steps/index.d.ts.map +1 -1
  32. package/lib/typescript/src/components/tag-input/index.d.ts +240 -0
  33. package/lib/typescript/src/components/tag-input/index.d.ts.map +1 -0
  34. package/lib/typescript/src/components/tour/index.d.ts +168 -0
  35. package/lib/typescript/src/components/tour/index.d.ts.map +1 -0
  36. package/lib/typescript/src/index.d.ts +4 -0
  37. package/lib/typescript/src/index.d.ts.map +1 -1
  38. package/lib/typescript/src/utils/chart.d.ts +98 -0
  39. package/lib/typescript/src/utils/chart.d.ts.map +1 -1
  40. package/package.json +1 -1
  41. package/src/components/hex-chart/index.tsx +1009 -0
  42. package/src/components/qr-code/index.tsx +663 -0
  43. package/src/components/qr-code/qr-encode.ts +548 -0
  44. package/src/components/steps/index.tsx +103 -8
  45. package/src/components/tour/index.tsx +933 -0
  46. package/src/index.ts +41 -0
  47. package/src/utils/chart.ts +236 -0
package/src/index.ts CHANGED
@@ -258,6 +258,20 @@ export {
258
258
  type HeatmapCell,
259
259
  type HeatmapLayout,
260
260
  } from './components/heatmap-chart';
261
+ export {
262
+ HexChart,
263
+ useHexChart,
264
+ type HexChartProps,
265
+ type HexChartHandle,
266
+ type HexChartHeaderProps,
267
+ type HexChartCellsProps,
268
+ type HexChartTooltipProps,
269
+ type HexChartLegendProps,
270
+ type HexChartSkeletonProps,
271
+ type HexChartStatus,
272
+ type HexDatum,
273
+ type HexShape,
274
+ } from './components/hex-chart';
261
275
  export {
262
276
  Map,
263
277
  useMap,
@@ -668,6 +682,25 @@ export {
668
682
  type PostVote,
669
683
  } from './components/post';
670
684
  export { Progress, type ProgressProps } from './components/progress';
685
+ export {
686
+ QRCode,
687
+ type QRCodeProps,
688
+ type QRCodeCanvasProps,
689
+ type QRCodeFrameProps,
690
+ type QRCodeHeaderProps,
691
+ type QRCodeTitleProps,
692
+ type QRCodeActionProps,
693
+ type QRCodePanelProps,
694
+ type QRCodeDescriptionProps,
695
+ type QRCodeCaptionProps,
696
+ type QRCodeValueProps,
697
+ type QRCodeLogoProps,
698
+ type QRCodeTriggerProps,
699
+ type QRCodeContentProps,
700
+ type QRCodeSize,
701
+ type QRCodePresentation,
702
+ type ErrorCorrectionLevel,
703
+ } from './components/qr-code';
671
704
  export {
672
705
  Questionnaire,
673
706
  type QuestionnaireProps,
@@ -919,6 +952,14 @@ export {
919
952
  type TooltipOpenOn,
920
953
  type TooltipVariant,
921
954
  } from './components/tooltip';
955
+ export {
956
+ Tour,
957
+ type TourProps,
958
+ type TourStepProps,
959
+ type TourShape,
960
+ type TourPlacement,
961
+ type TourLabels,
962
+ } from './components/tour';
922
963
  export {
923
964
  Typography,
924
965
  type TypographyProps,
@@ -504,6 +504,242 @@ export function radarPath(
504
504
  return `${path}Z`;
505
505
  }
506
506
 
507
+ /**
508
+ * A pointy-top hexagon's metrics, from the radius of the circle through its
509
+ * corners.
510
+ *
511
+ * Pointy-top rather than flat-top because it is the rows that have to tile: a
512
+ * row of pointy-top cells has a straight top and bottom edge, so the next row
513
+ * nests into it by half a cell and the field reads as a honeycomb. Flat-top
514
+ * cells tile by column instead, which gives the same shape turned a quarter
515
+ * turn and a field that is taller than it is wide for the same cell count.
516
+ */
517
+ export interface HexMetrics {
518
+ /** Centre to corner. */
519
+ radius: number;
520
+ /** Across the flats — the full width of one cell. */
521
+ width: number;
522
+ /** Point to point — the full height of one cell. */
523
+ height: number;
524
+ /** Centre to centre along a row. */
525
+ stepX: number;
526
+ /**
527
+ * Centre to centre between rows. Three quarters of the height, not all of
528
+ * it: consecutive rows interlock, and each one only costs the height of the
529
+ * cell less the point it slots into.
530
+ */
531
+ stepY: number;
532
+ }
533
+
534
+ export function hexMetrics(radius: number): HexMetrics {
535
+ 'worklet';
536
+ const width = Math.sqrt(3) * radius;
537
+ return { radius, width, height: radius * 2, stepX: width, stepY: radius * 1.5 };
538
+ }
539
+
540
+ /** The cell radius that fits `columns` of them across `width`. */
541
+ export function hexRadiusFor(width: number, columns: number): number {
542
+ 'worklet';
543
+ if (width <= 0 || columns <= 0) return 0;
544
+ // Half a cell wider than the columns alone: the odd rows are offset by that
545
+ // much, and a radius derived without it runs them off the right edge.
546
+ return width / (columns + 0.5) / Math.sqrt(3);
547
+ }
548
+
549
+ /** How many rows of cells fit in `height`. */
550
+ export function hexRowsFor(height: number, metrics: HexMetrics): number {
551
+ 'worklet';
552
+ if (height <= 0 || metrics.stepY <= 0) return 0;
553
+ return Math.max(1, Math.floor((height - metrics.height / 4) / metrics.stepY));
554
+ }
555
+
556
+ /** The centre of the cell at `column`, `row`, with odd rows nested half a cell right. */
557
+ export function hexCenter(
558
+ column: number,
559
+ row: number,
560
+ metrics: HexMetrics,
561
+ left: number,
562
+ top: number
563
+ ): ChartPoint {
564
+ 'worklet';
565
+ const offset = row % 2 === 1 ? metrics.stepX / 2 : 0;
566
+ return {
567
+ x: left + metrics.stepX / 2 + offset + column * metrics.stepX,
568
+ y: top + metrics.radius + row * metrics.stepY,
569
+ };
570
+ }
571
+
572
+ /**
573
+ * One hexagon as a closed path.
574
+ *
575
+ * Corners are turns from twelve o'clock like everything else polar in this
576
+ * file, which puts the first one straight up — the definition of pointy-top.
577
+ *
578
+ * The coordinates are rounded to two decimals because these paths are
579
+ * concatenated by the hundred: a field is one path string per series rather
580
+ * than one node per cell, and full float precision would make each of those
581
+ * strings several times longer for a difference no display can resolve.
582
+ */
583
+ export function hexPath(cx: number, cy: number, radius: number): string {
584
+ 'worklet';
585
+ if (radius <= 0) return '';
586
+ let d = '';
587
+ for (let corner = 0; corner < 6; corner += 1) {
588
+ const point = polarPoint(cx, cy, radius, corner / 6);
589
+ d += `${corner === 0 ? 'M' : 'L'}${point.x.toFixed(2)},${point.y.toFixed(2)}`;
590
+ }
591
+ return `${d}Z`;
592
+ }
593
+
594
+ /**
595
+ * A number in 0…1 from a pair of coordinates, the same one every time.
596
+ *
597
+ * `Math.random` would give the honeycomb a different edge on every render,
598
+ * which turns a re-render into an animation nobody asked for and means the
599
+ * same data never screenshots twice.
600
+ */
601
+ export function hashUnit(a: number, b: number): number {
602
+ 'worklet';
603
+ let h = Math.imul(a + 1, 374761393) ^ Math.imul(b + 1, 668265263);
604
+ h = Math.imul(h ^ (h >>> 13), 1274126177);
605
+ return ((h ^ (h >>> 16)) >>> 0) / 4294967296;
606
+ }
607
+
608
+ /**
609
+ * How many cells each value gets out of `budget`, by largest remainder.
610
+ *
611
+ * Rounding each share on its own does not add up — three equal parts of a
612
+ * hundred round to 33 each and leave one over — and a spare cell in a
613
+ * honeycomb is not a rounding error the reader can shrug off, it is a cell of
614
+ * some colour that nothing in the data accounts for. Flooring every share and
615
+ * then handing the leftovers to the largest fractions spends the budget
616
+ * exactly, and spends it on the series with the strongest claim to each one.
617
+ */
618
+ export function shareCounts(values: number[], budget: number): number[] {
619
+ const total = values.reduce((sum, value) => sum + Math.max(0, value), 0);
620
+ if (total <= 0 || budget <= 0) return values.map(() => 0);
621
+
622
+ const exact = values.map((value) => (Math.max(0, value) / total) * budget);
623
+ const counts = exact.map((value) => Math.floor(value));
624
+ // Every fraction is under one, so there is always less than one cell per
625
+ // series left over and a single pass down the order places all of them. The
626
+ // clamp is against float drift in the sum rather than against the maths.
627
+ const spare = Math.min(
628
+ budget - counts.reduce((sum, count) => sum + count, 0),
629
+ values.length
630
+ );
631
+
632
+ const byFraction = exact
633
+ .map((value, index) => ({ index, fraction: value - Math.floor(value) }))
634
+ .sort((a, b) => b.fraction - a.fraction || a.index - b.index);
635
+
636
+ for (let i = 0; i < spare; i += 1) {
637
+ const index = byFraction[i]!.index;
638
+ counts[index] = counts[index]! + 1;
639
+ }
640
+ return counts;
641
+ }
642
+
643
+ /** How the filled cells are arranged in the field. */
644
+ export type HexShape = 'blob' | 'grid';
645
+
646
+ export interface HexCell {
647
+ column: number;
648
+ row: number;
649
+ }
650
+
651
+ /** How far off its ring a cell may be nudged, in field widths. About one cell. */
652
+ const BLOB_WOBBLE = 0.09;
653
+
654
+ /**
655
+ * Every cell of the field, in the order the series fill it.
656
+ *
657
+ * `grid` is reading order, which is the honest arrangement: counting cells off
658
+ * a row is something a reader can actually do. `blob` grows out from the middle
659
+ * instead, which counts for nothing but shows the shape of the split at a
660
+ * glance — the smallest series in the centre with each larger one wrapped
661
+ * around it.
662
+ *
663
+ * Two things make the blob look grown rather than stamped. Distance is measured
664
+ * in the field's own proportions, so a wide field grows a wide blob instead of
665
+ * a circle with empty shoulders either side of it; and each cell's distance is
666
+ * nudged by a hash of its own coordinates, so the boundary between two series
667
+ * comes out ragged instead of as a clean arc. The nudge is a hash and not a
668
+ * random number, so the same data draws the same honeycomb every time.
669
+ *
670
+ * Computed once per layout rather than per frame, so it is a plain function.
671
+ */
672
+ export function hexFillOrder(columns: number, rows: number, shape: HexShape): HexCell[] {
673
+ const cells: HexCell[] = [];
674
+ for (let row = 0; row < rows; row += 1) {
675
+ for (let column = 0; column < columns; column += 1) {
676
+ cells.push({ column, row });
677
+ }
678
+ }
679
+ if (shape === 'grid' || columns < 2 || rows < 2) return cells;
680
+
681
+ const midColumn = (columns - 1) / 2;
682
+ const midRow = (rows - 1) / 2;
683
+
684
+ return cells
685
+ .map((cell) => {
686
+ // Odd rows sit half a cell right, and a distance measured without that
687
+ // leans the whole blob to one side.
688
+ const column = cell.column + (cell.row % 2 === 1 ? 0.5 : 0);
689
+ const dx = (column - midColumn) / columns;
690
+ const dy = (cell.row - midRow) / rows;
691
+ const wobble = (hashUnit(cell.column, cell.row) - 0.5) * BLOB_WOBBLE;
692
+ return { cell, key: Math.sqrt(dx * dx + dy * dy) + wobble };
693
+ })
694
+ .sort((a, b) => a.key - b.key)
695
+ .map((entry) => entry.cell);
696
+ }
697
+
698
+ /**
699
+ * The cell under a point, or null when the point is between cells or outside
700
+ * the field.
701
+ *
702
+ * Only three rows can be near any y, and one column near any x within a row, so
703
+ * this checks six candidates rather than solving the cube-rounding — the same
704
+ * answer, and short enough to read.
705
+ */
706
+ export function hexAt(
707
+ x: number,
708
+ y: number,
709
+ metrics: HexMetrics,
710
+ left: number,
711
+ top: number,
712
+ columns: number,
713
+ rows: number
714
+ ): HexCell | null {
715
+ 'worklet';
716
+ if (metrics.radius <= 0) return null;
717
+
718
+ const approximateRow = Math.round((y - top - metrics.radius) / metrics.stepY);
719
+ let best: HexCell | null = null;
720
+ let bestDistance = Infinity;
721
+
722
+ for (let row = approximateRow - 1; row <= approximateRow + 1; row += 1) {
723
+ if (row < 0 || row >= rows) continue;
724
+ const offset = row % 2 === 1 ? metrics.stepX / 2 : 0;
725
+ const column = Math.round((x - left - metrics.stepX / 2 - offset) / metrics.stepX);
726
+ if (column < 0 || column >= columns) continue;
727
+
728
+ const centre = hexCenter(column, row, metrics, left, top);
729
+ const dx = x - centre.x;
730
+ const dy = y - centre.y;
731
+ const distance = dx * dx + dy * dy;
732
+ if (distance < bestDistance) {
733
+ bestDistance = distance;
734
+ best = { column, row };
735
+ }
736
+ }
737
+
738
+ // Further than a radius from the nearest centre is outside the honeycomb, not
739
+ // a near miss worth rounding into it.
740
+ return best !== null && bestDistance <= metrics.radius * metrics.radius ? best : null;
741
+ }
742
+
507
743
  /** The numbers of one column, with anything unusable left as a gap. */
508
744
  export function columnValues(
509
745
  data: Record<string, unknown>[],