@ankhorage/zora 25.2.0 → 25.3.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 (221) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +2 -1
  3. package/dist/features/grid-rulers/adapters/inbound/GridLineOverlay.d.ts +5 -0
  4. package/dist/features/grid-rulers/adapters/inbound/GridLineOverlay.d.ts.map +1 -0
  5. package/dist/features/grid-rulers/adapters/inbound/GridLineOverlay.js +69 -0
  6. package/dist/features/grid-rulers/adapters/inbound/GridLineOverlay.js.map +1 -0
  7. package/dist/features/grid-rulers/adapters/inbound/GridRuler.d.ts +5 -0
  8. package/dist/features/grid-rulers/adapters/inbound/GridRuler.d.ts.map +1 -0
  9. package/dist/features/grid-rulers/adapters/inbound/GridRuler.js +59 -0
  10. package/dist/features/grid-rulers/adapters/inbound/GridRuler.js.map +1 -0
  11. package/dist/features/grid-rulers/application/projectGridRulerPosition.d.ts +4 -0
  12. package/dist/features/grid-rulers/application/projectGridRulerPosition.d.ts.map +1 -0
  13. package/dist/features/grid-rulers/application/projectGridRulerPosition.js +10 -0
  14. package/dist/features/grid-rulers/application/projectGridRulerPosition.js.map +1 -0
  15. package/dist/features/grid-rulers/application/resolveGridRulerMarks.d.ts +5 -0
  16. package/dist/features/grid-rulers/application/resolveGridRulerMarks.d.ts.map +1 -0
  17. package/dist/features/grid-rulers/application/resolveGridRulerMarks.js +22 -0
  18. package/dist/features/grid-rulers/application/resolveGridRulerMarks.js.map +1 -0
  19. package/dist/features/grid-rulers/meta.d.ts +21 -0
  20. package/dist/features/grid-rulers/meta.d.ts.map +1 -0
  21. package/dist/features/grid-rulers/meta.js +22 -0
  22. package/dist/features/grid-rulers/meta.js.map +1 -0
  23. package/dist/features/grid-rulers/public.d.ts +5 -0
  24. package/dist/features/grid-rulers/public.d.ts.map +1 -0
  25. package/dist/features/grid-rulers/public.js +4 -0
  26. package/dist/features/grid-rulers/public.js.map +1 -0
  27. package/dist/features/registry/ZORA_COMPONENT_REGISTRY.d.ts.map +1 -1
  28. package/dist/features/registry/ZORA_COMPONENT_REGISTRY.js +3 -0
  29. package/dist/features/registry/ZORA_COMPONENT_REGISTRY.js.map +1 -1
  30. package/dist/features/registry/componentMeta.d.ts.map +1 -1
  31. package/dist/features/registry/componentMeta.js +3 -0
  32. package/dist/features/registry/componentMeta.js.map +1 -1
  33. package/dist/index.d.ts +2 -0
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +1 -0
  36. package/dist/index.js.map +1 -1
  37. package/dist/types/grid-rulers.d.ts +48 -0
  38. package/dist/types/grid-rulers.d.ts.map +1 -0
  39. package/dist/types/grid-rulers.js +2 -0
  40. package/dist/types/grid-rulers.js.map +1 -0
  41. package/package.json +6 -6
  42. package/src/features/grid-rulers/adapters/inbound/GridLineOverlay.tsx +123 -0
  43. package/src/features/grid-rulers/adapters/inbound/GridRuler.tsx +96 -0
  44. package/src/features/grid-rulers/adapters/inbound/GridRulers.test.tsx +162 -0
  45. package/src/features/grid-rulers/application/projectGridRulerPosition.ts +19 -0
  46. package/src/features/grid-rulers/application/resolveGridRulerMarks.test.ts +90 -0
  47. package/src/features/grid-rulers/application/resolveGridRulerMarks.ts +39 -0
  48. package/src/features/grid-rulers/meta.ts +26 -0
  49. package/src/features/grid-rulers/public.ts +10 -0
  50. package/src/features/registry/ZORA_COMPONENT_REGISTRY.ts +3 -0
  51. package/src/features/registry/componentMeta.ts +3 -0
  52. package/src/index.ts +8 -0
  53. package/src/types/grid-rulers.ts +59 -0
  54. package/web-dist/chunks/{Accordion-mh67wb34.js → Accordion-090y18cv.js} +2 -2
  55. package/web-dist/chunks/{Accordion-7yazfxr6.js → Accordion-3d4wkzg7.js} +4 -4
  56. package/web-dist/chunks/{Accordion-ndf3jqhb.js → Accordion-40tenzcg.js} +3 -3
  57. package/web-dist/chunks/{Accordion-cbfa2b8j.js → Accordion-4s68t3h7.js} +5 -5
  58. package/web-dist/chunks/{Accordion-7asr7vb0.js → Accordion-4zqd7twp.js} +4 -4
  59. package/web-dist/chunks/{Accordion-sdcfa14w.js → Accordion-5g05kfmx.js} +3 -3
  60. package/web-dist/chunks/{Accordion-wgejzesa.js → Accordion-61ntvh14.js} +3 -3
  61. package/web-dist/chunks/{Accordion-kkr7xfh2.js → Accordion-689w7frk.js} +4 -4
  62. package/web-dist/chunks/{Accordion-q09fjenx.js → Accordion-6rrqpb2h.js} +1 -1
  63. package/web-dist/chunks/{Accordion-mej71qnw.js → Accordion-a4cma5b6.js} +2 -2
  64. package/web-dist/chunks/Accordion-e6ddpz5v.js +209 -0
  65. package/web-dist/chunks/Accordion-e9ewv367.js +201 -0
  66. package/web-dist/chunks/{Accordion-9dqkq9am.js → Accordion-ejx5gcf9.js} +8 -8
  67. package/web-dist/chunks/{Accordion-pe66x9cv.js → Accordion-ewf1zen9.js} +4 -4
  68. package/web-dist/chunks/{Accordion-xvzrx6bh.js → Accordion-fjxcn6g9.js} +7 -7
  69. package/web-dist/chunks/{Accordion-teg40mpt.js → Accordion-fmzxat3v.js} +3 -3
  70. package/web-dist/chunks/{Accordion-016wvwpg.js → Accordion-h3y8qev2.js} +1 -1
  71. package/web-dist/chunks/{Accordion-t0hbez90.js → Accordion-h7zsx9eg.js} +2 -2
  72. package/web-dist/chunks/{Accordion-ch4q5w0z.js → Accordion-jnc315h7.js} +1 -1
  73. package/web-dist/chunks/{Accordion-fkc50r9z.js → Accordion-kjtcbe09.js} +2 -2
  74. package/web-dist/chunks/{Accordion-tmrjy46p.js → Accordion-nssr5gep.js} +1 -1
  75. package/web-dist/chunks/{Accordion-ntndskbj.js → Accordion-pvj5npzc.js} +2 -2
  76. package/web-dist/chunks/{Accordion-54k0ncs3.js → Accordion-q2z00p5r.js} +5 -73
  77. package/web-dist/chunks/{Accordion-ggwxv6k3.js → Accordion-r1cj2w9e.js} +2 -2
  78. package/web-dist/chunks/{Accordion-5ga4549x.js → Accordion-sp4k08kw.js} +3 -3
  79. package/web-dist/chunks/{Accordion-1p9r2ack.js → Accordion-sy1cc5my.js} +2 -2
  80. package/web-dist/chunks/{Accordion-hmhdwvqp.js → Accordion-tqpsmdrk.js} +3 -3
  81. package/web-dist/chunks/{Accordion-fzkrrc4z.js → Accordion-v0grdh6q.js} +1 -1
  82. package/web-dist/chunks/{Accordion-ajawc6nq.js → Accordion-zt9yghdz.js} +3 -3
  83. package/web-dist/components/accordion/Accordion.js +4 -4
  84. package/web-dist/components/accordion-item/AccordionItem.js +4 -4
  85. package/web-dist/components/app-bar/AppBar.js +6 -6
  86. package/web-dist/components/app-header/AppHeader.js +6 -6
  87. package/web-dist/components/app-shell/AppShell.js +1 -1
  88. package/web-dist/components/auth-screen/AuthScreen.js +9 -9
  89. package/web-dist/components/avatar/Avatar.js +4 -4
  90. package/web-dist/components/avatar-group/AvatarGroup.js +4 -4
  91. package/web-dist/components/badge/Badge.js +1 -1
  92. package/web-dist/components/barcode-scanner-view/BarcodeScannerView.js +9 -9
  93. package/web-dist/components/bottom-sheet/BottomSheet.js +2 -2
  94. package/web-dist/components/bottom-sheet-flat-list/BottomSheetFlatList.js +2 -2
  95. package/web-dist/components/bottom-sheet-scroll-view/BottomSheetScrollView.js +2 -2
  96. package/web-dist/components/bottom-sheet-section-list/BottomSheetSectionList.js +2 -2
  97. package/web-dist/components/bottom-sheet-view/BottomSheetView.js +2 -2
  98. package/web-dist/components/bottom-sheet-virtualized-list/BottomSheetVirtualizedList.js +2 -2
  99. package/web-dist/components/breadcrumbs/Breadcrumbs.js +4 -4
  100. package/web-dist/components/button/Button.js +2 -2
  101. package/web-dist/components/button-group/ButtonGroup.js +2 -2
  102. package/web-dist/components/camera-permission-view/CameraPermissionView.js +9 -9
  103. package/web-dist/components/card/Card.js +8 -8
  104. package/web-dist/components/card-back/CardBack.js +1 -1
  105. package/web-dist/components/card-hand/CardHand.js +1 -1
  106. package/web-dist/components/chat-list-item/ChatListItem.js +6 -6
  107. package/web-dist/components/checkbox/Checkbox.js +3 -3
  108. package/web-dist/components/checkbox-group/CheckboxGroup.js +3 -3
  109. package/web-dist/components/chess-board/ChessBoard.js +1 -1
  110. package/web-dist/components/chip/Chip.js +4 -4
  111. package/web-dist/components/chip-group/ChipGroup.js +4 -4
  112. package/web-dist/components/collection-editor/CollectionEditor.js +8 -8
  113. package/web-dist/components/content-rail/ContentRail.js +3 -3
  114. package/web-dist/components/data-table/DataTable.js +13 -13
  115. package/web-dist/components/date-picker/DatePicker.js +4 -4
  116. package/web-dist/components/dialog/Dialog.js +3 -3
  117. package/web-dist/components/divider/Divider.js +1 -1
  118. package/web-dist/components/empty-state/EmptyState.js +9 -9
  119. package/web-dist/components/explorer/Explorer.js +6 -6
  120. package/web-dist/components/field/Field.js +3 -3
  121. package/web-dist/components/file-explorer/FileExplorer.js +6 -6
  122. package/web-dist/components/flat-list/FlatList.js +4 -4
  123. package/web-dist/components/forgot-password-form/ForgotPasswordForm.js +9 -9
  124. package/web-dist/components/form/Form.js +5 -5
  125. package/web-dist/components/form-actions/FormActions.js +5 -5
  126. package/web-dist/components/form-error/FormError.js +5 -5
  127. package/web-dist/components/game/Game.js +2 -2
  128. package/web-dist/components/game-entity/GameEntity.js +2 -2
  129. package/web-dist/components/game-field/GameField.js +2 -2
  130. package/web-dist/components/game-input-zone/GameInputZone.js +2 -2
  131. package/web-dist/components/game-measurement-probe/GameMeasurementProbe.js +2 -2
  132. package/web-dist/components/game-overlay/GameOverlay.js +2 -2
  133. package/web-dist/components/gradient/Gradient.js +2 -2
  134. package/web-dist/components/gradient-renderer-provider/GradientRendererProvider.js +2 -2
  135. package/web-dist/components/grid/Grid.js +1 -1
  136. package/web-dist/components/grid-line-overlay/GridLineOverlay.d.ts +4 -0
  137. package/web-dist/components/grid-line-overlay/GridLineOverlay.js +13 -0
  138. package/web-dist/components/grid-line-overlay/index.d.ts +1 -0
  139. package/web-dist/components/grid-line-overlay/index.js +2 -0
  140. package/web-dist/components/grid-ruler/GridRuler.d.ts +4 -0
  141. package/web-dist/components/grid-ruler/GridRuler.js +13 -0
  142. package/web-dist/components/grid-ruler/index.d.ts +1 -0
  143. package/web-dist/components/grid-ruler/index.js +2 -0
  144. package/web-dist/components/grid-view/GridView.js +1 -1
  145. package/web-dist/components/heading/Heading.js +1 -1
  146. package/web-dist/components/hero/Hero.js +8 -8
  147. package/web-dist/components/icon/Icon.js +1 -1
  148. package/web-dist/components/icon-button/IconButton.js +2 -2
  149. package/web-dist/components/image/Image.js +3 -3
  150. package/web-dist/components/keyboard-avoiding-view/KeyboardAvoidingView.js +1 -1
  151. package/web-dist/components/list/List.js +4 -4
  152. package/web-dist/components/list-item/ListItem.js +4 -4
  153. package/web-dist/components/list-section/ListSection.js +4 -4
  154. package/web-dist/components/media-card/MediaCard.js +8 -8
  155. package/web-dist/components/media-explorer/MediaExplorer.js +6 -6
  156. package/web-dist/components/message-bubble/MessageBubble.js +6 -6
  157. package/web-dist/components/metric-card/MetricCard.js +8 -8
  158. package/web-dist/components/missing-element/MissingElement.js +3 -3
  159. package/web-dist/components/o-auth-provider-button/OAuthProviderButton.js +9 -9
  160. package/web-dist/components/o-auth-provider-list/OAuthProviderList.js +9 -9
  161. package/web-dist/components/opening-book/OpeningBook.js +1 -1
  162. package/web-dist/components/otp-form/OtpForm.js +9 -9
  163. package/web-dist/components/pagination/Pagination.js +3 -3
  164. package/web-dist/components/palette-item/PaletteItem.js +8 -8
  165. package/web-dist/components/playing-card/PlayingCard.js +1 -1
  166. package/web-dist/components/poker-training-table/PokerTrainingTable.js +1 -1
  167. package/web-dist/components/popover-menu/PopoverMenu.js +3 -3
  168. package/web-dist/components/post-card/PostCard.js +8 -8
  169. package/web-dist/components/product-card/ProductCard.js +8 -8
  170. package/web-dist/components/progress/Progress.js +3 -3
  171. package/web-dist/components/progress-ring/ProgressRing.js +3 -3
  172. package/web-dist/components/radio/Radio.js +4 -4
  173. package/web-dist/components/radio-group/RadioGroup.js +4 -4
  174. package/web-dist/components/rating/Rating.js +2 -2
  175. package/web-dist/components/reader/Reader.js +9 -9
  176. package/web-dist/components/scan-overlay/ScanOverlay.js +9 -9
  177. package/web-dist/components/screen/Screen.js +1 -1
  178. package/web-dist/components/screen-section/ScreenSection.js +3 -3
  179. package/web-dist/components/scroll-view/ScrollView.js +1 -1
  180. package/web-dist/components/search-input/SearchInput.js +3 -3
  181. package/web-dist/components/section-header/SectionHeader.js +3 -3
  182. package/web-dist/components/section-list/SectionList.js +4 -4
  183. package/web-dist/components/select/Select.js +3 -3
  184. package/web-dist/components/selectable-item/SelectableItem.js +1 -1
  185. package/web-dist/components/selection-provider/SelectionProvider.js +1 -1
  186. package/web-dist/components/sign-in-form/SignInForm.js +9 -9
  187. package/web-dist/components/sign-up-form/SignUpForm.js +9 -9
  188. package/web-dist/components/skeleton/Skeleton.js +11 -11
  189. package/web-dist/components/skeleton-card/SkeletonCard.js +11 -11
  190. package/web-dist/components/skeleton-list/SkeletonList.js +11 -11
  191. package/web-dist/components/skeleton-text/SkeletonText.js +11 -11
  192. package/web-dist/components/surface/Surface.js +1 -1
  193. package/web-dist/components/tab/Tab.js +1 -1
  194. package/web-dist/components/tab-list/TabList.js +1 -1
  195. package/web-dist/components/tab-panel/TabPanel.js +1 -1
  196. package/web-dist/components/tabletop-table/TabletopTable.js +1 -1
  197. package/web-dist/components/tabs/Tabs.js +1 -1
  198. package/web-dist/components/text/Text.js +1 -1
  199. package/web-dist/components/text-input/TextInput.js +3 -3
  200. package/web-dist/components/tile-grid/TileGrid.js +1 -1
  201. package/web-dist/components/time-picker/TimePicker.js +4 -4
  202. package/web-dist/components/timeline/Timeline.js +3 -3
  203. package/web-dist/components/toast/Toast.js +1 -1
  204. package/web-dist/components/toast-provider/ToastProvider.js +1 -1
  205. package/web-dist/components/toolbar/Toolbar.js +8 -8
  206. package/web-dist/components/tree-item/TreeItem.js +7 -7
  207. package/web-dist/components/uploader/Uploader.js +11 -11
  208. package/web-dist/components/view/View.js +1 -1
  209. package/web-dist/manifest.json +539 -493
  210. package/web-dist/runtime/ZoraProvider.js +3 -3
  211. /package/web-dist/chunks/{Accordion-qkh5yy94.js → Accordion-7kq500r1.js} +0 -0
  212. /package/web-dist/chunks/{Accordion-f0jsd9f9.js → Accordion-7tjz25jh.js} +0 -0
  213. /package/web-dist/chunks/{Accordion-3m3572sa.js → Accordion-88t8fgxv.js} +0 -0
  214. /package/web-dist/chunks/{Accordion-afmybp67.js → Accordion-9w8wg9mf.js} +0 -0
  215. /package/web-dist/chunks/{Accordion-bb8y34wh.js → Accordion-b96evb46.js} +0 -0
  216. /package/web-dist/chunks/{Accordion-nmpmdrba.js → Accordion-dbf93gxh.js} +0 -0
  217. /package/web-dist/chunks/{Accordion-x12fca21.js → Accordion-fhnazn3n.js} +0 -0
  218. /package/web-dist/chunks/{Accordion-n9c5tsqw.js → Accordion-qn398m1x.js} +0 -0
  219. /package/web-dist/chunks/{Accordion-2rgexkze.js → Accordion-rsvphbe4.js} +0 -0
  220. /package/web-dist/chunks/{Accordion-c69374jn.js → Accordion-sw7ep29d.js} +0 -0
  221. /package/web-dist/chunks/{Accordion-8jxc7tx9.js → Accordion-w6qedeba.js} +0 -0
