@aglyn/shared-ui-jsx 1.0.0-beta.143

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 (261) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +7 -0
  3. package/package.json +63 -0
  4. package/src/index.d.ts +48 -0
  5. package/src/index.js +179 -0
  6. package/src/index.js.map +1 -0
  7. package/src/lib/components/aglyn-text.d.ts +86 -0
  8. package/src/lib/components/aglyn-text.js +44 -0
  9. package/src/lib/components/aglyn-text.js.map +1 -0
  10. package/src/lib/components/app-link.d.ts +74 -0
  11. package/src/lib/components/app-link.js +139 -0
  12. package/src/lib/components/app-link.js.map +1 -0
  13. package/src/lib/components/aspect-ratio.d.ts +59 -0
  14. package/src/lib/components/aspect-ratio.js +153 -0
  15. package/src/lib/components/aspect-ratio.js.map +1 -0
  16. package/src/lib/components/background-image.component.d.ts +30 -0
  17. package/src/lib/components/background-image.component.js +42 -0
  18. package/src/lib/components/background-image.component.js.map +1 -0
  19. package/src/lib/components/card-columns.d.ts +86 -0
  20. package/src/lib/components/card-columns.js +148 -0
  21. package/src/lib/components/card-columns.js.map +1 -0
  22. package/src/lib/components/card-display.d.ts +36 -0
  23. package/src/lib/components/card-display.js +158 -0
  24. package/src/lib/components/card-display.js.map +1 -0
  25. package/src/lib/components/card-list-item.d.ts +35 -0
  26. package/src/lib/components/card-list-item.js +137 -0
  27. package/src/lib/components/card-list-item.js.map +1 -0
  28. package/src/lib/components/children-function-prop.d.ts +25 -0
  29. package/src/lib/components/children-function-prop.js +29 -0
  30. package/src/lib/components/children-function-prop.js.map +1 -0
  31. package/src/lib/components/confirmation-provider.component.d.ts +19 -0
  32. package/src/lib/components/confirmation-provider.component.js +83 -0
  33. package/src/lib/components/confirmation-provider.component.js.map +1 -0
  34. package/src/lib/components/container.d.ts +25 -0
  35. package/src/lib/components/container.js +62 -0
  36. package/src/lib/components/container.js.map +1 -0
  37. package/src/lib/components/data-table.component.d.ts +54 -0
  38. package/src/lib/components/data-table.component.js +211 -0
  39. package/src/lib/components/data-table.component.js.map +1 -0
  40. package/src/lib/components/dialog-confirm.d.ts +36 -0
  41. package/src/lib/components/dialog-confirm.js +74 -0
  42. package/src/lib/components/dialog-confirm.js.map +1 -0
  43. package/src/lib/components/ellipsis-pulse.component.d.ts +22 -0
  44. package/src/lib/components/ellipsis-pulse.component.js +51 -0
  45. package/src/lib/components/ellipsis-pulse.component.js.map +1 -0
  46. package/src/lib/components/emotion-cache-provider.d.ts +31 -0
  47. package/src/lib/components/emotion-cache-provider.js +40 -0
  48. package/src/lib/components/emotion-cache-provider.js.map +1 -0
  49. package/src/lib/components/empty-state.component.d.ts +63 -0
  50. package/src/lib/components/empty-state.component.js +214 -0
  51. package/src/lib/components/empty-state.component.js.map +1 -0
  52. package/src/lib/components/error-boundary.component.d.ts +28 -0
  53. package/src/lib/components/error-boundary.component.js +86 -0
  54. package/src/lib/components/error-boundary.component.js.map +1 -0
  55. package/src/lib/components/grid-buttons.d.ts +26 -0
  56. package/src/lib/components/grid-buttons.js +45 -0
  57. package/src/lib/components/grid-buttons.js.map +1 -0
  58. package/src/lib/components/grid-items.d.ts +103 -0
  59. package/src/lib/components/grid-items.js +336 -0
  60. package/src/lib/components/grid-items.js.map +1 -0
  61. package/src/lib/components/grid-list.d.ts +37 -0
  62. package/src/lib/components/grid-list.js +156 -0
  63. package/src/lib/components/grid-list.js.map +1 -0
  64. package/src/lib/components/help-tip.component.d.ts +30 -0
  65. package/src/lib/components/help-tip.component.js +139 -0
  66. package/src/lib/components/help-tip.component.js.map +1 -0
  67. package/src/lib/components/link-navigation-reporter.d.ts +24 -0
  68. package/src/lib/components/link-navigation-reporter.js +64 -0
  69. package/src/lib/components/link-navigation-reporter.js.map +1 -0
  70. package/src/lib/components/list-pagination.component.d.ts +57 -0
  71. package/src/lib/components/list-pagination.component.js +90 -0
  72. package/src/lib/components/list-pagination.component.js.map +1 -0
  73. package/src/lib/components/list-table.component.d.ts +155 -0
  74. package/src/lib/components/list-table.component.js +369 -0
  75. package/src/lib/components/list-table.component.js.map +1 -0
  76. package/src/lib/components/loading-layout-app.component.d.ts +35 -0
  77. package/src/lib/components/loading-layout-app.component.js +42 -0
  78. package/src/lib/components/loading-layout-app.component.js.map +1 -0
  79. package/src/lib/components/loading-modal-overlay.d.ts +30 -0
  80. package/src/lib/components/loading-modal-overlay.js +156 -0
  81. package/src/lib/components/loading-modal-overlay.js.map +1 -0
  82. package/src/lib/components/loading-modal.d.ts +19 -0
  83. package/src/lib/components/loading-modal.js +113 -0
  84. package/src/lib/components/loading-modal.js.map +1 -0
  85. package/src/lib/components/loading-text.component.d.ts +21 -0
  86. package/src/lib/components/loading-text.component.js +40 -0
  87. package/src/lib/components/loading-text.component.js.map +1 -0
  88. package/src/lib/components/mdi-icon/index.d.ts +2 -0
  89. package/src/lib/components/mdi-icon/index.js +4 -0
  90. package/src/lib/components/mdi-icon/index.js.map +1 -0
  91. package/src/lib/components/mdi-icon/md-icon-stack.d.ts +31 -0
  92. package/src/lib/components/mdi-icon/md-icon-stack.js +101 -0
  93. package/src/lib/components/mdi-icon/md-icon-stack.js.map +1 -0
  94. package/src/lib/components/mdi-icon/md-icon.d.ts +32 -0
  95. package/src/lib/components/mdi-icon/md-icon.js +147 -0
  96. package/src/lib/components/mdi-icon/md-icon.js.map +1 -0
  97. package/src/lib/components/mdi-icon/mdi-icon-from-id.d.ts +23 -0
  98. package/src/lib/components/mdi-icon/mdi-icon-from-id.js +35 -0
  99. package/src/lib/components/mdi-icon/mdi-icon-from-id.js.map +1 -0
  100. package/src/lib/components/mdi-icon/mdi-icon.d.ts +24 -0
  101. package/src/lib/components/mdi-icon/mdi-icon.js +46 -0
  102. package/src/lib/components/mdi-icon/mdi-icon.js.map +1 -0
  103. package/src/lib/components/measured-figures.component.d.ts +64 -0
  104. package/src/lib/components/measured-figures.component.js +161 -0
  105. package/src/lib/components/measured-figures.component.js.map +1 -0
  106. package/src/lib/components/menu.d.ts +45 -0
  107. package/src/lib/components/menu.js +229 -0
  108. package/src/lib/components/menu.js.map +1 -0
  109. package/src/lib/components/mui-shadow-dom.d.ts +38 -0
  110. package/src/lib/components/mui-shadow-dom.js +161 -0
  111. package/src/lib/components/mui-shadow-dom.js.map +1 -0
  112. package/src/lib/components/navigation-drawer.component.d.ts +31 -0
  113. package/src/lib/components/navigation-drawer.component.js +76 -0
  114. package/src/lib/components/navigation-drawer.component.js.map +1 -0
  115. package/src/lib/components/navigation-intent.d.ts +18 -0
  116. package/src/lib/components/navigation-intent.js +56 -0
  117. package/src/lib/components/navigation-intent.js.map +1 -0
  118. package/src/lib/components/navigation-view/index.d.ts +18 -0
  119. package/src/lib/components/navigation-view/index.js +19 -0
  120. package/src/lib/components/navigation-view/index.js.map +1 -0
  121. package/src/lib/components/navigation-view/navigation-view.d.ts +35 -0
  122. package/src/lib/components/navigation-view/navigation-view.js +175 -0
  123. package/src/lib/components/navigation-view/navigation-view.js.map +1 -0
  124. package/src/lib/components/next-link.d.ts +27 -0
  125. package/src/lib/components/next-link.js +88 -0
  126. package/src/lib/components/next-link.js.map +1 -0
  127. package/src/lib/components/popper-styled.component.d.ts +25 -0
  128. package/src/lib/components/popper-styled.component.js +129 -0
  129. package/src/lib/components/popper-styled.component.js.map +1 -0
  130. package/src/lib/components/quota-readout.component.d.ts +69 -0
  131. package/src/lib/components/quota-readout.component.js +52 -0
  132. package/src/lib/components/quota-readout.component.js.map +1 -0
  133. package/src/lib/components/resizeable-stack.d.ts +4 -0
  134. package/src/lib/components/resizeable-stack.js +23 -0
  135. package/src/lib/components/resizeable-stack.js.map +1 -0
  136. package/src/lib/components/row-actions-menu.component.d.ts +53 -0
  137. package/src/lib/components/row-actions-menu.component.js +132 -0
  138. package/src/lib/components/row-actions-menu.component.js.map +1 -0
  139. package/src/lib/components/ruler-guides.component.d.ts +35 -0
  140. package/src/lib/components/ruler-guides.component.js +166 -0
  141. package/src/lib/components/ruler-guides.component.js.map +1 -0
  142. package/src/lib/components/sandbox-frame.d.ts +42 -0
  143. package/src/lib/components/sandbox-frame.js +122 -0
  144. package/src/lib/components/sandbox-frame.js.map +1 -0
  145. package/src/lib/components/scroll-reaction.d.ts +59 -0
  146. package/src/lib/components/scroll-reaction.js +43 -0
  147. package/src/lib/components/scroll-reaction.js.map +1 -0
  148. package/src/lib/components/scroll-table.component.d.ts +124 -0
  149. package/src/lib/components/scroll-table.component.js +103 -0
  150. package/src/lib/components/scroll-table.component.js.map +1 -0
  151. package/src/lib/components/shadow-dom/index.d.ts +18 -0
  152. package/src/lib/components/shadow-dom/index.js +19 -0
  153. package/src/lib/components/shadow-dom/index.js.map +1 -0
  154. package/src/lib/components/shadow-dom/shadow-dom.d.ts +42 -0
  155. package/src/lib/components/shadow-dom/shadow-dom.js +123 -0
  156. package/src/lib/components/shadow-dom/shadow-dom.js.map +1 -0
  157. package/src/lib/components/splash-screen.d.ts +33 -0
  158. package/src/lib/components/splash-screen.js +105 -0
  159. package/src/lib/components/splash-screen.js.map +1 -0
  160. package/src/lib/components/sr-only.d.ts +20 -0
  161. package/src/lib/components/sr-only.js +25 -0
  162. package/src/lib/components/sr-only.js.map +1 -0
  163. package/src/lib/components/status-screen-plain.component.d.ts +27 -0
  164. package/src/lib/components/status-screen-plain.component.js +178 -0
  165. package/src/lib/components/status-screen-plain.component.js.map +1 -0
  166. package/src/lib/components/usage-meter.component.d.ts +30 -0
  167. package/src/lib/components/usage-meter.component.js +92 -0
  168. package/src/lib/components/usage-meter.component.js.map +1 -0
  169. package/src/lib/components/variable-text-field.component.d.ts +50 -0
  170. package/src/lib/components/variable-text-field.component.js +188 -0
  171. package/src/lib/components/variable-text-field.component.js.map +1 -0
  172. package/src/lib/components/zoomable-panning-component/index.d.ts +1 -0
  173. package/src/lib/components/zoomable-panning-component/index.js +3 -0
  174. package/src/lib/components/zoomable-panning-component/index.js.map +1 -0
  175. package/src/lib/components/zoomable-panning-component/util.d.ts +58 -0
  176. package/src/lib/components/zoomable-panning-component/util.js +114 -0
  177. package/src/lib/components/zoomable-panning-component/util.js.map +1 -0
  178. package/src/lib/components/zoomable-panning-component/zoomable-panning-component.d.ts +132 -0
  179. package/src/lib/components/zoomable-panning-component/zoomable-panning-component.js +728 -0
  180. package/src/lib/components/zoomable-panning-component/zoomable-panning-component.js.map +1 -0
  181. package/src/lib/const/aglyn-besigner-logo-full.d.ts +39 -0
  182. package/src/lib/const/aglyn-besigner-logo-full.js +146 -0
  183. package/src/lib/const/aglyn-besigner-logo-full.js.map +1 -0
  184. package/src/lib/const/aglyn-console-logo-full.d.ts +34 -0
  185. package/src/lib/const/aglyn-console-logo-full.js +142 -0
  186. package/src/lib/const/aglyn-console-logo-full.js.map +1 -0
  187. package/src/lib/const/aglyn-logo-class-keys.d.ts +30 -0
  188. package/src/lib/const/aglyn-logo-class-keys.js +32 -0
  189. package/src/lib/const/aglyn-logo-class-keys.js.map +1 -0
  190. package/src/lib/const/aglyn-logo-full.d.ts +29 -0
  191. package/src/lib/const/aglyn-logo-full.js +111 -0
  192. package/src/lib/const/aglyn-logo-full.js.map +1 -0
  193. package/src/lib/const/list-filter.d.ts +260 -0
  194. package/src/lib/const/list-filter.js +409 -0
  195. package/src/lib/const/list-filter.js.map +1 -0
  196. package/src/lib/const/prebuilt-components.d.ts +17 -0
  197. package/src/lib/const/prebuilt-components.js +22 -0
  198. package/src/lib/const/prebuilt-components.js.map +1 -0
  199. package/src/lib/const/svg-icons.d.ts +75 -0
  200. package/src/lib/const/svg-icons.js +313 -0
  201. package/src/lib/const/svg-icons.js.map +1 -0
  202. package/src/lib/const/table-pagination.d.ts +106 -0
  203. package/src/lib/const/table-pagination.js +106 -0
  204. package/src/lib/const/table-pagination.js.map +1 -0
  205. package/src/lib/contexts/confirmation.context.d.ts +996 -0
  206. package/src/lib/contexts/confirmation.context.js +49 -0
  207. package/src/lib/contexts/confirmation.context.js.map +1 -0
  208. package/src/lib/contexts/loading.context.d.ts +93 -0
  209. package/src/lib/contexts/loading.context.js +126 -0
  210. package/src/lib/contexts/loading.context.js.map +1 -0
  211. package/src/lib/hocs/create-hoc-with-context-consumer.d.ts +36 -0
  212. package/src/lib/hocs/create-hoc-with-context-consumer.js +44 -0
  213. package/src/lib/hocs/create-hoc-with-context-consumer.js.map +1 -0
  214. package/src/lib/hocs/with-hoc.d.ts +32 -0
  215. package/src/lib/hocs/with-hoc.js +37 -0
  216. package/src/lib/hocs/with-hoc.js.map +1 -0
  217. package/src/lib/hooks/mdi-icon/use-mdi-icon.d.ts +20 -0
  218. package/src/lib/hooks/mdi-icon/use-mdi-icon.js +48 -0
  219. package/src/lib/hooks/mdi-icon/use-mdi-icon.js.map +1 -0
  220. package/src/lib/hooks/mdi-icon/use-mdi-icons-fuzzy.d.ts +26 -0
  221. package/src/lib/hooks/mdi-icon/use-mdi-icons-fuzzy.js +73 -0
  222. package/src/lib/hooks/mdi-icon/use-mdi-icons-fuzzy.js.map +1 -0
  223. package/src/lib/hooks/mdi-icon/use-mdi-icons.d.ts +19 -0
  224. package/src/lib/hooks/mdi-icon/use-mdi-icons.js +49 -0
  225. package/src/lib/hooks/mdi-icon/use-mdi-icons.js.map +1 -0
  226. package/src/lib/hooks/router-events.d.ts +56 -0
  227. package/src/lib/hooks/router-events.js +88 -0
  228. package/src/lib/hooks/router-events.js.map +1 -0
  229. package/src/lib/hooks/use-async-effect.d.ts +24 -0
  230. package/src/lib/hooks/use-async-effect.js +44 -0
  231. package/src/lib/hooks/use-async-effect.js.map +1 -0
  232. package/src/lib/hooks/use-callback-param-ref.d.ts +19 -0
  233. package/src/lib/hooks/use-callback-param-ref.js +29 -0
  234. package/src/lib/hooks/use-callback-param-ref.js.map +1 -0
  235. package/src/lib/hooks/use-id.d.ts +21 -0
  236. package/src/lib/hooks/use-id.js +28 -0
  237. package/src/lib/hooks/use-id.js.map +1 -0
  238. package/src/lib/hooks/use-isomorphic-layout-effect.d.ts +19 -0
  239. package/src/lib/hooks/use-isomorphic-layout-effect.js +20 -0
  240. package/src/lib/hooks/use-isomorphic-layout-effect.js.map +1 -0
  241. package/src/lib/hooks/use-merge-refs.d.ts +38 -0
  242. package/src/lib/hooks/use-merge-refs.js +52 -0
  243. package/src/lib/hooks/use-merge-refs.js.map +1 -0
  244. package/src/lib/hooks/use-subscribable.d.ts +29 -0
  245. package/src/lib/hooks/use-subscribable.js +50 -0
  246. package/src/lib/hooks/use-subscribable.js.map +1 -0
  247. package/src/lib/types.d.ts +18 -0
  248. package/src/lib/types.js +18 -0
  249. package/src/lib/types.js.map +1 -0
  250. package/src/lib/utils/make-link-elements.d.ts +24 -0
  251. package/src/lib/utils/make-link-elements.js +32 -0
  252. package/src/lib/utils/make-link-elements.js.map +1 -0
  253. package/src/lib/utils/make-meta-elements.d.ts +24 -0
  254. package/src/lib/utils/make-meta-elements.js +32 -0
  255. package/src/lib/utils/make-meta-elements.js.map +1 -0
  256. package/src/lib/utils/scroll-overflow.d.ts +120 -0
  257. package/src/lib/utils/scroll-overflow.js +115 -0
  258. package/src/lib/utils/scroll-overflow.js.map +1 -0
  259. package/src/lib/utils/vendor.d.ts +17 -0
  260. package/src/lib/utils/vendor.js +18 -0
  261. package/src/lib/utils/vendor.js.map +1 -0
