@surdeddd/wmkit 0.7.2 → 0.9.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 (91) hide show
  1. package/README.md +60 -3
  2. package/README.ru.md +60 -3
  3. package/dist/angular.cjs +4 -3
  4. package/dist/angular.cjs.map +1 -1
  5. package/dist/angular.d.cts +2 -1
  6. package/dist/angular.d.ts +2 -1
  7. package/dist/angular.js +2 -1
  8. package/dist/angular.js.map +1 -1
  9. package/dist/binder-DGoj0xG1.d.cts +13 -0
  10. package/dist/binder-Lo13iTVB.d.ts +13 -0
  11. package/dist/chrome.cjs +122 -0
  12. package/dist/chrome.cjs.map +1 -0
  13. package/dist/chrome.d.cts +25 -0
  14. package/dist/chrome.d.ts +25 -0
  15. package/dist/chrome.js +114 -0
  16. package/dist/chrome.js.map +1 -0
  17. package/dist/chunk-5XVIC5LH.js +140 -0
  18. package/dist/chunk-5XVIC5LH.js.map +1 -0
  19. package/dist/chunk-BDHFXEJN.cjs +149 -0
  20. package/dist/chunk-BDHFXEJN.cjs.map +1 -0
  21. package/dist/{chunk-NJAI5R4J.cjs → chunk-TUGQX3CX.js} +205 -175
  22. package/dist/chunk-TUGQX3CX.js.map +1 -0
  23. package/dist/{chunk-FOWW4LXS.js → chunk-ZWUVWRUS.cjs} +236 -178
  24. package/dist/chunk-ZWUVWRUS.cjs.map +1 -0
  25. package/dist/devtools.cjs +330 -0
  26. package/dist/devtools.cjs.map +1 -0
  27. package/dist/devtools.d.cts +43 -0
  28. package/dist/devtools.d.ts +43 -0
  29. package/dist/devtools.js +326 -0
  30. package/dist/devtools.js.map +1 -0
  31. package/dist/gestures.cjs +219 -0
  32. package/dist/gestures.cjs.map +1 -0
  33. package/dist/gestures.d.cts +19 -0
  34. package/dist/gestures.d.ts +19 -0
  35. package/dist/gestures.js +215 -0
  36. package/dist/gestures.js.map +1 -0
  37. package/dist/index.cjs +35 -34
  38. package/dist/index.d.cts +3 -2
  39. package/dist/index.d.ts +3 -2
  40. package/dist/index.js +2 -1
  41. package/dist/react.cjs +4 -3
  42. package/dist/react.cjs.map +1 -1
  43. package/dist/react.d.cts +2 -1
  44. package/dist/react.d.ts +2 -1
  45. package/dist/react.js +2 -1
  46. package/dist/react.js.map +1 -1
  47. package/dist/{binder-DvQFmO3K.d.ts → shared-BoHXlPCu.d.ts} +55 -8
  48. package/dist/{binder-_L3Iww0u.d.cts → shared-DdGFz3oy.d.cts} +55 -8
  49. package/dist/solid.cjs +4 -3
  50. package/dist/solid.cjs.map +1 -1
  51. package/dist/solid.d.cts +2 -1
  52. package/dist/solid.d.ts +2 -1
  53. package/dist/solid.js +2 -1
  54. package/dist/solid.js.map +1 -1
  55. package/dist/svelte.cjs +4 -3
  56. package/dist/svelte.cjs.map +1 -1
  57. package/dist/svelte.d.cts +2 -1
  58. package/dist/svelte.d.ts +2 -1
  59. package/dist/svelte.js +2 -1
  60. package/dist/svelte.js.map +1 -1
  61. package/dist/themes/amber.css +316 -0
  62. package/dist/themes/aqua.css +285 -0
  63. package/dist/themes/blueprint.css +303 -0
  64. package/dist/themes/brutalist.css +307 -0
  65. package/dist/themes/candy.css +277 -0
  66. package/dist/themes/carbon.css +297 -0
  67. package/dist/themes/forest.css +278 -0
  68. package/dist/themes/frost.css +279 -0
  69. package/dist/themes/glass.css +2 -1
  70. package/dist/themes/light.css +2 -1
  71. package/dist/themes/neon.css +289 -0
  72. package/dist/themes/noir.css +282 -0
  73. package/dist/themes/paper.css +258 -0
  74. package/dist/themes/retro.css +8 -0
  75. package/dist/themes/synth.css +283 -0
  76. package/dist/themes/terminal.css +273 -0
  77. package/dist/themes.cjs +8747 -0
  78. package/dist/themes.cjs.map +1 -0
  79. package/dist/themes.d.cts +12 -0
  80. package/dist/themes.d.ts +12 -0
  81. package/dist/themes.js +8742 -0
  82. package/dist/themes.js.map +1 -0
  83. package/dist/vue.cjs +4 -3
  84. package/dist/vue.cjs.map +1 -1
  85. package/dist/vue.d.cts +2 -1
  86. package/dist/vue.d.ts +2 -1
  87. package/dist/vue.js +2 -1
  88. package/dist/vue.js.map +1 -1
  89. package/package.json +80 -7
  90. package/dist/chunk-FOWW4LXS.js.map +0 -1
  91. package/dist/chunk-NJAI5R4J.cjs.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/plugins/chrome/messages.ts","../src/plugins/chrome/template.ts","../src/plugins/chrome/index.ts"],"names":[],"mappings":"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","file":"chrome.js","sourcesContent":["export interface ChromeMessages {\n minimize: string\n maximize: string\n close: string\n}\n\nexport const chromeMessages: ChromeMessages = {\n minimize: 'Minimize',\n maximize: 'Maximize',\n close: 'Close',\n}\n\nexport const chromeMessagesRu: ChromeMessages = {\n minimize: 'Свернуть',\n maximize: 'Развернуть',\n close: 'Закрыть',\n}\n","const ESCAPES: Record<string, string> = {\n '&': '&amp;',\n '<': '&lt;',\n '>': '&gt;',\n '\"': '&quot;',\n \"'\": '&#39;',\n}\n\nfunction escapeHtml(value: string): string {\n return value.replace(/[&<>\"']/g, (char) => ESCAPES[char] as string)\n}\n\nexport function compileTemplate(template: string): (values: Record<string, string>) => string {\n const parts = template.split(/\\{\\{\\s*([a-z]+)\\s*\\}\\}/i)\n return (values) =>\n parts.map((part, index) => (index % 2 === 0 ? part : escapeHtml(values[part] ?? ''))).join('')\n}\n","import type { WindowState } from '../../core/types'\nimport type { SkinMount, WindowSkin } from '../../dom/shared'\nimport { type ChromeMessages, chromeMessages } from './messages'\nimport { compileTemplate } from './template'\n\nexport type { ChromeMessages } from './messages'\nexport { chromeMessages, chromeMessagesRu } from './messages'\nexport { compileTemplate } from './template'\n\nexport interface SkinSpec {\n template: string\n styles?: string\n shadow?: boolean\n name?: string\n}\n\nconst SLOT = '[data-wm-content]'\nconst SLOT_PATTERN = /data-wm-content\\b/g\n\nconst BAD_SLOT_COUNT = 'wmkit: a skin template needs exactly one [data-wm-content]'\nconst BAD_STRUCTURE =\n 'wmkit: a skin template needs a single root element that holds its [data-wm-content]'\nconst UNSCOPED_STYLES = 'wmkit: a skin with styles needs a name to scope them to'\n\nfunction valuesOf(win: WindowState): Record<string, string> {\n const variant = win.meta.variant\n return {\n id: win.id,\n title: win.title,\n stage: win.stage,\n layer: win.layer,\n workspace: String(win.workspace),\n variant: typeof variant === 'string' ? variant : '',\n }\n}\n\nfunction injectStyles(doc: Document, name: string, styles: string): void {\n if (doc.head.querySelector(`style[data-wm-skin-styles=\"${name}\"]`)) return\n const tag = doc.createElement('style')\n tag.dataset.wmSkinStyles = name\n tag.textContent = styles\n doc.head.append(tag)\n}\n\nexport function skin(spec: SkinSpec): WindowSkin {\n if ((spec.template.match(SLOT_PATTERN) ?? []).length !== 1) throw new Error(BAD_SLOT_COUNT)\n if (spec.styles !== undefined && spec.name === undefined) throw new Error(UNSCOPED_STYLES)\n const render = compileTemplate(spec.template)\n const sheets = new WeakMap<Document, CSSStyleSheet>()\n\n function adopt(root: ShadowRoot, doc: Document, css: string): void {\n if (typeof CSSStyleSheet.prototype.replaceSync !== 'function') {\n const tag = doc.createElement('style')\n tag.textContent = css\n root.prepend(tag)\n return\n }\n let sheet = sheets.get(doc)\n if (!sheet) {\n sheet = new CSSStyleSheet()\n sheet.replaceSync(css)\n sheets.set(doc, sheet)\n }\n root.adoptedStyleSheets = [...root.adoptedStyleSheets, sheet]\n }\n\n return ({ doc, window: win }): SkinMount => {\n const holder = doc.createElement('template')\n holder.innerHTML = render(valuesOf(win))\n const built = holder.content.firstElementChild as HTMLElement | null\n const slot =\n built === null ? null : built.matches(SLOT) ? built : built.querySelector<HTMLElement>(SLOT)\n if (built === null || slot === null) throw new Error(BAD_STRUCTURE)\n\n if (spec.shadow !== true) {\n if (spec.name !== undefined) built.dataset.wmSkin = spec.name\n if (spec.styles !== undefined) injectStyles(doc, spec.name as string, spec.styles)\n return { element: built, content: slot }\n }\n\n const element = doc.createElement('div')\n if (spec.name !== undefined) element.dataset.wmSkin = spec.name\n const root = element.attachShadow({ mode: 'open' })\n const projection = doc.createElement('slot')\n if (slot === built) root.append(projection)\n else {\n slot.replaceWith(projection)\n root.append(...built.childNodes)\n }\n if (spec.styles !== undefined) adopt(root, doc, spec.styles)\n const content = doc.createElement('div')\n content.dataset.wmContent = ''\n element.append(content)\n return { element, content }\n }\n}\n\nexport function windowChrome(messages: ChromeMessages = chromeMessages): WindowSkin {\n return skin({\n name: 'wmkit',\n template:\n '<section>' +\n '<header data-wm-drag>' +\n '<span data-wm-title>{{title}}</span>' +\n '<span data-wm-controls>' +\n `<button type=\"button\" data-wm-minimize aria-label=\"${messages.minimize}\"></button>` +\n `<button type=\"button\" data-wm-maximize aria-label=\"${messages.maximize}\"></button>` +\n `<button type=\"button\" data-wm-close aria-label=\"${messages.close}\"></button>` +\n '</span>' +\n '</header>' +\n '<div data-wm-content></div>' +\n '</section>',\n })\n}\n\nexport const defaultSkin: WindowSkin = windowChrome()\n\nexport const barebones: WindowSkin = skin({\n name: 'barebones',\n template:\n '<section>' +\n '<header data-wm-drag><span data-wm-title>{{title}}</span></header>' +\n '<div data-wm-content></div>' +\n '</section>',\n})\n"]}