@@ -0,0 +1,162 @@
1
+ import { join } from 'node:path';
2
+ import { pathToFileURL } from 'node:url';
3
+
4
+ import type { GridViewport } from '@ankhorage/grid-view';
5
+ import { expect, test } from 'bun:test';
6
+ import { Window } from 'happy-dom';
7
+ import React from 'react';
8
+ import { renderToStaticMarkup } from 'react-dom/server';
9
+
10
+ import type { GridLineOverlayProps, GridRulerProps } from '../../../../types/grid-rulers';
11
+ import type { ZoraProviderProps } from '../../../theme/adapters/inbound/ZoraProvider';
12
+
13
+ const webDistRoot = join(import.meta.dir, '../../../../../web-dist');
14
+
15
+ /*** Loads the independently bundled React Native Web implementation under test. */
16
+ function load(path: string) {
17
+ return import(pathToFileURL(join(webDistRoot, path)).href);
18
+ }
19
+
20
+ const viewport: GridViewport = {
21
+ height: 100,
22
+ offsetX: 40,
23
+ offsetY: 20,
24
+ pixelsPerUnitX: 2,
25
+ pixelsPerUnitY: 2,
26
+ width: 300,
27
+ };
28
+
29
+ const xTickSource = { kind: 'ticks' as const, specification: { mode: 'fixed' as const, step: 20 } };
30
+
31
+ /*** Renders the public native components through React Native Web for behavioral DOM assertions. */
32
+ function renderGridRulers(
33
+ ZoraProvider: React.ComponentType<ZoraProviderProps>,
34
+ GridRuler: React.ComponentType<GridRulerProps>,
35
+ GridLineOverlay: React.ComponentType<GridLineOverlayProps>,
36
+ direction: 'ltr' | 'rtl',
37
+ guides = [{ axis: 'x' as const, id: 'playhead', label: 'Playhead', position: 60 }],
38
+ ) {
39
+ return renderToStaticMarkup(
40
+ <ZoraProvider mode="light">
41
+ <GridRuler
42
+ axis="x"
43
+ direction={direction}
44
+ testID="ruler"
45
+ tickSource={xTickSource}
46
+ viewport={viewport}
47
+ />
48
+ <GridLineOverlay
49
+ direction={direction}
50
+ guides={guides}
51
+ testID="overlay"
52
+ viewport={viewport}
53
+ xTickSource={xTickSource}
54
+ />
55
+ </ZoraProvider>,
56
+ );
57
+ }
58
+
59
+ /*** Reads React Native Web output without relying on implementation-only geometry helpers. */
60
+ function inspectMarkup(markup: string): Window {
61
+ const browserWindow = new Window();
62
+ browserWindow.document.body.innerHTML = markup;
63
+ return browserWindow;
64
+ }
65
+
66
+ test('renders ruler ticks, grid lines, and guides at the same non-zero-offset world projection in LTR and RTL', async () => {
67
+ const { ZoraProvider } = (await load('runtime/ZoraProvider.js')) as {
68
+ ZoraProvider: React.ComponentType<ZoraProviderProps>;
69
+ };
70
+ const { GridRuler } = (await load('components/grid-ruler/GridRuler.js')) as {
71
+ GridRuler: React.ComponentType<GridRulerProps>;
72
+ };
73
+ const { GridLineOverlay } = (await load('components/grid-line-overlay/GridLineOverlay.js')) as {
74
+ GridLineOverlay: React.ComponentType<GridLineOverlayProps>;
75
+ };
76
+ const ltr = inspectMarkup(renderGridRulers(ZoraProvider, GridRuler, GridLineOverlay, 'ltr'));
77
+ const rtl = inspectMarkup(renderGridRulers(ZoraProvider, GridRuler, GridLineOverlay, 'rtl'));
78
+
79
+ try {
80
+ for (const [browserWindow, expectedPosition] of [
81
+ [ltr, '40px'],
82
+ [rtl, '260px'],
83
+ ] as const) {
84
+ expect(
85
+ browserWindow.document.querySelector('[data-testid="ruler-tick-1"]')?.getAttribute('style'),
86
+ ).toContain(`left:${expectedPosition}`);
87
+ expect(
88
+ browserWindow.document
89
+ .querySelector('[data-testid="overlay-x-line-1"]')
90
+ ?.getAttribute('style'),
91
+ ).toContain(`left:${expectedPosition}`);
92
+ expect(
93
+ browserWindow.document
94
+ .querySelector('[data-testid="overlay-guide-playhead"]')
95
+ ?.getAttribute('style'),
96
+ ).toContain(`left:${expectedPosition}`);
97
+ }
98
+ } finally {
99
+ ltr.close();
100
+ rtl.close();
101
+ }
102
+ });
103
+
104
+ test('renders only finite visible guides and exposes each guide without making grid decoration navigable', async () => {
105
+ const { ZoraProvider } = (await load('runtime/ZoraProvider.js')) as {
106
+ ZoraProvider: React.ComponentType<ZoraProviderProps>;
107
+ };
108
+ const { GridRuler } = (await load('components/grid-ruler/GridRuler.js')) as {
109
+ GridRuler: React.ComponentType<GridRulerProps>;
110
+ };
111
+ const { GridLineOverlay } = (await load('components/grid-line-overlay/GridLineOverlay.js')) as {
112
+ GridLineOverlay: React.ComponentType<GridLineOverlayProps>;
113
+ };
114
+ const browserWindow = inspectMarkup(
115
+ renderGridRulers(ZoraProvider, GridRuler, GridLineOverlay, 'ltr', [
116
+ { axis: 'x', id: 'before', position: 39 },
117
+ { axis: 'x', id: 'start', label: 'Start', position: 40 },
118
+ { axis: 'x', id: 'end', label: 'End', position: 190 },
119
+ { axis: 'x', id: 'after', position: 191 },
120
+ { axis: 'y', id: 'invalid', position: Number.POSITIVE_INFINITY },
121
+ ]),
122
+ );
123
+
124
+ try {
125
+ expect(browserWindow.document.querySelectorAll('[data-testid^="overlay-guide-"]')).toHaveLength(
126
+ 2,
127
+ );
128
+ expect(
129
+ browserWindow.document.querySelector('[data-testid="overlay-guide-start"]'),
130
+ ).not.toBeNull();
131
+ expect(
132
+ browserWindow.document.querySelector('[data-testid="overlay-guide-end"]'),
133
+ ).not.toBeNull();
134
+ expect(browserWindow.document.querySelector('[data-testid="overlay-guide-before"]')).toBeNull();
135
+ expect(browserWindow.document.querySelector('[data-testid="overlay-guide-after"]')).toBeNull();
136
+ expect(
137
+ browserWindow.document.querySelector('[data-testid="overlay"]')?.getAttribute('aria-label'),
138
+ ).toBeNull();
139
+ expect(
140
+ browserWindow.document
141
+ .querySelector('[data-testid="overlay-guide-start"]')
142
+ ?.getAttribute('aria-label'),
143
+ ).toBe('Grid guides: Start at 40');
144
+ expect(
145
+ browserWindow.document
146
+ .querySelector('[data-testid="overlay-guide-end"]')
147
+ ?.getAttribute('aria-label'),
148
+ ).toBe('Grid guides: End at 190');
149
+ expect(
150
+ browserWindow.document
151
+ .querySelector('[data-testid="overlay-x-line-1"]')
152
+ ?.getAttribute('aria-label'),
153
+ ).toBeNull();
154
+ expect(
155
+ browserWindow.document
156
+ .querySelector('[data-testid="overlay-x-line-1"]')
157
+ ?.getAttribute('role'),
158
+ ).toBeNull();
159
+ } finally {
160
+ browserWindow.close();
161
+ }
162
+ });
@@ -0,0 +1,19 @@
1
+ import { type GridAxisName, type GridViewport, worldToViewport } from '@ankhorage/grid-view';
2
+
3
+ /*** Projects a stable world-axis coordinate into the matching visual ruler or overlay position. */
4
+ export function projectGridRulerPosition(
5
+ viewport: GridViewport,
6
+ axis: GridAxisName,
7
+ position: number,
8
+ direction: 'ltr' | 'rtl' = 'ltr',
9
+ ): number {
10
+ const point = worldToViewport(
11
+ axis === 'x' ? { x: position, y: viewport.offsetY } : { x: viewport.offsetX, y: position },
12
+ viewport,
13
+ );
14
+ const projectedPosition = axis === 'x' ? point.x : point.y;
15
+
16
+ return axis === 'x' && direction === 'rtl'
17
+ ? viewport.width - projectedPosition
18
+ : projectedPosition;
19
+ }
@@ -0,0 +1,90 @@
1
+ import { type GridViewport, snapWorldCoordinate } from '@ankhorage/grid-view';
2
+ import { describe, expect, test } from 'bun:test';
3
+
4
+ import { resolveGridRulerMarks } from './resolveGridRulerMarks';
5
+
6
+ const viewport: GridViewport = {
7
+ width: 300,
8
+ height: 160,
9
+ offsetX: 40,
10
+ offsetY: 20,
11
+ pixelsPerUnitX: 2,
12
+ pixelsPerUnitY: 4,
13
+ };
14
+
15
+ describe('resolveGridRulerMarks', () => {
16
+ test('aligns independent axis scales and scroll offsets through the published viewport transform', () => {
17
+ expect(
18
+ resolveGridRulerMarks(viewport, 'x', {
19
+ kind: 'ticks',
20
+ specification: { mode: 'fixed', step: 20 },
21
+ }).map((mark) => mark.position),
22
+ ).toEqual([0, 40, 80, 120, 160, 200, 240, 280]);
23
+ expect(
24
+ resolveGridRulerMarks(viewport, 'y', {
25
+ kind: 'ticks',
26
+ specification: { mode: 'fixed', step: 10 },
27
+ }).map((mark) => mark.position),
28
+ ).toEqual([0, 40, 80, 120, 160]);
29
+ });
30
+
31
+ test('projects only visible variable-width category starts', () => {
32
+ expect(
33
+ resolveGridRulerMarks(viewport, 'x', {
34
+ kind: 'categories',
35
+ categories: [
36
+ { id: 'before', size: 10, start: 20 },
37
+ { id: 'visible-a', size: 37, start: 40 },
38
+ { id: 'visible-b', size: 91, start: 77 },
39
+ ],
40
+ }),
41
+ ).toEqual([
42
+ { categoryId: 'visible-a', level: 'major', position: 0 },
43
+ { categoryId: 'visible-b', level: 'major', position: 74 },
44
+ ]);
45
+ });
46
+
47
+ test('keeps a fixed interaction snap unchanged when display density changes', () => {
48
+ const snap = { mode: 'fixed' as const, step: 25 };
49
+ const sparse = resolveGridRulerMarks(viewport, 'x', {
50
+ kind: 'ticks',
51
+ specification: { mode: 'fixed', step: 50 },
52
+ });
53
+ const dense = resolveGridRulerMarks(viewport, 'x', {
54
+ kind: 'ticks',
55
+ specification: { mode: 'fixed', step: 5 },
56
+ });
57
+
58
+ expect(dense.length).toBeGreaterThan(sparse.length);
59
+ expect(snapWorldCoordinate(62, snap)).toBe(50);
60
+ });
61
+
62
+ test('accepts externally supplied labels without introducing domain adapters', () => {
63
+ expect(
64
+ resolveGridRulerMarks(
65
+ viewport,
66
+ 'x',
67
+ { kind: 'ticks', specification: { mode: 'fixed', step: 20 } },
68
+ (tick) => `bar ${tick.position}`,
69
+ )[0],
70
+ ).toEqual({ label: 'bar 40', level: 'major', position: 0 });
71
+ });
72
+
73
+ test('projects horizontal marks consistently in RTL and evaluates each formatter once', () => {
74
+ let formatterCalls = 0;
75
+
76
+ const marks = resolveGridRulerMarks(
77
+ viewport,
78
+ 'x',
79
+ { kind: 'ticks', specification: { mode: 'fixed', step: 20 } },
80
+ (tick) => {
81
+ formatterCalls += 1;
82
+ return `tick ${tick.position}`;
83
+ },
84
+ 'rtl',
85
+ );
86
+
87
+ expect(marks.map((mark) => mark.position)).toEqual([300, 260, 220, 180, 140, 100, 60, 20]);
88
+ expect(formatterCalls).toBe(marks.length);
89
+ });
90
+ });
@@ -0,0 +1,39 @@
1
+ import {
2
+ getAxisCategoryTicks,
3
+ getAxisTicks,
4
+ type GridAxisName,
5
+ type GridAxisTick,
6
+ type GridViewport,
7
+ } from '@ankhorage/grid-view';
8
+
9
+ import type { GridRulerMark, GridRulerTickSource } from '../../../types/grid-rulers';
10
+ import { projectGridRulerPosition } from './projectGridRulerPosition';
11
+
12
+ /*** Resolves bounded engine ticks into viewport-pixel ruler marks without changing interaction state. */
13
+ export function resolveGridRulerMarks(
14
+ viewport: GridViewport,
15
+ axis: GridAxisName,
16
+ tickSource: GridRulerTickSource,
17
+ formatLabel?: (tick: GridAxisTick) => string | undefined,
18
+ direction: 'ltr' | 'rtl' = 'ltr',
19
+ ): readonly GridRulerMark[] {
20
+ const ticks =
21
+ tickSource.kind === 'categories'
22
+ ? getAxisCategoryTicks(viewport, axis, tickSource.categories)
23
+ : getAxisTicks(viewport, axis, tickSource.specification, tickSource.provider);
24
+
25
+ return ticks.map((tick) => {
26
+ const formattedLabel = formatLabel?.(tick);
27
+
28
+ return {
29
+ position: projectGridRulerPosition(viewport, axis, tick.position, direction),
30
+ level: tick.level,
31
+ ...(tick.label !== undefined || formattedLabel !== undefined
32
+ ? { label: formattedLabel ?? tick.label }
33
+ : {}),
34
+ ...('categoryId' in tick && typeof tick.categoryId === 'string'
35
+ ? { categoryId: tick.categoryId }
36
+ : {}),
37
+ };
38
+ });
39
+ }
@@ -0,0 +1,26 @@
1
+ import type { ZoraComponentMeta } from '../../types/authoring';
2
+
3
+ const GRID_RULERS_NOTE =
4
+ 'Requires a live viewport and optional executable tick/label providers; available through code APIs, not a serializable manifest node.';
5
+
6
+ /** Passive viewport decorations intentionally remain outside direct manifest authoring. */
7
+ export const gridLineOverlayMeta = {
8
+ name: 'GridLineOverlay',
9
+ category: 'foundation',
10
+ description: 'Projects bounded world-grid lines and explicit guides from a supplied viewport.',
11
+ directManifestNode: false,
12
+ note: GRID_RULERS_NOTE,
13
+ allowedChildren: [],
14
+ props: {},
15
+ } as const satisfies ZoraComponentMeta;
16
+
17
+ /** Rulers accept code-dependent tick and label providers and therefore remain code-only. */
18
+ export const gridRulerMeta = {
19
+ name: 'GridRuler',
20
+ category: 'foundation',
21
+ description: 'Projects visible world-axis ticks into a passive horizontal or vertical ruler.',
22
+ directManifestNode: false,
23
+ note: GRID_RULERS_NOTE,
24
+ allowedChildren: [],
25
+ props: {},
26
+ } as const satisfies ZoraComponentMeta;
@@ -0,0 +1,10 @@
1
+ export type {
2
+ GridGuide,
3
+ GridLineOverlayProps,
4
+ GridRulerMark,
5
+ GridRulerProps,
6
+ GridRulerTickSource,
7
+ } from '../../types/grid-rulers';
8
+ export { GridLineOverlay } from './adapters/inbound/GridLineOverlay';
9
+ export { GridRuler } from './adapters/inbound/GridRuler';
10
+ export { resolveGridRulerMarks } from './application/resolveGridRulerMarks';
@@ -47,6 +47,7 @@ import {
47
47
  GameOverlay,
48
48
  } from '../game/public';