@@ -0,0 +1,188 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ 'use client';
17
+ import { _ as _extends } from "@swc/helpers/_/_extends";
18
+ import { _ as _object_without_properties_loose } from "@swc/helpers/_/_object_without_properties_loose";
19
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
20
+ import { mdiPlus } from "@aglyn/shared-data-mdi";
21
+ import { IconButton, InputAdornment, ListItemText, Menu, MenuItem, TextField, Tooltip, Typography } from "@mui/material";
22
+ import { useCallback, useMemo, useRef, useState } from "react";
23
+ import { MdiIcon } from "./mdi-icon/mdi-icon.js";
24
+ /** `{{` plus the partial name after it, at the very end of what was typed. */ const OPEN_TOKEN = /\{\{([a-zA-Z][\w.]*)?$/;
25
+ export function VariableTextField(props) {
26
+ var _ref;
27
+ const { value, onChange, variables, insertLabel = 'Insert a variable', slotProps } = props, rest = _object_without_properties_loose(props, [
28
+ "value",
29
+ "onChange",
30
+ "variables",
31
+ "insertLabel",
32
+ "slotProps"
33
+ ]);
34
+ const inputRef = useRef(null);
35
+ /**
36
+ * The two ways the list opens, held APART.
37
+ *
38
+ * They insert differently — the `+` adds a variable at the caret, while
39
+ * autocomplete REPLACES the `{{` and the partial name under it — and one
40
+ * state for both got that wrong in the direction that is easy to miss: a
41
+ * `+` insert also ate the two characters after the caret, which on a title
42
+ * being edited in the middle silently deleted whatever was there.
43
+ *
44
+ * Both carry the caret as it was when the list opened, because opening a
45
+ * menu moves focus off the input and a caret read afterwards is 0.
46
+ */ const [picker, setPicker] = useState(null);
47
+ const [typing, setTyping] = useState(null);
48
+ const matches = useMemo(()=>{
49
+ var _ref;
50
+ var _typing_partial;
51
+ const partial = (_ref = typing == null ? void 0 : (_typing_partial = typing.partial) == null ? void 0 : _typing_partial.toLowerCase()) != null ? _ref : '';
52
+ if (!partial) return variables;
53
+ return variables.filter((variable)=>variable.name.toLowerCase().includes(partial) || variable.label.toLowerCase().includes(partial));
54
+ }, [
55
+ typing,
56
+ variables
57
+ ]);
58
+ /** Where the caret is, or the end of the value when there is no input yet. */ const caret = useCallback(()=>{
59
+ var _ref;
60
+ var _inputRef_current;
61
+ return (_ref = (_inputRef_current = inputRef.current) == null ? void 0 : _inputRef_current.selectionStart) != null ? _ref : value.length;
62
+ }, [
63
+ value.length
64
+ ]);
65
+ const insertAt = useCallback((name, from, to)=>{
66
+ const token = `{{${name}}}`;
67
+ onChange(`${value.slice(0, from)}${token}${value.slice(to)}`);
68
+ // After the paint that renders the new value, or the browser puts the
69
+ // caret back where the old value ended.
70
+ requestAnimationFrame(()=>{
71
+ const input = inputRef.current;
72
+ if (!input) return;
73
+ input.focus();
74
+ input.setSelectionRange(from + token.length, from + token.length);
75
+ });
76
+ }, [
77
+ onChange,
78
+ value
79
+ ]);
80
+ const handleChange = useCallback((event)=>{
81
+ var _event_target_selectionStart, _open_;
82
+ const next = event.target.value;
83
+ onChange(next);
84
+ // `{{` at the caret opens the list; anything else closes it. Read from
85
+ // the text BEFORE the caret only, so typing `{{` in the middle of a
86
+ // title offers the list for that position rather than for the end.
87
+ const before = next.slice(0, (_event_target_selectionStart = event.target.selectionStart) != null ? _event_target_selectionStart : next.length);
88
+ const open = OPEN_TOKEN.exec(before);
89
+ setTyping(open ? {
90
+ at: before.length - open[0].length,
91
+ partial: (_open_ = open[1]) != null ? _open_ : ''
92
+ } : null);
93
+ }, [
94
+ onChange
95
+ ]);
96
+ return /*#__PURE__*/ _jsxs(_Fragment, {
97
+ children: [
98
+ /*#__PURE__*/ _jsx(TextField, _extends({}, rest, {
99
+ value: value,
100
+ onChange: handleChange,
101
+ onBlur: (event)=>{
102
+ setTyping(null);
103
+ rest.onBlur == null ? void 0 : rest.onBlur.call(rest, event);
104
+ },
105
+ /*
106
+ * `inputRef`, not `slotProps.htmlInput.ref`. The slot's `ref` reaches
107
+ * the native input only on some of MUI's input variants, and a null
108
+ * ref here does not fail loudly — it silently makes `caret()` return
109
+ * the END of the value, so every insert lands after the title instead
110
+ * of where the author was typing. This is the prop `InputBase` forks
111
+ * into the input on every variant.
112
+ */ inputRef: inputRef,
113
+ slotProps: _extends({}, slotProps, {
114
+ input: _extends({}, slotProps == null ? void 0 : slotProps.input, {
115
+ endAdornment: /*#__PURE__*/ _jsx(InputAdornment, {
116
+ position: "end",
117
+ children: /*#__PURE__*/ _jsx(Tooltip, {
118
+ title: insertLabel,
119
+ children: /*#__PURE__*/ _jsx(IconButton, {
120
+ size: "small",
121
+ "aria-label": insertLabel,
122
+ onClick: (event)=>{
123
+ // The caret is read HERE, while the input still has it.
124
+ setTyping(null);
125
+ setPicker({
126
+ anchor: event.currentTarget,
127
+ at: caret()
128
+ });
129
+ },
130
+ children: /*#__PURE__*/ _jsx(MdiIcon, {
131
+ fontSize: "small",
132
+ path: mdiPlus.path
133
+ })
134
+ })
135
+ })
136
+ })
137
+ })
138
+ })
139
+ })),
140
+ /*#__PURE__*/ _jsx(Menu, {
141
+ open: Boolean(picker) || Boolean(typing) && matches.length > 0,
142
+ anchorEl: (_ref = picker == null ? void 0 : picker.anchor) != null ? _ref : inputRef.current,
143
+ onClose: ()=>{
144
+ setPicker(null);
145
+ setTyping(null);
146
+ },
147
+ // The menu must not steal the caret while somebody is mid-word: an
148
+ // autocomplete that focused itself would end the typing run it exists
149
+ // to complete.
150
+ disableAutoFocus: !picker,
151
+ disableEnforceFocus: !picker,
152
+ anchorOrigin: {
153
+ vertical: 'bottom',
154
+ horizontal: 'left'
155
+ },
156
+ children: matches.map((variable)=>/*#__PURE__*/ _jsx(MenuItem, {
157
+ onClick: ()=>{
158
+ if (typing) {
159
+ // Autocomplete REPLACES the `{{` and the partial under it.
160
+ const to = Math.min(typing.at + 2 + typing.partial.length, value.length);
161
+ insertAt(variable.name, typing.at, to);
162
+ } else {
163
+ var _ref;
164
+ // The `+` INSERTS, replacing nothing.
165
+ const at = (_ref = picker == null ? void 0 : picker.at) != null ? _ref : caret();
166
+ insertAt(variable.name, at, at);
167
+ }
168
+ setPicker(null);
169
+ setTyping(null);
170
+ },
171
+ children: /*#__PURE__*/ _jsx(ListItemText, {
172
+ primary: variable.label,
173
+ secondary: /*#__PURE__*/ _jsx(Typography, {
174
+ variant: "caption",
175
+ color: "text.secondary",
176
+ component: "span",
177
+ children: `{{${variable.name}}}${variable.description ? ` — ${variable.description}` : ''}`
178
+ })
179
+ })
180
+ }, variable.name))
181
+ })
182
+ ]
183
+ });
184
+ }
185
+ VariableTextField.displayName = 'VariableTextField';
186
+ export default VariableTextField;
187
+
188
+ //# sourceMappingURL=variable-text-field.component.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../../libs/shared/ui/jsx/src/lib/components/variable-text-field.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { mdiPlus } from '@aglyn/shared-data-mdi'\nimport {\n IconButton,\n InputAdornment,\n ListItemText,\n Menu,\n MenuItem,\n TextField,\n type TextFieldProps,\n Tooltip,\n Typography,\n} from '@mui/material'\nimport { useCallback, useMemo, useRef, useState } from 'react'\nimport { MdiIcon } from './mdi-icon/mdi-icon'\n\n/**\n * A text field whose value may name VARIABLES, with the two ways of reaching\n * them that the besigner's attribute fields already taught (AGL-3197).\n *\n * ## What it is, and what it deliberately is not\n *\n * It is a plain MUI `TextField`. The value in it is the value that is stored —\n * `{{site.name}}` reads as `{{site.name}}`, and what makes that legible is the\n * PREVIEW under the field, not a pill drawn over the text.\n *\n * The besigner's `TokenTextField` does draw pills, and this is not that on\n * purpose. That field is a contentEditable surface with its own selection\n * model, DOM/model sync contract, paste handling and undo trade-offs, wired to\n * react-final-form and to the canvas's binding and media pickers. It earns all\n * of that because a node attribute can hold a token referring to any record on\n * the page. Here there are three variables, the field is 60 characters long,\n * and the honest cost of a pill surface — an editor that fights the IME, an\n * undo stack that truncates, a value the author cannot select and retype — is\n * not worth paying for a title. A plain input, a menu and a live preview are.\n *\n * ## Two ways in, because people reach for different ones\n *\n * The `+` opens the list with a description under each name; typing `{{`\n * offers the same list inline, filtered by whatever follows. Either way the\n * insert lands at the caret, and the caret is left after it — not at the end\n * of the value, which is where an insert that rebuilt the string would leave\n * it, and which is wrong whenever somebody is editing the middle of a title.\n */\n\nexport interface VariableOption {\n /** Written between the braces. */\n name: string\n /** What the menu calls it. */\n label: string\n /** One line under the label. */\n description?: string\n}\n\nexport interface VariableTextFieldProps\n extends Omit<TextFieldProps, 'onChange' | 'value'> {\n value: string\n onChange: (value: string) => void\n /** Every variable this field offers. */\n variables: readonly VariableOption[]\n /** The tooltip on the insert control. */\n insertLabel?: string\n}\n\n/** `{{` plus the partial name after it, at the very end of what was typed. */\nconst OPEN_TOKEN = /\\{\\{([a-zA-Z][\\w.]*)?$/\n\nexport function VariableTextField(props: VariableTextFieldProps) {\n const {\n value,\n onChange,\n variables,\n insertLabel = 'Insert a variable',\n slotProps,\n ...rest\n } = props\n\n const inputRef = useRef<HTMLInputElement | null>(null)\n /**\n * The two ways the list opens, held APART.\n *\n * They insert differently — the `+` adds a variable at the caret, while\n * autocomplete REPLACES the `{{` and the partial name under it — and one\n * state for both got that wrong in the direction that is easy to miss: a\n * `+` insert also ate the two characters after the caret, which on a title\n * being edited in the middle silently deleted whatever was there.\n *\n * Both carry the caret as it was when the list opened, because opening a\n * menu moves focus off the input and a caret read afterwards is 0.\n */\n const [picker, setPicker] = useState<{ anchor: HTMLElement; at: number } | null>(null)\n const [typing, setTyping] = useState<{ at: number; partial: string } | null>(null)\n\n const matches = useMemo(() => {\n const partial = typing?.partial?.toLowerCase() ?? ''\n if (!partial) return variables\n return variables.filter(\n (variable) =>\n variable.name.toLowerCase().includes(partial) ||\n variable.label.toLowerCase().includes(partial),\n )\n }, [typing, variables])\n\n /** Where the caret is, or the end of the value when there is no input yet. */\n const caret = useCallback(\n () => inputRef.current?.selectionStart ?? value.length,\n [value.length],\n )\n\n const insertAt = useCallback(\n (name: string, from: number, to: number) => {\n const token = `{{${name}}}`\n onChange(`${value.slice(0, from)}${token}${value.slice(to)}`)\n // After the paint that renders the new value, or the browser puts the\n // caret back where the old value ended.\n requestAnimationFrame(() => {\n const input = inputRef.current\n if (!input) return\n input.focus()\n input.setSelectionRange(from + token.length, from + token.length)\n })\n },\n [onChange, value],\n )\n\n const handleChange = useCallback(\n (event: React.ChangeEvent<HTMLInputElement>) => {\n const next = event.target.value\n onChange(next)\n // `{{` at the caret opens the list; anything else closes it. Read from\n // the text BEFORE the caret only, so typing `{{` in the middle of a\n // title offers the list for that position rather than for the end.\n const before = next.slice(0, event.target.selectionStart ?? next.length)\n const open = OPEN_TOKEN.exec(before)\n setTyping(\n open\n ? { at: before.length - open[0].length, partial: open[1] ?? '' }\n : null,\n )\n },\n [onChange],\n )\n\n return (\n <>\n <TextField\n {...rest}\n value={value}\n onChange={handleChange}\n onBlur={(event) => {\n setTyping(null)\n rest.onBlur?.(event)\n }}\n /*\n * `inputRef`, not `slotProps.htmlInput.ref`. The slot's `ref` reaches\n * the native input only on some of MUI's input variants, and a null\n * ref here does not fail loudly — it silently makes `caret()` return\n * the END of the value, so every insert lands after the title instead\n * of where the author was typing. This is the prop `InputBase` forks\n * into the input on every variant.\n */\n inputRef={inputRef}\n slotProps={{\n ...slotProps,\n input: {\n ...(slotProps?.input as object),\n endAdornment: (\n <InputAdornment position=\"end\">\n <Tooltip title={insertLabel}>\n <IconButton\n size=\"small\"\n aria-label={insertLabel}\n onClick={(event) => {\n // The caret is read HERE, while the input still has it.\n setTyping(null)\n setPicker({ anchor: event.currentTarget, at: caret() })\n }}\n >\n <MdiIcon fontSize=\"small\" path={mdiPlus.path} />\n </IconButton>\n </Tooltip>\n </InputAdornment>\n ),\n },\n }}\n />\n <Menu\n open={Boolean(picker) || (Boolean(typing) && matches.length > 0)}\n anchorEl={picker?.anchor ?? inputRef.current}\n onClose={() => {\n setPicker(null)\n setTyping(null)\n }}\n // The menu must not steal the caret while somebody is mid-word: an\n // autocomplete that focused itself would end the typing run it exists\n // to complete.\n disableAutoFocus={!picker}\n disableEnforceFocus={!picker}\n anchorOrigin={{ vertical: 'bottom', horizontal: 'left' }}\n >\n {matches.map((variable) => (\n <MenuItem\n key={variable.name}\n onClick={() => {\n if (typing) {\n // Autocomplete REPLACES the `{{` and the partial under it.\n const to = Math.min(typing.at + 2 + typing.partial.length, value.length)\n insertAt(variable.name, typing.at, to)\n } else {\n // The `+` INSERTS, replacing nothing.\n const at = picker?.at ?? caret()\n insertAt(variable.name, at, at)\n }\n setPicker(null)\n setTyping(null)\n }}\n >\n <ListItemText\n primary={variable.label}\n secondary={\n <Typography variant=\"caption\" color=\"text.secondary\" component=\"span\">\n {`{{${variable.name}}}${\n variable.description ? ` — ${variable.description}` : ''\n }`}\n </Typography>\n }\n />\n </MenuItem>\n ))}\n </Menu>\n </>\n )\n}\nVariableTextField.displayName = 'VariableTextField'\n\nexport default VariableTextField\n"],"names":["mdiPlus","IconButton","InputAdornment","ListItemText","Menu","MenuItem","TextField","Tooltip","Typography","useCallback","useMemo","useRef","useState","MdiIcon","OPEN_TOKEN","VariableTextField","props","value","onChange","variables","insertLabel","slotProps","rest","inputRef","picker","setPicker","typing","setTyping","matches","partial","toLowerCase","filter","variable","name","includes","label","caret","current","selectionStart","length","insertAt","from","to","token","slice","requestAnimationFrame","input","focus","setSelectionRange","handleChange","event","open","next","target","before","exec","at","onBlur","endAdornment","position","title","size","aria-label","onClick","anchor","currentTarget","fontSize","path","Boolean","anchorEl","onClose","disableAutoFocus","disableEnforceFocus","anchorOrigin","vertical","horizontal","map","Math","min","primary","secondary","variant","color","component","description","displayName"],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export * from './zoomable-panning-component';
@@ -0,0 +1,3 @@
1
+ export * from "./zoomable-panning-component.js";
2
+
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../../../libs/shared/ui/jsx/src/lib/components/zoomable-panning-component/index.ts"],"sourcesContent":["export * from './zoomable-panning-component'\n"],"names":[],"mappings":"AAAA,cAAc,kCAA8B"}
@@ -0,0 +1,58 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2022 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ export type TransformationMatrix = {
18
+ a: number;
19
+ b: number;
20
+ c: number;
21
+ d: number;
22
+ x: number;
23
+ y: number;
24
+ };
25
+ export type BoundingBox = {
26
+ top: number;
27
+ left: number;
28
+ width: number;
29
+ height: number;
30
+ };
31
+ export type BoundaryRatio = {
32
+ vertical: number;
33
+ horizontal: number;
34
+ };
35
+ export type Coordinates = {
36
+ x: number;
37
+ y: number;
38
+ };
39
+ export type BoundCoordinates = {
40
+ boundX: number;
41
+ boundY: number;
42
+ offsetX: number;
43
+ offsetY: number;
44
+ };
45
+ export type TransformationParameters = {
46
+ angle: number;
47
+ scale: number;
48
+ offsetX: number;
49
+ offsetY: number;
50
+ };
51
+ export declare const ZOOM_SPEED_MULTIPLIER = 0.065;
52
+ export declare const TransformMatrix: (transformationParameters: TransformationParameters, centerCoordinates: Coordinates) => TransformationMatrix;
53
+ export declare const getTransformedBoundingBox: (transformationParameters: TransformationParameters, boundingBox: BoundingBox) => BoundingBox;
54
+ export declare const getScaleMultiplier: (delta: number, zoomSpeed?: number) => number;
55
+ export declare const boundCoordinates: (x: number, y: number, boundaryRatio: BoundaryRatio, boundingBox: BoundingBox, containerHeight: number, containerWidth: number, offsetX?: number, offsetY?: number) => BoundCoordinates;
56
+ export declare const preventDefault: (e: Event) => void;
57
+ export declare const captureTextSelection: () => void;
58
+ export declare const releaseTextSelection: () => void;
@@ -0,0 +1,114 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2022 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ export const ZOOM_SPEED_MULTIPLIER = 0.065;
17
+ // Transform matrix use to rotate, zoom and pan
18
+ // Can be written as T(centerX, centerY) * R(theta) * T(-centerX, -centerY) * S(scale, scale) + T(offsetX, offsetY)
19
+ // ( a , c, x )
20
+ // ( b , d, y )
21
+ // ( 0 , 0, 1 )
22
+ export const TransformMatrix = (transformationParameters, centerCoordinates)=>{
23
+ const { angle, scale, offsetX, offsetY } = transformationParameters;
24
+ const { x: centerX, y: centerY } = centerCoordinates;
25
+ const theta = angle * Math.PI / 180;
26
+ const a = Math.cos(theta) * scale;
27
+ const b = Math.sin(theta) * scale;
28
+ const c = -b;
29
+ const d = a;
30
+ const transformX = -centerX * a + centerY * b + centerX * scale;
31
+ const transformY = centerX * c - centerY * d + centerY * scale;
32
+ return {
33
+ a,
34
+ b,
35
+ c,
36
+ d,
37
+ x: transformX + offsetX,
38
+ y: transformY + offsetY
39
+ };
40
+ };
41
+ const applyTransformMatrix = (transformationParameters, centerCoordinates)=>(x, y)=>{
42
+ const { a, b, c, d, x: transformX, y: transformY } = TransformMatrix(transformationParameters, centerCoordinates);
43
+ return [
44
+ x * a + y * c + transformX,
45
+ x * b + y * d + transformY
46
+ ];
47
+ };
48
+ export const getTransformedBoundingBox = (transformationParameters, boundingBox)=>{
49
+ const { top, left, width, height } = boundingBox;
50
+ const center = {
51
+ x: width / 2,
52
+ y: height / 2
53
+ };
54
+ const getTransformedCoordinates = applyTransformMatrix(transformationParameters, center);
55
+ const [x1, y1] = getTransformedCoordinates(left, top);
56
+ const [x2, y2] = getTransformedCoordinates(left + width, top);
57
+ const [x3, y3] = getTransformedCoordinates(left + width, top + height);
58
+ const [x4, y4] = getTransformedCoordinates(left, top + height);
59
+ return {
60
+ top: Math.min(y1, y2, y3, y4),
61
+ left: Math.min(x1, x2, x3, x4),
62
+ width: Math.max(x1, x2, x3, x4) - Math.min(x1, x2, x3, x4),
63
+ height: Math.max(y1, y2, y3, y4) - Math.min(y1, y2, y3, y4)
64
+ };
65
+ };
66
+ export const getScaleMultiplier = (delta, zoomSpeed = 1)=>{
67
+ const speed = ZOOM_SPEED_MULTIPLIER * zoomSpeed;
68
+ let scaleMultiplier = 1;
69
+ if (delta > 0) {
70
+ scaleMultiplier = 1 - speed;
71
+ } else if (delta < 0) {
72
+ scaleMultiplier = 1 + speed;
73
+ }
74
+ return scaleMultiplier;
75
+ };
76
+ export const boundCoordinates = (x, y, boundaryRatio, boundingBox, containerHeight, containerWidth, offsetX = 0, offsetY = 0)=>{
77
+ const { top, left, width, height } = boundingBox;
78
+ // check that computed are inside boundaries otherwise set to the bounding box limits
79
+ let boundX = left;
80
+ let boundY = top;
81
+ if (boundY < -boundaryRatio.vertical * height) {
82
+ boundY = -boundaryRatio.vertical * height;
83
+ } else if (boundY > containerHeight - (1 - boundaryRatio.vertical) * height) {
84
+ boundY = containerHeight - (1 - boundaryRatio.vertical) * height;
85
+ }
86
+ if (boundX < -boundaryRatio.horizontal * width) {
87
+ boundX = -boundaryRatio.horizontal * width;
88
+ } else if (boundX > containerWidth - (1 - boundaryRatio.horizontal) * width) {
89
+ boundX = containerWidth - (1 - boundaryRatio.horizontal) * width;
90
+ }
91
+ // return new bounds coordinates for the transform matrix
92
+ // not the computed x/y coordinates
93
+ return {
94
+ boundX: x - (left - boundX),
95
+ boundY: y - (top - boundY),
96
+ offsetX: offsetX - (left - boundX),
97
+ offsetY: offsetY - (top - boundY)
98
+ };
99
+ };
100
+ export const preventDefault = (e)=>{
101
+ e.preventDefault();
102
+ };
103
+ export const captureTextSelection = ()=>{
104
+ if (typeof window !== 'undefined') {
105
+ window.addEventListener('selectstart', preventDefault);
106
+ }
107
+ };
108
+ export const releaseTextSelection = ()=>{
109
+ if (typeof window !== 'undefined') {
110
+ window.removeEventListener('selectstart', preventDefault);
111
+ }
112
+ };
113
+
114
+ //# sourceMappingURL=util.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../../../libs/shared/ui/jsx/src/lib/components/zoomable-panning-component/util.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2022 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\nexport type TransformationMatrix = {\n a: number,\n b: number,\n c: number,\n d: number,\n x: number,\n y: number,\n}\n\nexport type BoundingBox = {\n top: number,\n left: number,\n width: number,\n height: number,\n}\n\nexport type BoundaryRatio = {\n vertical: number,\n horizontal: number,\n}\n\nexport type Coordinates = {\n x: number,\n y: number,\n}\n\nexport type BoundCoordinates = {boundX: number, boundY: number, offsetX: number, offsetY: number}\n\nexport type TransformationParameters = {\n angle: number,\n scale: number,\n offsetX: number,\n offsetY: number,\n}\n\nexport const ZOOM_SPEED_MULTIPLIER = 0.065\n\n// Transform matrix use to rotate, zoom and pan\n// Can be written as T(centerX, centerY) * R(theta) * T(-centerX, -centerY) * S(scale, scale) + T(offsetX, offsetY)\n// ( a , c, x )\n// ( b , d, y )\n// ( 0 , 0, 1 )\nexport const TransformMatrix = (transformationParameters: TransformationParameters, centerCoordinates: Coordinates, ): TransformationMatrix => {\n const {angle, scale, offsetX, offsetY} = transformationParameters\n const {x: centerX, y: centerY} = centerCoordinates\n const theta = angle * Math.PI / 180\n const a = Math.cos(theta) * scale\n const b = Math.sin(theta) * scale\n const c = -b\n const d = a\n const transformX = -centerX * a + centerY * b + centerX * scale\n const transformY = centerX * c - centerY * d + centerY * scale\n return {a, b, c, d, x: transformX + offsetX, y: transformY + offsetY}\n}\n\nconst applyTransformMatrix = (\n transformationParameters: TransformationParameters,\n centerCoordinates: Coordinates,\n) => (x: number, y: number): [number, number] => {\n const {a, b, c, d, x: transformX, y: transformY} = TransformMatrix(transformationParameters, centerCoordinates)\n return [\n x * a + y * c + transformX,\n x * b + y * d + transformY,\n ]\n}\n\nexport const getTransformedBoundingBox = (\n transformationParameters: TransformationParameters,\n boundingBox: BoundingBox,\n): BoundingBox => {\n const {top, left, width, height} = boundingBox\n const center = {\n x: width / 2,\n y: height / 2,\n }\n\n const getTransformedCoordinates = applyTransformMatrix(transformationParameters, center)\n\n const [x1, y1] = getTransformedCoordinates(left, top)\n const [x2, y2] = getTransformedCoordinates(left + width, top)\n const [x3, y3] = getTransformedCoordinates(left + width, top + height)\n const [x4, y4] = getTransformedCoordinates(left, top + height)\n\n return {\n top: Math.min(y1, y2, y3, y4),\n left: Math.min(x1, x2, x3, x4),\n width: Math.max(x1, x2, x3, x4) - Math.min(x1, x2, x3, x4),\n height: Math.max(y1, y2, y3, y4) - Math.min(y1, y2, y3, y4),\n }\n}\n\nexport const getScaleMultiplier = (\n delta: number,\n zoomSpeed = 1,\n): number => {\n const speed = ZOOM_SPEED_MULTIPLIER * zoomSpeed\n let scaleMultiplier = 1\n if (delta > 0) { // zoom out\n scaleMultiplier = (1 - speed)\n }\n else if (delta < 0) { // zoom in\n scaleMultiplier = (1 + speed)\n }\n\n return scaleMultiplier\n}\n\nexport const boundCoordinates = (\n x: number,\n y: number,\n boundaryRatio: BoundaryRatio,\n boundingBox: BoundingBox,\n containerHeight: number,\n containerWidth: number,\n offsetX = 0,\n offsetY = 0,\n): BoundCoordinates => {\n\n const {top, left, width, height} = boundingBox\n // check that computed are inside boundaries otherwise set to the bounding box limits\n let boundX = left\n let boundY = top\n\n if (boundY < -boundaryRatio.vertical * height) {\n boundY = -boundaryRatio.vertical * height\n }\n else if (boundY > containerHeight - (1 - boundaryRatio.vertical) * height) {\n boundY = containerHeight - (1 - boundaryRatio.vertical) * height\n }\n\n if (boundX < -boundaryRatio.horizontal * width) {\n boundX = -boundaryRatio.horizontal * width\n }\n else if (boundX > containerWidth - (1 - boundaryRatio.horizontal) * width) {\n boundX = containerWidth - (1 - boundaryRatio.horizontal) * width\n }\n\n // return new bounds coordinates for the transform matrix\n // not the computed x/y coordinates\n return {\n boundX: x - (left - boundX),\n boundY: y - (top - boundY),\n offsetX: offsetX - (left - boundX),\n offsetY: offsetY - (top - boundY),\n }\n}\n\n\nexport const preventDefault = (e: Event) => {\n e.preventDefault()\n}\n\nexport const captureTextSelection = () => {\n if (typeof window !== 'undefined') {\n window.addEventListener('selectstart', preventDefault)\n }\n}\n\nexport const releaseTextSelection = () => {\n if (typeof window !== 'undefined') {\n window.removeEventListener('selectstart', preventDefault)\n }\n}\n"],"names":["ZOOM_SPEED_MULTIPLIER","TransformMatrix","transformationParameters","centerCoordinates","angle","scale","offsetX","offsetY","x","centerX","y","centerY","theta","Math","PI","a","cos","b","sin","c","d","transformX","transformY","applyTransformMatrix","getTransformedBoundingBox","boundingBox","top","left","width","height","center","getTransformedCoordinates","x1","y1","x2","y2","x3","y3","x4","y4","min","max","getScaleMultiplier","delta","zoomSpeed","speed","scaleMultiplier","boundCoordinates","boundaryRatio","containerHeight","containerWidth","boundX","boundY","vertical","horizontal","preventDefault","e","captureTextSelection","window","addEventListener","releaseTextSelection","removeEventListener"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAsCD,OAAO,MAAMA,wBAAwB,MAAK;AAE1C,+CAA+C;AAC/C,mHAAmH;AACnH,eAAe;AACf,eAAe;AACf,eAAe;AACf,OAAO,MAAMC,kBAAkB,CAACC,0BAAoDC;IAClF,MAAM,EAACC,KAAK,EAAEC,KAAK,EAAEC,OAAO,EAAEC,OAAO,EAAC,GAAGL;IACzC,MAAM,EAACM,GAAGC,OAAO,EAAEC,GAAGC,OAAO,EAAC,GAAGR;IACjC,MAAMS,QAAQR,QAAQS,KAAKC,EAAE,GAAG;IAChC,MAAMC,IAAIF,KAAKG,GAAG,CAACJ,SAASP;IAC5B,MAAMY,IAAIJ,KAAKK,GAAG,CAACN,SAASP;IAC5B,MAAMc,IAAI,CAACF;IACX,MAAMG,IAAIL;IACV,MAAMM,aAAa,CAACZ,UAAUM,IAAIJ,UAAUM,IAAIR,UAAUJ;IAC1D,MAAMiB,aAAab,UAAUU,IAAIR,UAAUS,IAAIT,UAAUN;IACzD,OAAO;QAACU;QAAGE;QAAGE;QAAGC;QAAGZ,GAAGa,aAAaf;QAASI,GAAGY,aAAaf;IAAO;AACtE,EAAC;AAED,MAAMgB,uBAAuB,CAC3BrB,0BACAC,oBACG,CAACK,GAAWE;QACf,MAAM,EAACK,CAAC,EAAEE,CAAC,EAAEE,CAAC,EAAEC,CAAC,EAAEZ,GAAGa,UAAU,EAAEX,GAAGY,UAAU,EAAC,GAAGrB,gBAAgBC,0BAA0BC;QAC7F,OAAO;YACLK,IAAIO,IAAIL,IAAIS,IAAIE;YAChBb,IAAIS,IAAIP,IAAIU,IAAIE;SACjB;IACH;AAEA,OAAO,MAAME,4BAA4B,CACvCtB,0BACAuB;IAEA,MAAM,EAACC,GAAG,EAAEC,IAAI,EAAEC,KAAK,EAAEC,MAAM,EAAC,GAAGJ;IACnC,MAAMK,SAAS;QACbtB,GAAGoB,QAAQ;QACXlB,GAAGmB,SAAS;IACd;IAEA,MAAME,4BAA4BR,qBAAqBrB,0BAA0B4B;IAEjF,MAAM,CAACE,IAAIC,GAAG,GAAGF,0BAA0BJ,MAAMD;IACjD,MAAM,CAACQ,IAAIC,GAAG,GAAGJ,0BAA0BJ,OAAOC,OAAOF;IACzD,MAAM,CAACU,IAAIC,GAAG,GAAGN,0BAA0BJ,OAAOC,OAAOF,MAAMG;IAC/D,MAAM,CAACS,IAAIC,GAAG,GAAGR,0BAA0BJ,MAAMD,MAAMG;IAEvD,OAAO;QACLH,KAAKb,KAAK2B,GAAG,CAACP,IAAIE,IAAIE,IAAIE;QAC1BZ,MAAMd,KAAK2B,GAAG,CAACR,IAAIE,IAAIE,IAAIE;QAC3BV,OAAOf,KAAK4B,GAAG,CAACT,IAAIE,IAAIE,IAAIE,MAAMzB,KAAK2B,GAAG,CAACR,IAAIE,IAAIE,IAAIE;QACvDT,QAAQhB,KAAK4B,GAAG,CAACR,IAAIE,IAAIE,IAAIE,MAAM1B,KAAK2B,GAAG,CAACP,IAAIE,IAAIE,IAAIE;IAC1D;AACF,EAAC;AAED,OAAO,MAAMG,qBAAqB,CAChCC,OACAC,YAAY,CAAC;IAEb,MAAMC,QAAQ7C,wBAAwB4C;IACtC,IAAIE,kBAAkB;IACtB,IAAIH,QAAQ,GAAG;QACbG,kBAAmB,IAAID;IACzB,OACK,IAAIF,QAAQ,GAAG;QAClBG,kBAAmB,IAAID;IACzB;IAEA,OAAOC;AACT,EAAC;AAED,OAAO,MAAMC,mBAAmB,CAC9BvC,GACAE,GACAsC,eACAvB,aACAwB,iBACAC,gBACA5C,UAAU,CAAC,EACXC,UAAU,CAAC;IAGX,MAAM,EAACmB,GAAG,EAAEC,IAAI,EAAEC,KAAK,EAAEC,MAAM,EAAC,GAAGJ;IACnC,qFAAqF;IACrF,IAAI0B,SAASxB;IACb,IAAIyB,SAAS1B;IAEb,IAAI0B,SAAS,CAACJ,cAAcK,QAAQ,GAAGxB,QAAQ;QAC7CuB,SAAS,CAACJ,cAAcK,QAAQ,GAAGxB;IACrC,OACK,IAAIuB,SAASH,kBAAkB,AAAC,CAAA,IAAID,cAAcK,QAAQ,AAAD,IAAKxB,QAAQ;QACzEuB,SAASH,kBAAkB,AAAC,CAAA,IAAID,cAAcK,QAAQ,AAAD,IAAKxB;IAC5D;IAEA,IAAIsB,SAAS,CAACH,cAAcM,UAAU,GAAG1B,OAAO;QAC9CuB,SAAS,CAACH,cAAcM,UAAU,GAAG1B;IACvC,OACK,IAAIuB,SAASD,iBAAiB,AAAC,CAAA,IAAIF,cAAcM,UAAU,AAAD,IAAK1B,OAAO;QACzEuB,SAASD,iBAAiB,AAAC,CAAA,IAAIF,cAAcM,UAAU,AAAD,IAAK1B;IAC7D;IAEA,yDAAyD;IACzD,mCAAmC;IACnC,OAAO;QACLuB,QAAQ3C,IAAKmB,CAAAA,OAAOwB,MAAK;QACzBC,QAAQ1C,IAAKgB,CAAAA,MAAM0B,MAAK;QACxB9C,SAASA,UAAWqB,CAAAA,OAAOwB,MAAK;QAChC5C,SAASA,UAAWmB,CAAAA,MAAM0B,MAAK;IACjC;AACF,EAAC;AAGD,OAAO,MAAMG,iBAAiB,CAACC;IAC7BA,EAAED,cAAc;AAClB,EAAC;AAED,OAAO,MAAME,uBAAuB;IAClC,IAAI,OAAOC,WAAW,aAAa;QACjCA,OAAOC,gBAAgB,CAAC,eAAeJ;IACzC;AACF,EAAC;AAED,OAAO,MAAMK,uBAAuB;IAClC,IAAI,OAAOF,WAAW,aAAa;QACjCA,OAAOG,mBAAmB,CAAC,eAAeN;IAC5C;AACF,EAAC"}
@@ -0,0 +1,132 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2021 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ import { Component, PointerEvent, MouseEvent, KeyboardEvent, TouchEvent, WheelEvent, HTMLAttributes, PropsWithRef } from 'react';
18
+ import { BoundCoordinates, Coordinates, TransformationMatrix, TransformationParameters } from './util';
19
+ type OnStateChangeData = {
20
+ x: number;
21
+ y: number;
22
+ scale: number;
23
+ angle: number;
24
+ };
25
+ export interface ZoomablePanningComponentProps extends PropsWithRef<HTMLAttributes<HTMLDivElement>> {
26
+ zoomSpeed: number;
27
+ doubleZoomSpeed: number;
28
+ disabled?: boolean;
29
+ autoCenter?: boolean;
30
+ autoCenterZoomLevel?: number;
31
+ disableKeyInteraction?: boolean;
32
+ disableDoubleClickZoom?: boolean;
33
+ disableScrollZoom?: boolean;
34
+ realPinch?: boolean;
35
+ keyMapping?: Record<string, {
36
+ x: number;
37
+ y: number;
38
+ z: number;
39
+ }>;
40
+ minZoom: number;
41
+ maxZoom: number;
42
+ preventPan: (event: PointerEvent<HTMLDivElement> | MouseEvent, x: number, y: number) => boolean;
43
+ noStateUpdate: boolean;
44
+ boundaryRatioVertical: number;
45
+ boundaryRatioHorizontal: number;
46
+ enableBoundingBox?: boolean;
47
+ onPanStart?: (event: MouseEvent<any> | TouchEvent<any>) => void;
48
+ onPan?: (event: MouseEvent<any> | TouchEvent<any>) => void;
49
+ onPanEnd?: (event: MouseEvent<any> | TouchEvent<any>) => void;
50
+ onStateChange?: (data: OnStateChangeData) => void;
51
+ }
52
+ type State = {
53
+ x: number;
54
+ y: number;
55
+ scale: number;
56
+ angle: number;
57
+ };
58
+ export declare class ZoomablePanningComponent extends Component<ZoomablePanningComponentProps, State> {
59
+ static defaultProps: {
60
+ zoomSpeed: number;
61
+ doubleZoomSpeed: number;
62
+ disabled: boolean;
63
+ minZoom: number;
64
+ maxZoom: number;
65
+ noStateUpdate: boolean;
66
+ boundaryRatioVertical: number;
67
+ boundaryRatioHorizontal: number;
68
+ disableDoubleClickZoom: boolean;
69
+ disableScrollZoom: boolean;
70
+ preventPan: () => boolean;
71
+ };
72
+ container: import("react").RefObject<HTMLDivElement>;
73
+ dragContainer: import("react").RefObject<HTMLDivElement>;
74
+ mousePos: {
75
+ x: number;
76
+ y: number;
77
+ };
78
+ panning: boolean;
79
+ touchInProgress: boolean;
80
+ panStartTriggered: boolean;
81
+ pinchZoomLength: number;
82
+ prevPanPosition: {
83
+ x: number;
84
+ y: number;
85
+ };
86
+ frameAnimation: number;
87
+ intermediateFrameAnimation: number;
88
+ transformMatrixString: string;
89
+ intermediateTransformMatrixString: string;
90
+ state: State;
91
+ componentDidMount(): void;
92
+ componentDidUpdate(prevProps: ZoomablePanningComponentProps, prevState: State): void;
93
+ componentWillUnmount(): void;
94
+ onDoubleClick: (e: MouseEvent<any>) => void;
95
+ onMouseDown: (e: MouseEvent<any>) => void;
96
+ onMouseMove: (e: MouseEvent<any>) => void;
97
+ onMouseUp: (e: MouseEvent<any>) => void;
98
+ onWheel: (e: WheelEvent<any>) => void;
99
+ onKeyDown: (e: KeyboardEvent<any>) => void;
100
+ onKeyUp: (e: KeyboardEvent<any>) => void;
101
+ onTouchStart: (e: TouchEvent<any>) => void;
102
+ onToucheMove: (e: TouchEvent<any>) => void;
103
+ onTouchEnd: (e: TouchEvent<any>) => void;
104
+ setMouseListeners: () => void;
105
+ cleanMouseListeners: () => void;
106
+ setTouchListeners: () => void;
107
+ cleanTouchListeners: () => void;
108
+ triggerOnPanStart: (e: MouseEvent<any> | TouchEvent<any>) => void;
109
+ triggerOnPan: (e: MouseEvent<any> | TouchEvent<any>) => void;
110
+ triggerOnPanEnd: (e: MouseEvent<any> | TouchEvent<any>) => void;
111
+ getPinchZoomLength: (finger1: Touch, finger2: Touch) => number;
112
+ getContainer: () => HTMLDivElement;
113
+ getDragContainer: () => HTMLDivElement;
114
+ autoCenter: (zoomLevel?: number, animate?: boolean) => void;
115
+ moveByRatio: (x: number, y: number, moveSpeedRatio?: number) => void;
116
+ moveBy: (dx: number, dy: number, noStateUpdate?: boolean) => void;
117
+ rotate: (value: number | ((prevAngle: number) => number)) => void;
118
+ zoomAbs: (x: number, y: number, zoomLevel: number) => void;
119
+ zoomTo: (x: number, y: number, ratio: number) => void;
120
+ centeredZoom: (delta: number, zoomSpeed?: number) => void;
121
+ zoomIn: (zoomSpeed?: number) => void;
122
+ zoomOut: (zoomSpeed?: number) => void;
123
+ reset: () => void;
124
+ getContainerBoundingRect: () => ClientRect;
125
+ getOffset: (e: MouseEvent<any> | Touch) => Coordinates;
126
+ getTransformMatrix: (x: number, y: number, scale: number, angle: number) => TransformationMatrix;
127
+ applyTransform: () => void;
128
+ applyIntermediateTransform: () => void;
129
+ getBoundCoordinates: (coordinates: Coordinates, transformationParameters: TransformationParameters) => BoundCoordinates;
130
+ render(): import("react").JSX.Element;
131
+ }
132
+ export default ZoomablePanningComponent;