@kanso-protocol/core 4.0.0 → 4.1.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.
@@ -55,6 +55,71 @@ function findPortalTarget(host, doc) {
55
55
  return doc.body;
56
56
  }
57
57
 
58
+ /**
59
+ * Shared overlay positioning math for floating UI (tooltip, popover, …).
60
+ *
61
+ * Pure function: given the trigger's rect, the overlay's measured size,
62
+ * a preferred side, a gap, and a cross-axis alignment, returns the
63
+ * fixed-position `{ x, y, side }` for the overlay — with viewport-edge
64
+ * flipping and clamping baked in.
65
+ *
66
+ * Kept framework-agnostic (no Angular imports) so it can be unit-tested
67
+ * in isolation and reused by any directive that parks a `position: fixed`
68
+ * element next to a trigger.
69
+ */
70
+ const OPPOSITE = {
71
+ top: 'bottom',
72
+ bottom: 'top',
73
+ left: 'right',
74
+ right: 'left',
75
+ };
76
+ function computeOverlayPosition(input) {
77
+ const { trigger, overlay, gap, viewport, align = 'center', alignInset = 0, gutter = 4, } = input;
78
+ const fits = (s) => {
79
+ switch (s) {
80
+ case 'top': return trigger.top - overlay.height - gap >= 0;
81
+ case 'bottom': return trigger.bottom + overlay.height + gap <= viewport.height;
82
+ case 'left': return trigger.left - overlay.width - gap >= 0;
83
+ case 'right': return trigger.right + overlay.width + gap <= viewport.width;
84
+ }
85
+ };
86
+ let side = input.side;
87
+ if (!fits(side) && fits(OPPOSITE[side]))
88
+ side = OPPOSITE[side];
89
+ // Offset along the cross-axis so the alignment anchor (arrow point /
90
+ // start / end) lines up with the trigger centre.
91
+ const offset = (extent) => {
92
+ switch (align) {
93
+ case 'start': return alignInset;
94
+ case 'end': return extent - alignInset;
95
+ default: return extent / 2;
96
+ }
97
+ };
98
+ let x, y;
99
+ switch (side) {
100
+ case 'top':
101
+ x = trigger.left + trigger.width / 2 - offset(overlay.width);
102
+ y = trigger.top - overlay.height - gap;
103
+ break;
104
+ case 'bottom':
105
+ x = trigger.left + trigger.width / 2 - offset(overlay.width);
106
+ y = trigger.bottom + gap;
107
+ break;
108
+ case 'left':
109
+ x = trigger.left - overlay.width - gap;
110
+ y = trigger.top + trigger.height / 2 - offset(overlay.height);
111
+ break;
112
+ case 'right':
113
+ x = trigger.right + gap;
114
+ y = trigger.top + trigger.height / 2 - offset(overlay.height);
115
+ break;
116
+ }
117
+ // Clamp within viewport gutter.
118
+ x = Math.max(gutter, Math.min(x, viewport.width - overlay.width - gutter));
119
+ y = Math.max(gutter, Math.min(y, viewport.height - overlay.height - gutter));
120
+ return { x, y, side };
121
+ }
122
+
58
123
  // Kanso Protocol — Core
59
124
  // This file re-exports generated token constants and core utilities.
60
125
  // Generated tokens will be available after running `npm run build:tokens`