49
49
  import { Gradient } from '../gradient/public';
50
+ import { GridLineOverlay, GridRuler } from '../grid-rulers/public';
50
51
  import { GridView, TileGrid } from '../grid-view/public';
51
52
  import { Hero } from '../hero/public';
52
53
  import { Icon } from '../icon/public';
@@ -194,6 +195,8 @@ const _ZORA_COMPONENT_REGISTRY = {
194
195
  EmptyState,
195
196
  Explorer,
196
197
  FileExplorer,
198
+ GridLineOverlay,
199
+ GridRuler,
197
200
  GridView,
198
201
  TileGrid,
199
202
  MediaExplorer,
@@ -53,6 +53,7 @@ import { gameMeasurementProbeMeta } from '../game/meta/gameMeasurementProbeMeta'
53
53
  import { gameMeta } from '../game/meta/gameMeta';
54
54
  import { gameOverlayMeta } from '../game/meta/gameOverlayMeta';
55
55
  import { gradientMeta } from '../gradient/gradientMeta';
56
+ import { gridLineOverlayMeta, gridRulerMeta } from '../grid-rulers/meta';
56
57
  import { gridViewMeta, tileGridMeta } from '../grid-view/meta';
57
58
  import { heroMeta } from '../hero/heroMeta';
58
59
  import { iconMeta } from '../icon/iconMeta';
@@ -198,6 +199,8 @@ export const ZORA_COMPONENT_META: ZoraComponentMetaRegistry = finalizeFeatureMet
198
199
  Explorer: explorerMeta,
199
200
  FileExplorer: fileExplorerMeta,
200
201
  MediaExplorer: mediaExplorerMeta,
202
+ GridLineOverlay: gridLineOverlayMeta,
203
+ GridRuler: gridRulerMeta,
201
204
  GridView: gridViewMeta,
202
205
  TileGrid: tileGridMeta,
203
206
  Hero: heroMeta,
package/src/index.ts CHANGED
@@ -263,6 +263,14 @@ export type {
263
263
  GradientRendererProviderProps,
264
264
  } from './features/gradient/public';
265
265
  export { Gradient, GradientRendererProvider } from './features/gradient/public';
266
+ export type {
267
+ GridGuide,
268
+ GridLineOverlayProps,
269
+ GridRulerMark,
270
+ GridRulerProps,
271
+ GridRulerTickSource,
272
+ } from './features/grid-rulers/public';
273
+ export { GridLineOverlay, GridRuler, resolveGridRulerMarks } from './features/grid-rulers/public';
266
274
  export type { GridViewProps, TileGridItem, TileGridProps } from './features/grid-view/public';
267
275
  export { GridView, layoutTileGrid, TileGrid } from './features/grid-view/public';
268
276
  export {
@@ -0,0 +1,59 @@
1
+ import type {
2
+ GridAxisCategory,
3
+ GridAxisName,
4
+ GridAxisTick,
5
+ GridTickProvider,
6
+ GridTickSpecification,
7
+ GridViewport,
8
+ } from '@ankhorage/grid-view';
9
+
10
+ import type { ZoraBaseProps } from './base';
11
+
12
+ /** A tick source is display-only; it never configures interaction snapping. */
13
+ export type GridRulerTickSource =
14
+ | {
15
+ readonly kind: 'ticks';
16
+ readonly specification: GridTickSpecification;
17
+ readonly provider?: GridTickProvider;
18
+ }
19
+ | { readonly kind: 'categories'; readonly categories: readonly GridAxisCategory[] };
20
+
21
+ /** A projected mark is measured in viewport pixels from the supplied viewport's origin. */
22
+ export interface GridRulerMark {
23
+ readonly position: number;
24
+ readonly level: GridAxisTick['level'];
25
+ readonly label?: string;
26
+ readonly categoryId?: string;
27
+ }
28
+
29
+ /** A passive guide belongs to one world-space axis. */
30
+ export interface GridGuide {
31
+ readonly id: string;
32
+ readonly axis: GridAxisName;
33
+ readonly position: number;
34
+ readonly label?: string;
35
+ }
36
+
37
+ /** A single horizontal or vertical passive ruler strip. */
38
+ export interface GridRulerProps extends ZoraBaseProps {
39
+ readonly axis: GridAxisName;
40
+ readonly viewport: GridViewport;
41
+ readonly tickSource: GridRulerTickSource;
42
+ readonly thickness?: number;
43
+ readonly offset?: number;
44
+ readonly position?: 'start' | 'end';
45
+ readonly direction?: 'ltr' | 'rtl';
46
+ readonly accessibilityLabel?: string;
47
+ readonly formatLabel?: (tick: GridAxisTick) => string | undefined;
48
+ }
49
+
50
+ /** A passive overlay of visible major/minor grid lines and explicit guides. */
51
+ export interface GridLineOverlayProps extends ZoraBaseProps {
52
+ readonly viewport: GridViewport;
53
+ /** Mirrors horizontal world positions using the same projection as an RTL GridRuler. */
54
+ readonly direction?: 'ltr' | 'rtl';
55
+ readonly xTickSource?: GridRulerTickSource;
56
+ readonly yTickSource?: GridRulerTickSource;
57
+ readonly guides?: readonly GridGuide[];
58
+ readonly accessibilityLabel?: string;
59
+ }
@@ -20,11 +20,11 @@ import {
20
20
  } from "./Accordion-42jfy1a1.js";
21
21
  import {
22
22
  View2
23
- } from "./Accordion-f0jsd9f9.js";
23
+ } from "./Accordion-7tjz25jh.js";
24
24
  import {
25
25
  Heading2,
26
26
  Text3
27
- } from "./Accordion-afmybp67.js";
27
+ } from "./Accordion-9w8wg9mf.js";
28
28
  import {
29
29
  resolveOverlayAnimation
30
30
  } from "./Accordion-n7y08rz5.js";