@@ -0,0 +1,140 @@
1
+ // src/core/geometry.ts
2
+ function clamp(value, min, max) {
3
+ return Math.min(Math.max(value, min), max);
4
+ }
5
+ function clampSize(size, min, max) {
6
+ return {
7
+ width: clamp(size.width, min.width, max ? max.width : Number.POSITIVE_INFINITY),
8
+ height: clamp(size.height, min.height, max ? max.height : Number.POSITIVE_INFINITY)
9
+ };
10
+ }
11
+ function clampToViewport(bounds, viewport, minVisible) {
12
+ if (viewport.width <= 0 || viewport.height <= 0) return bounds;
13
+ const visibleX = Math.min(minVisible, bounds.width);
14
+ const visibleY = Math.min(minVisible, bounds.height);
15
+ const x = clamp(bounds.x, visibleX - bounds.width, Math.max(0, viewport.width - visibleX));
16
+ const y = clamp(bounds.y, 0, Math.max(0, viewport.height - visibleY));
17
+ return x === bounds.x && y === bounds.y ? bounds : { ...bounds, x, y };
18
+ }
19
+ function boundsEqual(a, b) {
20
+ return a.x === b.x && a.y === b.y && a.width === b.width && a.height === b.height;
21
+ }
22
+ function applyAspect(size, ratio, min, max, drive) {
23
+ if (!Number.isFinite(ratio) || ratio <= 0) return clampSize(size, min, max);
24
+ const maxWidth = max ? max.width : Number.POSITIVE_INFINITY;
25
+ const maxHeight = max ? max.height : Number.POSITIVE_INFINITY;
26
+ if (drive === "height") {
27
+ const height = clamp(
28
+ size.height,
29
+ Math.max(min.height, min.width / ratio),
30
+ Math.min(maxHeight, maxWidth / ratio)
31
+ );
32
+ return { width: height * ratio, height };
33
+ }
34
+ const width = clamp(
35
+ size.width,
36
+ Math.max(min.width, min.height * ratio),
37
+ Math.min(maxWidth, maxHeight * ratio)
38
+ );
39
+ return { width, height: width / ratio };
40
+ }
41
+ function zoneBounds(zone, viewport) {
42
+ const w = viewport.width;
43
+ const h = viewport.height;
44
+ const halfW = Math.round(w / 2);
45
+ const halfH = Math.round(h / 2);
46
+ const oneThird = Math.round(w / 3);
47
+ const twoThirds = Math.round(w * 2 / 3);
48
+ switch (zone) {
49
+ case "left-third":
50
+ return { x: 0, y: 0, width: oneThird, height: h };
51
+ case "center-third":
52
+ return { x: oneThird, y: 0, width: twoThirds - oneThird, height: h };
53
+ case "right-third":
54
+ return { x: twoThirds, y: 0, width: w - twoThirds, height: h };
55
+ case "left":
56
+ return { x: 0, y: 0, width: halfW, height: h };
57
+ case "right":
58
+ return { x: halfW, y: 0, width: w - halfW, height: h };
59
+ case "top":
60
+ return { x: 0, y: 0, width: w, height: halfH };
61
+ case "bottom":
62
+ return { x: 0, y: halfH, width: w, height: h - halfH };
63
+ case "top-left":
64
+ return { x: 0, y: 0, width: halfW, height: halfH };
65
+ case "top-right":
66
+ return { x: halfW, y: 0, width: w - halfW, height: halfH };
67
+ case "bottom-left":
68
+ return { x: 0, y: halfH, width: halfW, height: h - halfH };
69
+ case "bottom-right":
70
+ return { x: halfW, y: halfH, width: w - halfW, height: h - halfH };
71
+ }
72
+ }
73
+ function detectSnapZone(x, y, viewport, options = {}) {
74
+ const threshold = options.threshold ?? 12;
75
+ const cornerSize = options.cornerSize ?? 64;
76
+ const w = viewport.width;
77
+ const h = viewport.height;
78
+ if (w <= 0 || h <= 0) return null;
79
+ const nearLeft = x <= threshold;
80
+ const nearRight = x >= w - threshold;
81
+ const nearTop = y <= threshold;
82
+ const nearBottom = y >= h - threshold;
83
+ if (nearLeft || nearRight) {
84
+ const side = nearLeft ? "left" : "right";
85
+ if (y <= cornerSize) return `top-${side}`;
86
+ if (y >= h - cornerSize) return `bottom-${side}`;
87
+ return side;
88
+ }
89
+ if (nearTop) {
90
+ if (x <= cornerSize) return "top-left";
91
+ if (x >= w - cornerSize) return "top-right";
92
+ return "top";
93
+ }
94
+ if (nearBottom) {
95
+ if (x <= cornerSize) return "bottom-left";
96
+ if (x >= w - cornerSize) return "bottom-right";
97
+ return "bottom";
98
+ }
99
+ return null;
100
+ }
101
+ function nearestEdge(low, high, targetLow, targetHigh) {
102
+ return [targetLow - low, targetHigh - low, targetLow - high, targetHigh - high];
103
+ }
104
+ function magnetize(bounds, targets, threshold) {
105
+ const result = { x: bounds.x, y: bounds.y, snappedX: false, snappedY: false };
106
+ if (threshold <= 0 || targets.length === 0) return result;
107
+ let bestX = threshold + 1;
108
+ let bestY = threshold + 1;
109
+ for (const target of targets) {
110
+ for (const delta of nearestEdge(
111
+ bounds.x,
112
+ bounds.x + bounds.width,
113
+ target.x,
114
+ target.x + target.width
115
+ )) {
116
+ if (Math.abs(delta) <= threshold && Math.abs(delta) < Math.abs(bestX)) bestX = delta;
117
+ }
118
+ for (const delta of nearestEdge(
119
+ bounds.y,
120
+ bounds.y + bounds.height,
121
+ target.y,
122
+ target.y + target.height
123
+ )) {
124
+ if (Math.abs(delta) <= threshold && Math.abs(delta) < Math.abs(bestY)) bestY = delta;
125
+ }
126
+ }
127
+ if (Math.abs(bestX) <= threshold) {
128
+ result.x = bounds.x + bestX;
129
+ result.snappedX = true;
130
+ }
131
+ if (Math.abs(bestY) <= threshold) {
132
+ result.y = bounds.y + bestY;
133
+ result.snappedY = true;
134
+ }
135
+ return result;
136
+ }
137
+
138
+ export { applyAspect, boundsEqual, clamp, clampSize, clampToViewport, detectSnapZone, magnetize, zoneBounds };
139
+ //# sourceMappingURL=chunk-5XVIC5LH.js.map
140
+ //# sourceMappingURL=chunk-5XVIC5LH.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/geometry.ts"],"names":[],"mappings":"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","file":"chunk-5XVIC5LH.js","sourcesContent":["import type { Bounds, Size, SnapZone } from './types'\n\nexport function clamp(value: number, min: number, max: number): number {\n return Math.min(Math.max(value, min), max)\n}\n\nexport function clampSize(size: Size, min: Size, max: Size | null): Size {\n return {\n width: clamp(size.width, min.width, max ? max.width : Number.POSITIVE_INFINITY),\n height: clamp(size.height, min.height, max ? max.height : Number.POSITIVE_INFINITY),\n }\n}\n\nexport function clampToViewport(bounds: Bounds, viewport: Size, minVisible: number): Bounds {\n if (viewport.width <= 0 || viewport.height <= 0) return bounds\n const visibleX = Math.min(minVisible, bounds.width)\n const visibleY = Math.min(minVisible, bounds.height)\n const x = clamp(bounds.x, visibleX - bounds.width, Math.max(0, viewport.width - visibleX))\n const y = clamp(bounds.y, 0, Math.max(0, viewport.height - visibleY))\n return x === bounds.x && y === bounds.y ? bounds : { ...bounds, x, y }\n}\n\nexport function boundsEqual(a: Bounds, b: Bounds): boolean {\n return a.x === b.x && a.y === b.y && a.width === b.width && a.height === b.height\n}\n\nexport function applyAspect(\n size: Size,\n ratio: number,\n min: Size,\n max: Size | null,\n drive: 'width' | 'height',\n): Size {\n if (!Number.isFinite(ratio) || ratio <= 0) return clampSize(size, min, max)\n const maxWidth = max ? max.width : Number.POSITIVE_INFINITY\n const maxHeight = max ? max.height : Number.POSITIVE_INFINITY\n if (drive === 'height') {\n const height = clamp(\n size.height,\n Math.max(min.height, min.width / ratio),\n Math.min(maxHeight, maxWidth / ratio),\n )\n return { width: height * ratio, height }\n }\n const width = clamp(\n size.width,\n Math.max(min.width, min.height * ratio),\n Math.min(maxWidth, maxHeight * ratio),\n )\n return { width, height: width / ratio }\n}\n\nexport function zoneBounds(zone: SnapZone, viewport: Size): Bounds {\n const w = viewport.width\n const h = viewport.height\n const halfW = Math.round(w / 2)\n const halfH = Math.round(h / 2)\n const oneThird = Math.round(w / 3)\n const twoThirds = Math.round((w * 2) / 3)\n switch (zone) {\n case 'left-third':\n return { x: 0, y: 0, width: oneThird, height: h }\n case 'center-third':\n return { x: oneThird, y: 0, width: twoThirds - oneThird, height: h }\n case 'right-third':\n return { x: twoThirds, y: 0, width: w - twoThirds, height: h }\n case 'left':\n return { x: 0, y: 0, width: halfW, height: h }\n case 'right':\n return { x: halfW, y: 0, width: w - halfW, height: h }\n case 'top':\n return { x: 0, y: 0, width: w, height: halfH }\n case 'bottom':\n return { x: 0, y: halfH, width: w, height: h - halfH }\n case 'top-left':\n return { x: 0, y: 0, width: halfW, height: halfH }\n case 'top-right':\n return { x: halfW, y: 0, width: w - halfW, height: halfH }\n case 'bottom-left':\n return { x: 0, y: halfH, width: halfW, height: h - halfH }\n case 'bottom-right':\n return { x: halfW, y: halfH, width: w - halfW, height: h - halfH }\n }\n}\n\nexport interface SnapDetectOptions {\n threshold?: number\n cornerSize?: number\n}\n\nexport function detectSnapZone(\n x: number,\n y: number,\n viewport: Size,\n options: SnapDetectOptions = {},\n): SnapZone | null {\n const threshold = options.threshold ?? 12\n const cornerSize = options.cornerSize ?? 64\n const w = viewport.width\n const h = viewport.height\n if (w <= 0 || h <= 0) return null\n\n const nearLeft = x <= threshold\n const nearRight = x >= w - threshold\n const nearTop = y <= threshold\n const nearBottom = y >= h - threshold\n\n if (nearLeft || nearRight) {\n const side = nearLeft ? 'left' : 'right'\n if (y <= cornerSize) return `top-${side}` as SnapZone\n if (y >= h - cornerSize) return `bottom-${side}` as SnapZone\n return side\n }\n if (nearTop) {\n if (x <= cornerSize) return 'top-left'\n if (x >= w - cornerSize) return 'top-right'\n return 'top'\n }\n if (nearBottom) {\n if (x <= cornerSize) return 'bottom-left'\n if (x >= w - cornerSize) return 'bottom-right'\n return 'bottom'\n }\n return null\n}\n\nexport interface MagnetResult {\n x: number\n y: number\n snappedX: boolean\n snappedY: boolean\n}\n\nfunction nearestEdge(low: number, high: number, targetLow: number, targetHigh: number): number[] {\n return [targetLow - low, targetHigh - low, targetLow - high, targetHigh - high]\n}\n\nexport function magnetize(\n bounds: Bounds,\n targets: readonly Bounds[],\n threshold: number,\n): MagnetResult {\n const result: MagnetResult = { x: bounds.x, y: bounds.y, snappedX: false, snappedY: false }\n if (threshold <= 0 || targets.length === 0) return result\n let bestX = threshold + 1\n let bestY = threshold + 1\n for (const target of targets) {\n for (const delta of nearestEdge(\n bounds.x,\n bounds.x + bounds.width,\n target.x,\n target.x + target.width,\n )) {\n if (Math.abs(delta) <= threshold && Math.abs(delta) < Math.abs(bestX)) bestX = delta\n }\n for (const delta of nearestEdge(\n bounds.y,\n bounds.y + bounds.height,\n target.y,\n target.y + target.height,\n )) {\n if (Math.abs(delta) <= threshold && Math.abs(delta) < Math.abs(bestY)) bestY = delta\n }\n }\n if (Math.abs(bestX) <= threshold) {\n result.x = bounds.x + bestX\n result.snappedX = true\n }\n if (Math.abs(bestY) <= threshold) {\n result.y = bounds.y + bestY\n result.snappedY = true\n }\n return result\n}\n"]}
@@ -0,0 +1,149 @@
1
+ 'use strict';
2
+
3
+ // src/core/geometry.ts
4
+ function clamp(value, min, max) {
5
+ return Math.min(Math.max(value, min), max);
6
+ }
7
+ function clampSize(size, min, max) {
8
+ return {
9
+ width: clamp(size.width, min.width, max ? max.width : Number.POSITIVE_INFINITY),
10
+ height: clamp(size.height, min.height, max ? max.height : Number.POSITIVE_INFINITY)
11
+ };
12
+ }
13
+ function clampToViewport(bounds, viewport, minVisible) {
14
+ if (viewport.width <= 0 || viewport.height <= 0) return bounds;
15
+ const visibleX = Math.min(minVisible, bounds.width);
16
+ const visibleY = Math.min(minVisible, bounds.height);
17
+ const x = clamp(bounds.x, visibleX - bounds.width, Math.max(0, viewport.width - visibleX));
18
+ const y = clamp(bounds.y, 0, Math.max(0, viewport.height - visibleY));
19
+ return x === bounds.x && y === bounds.y ? bounds : { ...bounds, x, y };
20
+ }
21
+ function boundsEqual(a, b) {
22
+ return a.x === b.x && a.y === b.y && a.width === b.width && a.height === b.height;
23
+ }
24
+ function applyAspect(size, ratio, min, max, drive) {
25
+ if (!Number.isFinite(ratio) || ratio <= 0) return clampSize(size, min, max);
26
+ const maxWidth = max ? max.width : Number.POSITIVE_INFINITY;
27
+ const maxHeight = max ? max.height : Number.POSITIVE_INFINITY;
28
+ if (drive === "height") {
29
+ const height = clamp(
30
+ size.height,
31
+ Math.max(min.height, min.width / ratio),
32
+ Math.min(maxHeight, maxWidth / ratio)
33
+ );
34
+ return { width: height * ratio, height };
35
+ }
36
+ const width = clamp(
37
+ size.width,
38
+ Math.max(min.width, min.height * ratio),
39
+ Math.min(maxWidth, maxHeight * ratio)
40
+ );
41
+ return { width, height: width / ratio };
42
+ }
43
+ function zoneBounds(zone, viewport) {
44
+ const w = viewport.width;
45
+ const h = viewport.height;
46
+ const halfW = Math.round(w / 2);
47
+ const halfH = Math.round(h / 2);
48
+ const oneThird = Math.round(w / 3);
49
+ const twoThirds = Math.round(w * 2 / 3);
50
+ switch (zone) {
51
+ case "left-third":
52
+ return { x: 0, y: 0, width: oneThird, height: h };
53
+ case "center-third":
54
+ return { x: oneThird, y: 0, width: twoThirds - oneThird, height: h };
55
+ case "right-third":
56
+ return { x: twoThirds, y: 0, width: w - twoThirds, height: h };
57
+ case "left":
58
+ return { x: 0, y: 0, width: halfW, height: h };
59
+ case "right":
60
+ return { x: halfW, y: 0, width: w - halfW, height: h };
61
+ case "top":
62
+ return { x: 0, y: 0, width: w, height: halfH };
63
+ case "bottom":
64
+ return { x: 0, y: halfH, width: w, height: h - halfH };
65
+ case "top-left":
66
+ return { x: 0, y: 0, width: halfW, height: halfH };
67
+ case "top-right":
68
+ return { x: halfW, y: 0, width: w - halfW, height: halfH };
69
+ case "bottom-left":
70
+ return { x: 0, y: halfH, width: halfW, height: h - halfH };
71
+ case "bottom-right":
72
+ return { x: halfW, y: halfH, width: w - halfW, height: h - halfH };
73
+ }
74
+ }
75
+ function detectSnapZone(x, y, viewport, options = {}) {
76
+ const threshold = options.threshold ?? 12;
77
+ const cornerSize = options.cornerSize ?? 64;
78
+ const w = viewport.width;
79
+ const h = viewport.height;
80
+ if (w <= 0 || h <= 0) return null;
81
+ const nearLeft = x <= threshold;
82
+ const nearRight = x >= w - threshold;
83
+ const nearTop = y <= threshold;
84
+ const nearBottom = y >= h - threshold;
85
+ if (nearLeft || nearRight) {
86
+ const side = nearLeft ? "left" : "right";
87
+ if (y <= cornerSize) return `top-${side}`;
88
+ if (y >= h - cornerSize) return `bottom-${side}`;
89
+ return side;
90
+ }
91
+ if (nearTop) {
92
+ if (x <= cornerSize) return "top-left";
93
+ if (x >= w - cornerSize) return "top-right";
94
+ return "top";
95
+ }
96
+ if (nearBottom) {
97
+ if (x <= cornerSize) return "bottom-left";
98
+ if (x >= w - cornerSize) return "bottom-right";
99
+ return "bottom";
100
+ }
101
+ return null;
102
+ }
103
+ function nearestEdge(low, high, targetLow, targetHigh) {
104
+ return [targetLow - low, targetHigh - low, targetLow - high, targetHigh - high];
105
+ }
106
+ function magnetize(bounds, targets, threshold) {
107
+ const result = { x: bounds.x, y: bounds.y, snappedX: false, snappedY: false };
108
+ if (threshold <= 0 || targets.length === 0) return result;
109
+ let bestX = threshold + 1;
110
+ let bestY = threshold + 1;
111
+ for (const target of targets) {
112
+ for (const delta of nearestEdge(
113
+ bounds.x,
114
+ bounds.x + bounds.width,
115
+ target.x,
116
+ target.x + target.width
117
+ )) {
118
+ if (Math.abs(delta) <= threshold && Math.abs(delta) < Math.abs(bestX)) bestX = delta;
119
+ }
120
+ for (const delta of nearestEdge(
121
+ bounds.y,
122
+ bounds.y + bounds.height,
123
+ target.y,
124
+ target.y + target.height
125
+ )) {
126
+ if (Math.abs(delta) <= threshold && Math.abs(delta) < Math.abs(bestY)) bestY = delta;
127
+ }
128
+ }
129
+ if (Math.abs(bestX) <= threshold) {
130
+ result.x = bounds.x + bestX;
131
+ result.snappedX = true;
132
+ }
133
+ if (Math.abs(bestY) <= threshold) {
134
+ result.y = bounds.y + bestY;
135
+ result.snappedY = true;
136
+ }
137
+ return result;
138
+ }
139
+
140
+ exports.applyAspect = applyAspect;
141
+ exports.boundsEqual = boundsEqual;
142
+ exports.clamp = clamp;
143
+ exports.clampSize = clampSize;
144
+ exports.clampToViewport = clampToViewport;
145
+ exports.detectSnapZone = detectSnapZone;
146
+ exports.magnetize = magnetize;
147
+ exports.zoneBounds = zoneBounds;
148
+ //# sourceMappingURL=chunk-BDHFXEJN.cjs.map
149
+ //# sourceMappingURL=chunk-BDHFXEJN.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/geometry.ts"],"names":[],"mappings":"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","file":"chunk-BDHFXEJN.cjs","sourcesContent":["import type { Bounds, Size, SnapZone } from './types'\n\nexport function clamp(value: number, min: number, max: number): number {\n return Math.min(Math.max(value, min), max)\n}\n\nexport function clampSize(size: Size, min: Size, max: Size | null): Size {\n return {\n width: clamp(size.width, min.width, max ? max.width : Number.POSITIVE_INFINITY),\n height: clamp(size.height, min.height, max ? max.height : Number.POSITIVE_INFINITY),\n }\n}\n\nexport function clampToViewport(bounds: Bounds, viewport: Size, minVisible: number): Bounds {\n if (viewport.width <= 0 || viewport.height <= 0) return bounds\n const visibleX = Math.min(minVisible, bounds.width)\n const visibleY = Math.min(minVisible, bounds.height)\n const x = clamp(bounds.x, visibleX - bounds.width, Math.max(0, viewport.width - visibleX))\n const y = clamp(bounds.y, 0, Math.max(0, viewport.height - visibleY))\n return x === bounds.x && y === bounds.y ? bounds : { ...bounds, x, y }\n}\n\nexport function boundsEqual(a: Bounds, b: Bounds): boolean {\n return a.x === b.x && a.y === b.y && a.width === b.width && a.height === b.height\n}\n\nexport function applyAspect(\n size: Size,\n ratio: number,\n min: Size,\n max: Size | null,\n drive: 'width' | 'height',\n): Size {\n if (!Number.isFinite(ratio) || ratio <= 0) return clampSize(size, min, max)\n const maxWidth = max ? max.width : Number.POSITIVE_INFINITY\n const maxHeight = max ? max.height : Number.POSITIVE_INFINITY\n if (drive === 'height') {\n const height = clamp(\n size.height,\n Math.max(min.height, min.width / ratio),\n Math.min(maxHeight, maxWidth / ratio),\n )\n return { width: height * ratio, height }\n }\n const width = clamp(\n size.width,\n Math.max(min.width, min.height * ratio),\n Math.min(maxWidth, maxHeight * ratio),\n )\n return { width, height: width / ratio }\n}\n\nexport function zoneBounds(zone: SnapZone, viewport: Size): Bounds {\n const w = viewport.width\n const h = viewport.height\n const halfW = Math.round(w / 2)\n const halfH = Math.round(h / 2)\n const oneThird = Math.round(w / 3)\n const twoThirds = Math.round((w * 2) / 3)\n switch (zone) {\n case 'left-third':\n return { x: 0, y: 0, width: oneThird, height: h }\n case 'center-third':\n return { x: oneThird, y: 0, width: twoThirds - oneThird, height: h }\n case 'right-third':\n return { x: twoThirds, y: 0, width: w - twoThirds, height: h }\n case 'left':\n return { x: 0, y: 0, width: halfW, height: h }\n case 'right':\n return { x: halfW, y: 0, width: w - halfW, height: h }\n case 'top':\n return { x: 0, y: 0, width: w, height: halfH }\n case 'bottom':\n return { x: 0, y: halfH, width: w, height: h - halfH }\n case 'top-left':\n return { x: 0, y: 0, width: halfW, height: halfH }\n case 'top-right':\n return { x: halfW, y: 0, width: w - halfW, height: halfH }\n case 'bottom-left':\n return { x: 0, y: halfH, width: halfW, height: h - halfH }\n case 'bottom-right':\n return { x: halfW, y: halfH, width: w - halfW, height: h - halfH }\n }\n}\n\nexport interface SnapDetectOptions {\n threshold?: number\n cornerSize?: number\n}\n\nexport function detectSnapZone(\n x: number,\n y: number,\n viewport: Size,\n options: SnapDetectOptions = {},\n): SnapZone | null {\n const threshold = options.threshold ?? 12\n const cornerSize = options.cornerSize ?? 64\n const w = viewport.width\n const h = viewport.height\n if (w <= 0 || h <= 0) return null\n\n const nearLeft = x <= threshold\n const nearRight = x >= w - threshold\n const nearTop = y <= threshold\n const nearBottom = y >= h - threshold\n\n if (nearLeft || nearRight) {\n const side = nearLeft ? 'left' : 'right'\n if (y <= cornerSize) return `top-${side}` as SnapZone\n if (y >= h - cornerSize) return `bottom-${side}` as SnapZone\n return side\n }\n if (nearTop) {\n if (x <= cornerSize) return 'top-left'\n if (x >= w - cornerSize) return 'top-right'\n return 'top'\n }\n if (nearBottom) {\n if (x <= cornerSize) return 'bottom-left'\n if (x >= w - cornerSize) return 'bottom-right'\n return 'bottom'\n }\n return null\n}\n\nexport interface MagnetResult {\n x: number\n y: number\n snappedX: boolean\n snappedY: boolean\n}\n\nfunction nearestEdge(low: number, high: number, targetLow: number, targetHigh: number): number[] {\n return [targetLow - low, targetHigh - low, targetLow - high, targetHigh - high]\n}\n\nexport function magnetize(\n bounds: Bounds,\n targets: readonly Bounds[],\n threshold: number,\n): MagnetResult {\n const result: MagnetResult = { x: bounds.x, y: bounds.y, snappedX: false, snappedY: false }\n if (threshold <= 0 || targets.length === 0) return result\n let bestX = threshold + 1\n let bestY = threshold + 1\n for (const target of targets) {\n for (const delta of nearestEdge(\n bounds.x,\n bounds.x + bounds.width,\n target.x,\n target.x + target.width,\n )) {\n if (Math.abs(delta) <= threshold && Math.abs(delta) < Math.abs(bestX)) bestX = delta\n }\n for (const delta of nearestEdge(\n bounds.y,\n bounds.y + bounds.height,\n target.y,\n target.y + target.height,\n )) {\n if (Math.abs(delta) <= threshold && Math.abs(delta) < Math.abs(bestY)) bestY = delta\n }\n }\n if (Math.abs(bestX) <= threshold) {\n result.x = bounds.x + bestX\n result.snappedX = true\n }\n if (Math.abs(bestY) <= threshold) {\n result.y = bounds.y + bestY\n result.snappedY = true\n }\n return result\n}\n"]}