@@ -64,5 +129,5 @@ function findPortalTarget(host, doc) {
64
129
  * Generated bundle index. Do not edit.
65
130
  */
66
131
 
67
- export { KP_ICON_SIZES, KP_RADII, KP_SIZES, findPortalTarget };
132
+ export { KP_ICON_SIZES, KP_RADII, KP_SIZES, computeOverlayPosition, findPortalTarget };
68
133
  //# sourceMappingURL=kanso-protocol-core.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"kanso-protocol-core.mjs","sources":["../../../../packages/core/src/lib/types.ts","../../../../packages/core/src/lib/portal.ts","../../../../packages/core/src/index.ts","../../../../packages/core/src/kanso-protocol-core.ts"],"sourcesContent":["/**\n * Kanso Protocol — Core Types\n *\n * These types define the shared API contract for all components.\n * Every interactive component MUST use these types consistently.\n */\n\n/** Component size scale */\nexport type KpSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl';\n\n/** Visual variant */\nexport type KpVariant = 'default' | 'subtle' | 'outline' | 'ghost';\n\n/** Color role */\nexport type KpColorRole = 'primary' | 'secondary' | 'danger' | 'warning' | 'success' | 'info' | 'neutral';\n\n/** Interactive component states (for documentation/testing) */\nexport type KpState = 'rest' | 'hover' | 'active' | 'focus' | 'disabled' | 'loading' | 'error';\n\n/** Size-to-value mappings */\nexport const KP_SIZES: Record<KpSize, number> = {\n xs: 24,\n sm: 28,\n md: 36,\n lg: 44,\n xl: 52,\n};\n\nexport const KP_RADII: Record<KpSize, number> = {\n xs: 8,\n sm: 10,\n md: 12,\n lg: 14,\n xl: 16,\n};\n\nexport const KP_ICON_SIZES: Record<KpSize, number> = {\n xs: 14,\n sm: 16,\n md: 18,\n lg: 22,\n xl: 24,\n};\n","/**\n * Find the right portal target for a floating overlay (select dropdown,\n * combobox listbox, popover panel, etc.) so it stays in the same\n * stacking context as its trigger.\n *\n * Background: Kanso `<kp-dialog>` uses native `<dialog>.showModal()`\n * which places the dialog in the browser's **top-layer**. Anything\n * portaled to `document.body` ends up BELOW the top-layer — invisible\n * and inert. To stay visible / interactive, overlays must portal\n * inside the nearest open `<dialog>` ancestor instead.\n *\n * Outside a dialog, `document.body` is still the right target so the\n * overlay isn't clipped by any transformed / overflow:hidden ancestor.\n *\n * @example\n * const target = findPortalTarget(this.host.nativeElement, this.doc);\n * if (target && el.parentElement !== target) target.appendChild(el);\n */\nexport function findPortalTarget(host: HTMLElement, doc: Document): HTMLElement {\n let el: HTMLElement | null = host;\n while (el) {\n if (el.tagName === 'DIALOG' && (el as HTMLDialogElement).open) return el;\n el = el.parentElement;\n }\n return doc.body;\n}\n","// Kanso Protocol — Core\n// This file re-exports generated token constants and core utilities.\n\n// Generated tokens will be available after running `npm run build:tokens`\n// import { kpColorPrimaryDefaultBgRest } from './_generated/tokens';\n\nexport * from './lib/types';\nexport * from './lib/portal';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"AAAA;;;;;AAKG;AAcH;AACO,MAAM,QAAQ,GAA2B;AAC9C,IAAA,EAAE,EAAE,EAAE;AACN,IAAA,EAAE,EAAE,EAAE;AACN,IAAA,EAAE,EAAE,EAAE;AACN,IAAA,EAAE,EAAE,EAAE;AACN,IAAA,EAAE,EAAE,EAAE;;AAGD,MAAM,QAAQ,GAA2B;AAC9C,IAAA,EAAE,EAAE,CAAC;AACL,IAAA,EAAE,EAAE,EAAE;AACN,IAAA,EAAE,EAAE,EAAE;AACN,IAAA,EAAE,EAAE,EAAE;AACN,IAAA,EAAE,EAAE,EAAE;;AAGD,MAAM,aAAa,GAA2B;AACnD,IAAA,EAAE,EAAE,EAAE;AACN,IAAA,EAAE,EAAE,EAAE;AACN,IAAA,EAAE,EAAE,EAAE;AACN,IAAA,EAAE,EAAE,EAAE;AACN,IAAA,EAAE,EAAE,EAAE;;;ACzCR;;;;;;;;;;;;;;;;;AAiBG;AACG,SAAU,gBAAgB,CAAC,IAAiB,EAAE,GAAa,EAAA;IAC/D,IAAI,EAAE,GAAuB,IAAI;IACjC,OAAO,EAAE,EAAE;QACT,IAAI,EAAE,CAAC,OAAO,KAAK,QAAQ,IAAK,EAAwB,CAAC,IAAI;AAAE,YAAA,OAAO,EAAE;AACxE,QAAA,EAAE,GAAG,EAAE,CAAC,aAAa;IACvB;IACA,OAAO,GAAG,CAAC,IAAI;AACjB;;ACzBA;AACA;AAEA;AACA;;ACJA;;AAEG;;;;"}
1
+ {"version":3,"file":"kanso-protocol-core.mjs","sources":["../../../../packages/core/src/lib/types.ts","../../../../packages/core/src/lib/portal.ts","../../../../packages/core/src/lib/overlay-position.ts","../../../../packages/core/src/index.ts","../../../../packages/core/src/kanso-protocol-core.ts"],"sourcesContent":["/**\n * Kanso Protocol — Core Types\n *\n * These types define the shared API contract for all components.\n * Every interactive component MUST use these types consistently.\n */\n\n/** Component size scale */\nexport type KpSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl';\n\n/** Visual variant */\nexport type KpVariant = 'default' | 'subtle' | 'outline' | 'ghost';\n\n/** Color role */\nexport type KpColorRole = 'primary' | 'secondary' | 'danger' | 'warning' | 'success' | 'info' | 'neutral';\n\n/** Interactive component states (for documentation/testing) */\nexport type KpState = 'rest' | 'hover' | 'active' | 'focus' | 'disabled' | 'loading' | 'error';\n\n/** Size-to-value mappings */\nexport const KP_SIZES: Record<KpSize, number> = {\n xs: 24,\n sm: 28,\n md: 36,\n lg: 44,\n xl: 52,\n};\n\nexport const KP_RADII: Record<KpSize, number> = {\n xs: 8,\n sm: 10,\n md: 12,\n lg: 14,\n xl: 16,\n};\n\nexport const KP_ICON_SIZES: Record<KpSize, number> = {\n xs: 14,\n sm: 16,\n md: 18,\n lg: 22,\n xl: 24,\n};\n","/**\n * Find the right portal target for a floating overlay (select dropdown,\n * combobox listbox, popover panel, etc.) so it stays in the same\n * stacking context as its trigger.\n *\n * Background: Kanso `<kp-dialog>` uses native `<dialog>.showModal()`\n * which places the dialog in the browser's **top-layer**. Anything\n * portaled to `document.body` ends up BELOW the top-layer — invisible\n * and inert. To stay visible / interactive, overlays must portal\n * inside the nearest open `<dialog>` ancestor instead.\n *\n * Outside a dialog, `document.body` is still the right target so the\n * overlay isn't clipped by any transformed / overflow:hidden ancestor.\n *\n * @example\n * const target = findPortalTarget(this.host.nativeElement, this.doc);\n * if (target && el.parentElement !== target) target.appendChild(el);\n */\nexport function findPortalTarget(host: HTMLElement, doc: Document): HTMLElement {\n let el: HTMLElement | null = host;\n while (el) {\n if (el.tagName === 'DIALOG' && (el as HTMLDialogElement).open) return el;\n el = el.parentElement;\n }\n return doc.body;\n}\n","/**\n * Shared overlay positioning math for floating UI (tooltip, popover, …).\n *\n * Pure function: given the trigger's rect, the overlay's measured size,\n * a preferred side, a gap, and a cross-axis alignment, returns the\n * fixed-position `{ x, y, side }` for the overlay — with viewport-edge\n * flipping and clamping baked in.\n *\n * Kept framework-agnostic (no Angular imports) so it can be unit-tested\n * in isolation and reused by any directive that parks a `position: fixed`\n * element next to a trigger.\n */\n\nexport type KpOverlaySide = 'top' | 'right' | 'bottom' | 'left';\nexport type KpOverlayAlign = 'start' | 'center' | 'end';\n\nexport interface KpOverlayRect {\n top: number;\n left: number;\n right: number;\n bottom: number;\n width: number;\n height: number;\n}\n\nexport interface KpOverlayPositionInput {\n /** Trigger bounding rect (viewport coords, e.g. from getBoundingClientRect). */\n trigger: KpOverlayRect;\n /** Overlay measured size. */\n overlay: { width: number; height: number };\n /** Preferred side. Flips to the opposite side if it doesn't fit. */\n side: KpOverlaySide;\n /** Distance in px between trigger edge and overlay. */\n gap: number;\n /** Viewport size. */\n viewport: { width: number; height: number };\n /**\n * Cross-axis alignment. For top/bottom this shifts horizontally; for\n * left/right it shifts vertically. `inset` is the distance from the\n * overlay corner to the alignment anchor (e.g. an arrow centre).\n */\n align?: KpOverlayAlign;\n alignInset?: number;\n /** Viewport gutter to clamp within. Default 4. */\n gutter?: number;\n}\n\nexport interface KpOverlayPositionResult {\n x: number;\n y: number;\n /** Final side after flipping — may differ from the requested side. */\n side: KpOverlaySide;\n}\n\nconst OPPOSITE: Record<KpOverlaySide, KpOverlaySide> = {\n top: 'bottom',\n bottom: 'top',\n left: 'right',\n right: 'left',\n};\n\nexport function computeOverlayPosition(input: KpOverlayPositionInput): KpOverlayPositionResult {\n const {\n trigger, overlay, gap, viewport,\n align = 'center', alignInset = 0, gutter = 4,\n } = input;\n\n const fits = (s: KpOverlaySide): boolean => {\n switch (s) {\n case 'top': return trigger.top - overlay.height - gap >= 0;\n case 'bottom': return trigger.bottom + overlay.height + gap <= viewport.height;\n case 'left': return trigger.left - overlay.width - gap >= 0;\n case 'right': return trigger.right + overlay.width + gap <= viewport.width;\n }\n };\n\n let side = input.side;\n if (!fits(side) && fits(OPPOSITE[side])) side = OPPOSITE[side];\n\n // Offset along the cross-axis so the alignment anchor (arrow point /\n // start / end) lines up with the trigger centre.\n const offset = (extent: number): number => {\n switch (align) {\n case 'start': return alignInset;\n case 'end': return extent - alignInset;\n default: return extent / 2;\n }\n };\n\n let x: number, y: number;\n switch (side) {\n case 'top':\n x = trigger.left + trigger.width / 2 - offset(overlay.width);\n y = trigger.top - overlay.height - gap;\n break;\n case 'bottom':\n x = trigger.left + trigger.width / 2 - offset(overlay.width);\n y = trigger.bottom + gap;\n break;\n case 'left':\n x = trigger.left - overlay.width - gap;\n y = trigger.top + trigger.height / 2 - offset(overlay.height);\n break;\n case 'right':\n x = trigger.right + gap;\n y = trigger.top + trigger.height / 2 - offset(overlay.height);\n break;\n }\n\n // Clamp within viewport gutter.\n x = Math.max(gutter, Math.min(x, viewport.width - overlay.width - gutter));\n y = Math.max(gutter, Math.min(y, viewport.height - overlay.height - gutter));\n\n return { x, y, side };\n}\n","// Kanso Protocol — Core\n// This file re-exports generated token constants and core utilities.\n\n// Generated tokens will be available after running `npm run build:tokens`\n// import { kpColorPrimaryDefaultBgRest } from './_generated/tokens';\n\nexport * from './lib/types';\nexport * from './lib/portal';\nexport * from './lib/overlay-position';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kanso-protocol/core",
3
- "version": "4.0.0",
3
+ "version": "4.1.0",
4
4
  "description": "Kanso Protocol — core design tokens (CSS/SCSS variables + generated TS constants).",
5
5
  "license": "MIT",
6
6
  "author": "GregNBlack",
@@ -37,5 +37,62 @@ declare const KP_ICON_SIZES: Record<KpSize, number>;
37
37
  */
38
38
  declare function findPortalTarget(host: HTMLElement, doc: Document): HTMLElement;
39
39
 
40
- export { KP_ICON_SIZES, KP_RADII, KP_SIZES, findPortalTarget };
41
- export type { KpColorRole, KpSize, KpState, KpVariant };
40
+ /**
41
+ * Shared overlay positioning math for floating UI (tooltip, popover, …).
42
+ *
43
+ * Pure function: given the trigger's rect, the overlay's measured size,
44
+ * a preferred side, a gap, and a cross-axis alignment, returns the
45
+ * fixed-position `{ x, y, side }` for the overlay — with viewport-edge
46
+ * flipping and clamping baked in.
47
+ *
48
+ * Kept framework-agnostic (no Angular imports) so it can be unit-tested
49
+ * in isolation and reused by any directive that parks a `position: fixed`
50
+ * element next to a trigger.
51
+ */
52
+ type KpOverlaySide = 'top' | 'right' | 'bottom' | 'left';
53
+ type KpOverlayAlign = 'start' | 'center' | 'end';
54
+ interface KpOverlayRect {
55
+ top: number;
56
+ left: number;
57
+ right: number;
58
+ bottom: number;
59
+ width: number;
60
+ height: number;
61
+ }
62
+ interface KpOverlayPositionInput {
63
+ /** Trigger bounding rect (viewport coords, e.g. from getBoundingClientRect). */
64
+ trigger: KpOverlayRect;
65
+ /** Overlay measured size. */
66
+ overlay: {
67
+ width: number;
68
+ height: number;
69
+ };
70
+ /** Preferred side. Flips to the opposite side if it doesn't fit. */
71
+ side: KpOverlaySide;
72
+ /** Distance in px between trigger edge and overlay. */
73
+ gap: number;
74
+ /** Viewport size. */
75
+ viewport: {
76
+ width: number;
77
+ height: number;
78
+ };
79
+ /**
80
+ * Cross-axis alignment. For top/bottom this shifts horizontally; for
81
+ * left/right it shifts vertically. `inset` is the distance from the
82
+ * overlay corner to the alignment anchor (e.g. an arrow centre).
83
+ */
84
+ align?: KpOverlayAlign;
85
+ alignInset?: number;
86
+ /** Viewport gutter to clamp within. Default 4. */
87
+ gutter?: number;
88
+ }
89
+ interface KpOverlayPositionResult {
90
+ x: number;
91
+ y: number;
92
+ /** Final side after flipping — may differ from the requested side. */
93
+ side: KpOverlaySide;
94
+ }
95
+ declare function computeOverlayPosition(input: KpOverlayPositionInput): KpOverlayPositionResult;
96
+
97
+ export { KP_ICON_SIZES, KP_RADII, KP_SIZES, computeOverlayPosition, findPortalTarget };
98
+ export type { KpColorRole, KpOverlayAlign, KpOverlayPositionInput, KpOverlayPositionResult, KpOverlayRect, KpOverlaySide, KpSize, KpState, KpVariant };