@@ -7,16 +7,16 @@ import {
7
7
  } from "./Accordion-42jfy1a1.js";
8
8
  import {
9
9
  View2
10
- } from "./Accordion-f0jsd9f9.js";
10
+ } from "./Accordion-7tjz25jh.js";
11
11
  import {
12
12
  Text3
13
- } from "./Accordion-afmybp67.js";
13
+ } from "./Accordion-9w8wg9mf.js";
14
14
  import {
15
15
  Avatar2
16
- } from "./Accordion-wgejzesa.js";
16
+ } from "./Accordion-61ntvh14.js";
17
17
  import {
18
18
  Badge2
19
- } from "./Accordion-qkh5yy94.js";
19
+ } from "./Accordion-7kq500r1.js";
20
20
  import {
21
21
  useZoraTheme2
22
22
  } from "./Accordion-srjya6dz.js";
@@ -1,16 +1,16 @@
1
1
  'use client';
2
2
  import {
3
3
  Button2
4
- } from "./Accordion-q09fjenx.js";
4
+ } from "./Accordion-6rrqpb2h.js";
5
5
  import {
6
6
  withZoraThemeScope
7
7
  } from "./Accordion-42jfy1a1.js";
8
8
  import {
9
9
  View2
10
- } from "./Accordion-f0jsd9f9.js";
10
+ } from "./Accordion-7tjz25jh.js";
11
11
  import {
12
12
  Text3
13
- } from "./Accordion-afmybp67.js";
13
+ } from "./Accordion-9w8wg9mf.js";
14
14
  import {
15
15
  useZoraTheme2
16
16
  } from "./Accordion-srjya6dz.js";
@@ -1,22 +1,22 @@
1
1
  'use client';
2
2
  import {
3
3
  Button2
4
- } from "./Accordion-q09fjenx.js";
4
+ } from "./Accordion-6rrqpb2h.js";
5
5
  import {
6
6
  withZoraThemeScope
7
7
  } from "./Accordion-42jfy1a1.js";
8
8
  import {
9
9
  View2
10
- } from "./Accordion-f0jsd9f9.js";
10
+ } from "./Accordion-7tjz25jh.js";
11
11
  import {
12
12
  Card2
13
- } from "./Accordion-xvzrx6bh.js";
13
+ } from "./Accordion-fjxcn6g9.js";
14
14
  import {
15
15
  Text3
16
- } from "./Accordion-afmybp67.js";
16
+ } from "./Accordion-9w8wg9mf.js";
17
17
  import {
18
18
  Badge2
19
- } from "./Accordion-qkh5yy94.js";
19
+ } from "./Accordion-7kq500r1.js";
20
20
  import {
21
21
  useZoraTheme2
22
22
  } from "./Accordion-srjya6dz.js";
@@ -4,16 +4,16 @@ import {
4
4
  } from "./Accordion-42jfy1a1.js";
5
5
  import {
6
6
  View2
7
- } from "./Accordion-f0jsd9f9.js";
7
+ } from "./Accordion-7tjz25jh.js";
8
8
  import {
9
9
  ButtonGroup2
10
- } from "./Accordion-q09fjenx.js";
10
+ } from "./Accordion-6rrqpb2h.js";
11
11
  import {
12
12
  Card2
13
- } from "./Accordion-xvzrx6bh.js";
13
+ } from "./Accordion-fjxcn6g9.js";
14
14
  import {
15
15
  List2
16
- } from "./Accordion-sdcfa14w.js";
16
+ } from "./Accordion-5g05kfmx.js";
17
17
  import {
18
18
  useZoraTheme2
19
19
  } from "./Accordion-srjya6dz.js";
@@ -7,14 +7,14 @@ import {
7
7
  } from "./Accordion-42jfy1a1.js";
8
8
  import {
9
9
  Text3
10
- } from "./Accordion-afmybp67.js";
10
+ } from "./Accordion-9w8wg9mf.js";
11
11
  import {
12
12
  View3
13
13
  } from "./Accordion-z5a0tx18.js";
14
14
  import {
15
15
  View2,
16
16
  Divider2
17
- } from "./Accordion-f0jsd9f9.js";
17
+ } from "./Accordion-7tjz25jh.js";
18
18
  import {
19
19
  Pressable
20
20
  } from "./Accordion-nnv76yff.js";
@@ -23,7 +23,7 @@ import {
23
23
  } from "./Accordion-wyxmd0n0.js";
24
24
  import {
25
25
  SectionHeader2
26
- } from "./Accordion-1p9r2ack.js";
26
+ } from "./Accordion-sy1cc5my.js";
27
27
  import {
28
28
  useZoraTheme2
29
29
  } from "./Accordion-srjya6dz.js";
@@ -4,13 +4,13 @@ import {
4
4
  } from "./Accordion-42jfy1a1.js";
5
5
  import {
6
6
  Icon2
7
- } from "./Accordion-bb8y34wh.js";
7
+ } from "./Accordion-b96evb46.js";
8
8
  import {
9
9
  View2
10
- } from "./Accordion-f0jsd9f9.js";
10
+ } from "./Accordion-7tjz25jh.js";
11
11
  import {
12
12
  Text3
13
- } from "./Accordion-afmybp67.js";
13
+ } from "./Accordion-9w8wg9mf.js";
14
14
  import {
15
15
  useZoraTheme2
16
16
  } from "./Accordion-srjya6dz.js";
@@ -1,19 +1,19 @@
1
1
  'use client';
2
2
  import {
3
3
  TileGrid2
4
- } from "./Accordion-54k0ncs3.js";
4
+ } from "./Accordion-q2z00p5r.js";
5
5
  import {
6
6
  withZoraThemeScope
7
7
  } from "./Accordion-42jfy1a1.js";
8
8
  import {
9
9
  Icon2
10
- } from "./Accordion-bb8y34wh.js";
10
+ } from "./Accordion-b96evb46.js";
11
11
  import {
12
12
  Image2
13
- } from "./Accordion-mej71qnw.js";
13
+ } from "./Accordion-a4cma5b6.js";
14
14
  import {
15
15
  Text3
16
- } from "./Accordion-afmybp67.js";
16
+ } from "./Accordion-9w8wg9mf.js";
17
17
  import {
18
18
  applySelectionIntent
19
19
  } from "./Accordion-n4et7etk.js";
@@ -19,7 +19,7 @@ import {
19
19
  } from "./Accordion-42jfy1a1.js";
20
20
  import {
21
21
  View2
22
- } from "./Accordion-f0jsd9f9.js";
22
+ } from "./Accordion-7tjz25jh.js";
23
23
  import {
24
24
  resolveControlSize,
25
25
  resolveIconSize2,
@@ -7,10 +7,10 @@ import {
7
7
  } from "./Accordion-42jfy1a1.js";
8
8
  import {
9
9
  Icon2
10
- } from "./Accordion-bb8y34wh.js";
10
+ } from "./Accordion-b96evb46.js";
11
11
  import {
12
12
  View2
13
- } from "./Accordion-f0jsd9f9.js";
13
+ } from "./Accordion-7tjz25jh.js";
14
14
  import {
15
15
  useZoraTheme2
16
16
  } from "./Accordion-srjya6dz.js";