kaplay-plugin-ui 1.3.0 → 1.4.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.
package/dist/plugin.cjs CHANGED
@@ -148,12 +148,13 @@ function createModalManager(k) {
148
148
  function pauseSceneTree(gameObj) {
149
149
  if (isModal(gameObj) || isInsideModal(gameObj)) return;
150
150
  pausedScene ??= /* @__PURE__ */ new Map();
151
- // v8 ignore next
151
+ // v8 ignore if
152
152
  if (!pausedScene.has(gameObj)) pausedScene.set(gameObj, gameObj.paused);
153
153
  setPaused(gameObj);
154
154
  gameObj.children.forEach(pauseSceneTree);
155
155
  }
156
156
  function openModal(modal) {
157
+ k.setCursor("default");
157
158
  if (modals.length > 0) setPaused(getLast(modals), true, true);
158
159
  modals.push(modal);
159
160
  if (modals.length === 1) {
@@ -175,7 +176,7 @@ function createModalManager(k) {
175
176
  if (!modals.length) {
176
177
  cancelPauseOnAdd?.();
177
178
  cancelPauseOnAdd = void 0;
178
- // v8 ignore next
179
+ // v8 ignore if
179
180
  if (pausedScene) {
180
181
  for (const [gameObj, paused] of pausedScene) if (gameObj.exists()) setPaused(gameObj, paused);
181
182
  pausedScene = void 0;
@@ -250,6 +251,7 @@ function createButton(k, theme, text, opt) {
250
251
  const radius = opt.radius ?? theme.shape.borderRadius;
251
252
  const { minWidth, paddingX, paddingY } = theme.components.button;
252
253
  const baseColor = toColor(k, opt.color ?? opt.fill ?? theme.palette.primary);
254
+ let disabled = opt.disabled ?? false;
253
255
  const hoverColor = toColor(k, opt.hoverColor ?? theme.palette.hover);
254
256
  const pressedColor = toColor(k, opt.pressedColor ?? theme.palette.pressed);
255
257
  const textColor = toColor(k, opt.textColor ?? theme.palette.text);
@@ -267,7 +269,7 @@ function createButton(k, theme, text, opt) {
267
269
  k.pos(pos),
268
270
  k.area(),
269
271
  k.scale(),
270
- k.color(baseColor),
272
+ k.color(disabled ? toColor(k, theme.palette.disabled) : baseColor),
271
273
  k.anchor("center")
272
274
  ];
273
275
  const button = opt.parent ? opt.parent.add(components) : k.add(components);
@@ -332,6 +334,7 @@ function createButton(k, theme, text, opt) {
332
334
  layout();
333
335
  if (opt.width === void 0 || opt.height === void 0 || opt.icon !== void 0 || opt.background !== void 0) button.onUpdate(layout);
334
336
  button.onClick(() => {
337
+ if (disabled) return;
335
338
  k.wait(0, () => {
336
339
  button.color = toColor(k, pressedColor);
337
340
  if (icon && opt.pressedIcon) {
@@ -343,12 +346,18 @@ function createButton(k, theme, text, opt) {
343
346
  background.sprite = opt.pressedBackground;
344
347
  }
345
348
  opt.onClick?.();
349
+ // v8 ignore if
350
+ if (disabled) k.setCursor("not-allowed");
351
+ else if (!button.isHovering()) k.setCursor("default");
346
352
  });
347
353
  });
348
354
  button.onHover(() => {
355
+ if (disabled) {
356
+ k.setCursor("not-allowed");
357
+ return;
358
+ } else k.setCursor("pointer");
349
359
  button.color = toColor(k, hoverColor);
350
360
  button.scale = k.vec2(1.05, 1.05);
351
- k.setCursor("pointer");
352
361
  if (icon && opt.hoverIcon) {
353
362
  iconState = "hover";
354
363
  icon.sprite = opt.hoverIcon;
@@ -359,9 +368,10 @@ function createButton(k, theme, text, opt) {
359
368
  }
360
369
  });
361
370
  button.onHoverEnd(() => {
371
+ k.setCursor("default");
372
+ if (disabled) return;
362
373
  button.color = baseColor;
363
374
  button.scale = k.vec2(1, 1);
364
- k.setCursor("default");
365
375
  if (icon && opt.icon) {
366
376
  iconState = "base";
367
377
  icon.sprite = opt.icon;
@@ -372,11 +382,29 @@ function createButton(k, theme, text, opt) {
372
382
  }
373
383
  if (icon || background) layout();
374
384
  });
375
- return Object.assign(button, {
385
+ button.onDestroy(() => {
386
+ if (button.isHovering()) k.setCursor("default");
387
+ });
388
+ function applyDisabledState() {
389
+ button.color = disabled ? toColor(k, theme.palette.disabled) : baseColor;
390
+ }
391
+ applyDisabledState();
392
+ const assignedButton = Object.assign(button, {
376
393
  background,
377
394
  icon,
378
395
  label
379
396
  });
397
+ return Object.defineProperty(assignedButton, "disabled", {
398
+ get() {
399
+ return disabled;
400
+ },
401
+ set(value) {
402
+ disabled = value;
403
+ applyDisabledState();
404
+ },
405
+ enumerable: true,
406
+ configurable: true
407
+ });
380
408
  }
381
409
  // src/theme.ts
382
410
  const PRIMARY = "#4f8cff";
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.cjs","names":[],"sources":["../src/modal.ts","../src/utils.ts","../src/button.ts","../src/theme.ts","../src/plugin.ts"],"sourcesContent":["import type { GameObj, KAPLAYCtx } from 'kaplay';\n\nimport { createButton } from './button';\nimport type { ModalOpt, Theme } from './types';\nimport { getLast, isModal, setPaused, toColor, uniqueTag } from './utils';\n\n/** Tag applied to modal game objects. */\nexport const MODAL_TAG = uniqueTag('modal');\n\ntype SizedGameObj = GameObj & {\n height: number;\n width: number;\n};\n\nexport interface ModalHooks {\n isTopmost: (modal: GameObj) => boolean;\n onClose: (modal: GameObj) => void;\n onOpen: (modal: GameObj) => void;\n}\n\nfunction isSized(obj: GameObj): obj is SizedGameObj {\n const sized = obj as Partial<SizedGameObj>;\n return typeof sized.width === 'number' && typeof sized.height === 'number';\n}\n\nexport function createModal(\n k: KAPLAYCtx,\n theme: Theme,\n opt: ModalOpt,\n hooks: ModalHooks,\n) {\n const {\n backdropOpacity,\n borderRadius,\n gap,\n maxWidth,\n minWidth,\n paddingX,\n paddingY,\n } = theme.components.modal;\n const fontSize = theme.typography.fontSize;\n const titleSize = Math.round(fontSize * 1.25);\n const font = theme.typography.fontFamily;\n const dismissible = opt.dismissible ?? true;\n\n const modal = k.add([\n k.pos(k.center()),\n k.fixed(),\n k.z(opt.z ?? 1000),\n MODAL_TAG,\n ]);\n\n modal.onDestroy(() => {\n hooks.onClose(modal);\n });\n\n hooks.onOpen(modal);\n\n const backdrop = modal.add([\n k.rect(k.width(), k.height()),\n k.pos(),\n k.color(0, 0, 0),\n k.opacity(backdropOpacity),\n k.anchor('center'),\n k.area(),\n ]);\n\n const content = modal.add([\n k.rect(0, 0, borderRadius > 0 ? { radius: borderRadius } : undefined),\n k.pos(),\n k.color(toColor(k, theme.palette.surface)),\n k.anchor('center'),\n k.area(),\n ]);\n\n const actions = content.add([k.pos()]);\n\n for (const action of opt.actions ?? []) {\n createButton(k, theme, action.text, {\n parent: actions,\n onClick: () => {\n if (action.close !== false) {\n modal.destroy();\n }\n\n action.onClick?.();\n },\n });\n }\n\n const titleMeasure = opt.title\n ? k.text(opt.title, { size: titleSize, font })\n : undefined;\n const bodyMeasure = opt.text\n ? k.text(opt.text, { size: fontSize, font })\n : undefined;\n const textWrapWidth = Math.max(0, (opt.width ?? maxWidth) - paddingX * 2);\n\n const title = opt.title\n ? content.add([\n k.text(opt.title, {\n size: titleSize,\n font,\n width: textWrapWidth > 0 ? textWrapWidth : undefined,\n align: 'center',\n }),\n k.pos(),\n k.color(toColor(k, theme.palette.text)),\n k.anchor('center'),\n ])\n : undefined;\n\n const body = opt.text\n ? content.add([\n k.text(opt.text, {\n size: fontSize,\n font,\n width: textWrapWidth > 0 ? textWrapWidth : undefined,\n align: 'center',\n }),\n k.pos(),\n k.color(toColor(k, theme.palette.textSecondary)),\n k.anchor('center'),\n ])\n : undefined;\n\n /**\n * Update modal layout:\n *\n * - fits the backdrop to the screen,\n * - sizes the content to its title, text, and actions,\n * - repositions the title, text, and actions.\n */\n function layout() {\n const screenWidth = k.width();\n const screenHeight = k.height();\n\n if (backdrop.width !== screenWidth) {\n backdrop.width = screenWidth;\n }\n\n if (backdrop.height !== screenHeight) {\n backdrop.height = screenHeight;\n }\n\n modal.pos = k.center();\n\n const buttons = actions.children.filter(isSized);\n const actionsWidth =\n buttons.reduce((width, button) => width + button.width, 0) +\n gap * Math.max(0, buttons.length - 1);\n const actionsHeight = Math.max(\n 0,\n ...buttons.map((button) => button.height),\n );\n\n const textContentWidth = Math.max(\n titleMeasure?.width ?? 0,\n bodyMeasure?.width ?? 0,\n );\n const nextWidth =\n opt.width ??\n Math.max(\n Math.min(textContentWidth, maxWidth - paddingX * 2) + paddingX * 2,\n actionsWidth + paddingX * 2,\n minWidth,\n );\n\n const titleHeight = title?.height ?? 0;\n const bodyHeight = body?.height ?? 0;\n const hasTitle = title !== undefined;\n const hasBody = body !== undefined;\n const hasActions = buttons.length > 0;\n const blockCount = Number(hasTitle) + Number(hasBody) + Number(hasActions);\n const contentHeight =\n titleHeight +\n bodyHeight +\n actionsHeight +\n gap * Math.max(0, blockCount - 1);\n const nextHeight = opt.height ?? contentHeight + paddingY * 2;\n\n if (content.width !== nextWidth) {\n content.width = nextWidth;\n }\n\n if (content.height !== nextHeight) {\n content.height = nextHeight;\n }\n\n let y = -nextHeight / 2 + (nextHeight - contentHeight) / 2;\n\n if (title) {\n title.pos = k.vec2(0, y + title.height / 2);\n y += title.height + gap;\n }\n\n if (body) {\n body.pos = k.vec2(0, y + body.height / 2);\n y += body.height + gap;\n }\n\n if (hasActions) {\n let x = -actionsWidth / 2;\n\n for (const button of buttons) {\n button.pos = k.vec2(x + button.width / 2, y + actionsHeight / 2);\n x += button.width + gap;\n }\n }\n }\n\n layout();\n modal.onUpdate(layout);\n\n if (dismissible) {\n // prevent click-through so modal isn't dismissed right after creation\n k.wait(0, () => {\n backdrop.onClick(() => {\n if (!hooks.isTopmost(modal)) {\n return;\n }\n\n if (content.hasPoint(k.mousePos())) {\n return;\n }\n\n modal.destroy();\n });\n });\n\n modal.onKeyPress('escape', () => {\n if (hooks.isTopmost(modal)) {\n modal.destroy();\n }\n });\n }\n\n return Object.assign(modal, {\n actions,\n content,\n });\n}\n\nexport interface ModalManager {\n openModal: ModalHooks['onOpen'];\n closeModal: ModalHooks['onClose'];\n isTopmost: ModalHooks['isTopmost'];\n isInsideModal: (gameObj: GameObj) => boolean;\n}\n\nexport function createModalManager(k: KAPLAYCtx): ModalManager {\n const modals: GameObj[] = [];\n let pausedScene: Map<GameObj, boolean> | undefined;\n let cancelPauseOnAdd: (() => void) | undefined;\n\n const isInsideModal = (gameObj: GameObj) =>\n modals.some((modal) => modal === gameObj || modal.isAncestorOf(gameObj));\n\n function pauseSceneTree(gameObj: GameObj) {\n if (isModal(gameObj) || isInsideModal(gameObj)) {\n return;\n }\n\n pausedScene ??= new Map();\n\n // v8 ignore next\n if (!pausedScene.has(gameObj)) {\n pausedScene.set(gameObj, gameObj.paused);\n }\n\n setPaused(gameObj);\n gameObj.children.forEach(pauseSceneTree);\n }\n\n function openModal(modal: GameObj) {\n if (modals.length > 0) {\n setPaused(getLast(modals), true, true);\n }\n\n modals.push(modal);\n\n if (modals.length === 1) {\n pausedScene = new Map();\n k.getTreeRoot().children.forEach(pauseSceneTree);\n\n const controller = k.onAdd((gameObj) => {\n if (!isModal(gameObj) && !isInsideModal(gameObj)) {\n pauseSceneTree(gameObj);\n }\n });\n\n cancelPauseOnAdd = () => {\n controller.cancel();\n };\n }\n }\n\n function closeModal(modal: GameObj) {\n const index = modals.indexOf(modal);\n\n if (index === -1) {\n return;\n }\n\n const wasTopmost = index === modals.length - 1;\n\n modals.splice(index, 1);\n\n if (!modals.length) {\n cancelPauseOnAdd?.();\n cancelPauseOnAdd = undefined;\n\n // v8 ignore next\n if (pausedScene) {\n for (const [gameObj, paused] of pausedScene) {\n if (gameObj.exists()) {\n setPaused(gameObj, paused);\n }\n }\n\n pausedScene = undefined;\n }\n } else if (wasTopmost) {\n setPaused(getLast(modals), false, true);\n }\n }\n\n const isTopmost = (modal: GameObj) => getLast(modals) === modal;\n\n return { openModal, closeModal, isTopmost, isInsideModal };\n}\n","import type { Color, GameObj, KAPLAYCtx } from 'kaplay';\n\nimport { MODAL_TAG } from './modal';\n\n/**\n * Get the last item in an array.\n *\n * @param items - The items.\n * @returns The last item.\n */\nexport function getLast<T>(items: T[]): T {\n return items[items.length - 1];\n}\n\n/**\n * Check if the game object is a modal.\n *\n * @param gameObj - The game object.\n * @returns Whether game object is a modal.\n */\nexport function isModal(gameObj: GameObj): boolean {\n return gameObj.is(MODAL_TAG);\n}\n\n/**\n * Pause or unpause game object(s).\n *\n * @param gameObj - The root game object.\n * @param isPaused - Whether it should be paused.\n * @param includeTree - Whether to pause all game objects in tree.\n */\nexport function setPaused(\n gameObj: GameObj,\n isPaused = true,\n includeTree = false,\n): void {\n gameObj.paused = isPaused;\n\n if (includeTree) {\n gameObj.children.forEach((childGameObj) => {\n setPaused(childGameObj, isPaused, includeTree);\n });\n }\n}\n\n/**\n * Convert a hex string or color to color.\n *\n * @param k - The Kaplay context.\n * @param color - The hex string or color.\n * @returns The color.\n */\nexport function toColor(k: KAPLAYCtx, color: string | Color): Color {\n return typeof color === 'string' ? k.rgb(color) : color;\n}\n\n/**\n * Generate a unique tag.\n *\n * @param id - The identifier.\n * @returns The tag.\n */\nexport function uniqueTag(id: string): string {\n return [\n 'kaplay-plugin-ui',\n id,\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n crypto?.randomUUID?.(),\n ].join(':');\n}\n","import type { Color, KAPLAYCtx, Vec2 } from 'kaplay';\n\nimport type { ButtonOpt, Theme } from './types';\nimport { toColor } from './utils';\n\nexport function createButton(\n k: KAPLAYCtx,\n theme: Theme,\n text: string,\n opt: ButtonOpt & {\n pos?: Vec2;\n onClick?: () => void;\n color?: string | Color;\n hoverColor?: string | Color;\n pressedColor?: string | Color;\n textColor?: string | Color;\n },\n) {\n const radius = opt.radius ?? theme.shape.borderRadius;\n const { minWidth, paddingX, paddingY } = theme.components.button;\n const baseColor = toColor(k, opt.color ?? opt.fill ?? theme.palette.primary);\n const hoverColor = toColor(k, opt.hoverColor ?? theme.palette.hover);\n const pressedColor = toColor(k, opt.pressedColor ?? theme.palette.pressed);\n const textColor = toColor(k, opt.textColor ?? theme.palette.text);\n const size = opt.size ?? theme.typography.fontSize;\n const font = opt.font ?? theme.typography.fontFamily;\n const pos = opt.pos ?? k.vec2();\n\n const textComp = k.text(text, { size, font });\n const isIconOnly = opt.icon !== undefined && !text.length;\n const hasBackground = opt.background !== undefined;\n const showFill = !hasBackground && !isIconOnly;\n const rectOpt = showFill\n ? radius > 0\n ? { radius }\n : undefined\n : { fill: false };\n\n const components = [\n k.rect(0, 0, rectOpt),\n k.pos(pos),\n k.area(),\n k.scale(),\n k.color(baseColor),\n k.anchor('center'),\n ];\n const button = opt.parent ? opt.parent.add(components) : k.add(components);\n\n const background = opt.background\n ? button.add([k.sprite(opt.background), k.pos(), k.anchor('center')])\n : undefined;\n\n const icon = opt.icon\n ? button.add([k.sprite(opt.icon), k.pos(), k.anchor('center')])\n : undefined;\n\n const label = button.add([\n textComp,\n k.color(textColor as never),\n k.pos(),\n k.anchor('center'),\n ]);\n\n let iconWidth = 0;\n let iconHeight = 0;\n let iconState: 'base' | 'hover' | 'pressed' = 'base';\n let backgroundWidth = 0;\n let backgroundHeight = 0;\n let backgroundMeasured = false;\n let backgroundState: 'base' | 'hover' | 'pressed' = 'base';\n\n /**\n * Update button layout:\n *\n * - caches the base icon/background dimensions,\n * - calculates content width/height,\n * - updates `button.width` / `button.height`,\n * - stretches the background and repositions the icon and label.\n */\n function layout() {\n if (icon && iconState === 'base') {\n iconWidth = icon.width;\n iconHeight = icon.height;\n }\n\n if (\n background &&\n backgroundState === 'base' &&\n !backgroundMeasured &&\n (background.width > 0 || background.height > 0)\n ) {\n backgroundWidth = background.width;\n backgroundHeight = background.height;\n backgroundMeasured = true;\n }\n\n const gap = icon && iconWidth > 0 && label.width > 0 ? theme.spacing : 0;\n const contentWidth = iconWidth + gap + label.width;\n const contentHeight = Math.max(iconHeight, label.height);\n const contentBoxWidth = contentWidth + paddingX * 2;\n const contentBoxHeight = contentHeight + paddingY * 2;\n\n const nextWidth =\n opt.width ??\n (background\n ? Math.max(contentBoxWidth, backgroundWidth)\n : Math.max(contentBoxWidth, minWidth));\n const nextHeight =\n opt.height ??\n (background\n ? Math.max(contentBoxHeight, backgroundHeight)\n : contentBoxHeight);\n\n if (button.width !== nextWidth) {\n button.width = nextWidth;\n }\n\n if (button.height !== nextHeight) {\n button.height = nextHeight;\n }\n\n if (background && backgroundMeasured) {\n background.width = nextWidth;\n background.height = nextHeight;\n background.pos = k.vec2();\n }\n\n if (icon) {\n icon.pos = k.vec2(-contentWidth / 2 + iconWidth / 2, 0);\n }\n\n label.pos = k.vec2(\n -contentWidth / 2 + iconWidth + gap + label.width / 2,\n 0,\n );\n }\n\n layout();\n\n if (\n opt.width === undefined ||\n opt.height === undefined ||\n opt.icon !== undefined ||\n opt.background !== undefined\n ) {\n button.onUpdate(layout);\n }\n\n button.onClick(() => {\n // prevent stray click from click-through\n k.wait(0, () => {\n button.color = toColor(k, pressedColor);\n\n if (icon && opt.pressedIcon) {\n iconState = 'pressed';\n icon.sprite = opt.pressedIcon as string;\n }\n\n if (background && opt.pressedBackground) {\n backgroundState = 'pressed';\n background.sprite = opt.pressedBackground as string;\n }\n\n opt.onClick?.();\n });\n });\n\n button.onHover(() => {\n button.color = toColor(k, hoverColor);\n button.scale = k.vec2(1.05, 1.05);\n k.setCursor('pointer');\n\n if (icon && opt.hoverIcon) {\n iconState = 'hover';\n icon.sprite = opt.hoverIcon as string;\n }\n\n if (background && opt.hoverBackground) {\n backgroundState = 'hover';\n background.sprite = opt.hoverBackground as string;\n }\n });\n\n button.onHoverEnd(() => {\n button.color = baseColor;\n button.scale = k.vec2(1, 1);\n k.setCursor('default');\n\n if (icon && opt.icon) {\n iconState = 'base';\n icon.sprite = opt.icon as string;\n }\n\n if (background && opt.background) {\n backgroundState = 'base';\n background.sprite = opt.background as string;\n }\n\n if (icon || background) {\n layout();\n }\n });\n\n return Object.assign(button, {\n background,\n icon,\n label,\n });\n}\n","import type { DeepPartial, Theme } from './types';\n\nconst PRIMARY = '#4f8cff';\nconst TEXT = '#ffffff';\n\n/** Default theme. */\nconst theme: Theme = {\n palette: {\n primary: PRIMARY,\n secondary: PRIMARY,\n background: '#111827',\n surface: '#1f2937',\n text: TEXT,\n textSecondary: TEXT,\n hover: PRIMARY,\n pressed: PRIMARY,\n disabled: '#6b7280',\n error: '#ef4444',\n },\n typography: {\n fontFamily: 'sans-serif',\n fontSize: 18,\n fontWeight: 400,\n lineHeight: 1.2,\n },\n shape: {\n borderRadius: 0,\n },\n spacing: 8,\n components: {\n button: {\n minWidth: 120,\n paddingX: 12,\n paddingY: 8,\n textTransform: 'none',\n },\n modal: {\n backdropOpacity: 0.5,\n borderRadius: 8,\n gap: 16,\n maxWidth: 480,\n minWidth: 280,\n paddingX: 24,\n paddingY: 20,\n },\n tooltip: {\n background: '#111827',\n textColor: TEXT,\n delay: 300,\n },\n },\n};\n\n/** Resolve a partial theme into a fully resolved theme. */\nexport function resolveTheme(input?: DeepPartial<Theme>): Theme {\n return {\n palette: { ...theme.palette, ...input?.palette },\n typography: { ...theme.typography, ...input?.typography },\n shape: { ...theme.shape, ...input?.shape },\n spacing: input?.spacing ?? theme.spacing,\n components: {\n button: {\n ...theme.components.button,\n ...input?.components?.button,\n },\n modal: {\n ...theme.components.modal,\n ...input?.components?.modal,\n },\n tooltip: {\n ...theme.components.tooltip,\n ...input?.components?.tooltip,\n },\n },\n };\n}\n","import type { KAPLAYCtx } from 'kaplay';\n\nimport { createButton } from './button';\nimport { createModal, createModalManager } from './modal';\nimport { resolveTheme } from './theme';\nimport type {\n AddButton,\n AddModal,\n DeepPartial,\n GetTheme,\n SetTheme,\n Theme,\n} from './types';\n\n/**\n * Kaplay UI plugin.\n *\n * @param k - The Kaplay context.\n * @param theme - The theme config.\n * @returns The plugin methods.\n */\nexport function uiPlugin(k: KAPLAYCtx, theme: DeepPartial<Theme> = {}) {\n let userTheme: DeepPartial<Theme> = theme;\n let currentTheme: Theme = resolveTheme(userTheme);\n\n const getTheme: GetTheme = () => currentTheme;\n\n const setTheme: SetTheme = (next) => {\n userTheme = { ...userTheme, ...next };\n currentTheme = resolveTheme(userTheme);\n };\n\n const addButton: AddButton = (text = '', opt = {}) => {\n return createButton(k, currentTheme, text, opt);\n };\n\n const modalManager = createModalManager(k);\n\n const addModal: AddModal = (opt = {}) => {\n return createModal(k, currentTheme, opt, {\n isTopmost: modalManager.isTopmost,\n onClose: modalManager.closeModal,\n onOpen: modalManager.openModal,\n });\n };\n\n return {\n addButton,\n addModal,\n setTheme,\n getTheme,\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"plugin.cjs","names":[],"sources":["../src/modal.ts","../src/utils.ts","../src/button.ts","../src/theme.ts","../src/plugin.ts"],"sourcesContent":["import type { GameObj, KAPLAYCtx } from 'kaplay';\n\nimport { createButton } from './button';\nimport type { ModalOpt, Theme } from './types';\nimport { getLast, isModal, setPaused, toColor, uniqueTag } from './utils';\n\n/** Tag applied to modal game objects. */\nexport const MODAL_TAG = uniqueTag('modal');\n\ntype SizedGameObj = GameObj & {\n height: number;\n width: number;\n};\n\nexport interface ModalHooks {\n isTopmost: (modal: GameObj) => boolean;\n onClose: (modal: GameObj) => void;\n onOpen: (modal: GameObj) => void;\n}\n\nfunction isSized(obj: GameObj): obj is SizedGameObj {\n const sized = obj as Partial<SizedGameObj>;\n return typeof sized.width === 'number' && typeof sized.height === 'number';\n}\n\nexport function createModal(\n k: KAPLAYCtx,\n theme: Theme,\n opt: ModalOpt,\n hooks: ModalHooks,\n) {\n const {\n backdropOpacity,\n borderRadius,\n gap,\n maxWidth,\n minWidth,\n paddingX,\n paddingY,\n } = theme.components.modal;\n const fontSize = theme.typography.fontSize;\n const titleSize = Math.round(fontSize * 1.25);\n const font = theme.typography.fontFamily;\n const dismissible = opt.dismissible ?? true;\n\n const modal = k.add([\n k.pos(k.center()),\n k.fixed(),\n k.z(opt.z ?? 1000),\n MODAL_TAG,\n ]);\n\n modal.onDestroy(() => {\n hooks.onClose(modal);\n });\n\n hooks.onOpen(modal);\n\n const backdrop = modal.add([\n k.rect(k.width(), k.height()),\n k.pos(),\n k.color(0, 0, 0),\n k.opacity(backdropOpacity),\n k.anchor('center'),\n k.area(),\n ]);\n\n const content = modal.add([\n k.rect(0, 0, borderRadius > 0 ? { radius: borderRadius } : undefined),\n k.pos(),\n k.color(toColor(k, theme.palette.surface)),\n k.anchor('center'),\n k.area(),\n ]);\n\n const actions = content.add([k.pos()]);\n\n for (const action of opt.actions ?? []) {\n createButton(k, theme, action.text, {\n parent: actions,\n onClick: () => {\n if (action.close !== false) {\n modal.destroy();\n }\n\n action.onClick?.();\n },\n });\n }\n\n const titleMeasure = opt.title\n ? k.text(opt.title, { size: titleSize, font })\n : undefined;\n const bodyMeasure = opt.text\n ? k.text(opt.text, { size: fontSize, font })\n : undefined;\n const textWrapWidth = Math.max(0, (opt.width ?? maxWidth) - paddingX * 2);\n\n const title = opt.title\n ? content.add([\n k.text(opt.title, {\n size: titleSize,\n font,\n width: textWrapWidth > 0 ? textWrapWidth : undefined,\n align: 'center',\n }),\n k.pos(),\n k.color(toColor(k, theme.palette.text)),\n k.anchor('center'),\n ])\n : undefined;\n\n const body = opt.text\n ? content.add([\n k.text(opt.text, {\n size: fontSize,\n font,\n width: textWrapWidth > 0 ? textWrapWidth : undefined,\n align: 'center',\n }),\n k.pos(),\n k.color(toColor(k, theme.palette.textSecondary)),\n k.anchor('center'),\n ])\n : undefined;\n\n /**\n * Update modal layout:\n *\n * - fits the backdrop to the screen,\n * - sizes the content to its title, text, and actions,\n * - repositions the title, text, and actions.\n */\n function layout() {\n const screenWidth = k.width();\n const screenHeight = k.height();\n\n if (backdrop.width !== screenWidth) {\n backdrop.width = screenWidth;\n }\n\n if (backdrop.height !== screenHeight) {\n backdrop.height = screenHeight;\n }\n\n modal.pos = k.center();\n\n const buttons = actions.children.filter(isSized);\n const actionsWidth =\n buttons.reduce((width, button) => width + button.width, 0) +\n gap * Math.max(0, buttons.length - 1);\n const actionsHeight = Math.max(\n 0,\n ...buttons.map((button) => button.height),\n );\n\n const textContentWidth = Math.max(\n titleMeasure?.width ?? 0,\n bodyMeasure?.width ?? 0,\n );\n const nextWidth =\n opt.width ??\n Math.max(\n Math.min(textContentWidth, maxWidth - paddingX * 2) + paddingX * 2,\n actionsWidth + paddingX * 2,\n minWidth,\n );\n\n const titleHeight = title?.height ?? 0;\n const bodyHeight = body?.height ?? 0;\n const hasTitle = title !== undefined;\n const hasBody = body !== undefined;\n const hasActions = buttons.length > 0;\n const blockCount = Number(hasTitle) + Number(hasBody) + Number(hasActions);\n const contentHeight =\n titleHeight +\n bodyHeight +\n actionsHeight +\n gap * Math.max(0, blockCount - 1);\n const nextHeight = opt.height ?? contentHeight + paddingY * 2;\n\n if (content.width !== nextWidth) {\n content.width = nextWidth;\n }\n\n if (content.height !== nextHeight) {\n content.height = nextHeight;\n }\n\n let y = -nextHeight / 2 + (nextHeight - contentHeight) / 2;\n\n if (title) {\n title.pos = k.vec2(0, y + title.height / 2);\n y += title.height + gap;\n }\n\n if (body) {\n body.pos = k.vec2(0, y + body.height / 2);\n y += body.height + gap;\n }\n\n if (hasActions) {\n let x = -actionsWidth / 2;\n\n for (const button of buttons) {\n button.pos = k.vec2(x + button.width / 2, y + actionsHeight / 2);\n x += button.width + gap;\n }\n }\n }\n\n layout();\n modal.onUpdate(layout);\n\n if (dismissible) {\n // prevent click-through so modal isn't dismissed right after creation\n k.wait(0, () => {\n backdrop.onClick(() => {\n if (!hooks.isTopmost(modal)) {\n return;\n }\n\n if (content.hasPoint(k.mousePos())) {\n return;\n }\n\n modal.destroy();\n });\n });\n\n modal.onKeyPress('escape', () => {\n if (hooks.isTopmost(modal)) {\n modal.destroy();\n }\n });\n }\n\n return Object.assign(modal, {\n actions,\n content,\n });\n}\n\nexport interface ModalManager {\n openModal: ModalHooks['onOpen'];\n closeModal: ModalHooks['onClose'];\n isTopmost: ModalHooks['isTopmost'];\n isInsideModal: (gameObj: GameObj) => boolean;\n}\n\nexport function createModalManager(k: KAPLAYCtx): ModalManager {\n const modals: GameObj[] = [];\n let pausedScene: Map<GameObj, boolean> | undefined;\n let cancelPauseOnAdd: (() => void) | undefined;\n\n const isInsideModal = (gameObj: GameObj) =>\n modals.some((modal) => modal === gameObj || modal.isAncestorOf(gameObj));\n\n function pauseSceneTree(gameObj: GameObj) {\n if (isModal(gameObj) || isInsideModal(gameObj)) {\n return;\n }\n\n pausedScene ??= new Map();\n\n // v8 ignore if\n if (!pausedScene.has(gameObj)) {\n pausedScene.set(gameObj, gameObj.paused);\n }\n\n setPaused(gameObj);\n gameObj.children.forEach(pauseSceneTree);\n }\n\n function openModal(modal: GameObj) {\n k.setCursor('default');\n\n if (modals.length > 0) {\n setPaused(getLast(modals), true, true);\n }\n\n modals.push(modal);\n\n if (modals.length === 1) {\n pausedScene = new Map();\n k.getTreeRoot().children.forEach(pauseSceneTree);\n\n const controller = k.onAdd((gameObj) => {\n if (!isModal(gameObj) && !isInsideModal(gameObj)) {\n pauseSceneTree(gameObj);\n }\n });\n\n cancelPauseOnAdd = () => {\n controller.cancel();\n };\n }\n }\n\n function closeModal(modal: GameObj) {\n const index = modals.indexOf(modal);\n\n if (index === -1) {\n return;\n }\n\n const wasTopmost = index === modals.length - 1;\n\n modals.splice(index, 1);\n\n if (!modals.length) {\n cancelPauseOnAdd?.();\n cancelPauseOnAdd = undefined;\n\n // v8 ignore if\n if (pausedScene) {\n for (const [gameObj, paused] of pausedScene) {\n if (gameObj.exists()) {\n setPaused(gameObj, paused);\n }\n }\n\n pausedScene = undefined;\n }\n } else if (wasTopmost) {\n setPaused(getLast(modals), false, true);\n }\n }\n\n const isTopmost = (modal: GameObj) => getLast(modals) === modal;\n\n return { openModal, closeModal, isTopmost, isInsideModal };\n}\n","import type { Color, GameObj, KAPLAYCtx } from 'kaplay';\n\nimport { MODAL_TAG } from './modal';\n\n/**\n * Get the last item in an array.\n *\n * @param items - The items.\n * @returns The last item.\n */\nexport function getLast<T>(items: T[]): T {\n return items[items.length - 1];\n}\n\n/**\n * Check if the game object is a modal.\n *\n * @param gameObj - The game object.\n * @returns Whether game object is a modal.\n */\nexport function isModal(gameObj: GameObj): boolean {\n return gameObj.is(MODAL_TAG);\n}\n\n/**\n * Pause or unpause game object(s).\n *\n * @param gameObj - The root game object.\n * @param isPaused - Whether it should be paused.\n * @param includeTree - Whether to pause all game objects in tree.\n */\nexport function setPaused(\n gameObj: GameObj,\n isPaused = true,\n includeTree = false,\n): void {\n gameObj.paused = isPaused;\n\n if (includeTree) {\n gameObj.children.forEach((childGameObj) => {\n setPaused(childGameObj, isPaused, includeTree);\n });\n }\n}\n\n/**\n * Convert a hex string or color to color.\n *\n * @param k - The Kaplay context.\n * @param color - The hex string or color.\n * @returns The color.\n */\nexport function toColor(k: KAPLAYCtx, color: string | Color): Color {\n return typeof color === 'string' ? k.rgb(color) : color;\n}\n\n/**\n * Generate a unique tag.\n *\n * @param id - The identifier.\n * @returns The tag.\n */\nexport function uniqueTag(id: string): string {\n return [\n 'kaplay-plugin-ui',\n id,\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n crypto?.randomUUID?.(),\n ].join(':');\n}\n","import type { Color, KAPLAYCtx, Vec2 } from 'kaplay';\n\nimport type { ButtonOpt, Theme } from './types';\nimport { toColor } from './utils';\n\nexport function createButton(\n k: KAPLAYCtx,\n theme: Theme,\n text: string,\n opt: ButtonOpt & {\n pos?: Vec2;\n onClick?: () => void;\n color?: string | Color;\n hoverColor?: string | Color;\n pressedColor?: string | Color;\n textColor?: string | Color;\n },\n) {\n const radius = opt.radius ?? theme.shape.borderRadius;\n const { minWidth, paddingX, paddingY } = theme.components.button;\n const baseColor = toColor(k, opt.color ?? opt.fill ?? theme.palette.primary);\n let disabled = opt.disabled ?? false;\n const hoverColor = toColor(k, opt.hoverColor ?? theme.palette.hover);\n const pressedColor = toColor(k, opt.pressedColor ?? theme.palette.pressed);\n const textColor = toColor(k, opt.textColor ?? theme.palette.text);\n const size = opt.size ?? theme.typography.fontSize;\n const font = opt.font ?? theme.typography.fontFamily;\n const pos = opt.pos ?? k.vec2();\n\n const textComp = k.text(text, { size, font });\n const isIconOnly = opt.icon !== undefined && !text.length;\n const hasBackground = opt.background !== undefined;\n const showFill = !hasBackground && !isIconOnly;\n const rectOpt = showFill\n ? radius > 0\n ? { radius }\n : undefined\n : { fill: false };\n\n const components = [\n k.rect(0, 0, rectOpt),\n k.pos(pos),\n k.area(),\n k.scale(),\n k.color(disabled ? toColor(k, theme.palette.disabled) : baseColor),\n k.anchor('center'),\n ];\n const button = opt.parent ? opt.parent.add(components) : k.add(components);\n\n const background = opt.background\n ? button.add([k.sprite(opt.background), k.pos(), k.anchor('center')])\n : undefined;\n\n const icon = opt.icon\n ? button.add([k.sprite(opt.icon), k.pos(), k.anchor('center')])\n : undefined;\n\n const label = button.add([\n textComp,\n k.color(textColor as never),\n k.pos(),\n k.anchor('center'),\n ]);\n\n let iconWidth = 0;\n let iconHeight = 0;\n let iconState: 'base' | 'hover' | 'pressed' = 'base';\n let backgroundWidth = 0;\n let backgroundHeight = 0;\n let backgroundMeasured = false;\n let backgroundState: 'base' | 'hover' | 'pressed' = 'base';\n\n /**\n * Update button layout:\n *\n * - caches the base icon/background dimensions,\n * - calculates content width/height,\n * - updates `button.width` / `button.height`,\n * - stretches the background and repositions the icon and label.\n */\n function layout() {\n if (icon && iconState === 'base') {\n iconWidth = icon.width;\n iconHeight = icon.height;\n }\n\n if (\n background &&\n backgroundState === 'base' &&\n !backgroundMeasured &&\n (background.width > 0 || background.height > 0)\n ) {\n backgroundWidth = background.width;\n backgroundHeight = background.height;\n backgroundMeasured = true;\n }\n\n const gap = icon && iconWidth > 0 && label.width > 0 ? theme.spacing : 0;\n const contentWidth = iconWidth + gap + label.width;\n const contentHeight = Math.max(iconHeight, label.height);\n const contentBoxWidth = contentWidth + paddingX * 2;\n const contentBoxHeight = contentHeight + paddingY * 2;\n\n const nextWidth =\n opt.width ??\n (background\n ? Math.max(contentBoxWidth, backgroundWidth)\n : Math.max(contentBoxWidth, minWidth));\n const nextHeight =\n opt.height ??\n (background\n ? Math.max(contentBoxHeight, backgroundHeight)\n : contentBoxHeight);\n\n if (button.width !== nextWidth) {\n button.width = nextWidth;\n }\n\n if (button.height !== nextHeight) {\n button.height = nextHeight;\n }\n\n if (background && backgroundMeasured) {\n background.width = nextWidth;\n background.height = nextHeight;\n background.pos = k.vec2();\n }\n\n if (icon) {\n icon.pos = k.vec2(-contentWidth / 2 + iconWidth / 2, 0);\n }\n\n label.pos = k.vec2(\n -contentWidth / 2 + iconWidth + gap + label.width / 2,\n 0,\n );\n }\n\n layout();\n\n if (\n opt.width === undefined ||\n opt.height === undefined ||\n opt.icon !== undefined ||\n opt.background !== undefined\n ) {\n button.onUpdate(layout);\n }\n\n button.onClick(() => {\n if (disabled) {\n return;\n }\n\n // prevent click-through\n k.wait(0, () => {\n button.color = toColor(k, pressedColor);\n\n if (icon && opt.pressedIcon) {\n iconState = 'pressed';\n icon.sprite = opt.pressedIcon as string;\n }\n\n if (background && opt.pressedBackground) {\n backgroundState = 'pressed';\n background.sprite = opt.pressedBackground as string;\n }\n\n opt.onClick?.();\n\n // v8 ignore if\n if (disabled) {\n k.setCursor('not-allowed');\n } else if (!button.isHovering()) {\n k.setCursor('default');\n }\n });\n });\n\n button.onHover(() => {\n if (disabled) {\n k.setCursor('not-allowed');\n return;\n } else {\n k.setCursor('pointer');\n }\n\n button.color = toColor(k, hoverColor);\n button.scale = k.vec2(1.05, 1.05);\n\n if (icon && opt.hoverIcon) {\n iconState = 'hover';\n icon.sprite = opt.hoverIcon as string;\n }\n\n if (background && opt.hoverBackground) {\n backgroundState = 'hover';\n background.sprite = opt.hoverBackground as string;\n }\n });\n\n button.onHoverEnd(() => {\n k.setCursor('default');\n\n if (disabled) {\n return;\n }\n\n button.color = baseColor;\n button.scale = k.vec2(1, 1);\n\n if (icon && opt.icon) {\n iconState = 'base';\n icon.sprite = opt.icon as string;\n }\n\n if (background && opt.background) {\n backgroundState = 'base';\n background.sprite = opt.background as string;\n }\n\n if (icon || background) {\n layout();\n }\n });\n\n button.onDestroy(() => {\n if (button.isHovering()) {\n k.setCursor('default');\n }\n });\n\n function applyDisabledState() {\n button.color = disabled ? toColor(k, theme.palette.disabled) : baseColor;\n }\n\n applyDisabledState();\n\n const assignedButton = Object.assign(button, {\n background,\n icon,\n label,\n });\n\n const definedButton = Object.defineProperty(assignedButton, 'disabled', {\n get() {\n return disabled;\n },\n set(value: boolean) {\n disabled = value;\n applyDisabledState();\n },\n enumerable: true,\n configurable: true,\n }) as typeof assignedButton & { disabled: boolean };\n\n return definedButton;\n}\n","import type { DeepPartial, Theme } from './types';\n\nconst PRIMARY = '#4f8cff';\nconst TEXT = '#ffffff';\n\n/** Default theme. */\nconst theme: Theme = {\n palette: {\n primary: PRIMARY,\n secondary: PRIMARY,\n background: '#111827',\n surface: '#1f2937',\n text: TEXT,\n textSecondary: TEXT,\n hover: PRIMARY,\n pressed: PRIMARY,\n disabled: '#6b7280',\n error: '#ef4444',\n },\n typography: {\n fontFamily: 'sans-serif',\n fontSize: 18,\n fontWeight: 400,\n lineHeight: 1.2,\n },\n shape: {\n borderRadius: 0,\n },\n spacing: 8,\n components: {\n button: {\n minWidth: 120,\n paddingX: 12,\n paddingY: 8,\n textTransform: 'none',\n },\n modal: {\n backdropOpacity: 0.5,\n borderRadius: 8,\n gap: 16,\n maxWidth: 480,\n minWidth: 280,\n paddingX: 24,\n paddingY: 20,\n },\n tooltip: {\n background: '#111827',\n textColor: TEXT,\n delay: 300,\n },\n },\n};\n\n/** Resolve a partial theme into a fully resolved theme. */\nexport function resolveTheme(input?: DeepPartial<Theme>): Theme {\n return {\n palette: { ...theme.palette, ...input?.palette },\n typography: { ...theme.typography, ...input?.typography },\n shape: { ...theme.shape, ...input?.shape },\n spacing: input?.spacing ?? theme.spacing,\n components: {\n button: {\n ...theme.components.button,\n ...input?.components?.button,\n },\n modal: {\n ...theme.components.modal,\n ...input?.components?.modal,\n },\n tooltip: {\n ...theme.components.tooltip,\n ...input?.components?.tooltip,\n },\n },\n };\n}\n","import type { KAPLAYCtx } from 'kaplay';\n\nimport { createButton } from './button';\nimport { createModal, createModalManager } from './modal';\nimport { resolveTheme } from './theme';\nimport type {\n AddButton,\n AddModal,\n DeepPartial,\n GetTheme,\n SetTheme,\n Theme,\n} from './types';\n\n/**\n * Kaplay UI plugin.\n *\n * @param k - The Kaplay context.\n * @param theme - The theme config.\n * @returns The plugin methods.\n */\nexport function uiPlugin(k: KAPLAYCtx, theme: DeepPartial<Theme> = {}) {\n let userTheme: DeepPartial<Theme> = theme;\n let currentTheme: Theme = resolveTheme(userTheme);\n\n const getTheme: GetTheme = () => currentTheme;\n\n const setTheme: SetTheme = (next) => {\n userTheme = { ...userTheme, ...next };\n currentTheme = resolveTheme(userTheme);\n };\n\n const addButton: AddButton = (text = '', opt = {}) => {\n return createButton(k, currentTheme, text, opt);\n };\n\n const modalManager = createModalManager(k);\n\n const addModal: AddModal = (opt = {}) => {\n return createModal(k, currentTheme, opt, {\n isTopmost: modalManager.isTopmost,\n onClose: modalManager.closeModal,\n onOpen: modalManager.openModal,\n });\n };\n\n return {\n addButton,\n addModal,\n setTheme,\n getTheme,\n };\n}\n"],"mappings":"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"}
package/dist/plugin.d.cts CHANGED
@@ -10,6 +10,8 @@ declare function createButton(k: KAPLAYCtx, theme: Theme, text: string, opt: But
10
10
  background: import("kaplay").GameObj<import("kaplay").PosComp | import("kaplay").AnchorComp | import("kaplay").SpriteComp> | undefined;
11
11
  icon: import("kaplay").GameObj<import("kaplay").PosComp | import("kaplay").AnchorComp | import("kaplay").SpriteComp> | undefined;
12
12
  label: import("kaplay").GameObj<import("kaplay").PosComp | import("kaplay").ColorComp | import("kaplay").AnchorComp | import("kaplay").TextComp>;
13
+ } & {
14
+ disabled: boolean;
13
15
  };
14
16
  interface ModalHooks {
15
17
  isTopmost: (modal: GameObj) => boolean;
@@ -134,6 +136,8 @@ interface ButtonOpt {
134
136
  hoverBackground?: SpriteSource;
135
137
  /** Sprite drawn behind the label when pressed. */
136
138
  pressedBackground?: SpriteSource;
139
+ /** Disable the button. Greys it out and ignores click/hover. */
140
+ disabled?: boolean;
137
141
  }
138
142
  /**
139
143
  * Kaplay UI plugin.
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.d.cts","names":[],"sources":["../src/button.ts","../src/modal.ts","../src/types.ts","../src/plugin.ts"],"mappings":";iBAKgB,aACd,GAAG,WACH,OAAO,OACP,cACA,KAAK;EACH,MAAM;EACN;EACA,iBAAiB;EACjB,sBAAsB;EACtB,wBAAwB;EACxB,qBAAqB;qBACtB,8BAAA,4BAAA,2BAAA,4BAAA,6BAAA,8BAAA,4BAAA;;;;;UCFc;EACf,YAAY,OAAO;EACnB,UAAU,OAAO;EACjB,SAAS,OAAO;;iBAQF,YACd,GAAG,WACH,OAAO,OACP,KAAK,UACL,OAAO,8BAAU,8BAAA,4BAAA,2BAAA,6BAAA;;;;KCxBd,wBAAwB,aAAa,MAAM;;YAGpC;IACR,WAAW;IACX,UAAU;IACV,UAAU;IACV,UAAU;;;KAIF,aACV,eACA,MAAM;EACJ,MAAM;EACN;EACA,iBAAiB;EACjB,sBAAsB;EACtB,wBAAwB;EACxB,qBAAqB;MAEpB,kBAAkB;KAEX,YAAY,MAAM,aAAa,kBAAkB;KAEjD,iBAAiB;KAEjB,YAAY,OAAO,YAAY;;KAG/B,YAAY,QACrB,WAAW,KAAK,EAAE,oBAAoB,YAAY,EAAE,MAAM,EAAE;;UAI9C;EACf;IACE,kBAAkB;IAClB,oBAAoB;IACpB,qBAAqB;IACrB,kBAAkB;IAClB,eAAe;IACf,wBAAwB;IACxB,gBAAgB;IAChB,kBAAkB;IAClB,mBAAmB;IACnB,gBAAgB;;EAElB;IACE;IACA;IACA;IACA;;EAEF;IACE;;EAEF;EACA;IACE;MACE;MACA;MACA;MACA;;IAEF;MACE;MACA;MACA;MACA;MACA;MACA;MACA;;IAEF;MACE,qBAAqB;MACrB,oBAAoB;MACpB;;;;;UAMW;EACf;EACA;EACA;EACA;EACA,UAAU;;EAEV;;EAEA;;;UAIe;EACf;;EAEA;;EAEA;;;UAIe;EACf;EACA;;EAEA,SAAS;EACT;EACA;EACA,gBAAgB;EAChB,qBAAqB;EACrB;EACA,qBAAqB;EACrB,uBAAuB;;EAEvB,OAAO;;EAEP,YAAY;;EAEZ,cAAc;;EAEd,aAAa;;EAEb,kBAAkB;;EAElB,oBAAoB;;;;;;;;;wBChHN,SAAS,GAAG,WAAW,QAAO,YAAY"}
1
+ {"version":3,"file":"plugin.d.cts","names":[],"sources":["../src/button.ts","../src/modal.ts","../src/types.ts","../src/plugin.ts"],"mappings":";iBAKgB,aACd,GAAG,WACH,OAAO,OACP,cACA,KAAK;EACH,MAAM;EACN;EACA,iBAAiB;EACjB,sBAAsB;EACtB,wBAAwB;EACxB,qBAAqB;qBACtB,8BAAA,4BAAA,2BAAA,4BAAA,6BAAA,8BAAA,4BAAA;;;;;EA8OyC;;UChP3B;EACf,YAAY,OAAO;EACnB,UAAU,OAAO;EACjB,SAAS,OAAO;;iBAQF,YACd,GAAG,WACH,OAAO,OACP,KAAK,UACL,OAAO,8BAAU,8BAAA,4BAAA,2BAAA,6BAAA;;;;KCxBd,wBAAwB,aAAa,MAAM;;YAGpC;IACR,WAAW;IACX,UAAU;IACV,UAAU;IACV,UAAU;;;KAIF,aACV,eACA,MAAM;EACJ,MAAM;EACN;EACA,iBAAiB;EACjB,sBAAsB;EACtB,wBAAwB;EACxB,qBAAqB;MAEpB,kBAAkB;KAEX,YAAY,MAAM,aAAa,kBAAkB;KAEjD,iBAAiB;KAEjB,YAAY,OAAO,YAAY;;KAG/B,YAAY,QACrB,WAAW,KAAK,EAAE,oBAAoB,YAAY,EAAE,MAAM,EAAE;;UAI9C;EACf;IACE,kBAAkB;IAClB,oBAAoB;IACpB,qBAAqB;IACrB,kBAAkB;IAClB,eAAe;IACf,wBAAwB;IACxB,gBAAgB;IAChB,kBAAkB;IAClB,mBAAmB;IACnB,gBAAgB;;EAElB;IACE;IACA;IACA;IACA;;EAEF;IACE;;EAEF;EACA;IACE;MACE;MACA;MACA;MACA;;IAEF;MACE;MACA;MACA;MACA;MACA;MACA;MACA;;IAEF;MACE,qBAAqB;MACrB,oBAAoB;MACpB;;;;;UAMW;EACf;EACA;EACA;EACA;EACA,UAAU;;EAEV;;EAEA;;;UAIe;EACf;;EAEA;;EAEA;;;UAIe;EACf;EACA;;EAEA,SAAS;EACT;EACA;EACA,gBAAgB;EAChB,qBAAqB;EACrB;EACA,qBAAqB;EACrB,uBAAuB;;EAEvB,OAAO;;EAEP,YAAY;;EAEZ,cAAc;;EAEd,aAAa;;EAEb,kBAAkB;;EAElB,oBAAoB;;EAEpB;;;;;;;;;wBClHc,SAAS,GAAG,WAAW,QAAO,YAAY"}
package/dist/plugin.d.mts CHANGED
@@ -10,6 +10,8 @@ declare function createButton(k: KAPLAYCtx, theme: Theme, text: string, opt: But
10
10
  background: import("kaplay").GameObj<import("kaplay").PosComp | import("kaplay").AnchorComp | import("kaplay").SpriteComp> | undefined;
11
11
  icon: import("kaplay").GameObj<import("kaplay").PosComp | import("kaplay").AnchorComp | import("kaplay").SpriteComp> | undefined;
12
12
  label: import("kaplay").GameObj<import("kaplay").PosComp | import("kaplay").ColorComp | import("kaplay").AnchorComp | import("kaplay").TextComp>;
13
+ } & {
14
+ disabled: boolean;
13
15
  };
14
16
  interface ModalHooks {
15
17
  isTopmost: (modal: GameObj) => boolean;
@@ -134,6 +136,8 @@ interface ButtonOpt {
134
136
  hoverBackground?: SpriteSource;
135
137
  /** Sprite drawn behind the label when pressed. */
136
138
  pressedBackground?: SpriteSource;
139
+ /** Disable the button. Greys it out and ignores click/hover. */
140
+ disabled?: boolean;
137
141
  }
138
142
  /**
139
143
  * Kaplay UI plugin.
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.d.mts","names":[],"sources":["../src/button.ts","../src/modal.ts","../src/types.ts","../src/plugin.ts"],"mappings":";iBAKgB,aACd,GAAG,WACH,OAAO,OACP,cACA,KAAK;EACH,MAAM;EACN;EACA,iBAAiB;EACjB,sBAAsB;EACtB,wBAAwB;EACxB,qBAAqB;qBACtB,8BAAA,4BAAA,2BAAA,4BAAA,6BAAA,8BAAA,4BAAA;;;;;UCFc;EACf,YAAY,OAAO;EACnB,UAAU,OAAO;EACjB,SAAS,OAAO;;iBAQF,YACd,GAAG,WACH,OAAO,OACP,KAAK,UACL,OAAO,8BAAU,8BAAA,4BAAA,2BAAA,6BAAA;;;;KCxBd,wBAAwB,aAAa,MAAM;;YAGpC;IACR,WAAW;IACX,UAAU;IACV,UAAU;IACV,UAAU;;;KAIF,aACV,eACA,MAAM;EACJ,MAAM;EACN;EACA,iBAAiB;EACjB,sBAAsB;EACtB,wBAAwB;EACxB,qBAAqB;MAEpB,kBAAkB;KAEX,YAAY,MAAM,aAAa,kBAAkB;KAEjD,iBAAiB;KAEjB,YAAY,OAAO,YAAY;;KAG/B,YAAY,QACrB,WAAW,KAAK,EAAE,oBAAoB,YAAY,EAAE,MAAM,EAAE;;UAI9C;EACf;IACE,kBAAkB;IAClB,oBAAoB;IACpB,qBAAqB;IACrB,kBAAkB;IAClB,eAAe;IACf,wBAAwB;IACxB,gBAAgB;IAChB,kBAAkB;IAClB,mBAAmB;IACnB,gBAAgB;;EAElB;IACE;IACA;IACA;IACA;;EAEF;IACE;;EAEF;EACA;IACE;MACE;MACA;MACA;MACA;;IAEF;MACE;MACA;MACA;MACA;MACA;MACA;MACA;;IAEF;MACE,qBAAqB;MACrB,oBAAoB;MACpB;;;;;UAMW;EACf;EACA;EACA;EACA;EACA,UAAU;;EAEV;;EAEA;;;UAIe;EACf;;EAEA;;EAEA;;;UAIe;EACf;EACA;;EAEA,SAAS;EACT;EACA;EACA,gBAAgB;EAChB,qBAAqB;EACrB;EACA,qBAAqB;EACrB,uBAAuB;;EAEvB,OAAO;;EAEP,YAAY;;EAEZ,cAAc;;EAEd,aAAa;;EAEb,kBAAkB;;EAElB,oBAAoB;;;;;;;;;wBChHN,SAAS,GAAG,WAAW,QAAO,YAAY"}
1
+ {"version":3,"file":"plugin.d.mts","names":[],"sources":["../src/button.ts","../src/modal.ts","../src/types.ts","../src/plugin.ts"],"mappings":";iBAKgB,aACd,GAAG,WACH,OAAO,OACP,cACA,KAAK;EACH,MAAM;EACN;EACA,iBAAiB;EACjB,sBAAsB;EACtB,wBAAwB;EACxB,qBAAqB;qBACtB,8BAAA,4BAAA,2BAAA,4BAAA,6BAAA,8BAAA,4BAAA;;;;;EA8OyC;;UChP3B;EACf,YAAY,OAAO;EACnB,UAAU,OAAO;EACjB,SAAS,OAAO;;iBAQF,YACd,GAAG,WACH,OAAO,OACP,KAAK,UACL,OAAO,8BAAU,8BAAA,4BAAA,2BAAA,6BAAA;;;;KCxBd,wBAAwB,aAAa,MAAM;;YAGpC;IACR,WAAW;IACX,UAAU;IACV,UAAU;IACV,UAAU;;;KAIF,aACV,eACA,MAAM;EACJ,MAAM;EACN;EACA,iBAAiB;EACjB,sBAAsB;EACtB,wBAAwB;EACxB,qBAAqB;MAEpB,kBAAkB;KAEX,YAAY,MAAM,aAAa,kBAAkB;KAEjD,iBAAiB;KAEjB,YAAY,OAAO,YAAY;;KAG/B,YAAY,QACrB,WAAW,KAAK,EAAE,oBAAoB,YAAY,EAAE,MAAM,EAAE;;UAI9C;EACf;IACE,kBAAkB;IAClB,oBAAoB;IACpB,qBAAqB;IACrB,kBAAkB;IAClB,eAAe;IACf,wBAAwB;IACxB,gBAAgB;IAChB,kBAAkB;IAClB,mBAAmB;IACnB,gBAAgB;;EAElB;IACE;IACA;IACA;IACA;;EAEF;IACE;;EAEF;EACA;IACE;MACE;MACA;MACA;MACA;;IAEF;MACE;MACA;MACA;MACA;MACA;MACA;MACA;;IAEF;MACE,qBAAqB;MACrB,oBAAoB;MACpB;;;;;UAMW;EACf;EACA;EACA;EACA;EACA,UAAU;;EAEV;;EAEA;;;UAIe;EACf;;EAEA;;EAEA;;;UAIe;EACf;EACA;;EAEA,SAAS;EACT;EACA;EACA,gBAAgB;EAChB,qBAAqB;EACrB;EACA,qBAAqB;EACrB,uBAAuB;;EAEvB,OAAO;;EAEP,YAAY;;EAEZ,cAAc;;EAEd,aAAa;;EAEb,kBAAkB;;EAElB,oBAAoB;;EAEpB;;;;;;;;;wBClHc,SAAS,GAAG,WAAW,QAAO,YAAY"}
package/dist/plugin.mjs CHANGED
@@ -147,12 +147,13 @@ function createModalManager(k) {
147
147
  function pauseSceneTree(gameObj) {
148
148
  if (isModal(gameObj) || isInsideModal(gameObj)) return;
149
149
  pausedScene ??= /* @__PURE__ */ new Map();
150
- // v8 ignore next
150
+ // v8 ignore if
151
151
  if (!pausedScene.has(gameObj)) pausedScene.set(gameObj, gameObj.paused);
152
152
  setPaused(gameObj);
153
153
  gameObj.children.forEach(pauseSceneTree);
154
154
  }
155
155
  function openModal(modal) {
156
+ k.setCursor("default");
156
157
  if (modals.length > 0) setPaused(getLast(modals), true, true);
157
158
  modals.push(modal);
158
159
  if (modals.length === 1) {
@@ -174,7 +175,7 @@ function createModalManager(k) {
174
175
  if (!modals.length) {
175
176
  cancelPauseOnAdd?.();
176
177
  cancelPauseOnAdd = void 0;
177
- // v8 ignore next
178
+ // v8 ignore if
178
179
  if (pausedScene) {
179
180
  for (const [gameObj, paused] of pausedScene) if (gameObj.exists()) setPaused(gameObj, paused);
180
181
  pausedScene = void 0;
@@ -249,6 +250,7 @@ function createButton(k, theme, text, opt) {
249
250
  const radius = opt.radius ?? theme.shape.borderRadius;
250
251
  const { minWidth, paddingX, paddingY } = theme.components.button;
251
252
  const baseColor = toColor(k, opt.color ?? opt.fill ?? theme.palette.primary);
253
+ let disabled = opt.disabled ?? false;
252
254
  const hoverColor = toColor(k, opt.hoverColor ?? theme.palette.hover);
253
255
  const pressedColor = toColor(k, opt.pressedColor ?? theme.palette.pressed);
254
256
  const textColor = toColor(k, opt.textColor ?? theme.palette.text);
@@ -266,7 +268,7 @@ function createButton(k, theme, text, opt) {
266
268
  k.pos(pos),
267
269
  k.area(),
268
270
  k.scale(),
269
- k.color(baseColor),
271
+ k.color(disabled ? toColor(k, theme.palette.disabled) : baseColor),
270
272
  k.anchor("center")
271
273
  ];
272
274
  const button = opt.parent ? opt.parent.add(components) : k.add(components);
@@ -331,6 +333,7 @@ function createButton(k, theme, text, opt) {
331
333
  layout();
332
334
  if (opt.width === void 0 || opt.height === void 0 || opt.icon !== void 0 || opt.background !== void 0) button.onUpdate(layout);
333
335
  button.onClick(() => {
336
+ if (disabled) return;
334
337
  k.wait(0, () => {
335
338
  button.color = toColor(k, pressedColor);
336
339
  if (icon && opt.pressedIcon) {
@@ -342,12 +345,18 @@ function createButton(k, theme, text, opt) {
342
345
  background.sprite = opt.pressedBackground;
343
346
  }
344
347
  opt.onClick?.();
348
+ // v8 ignore if
349
+ if (disabled) k.setCursor("not-allowed");
350
+ else if (!button.isHovering()) k.setCursor("default");
345
351
  });
346
352
  });
347
353
  button.onHover(() => {
354
+ if (disabled) {
355
+ k.setCursor("not-allowed");
356
+ return;
357
+ } else k.setCursor("pointer");
348
358
  button.color = toColor(k, hoverColor);
349
359
  button.scale = k.vec2(1.05, 1.05);
350
- k.setCursor("pointer");
351
360
  if (icon && opt.hoverIcon) {
352
361
  iconState = "hover";
353
362
  icon.sprite = opt.hoverIcon;
@@ -358,9 +367,10 @@ function createButton(k, theme, text, opt) {
358
367
  }
359
368
  });
360
369
  button.onHoverEnd(() => {
370
+ k.setCursor("default");
371
+ if (disabled) return;
361
372
  button.color = baseColor;
362
373
  button.scale = k.vec2(1, 1);
363
- k.setCursor("default");
364
374
  if (icon && opt.icon) {
365
375
  iconState = "base";
366
376
  icon.sprite = opt.icon;
@@ -371,11 +381,29 @@ function createButton(k, theme, text, opt) {
371
381
  }
372
382
  if (icon || background) layout();
373
383
  });
374
- return Object.assign(button, {
384
+ button.onDestroy(() => {
385
+ if (button.isHovering()) k.setCursor("default");
386
+ });
387
+ function applyDisabledState() {
388
+ button.color = disabled ? toColor(k, theme.palette.disabled) : baseColor;
389
+ }
390
+ applyDisabledState();
391
+ const assignedButton = Object.assign(button, {
375
392
  background,
376
393
  icon,
377
394
  label
378
395
  });
396
+ return Object.defineProperty(assignedButton, "disabled", {
397
+ get() {
398
+ return disabled;
399
+ },
400
+ set(value) {
401
+ disabled = value;
402
+ applyDisabledState();
403
+ },
404
+ enumerable: true,
405
+ configurable: true
406
+ });
379
407
  }
380
408
  // src/theme.ts
381
409
  const PRIMARY = "#4f8cff";
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.mjs","names":[],"sources":["../src/modal.ts","../src/utils.ts","../src/button.ts","../src/theme.ts","../src/plugin.ts"],"sourcesContent":["import type { GameObj, KAPLAYCtx } from 'kaplay';\n\nimport { createButton } from './button';\nimport type { ModalOpt, Theme } from './types';\nimport { getLast, isModal, setPaused, toColor, uniqueTag } from './utils';\n\n/** Tag applied to modal game objects. */\nexport const MODAL_TAG = uniqueTag('modal');\n\ntype SizedGameObj = GameObj & {\n height: number;\n width: number;\n};\n\nexport interface ModalHooks {\n isTopmost: (modal: GameObj) => boolean;\n onClose: (modal: GameObj) => void;\n onOpen: (modal: GameObj) => void;\n}\n\nfunction isSized(obj: GameObj): obj is SizedGameObj {\n const sized = obj as Partial<SizedGameObj>;\n return typeof sized.width === 'number' && typeof sized.height === 'number';\n}\n\nexport function createModal(\n k: KAPLAYCtx,\n theme: Theme,\n opt: ModalOpt,\n hooks: ModalHooks,\n) {\n const {\n backdropOpacity,\n borderRadius,\n gap,\n maxWidth,\n minWidth,\n paddingX,\n paddingY,\n } = theme.components.modal;\n const fontSize = theme.typography.fontSize;\n const titleSize = Math.round(fontSize * 1.25);\n const font = theme.typography.fontFamily;\n const dismissible = opt.dismissible ?? true;\n\n const modal = k.add([\n k.pos(k.center()),\n k.fixed(),\n k.z(opt.z ?? 1000),\n MODAL_TAG,\n ]);\n\n modal.onDestroy(() => {\n hooks.onClose(modal);\n });\n\n hooks.onOpen(modal);\n\n const backdrop = modal.add([\n k.rect(k.width(), k.height()),\n k.pos(),\n k.color(0, 0, 0),\n k.opacity(backdropOpacity),\n k.anchor('center'),\n k.area(),\n ]);\n\n const content = modal.add([\n k.rect(0, 0, borderRadius > 0 ? { radius: borderRadius } : undefined),\n k.pos(),\n k.color(toColor(k, theme.palette.surface)),\n k.anchor('center'),\n k.area(),\n ]);\n\n const actions = content.add([k.pos()]);\n\n for (const action of opt.actions ?? []) {\n createButton(k, theme, action.text, {\n parent: actions,\n onClick: () => {\n if (action.close !== false) {\n modal.destroy();\n }\n\n action.onClick?.();\n },\n });\n }\n\n const titleMeasure = opt.title\n ? k.text(opt.title, { size: titleSize, font })\n : undefined;\n const bodyMeasure = opt.text\n ? k.text(opt.text, { size: fontSize, font })\n : undefined;\n const textWrapWidth = Math.max(0, (opt.width ?? maxWidth) - paddingX * 2);\n\n const title = opt.title\n ? content.add([\n k.text(opt.title, {\n size: titleSize,\n font,\n width: textWrapWidth > 0 ? textWrapWidth : undefined,\n align: 'center',\n }),\n k.pos(),\n k.color(toColor(k, theme.palette.text)),\n k.anchor('center'),\n ])\n : undefined;\n\n const body = opt.text\n ? content.add([\n k.text(opt.text, {\n size: fontSize,\n font,\n width: textWrapWidth > 0 ? textWrapWidth : undefined,\n align: 'center',\n }),\n k.pos(),\n k.color(toColor(k, theme.palette.textSecondary)),\n k.anchor('center'),\n ])\n : undefined;\n\n /**\n * Update modal layout:\n *\n * - fits the backdrop to the screen,\n * - sizes the content to its title, text, and actions,\n * - repositions the title, text, and actions.\n */\n function layout() {\n const screenWidth = k.width();\n const screenHeight = k.height();\n\n if (backdrop.width !== screenWidth) {\n backdrop.width = screenWidth;\n }\n\n if (backdrop.height !== screenHeight) {\n backdrop.height = screenHeight;\n }\n\n modal.pos = k.center();\n\n const buttons = actions.children.filter(isSized);\n const actionsWidth =\n buttons.reduce((width, button) => width + button.width, 0) +\n gap * Math.max(0, buttons.length - 1);\n const actionsHeight = Math.max(\n 0,\n ...buttons.map((button) => button.height),\n );\n\n const textContentWidth = Math.max(\n titleMeasure?.width ?? 0,\n bodyMeasure?.width ?? 0,\n );\n const nextWidth =\n opt.width ??\n Math.max(\n Math.min(textContentWidth, maxWidth - paddingX * 2) + paddingX * 2,\n actionsWidth + paddingX * 2,\n minWidth,\n );\n\n const titleHeight = title?.height ?? 0;\n const bodyHeight = body?.height ?? 0;\n const hasTitle = title !== undefined;\n const hasBody = body !== undefined;\n const hasActions = buttons.length > 0;\n const blockCount = Number(hasTitle) + Number(hasBody) + Number(hasActions);\n const contentHeight =\n titleHeight +\n bodyHeight +\n actionsHeight +\n gap * Math.max(0, blockCount - 1);\n const nextHeight = opt.height ?? contentHeight + paddingY * 2;\n\n if (content.width !== nextWidth) {\n content.width = nextWidth;\n }\n\n if (content.height !== nextHeight) {\n content.height = nextHeight;\n }\n\n let y = -nextHeight / 2 + (nextHeight - contentHeight) / 2;\n\n if (title) {\n title.pos = k.vec2(0, y + title.height / 2);\n y += title.height + gap;\n }\n\n if (body) {\n body.pos = k.vec2(0, y + body.height / 2);\n y += body.height + gap;\n }\n\n if (hasActions) {\n let x = -actionsWidth / 2;\n\n for (const button of buttons) {\n button.pos = k.vec2(x + button.width / 2, y + actionsHeight / 2);\n x += button.width + gap;\n }\n }\n }\n\n layout();\n modal.onUpdate(layout);\n\n if (dismissible) {\n // prevent click-through so modal isn't dismissed right after creation\n k.wait(0, () => {\n backdrop.onClick(() => {\n if (!hooks.isTopmost(modal)) {\n return;\n }\n\n if (content.hasPoint(k.mousePos())) {\n return;\n }\n\n modal.destroy();\n });\n });\n\n modal.onKeyPress('escape', () => {\n if (hooks.isTopmost(modal)) {\n modal.destroy();\n }\n });\n }\n\n return Object.assign(modal, {\n actions,\n content,\n });\n}\n\nexport interface ModalManager {\n openModal: ModalHooks['onOpen'];\n closeModal: ModalHooks['onClose'];\n isTopmost: ModalHooks['isTopmost'];\n isInsideModal: (gameObj: GameObj) => boolean;\n}\n\nexport function createModalManager(k: KAPLAYCtx): ModalManager {\n const modals: GameObj[] = [];\n let pausedScene: Map<GameObj, boolean> | undefined;\n let cancelPauseOnAdd: (() => void) | undefined;\n\n const isInsideModal = (gameObj: GameObj) =>\n modals.some((modal) => modal === gameObj || modal.isAncestorOf(gameObj));\n\n function pauseSceneTree(gameObj: GameObj) {\n if (isModal(gameObj) || isInsideModal(gameObj)) {\n return;\n }\n\n pausedScene ??= new Map();\n\n // v8 ignore next\n if (!pausedScene.has(gameObj)) {\n pausedScene.set(gameObj, gameObj.paused);\n }\n\n setPaused(gameObj);\n gameObj.children.forEach(pauseSceneTree);\n }\n\n function openModal(modal: GameObj) {\n if (modals.length > 0) {\n setPaused(getLast(modals), true, true);\n }\n\n modals.push(modal);\n\n if (modals.length === 1) {\n pausedScene = new Map();\n k.getTreeRoot().children.forEach(pauseSceneTree);\n\n const controller = k.onAdd((gameObj) => {\n if (!isModal(gameObj) && !isInsideModal(gameObj)) {\n pauseSceneTree(gameObj);\n }\n });\n\n cancelPauseOnAdd = () => {\n controller.cancel();\n };\n }\n }\n\n function closeModal(modal: GameObj) {\n const index = modals.indexOf(modal);\n\n if (index === -1) {\n return;\n }\n\n const wasTopmost = index === modals.length - 1;\n\n modals.splice(index, 1);\n\n if (!modals.length) {\n cancelPauseOnAdd?.();\n cancelPauseOnAdd = undefined;\n\n // v8 ignore next\n if (pausedScene) {\n for (const [gameObj, paused] of pausedScene) {\n if (gameObj.exists()) {\n setPaused(gameObj, paused);\n }\n }\n\n pausedScene = undefined;\n }\n } else if (wasTopmost) {\n setPaused(getLast(modals), false, true);\n }\n }\n\n const isTopmost = (modal: GameObj) => getLast(modals) === modal;\n\n return { openModal, closeModal, isTopmost, isInsideModal };\n}\n","import type { Color, GameObj, KAPLAYCtx } from 'kaplay';\n\nimport { MODAL_TAG } from './modal';\n\n/**\n * Get the last item in an array.\n *\n * @param items - The items.\n * @returns The last item.\n */\nexport function getLast<T>(items: T[]): T {\n return items[items.length - 1];\n}\n\n/**\n * Check if the game object is a modal.\n *\n * @param gameObj - The game object.\n * @returns Whether game object is a modal.\n */\nexport function isModal(gameObj: GameObj): boolean {\n return gameObj.is(MODAL_TAG);\n}\n\n/**\n * Pause or unpause game object(s).\n *\n * @param gameObj - The root game object.\n * @param isPaused - Whether it should be paused.\n * @param includeTree - Whether to pause all game objects in tree.\n */\nexport function setPaused(\n gameObj: GameObj,\n isPaused = true,\n includeTree = false,\n): void {\n gameObj.paused = isPaused;\n\n if (includeTree) {\n gameObj.children.forEach((childGameObj) => {\n setPaused(childGameObj, isPaused, includeTree);\n });\n }\n}\n\n/**\n * Convert a hex string or color to color.\n *\n * @param k - The Kaplay context.\n * @param color - The hex string or color.\n * @returns The color.\n */\nexport function toColor(k: KAPLAYCtx, color: string | Color): Color {\n return typeof color === 'string' ? k.rgb(color) : color;\n}\n\n/**\n * Generate a unique tag.\n *\n * @param id - The identifier.\n * @returns The tag.\n */\nexport function uniqueTag(id: string): string {\n return [\n 'kaplay-plugin-ui',\n id,\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n crypto?.randomUUID?.(),\n ].join(':');\n}\n","import type { Color, KAPLAYCtx, Vec2 } from 'kaplay';\n\nimport type { ButtonOpt, Theme } from './types';\nimport { toColor } from './utils';\n\nexport function createButton(\n k: KAPLAYCtx,\n theme: Theme,\n text: string,\n opt: ButtonOpt & {\n pos?: Vec2;\n onClick?: () => void;\n color?: string | Color;\n hoverColor?: string | Color;\n pressedColor?: string | Color;\n textColor?: string | Color;\n },\n) {\n const radius = opt.radius ?? theme.shape.borderRadius;\n const { minWidth, paddingX, paddingY } = theme.components.button;\n const baseColor = toColor(k, opt.color ?? opt.fill ?? theme.palette.primary);\n const hoverColor = toColor(k, opt.hoverColor ?? theme.palette.hover);\n const pressedColor = toColor(k, opt.pressedColor ?? theme.palette.pressed);\n const textColor = toColor(k, opt.textColor ?? theme.palette.text);\n const size = opt.size ?? theme.typography.fontSize;\n const font = opt.font ?? theme.typography.fontFamily;\n const pos = opt.pos ?? k.vec2();\n\n const textComp = k.text(text, { size, font });\n const isIconOnly = opt.icon !== undefined && !text.length;\n const hasBackground = opt.background !== undefined;\n const showFill = !hasBackground && !isIconOnly;\n const rectOpt = showFill\n ? radius > 0\n ? { radius }\n : undefined\n : { fill: false };\n\n const components = [\n k.rect(0, 0, rectOpt),\n k.pos(pos),\n k.area(),\n k.scale(),\n k.color(baseColor),\n k.anchor('center'),\n ];\n const button = opt.parent ? opt.parent.add(components) : k.add(components);\n\n const background = opt.background\n ? button.add([k.sprite(opt.background), k.pos(), k.anchor('center')])\n : undefined;\n\n const icon = opt.icon\n ? button.add([k.sprite(opt.icon), k.pos(), k.anchor('center')])\n : undefined;\n\n const label = button.add([\n textComp,\n k.color(textColor as never),\n k.pos(),\n k.anchor('center'),\n ]);\n\n let iconWidth = 0;\n let iconHeight = 0;\n let iconState: 'base' | 'hover' | 'pressed' = 'base';\n let backgroundWidth = 0;\n let backgroundHeight = 0;\n let backgroundMeasured = false;\n let backgroundState: 'base' | 'hover' | 'pressed' = 'base';\n\n /**\n * Update button layout:\n *\n * - caches the base icon/background dimensions,\n * - calculates content width/height,\n * - updates `button.width` / `button.height`,\n * - stretches the background and repositions the icon and label.\n */\n function layout() {\n if (icon && iconState === 'base') {\n iconWidth = icon.width;\n iconHeight = icon.height;\n }\n\n if (\n background &&\n backgroundState === 'base' &&\n !backgroundMeasured &&\n (background.width > 0 || background.height > 0)\n ) {\n backgroundWidth = background.width;\n backgroundHeight = background.height;\n backgroundMeasured = true;\n }\n\n const gap = icon && iconWidth > 0 && label.width > 0 ? theme.spacing : 0;\n const contentWidth = iconWidth + gap + label.width;\n const contentHeight = Math.max(iconHeight, label.height);\n const contentBoxWidth = contentWidth + paddingX * 2;\n const contentBoxHeight = contentHeight + paddingY * 2;\n\n const nextWidth =\n opt.width ??\n (background\n ? Math.max(contentBoxWidth, backgroundWidth)\n : Math.max(contentBoxWidth, minWidth));\n const nextHeight =\n opt.height ??\n (background\n ? Math.max(contentBoxHeight, backgroundHeight)\n : contentBoxHeight);\n\n if (button.width !== nextWidth) {\n button.width = nextWidth;\n }\n\n if (button.height !== nextHeight) {\n button.height = nextHeight;\n }\n\n if (background && backgroundMeasured) {\n background.width = nextWidth;\n background.height = nextHeight;\n background.pos = k.vec2();\n }\n\n if (icon) {\n icon.pos = k.vec2(-contentWidth / 2 + iconWidth / 2, 0);\n }\n\n label.pos = k.vec2(\n -contentWidth / 2 + iconWidth + gap + label.width / 2,\n 0,\n );\n }\n\n layout();\n\n if (\n opt.width === undefined ||\n opt.height === undefined ||\n opt.icon !== undefined ||\n opt.background !== undefined\n ) {\n button.onUpdate(layout);\n }\n\n button.onClick(() => {\n // prevent stray click from click-through\n k.wait(0, () => {\n button.color = toColor(k, pressedColor);\n\n if (icon && opt.pressedIcon) {\n iconState = 'pressed';\n icon.sprite = opt.pressedIcon as string;\n }\n\n if (background && opt.pressedBackground) {\n backgroundState = 'pressed';\n background.sprite = opt.pressedBackground as string;\n }\n\n opt.onClick?.();\n });\n });\n\n button.onHover(() => {\n button.color = toColor(k, hoverColor);\n button.scale = k.vec2(1.05, 1.05);\n k.setCursor('pointer');\n\n if (icon && opt.hoverIcon) {\n iconState = 'hover';\n icon.sprite = opt.hoverIcon as string;\n }\n\n if (background && opt.hoverBackground) {\n backgroundState = 'hover';\n background.sprite = opt.hoverBackground as string;\n }\n });\n\n button.onHoverEnd(() => {\n button.color = baseColor;\n button.scale = k.vec2(1, 1);\n k.setCursor('default');\n\n if (icon && opt.icon) {\n iconState = 'base';\n icon.sprite = opt.icon as string;\n }\n\n if (background && opt.background) {\n backgroundState = 'base';\n background.sprite = opt.background as string;\n }\n\n if (icon || background) {\n layout();\n }\n });\n\n return Object.assign(button, {\n background,\n icon,\n label,\n });\n}\n","import type { DeepPartial, Theme } from './types';\n\nconst PRIMARY = '#4f8cff';\nconst TEXT = '#ffffff';\n\n/** Default theme. */\nconst theme: Theme = {\n palette: {\n primary: PRIMARY,\n secondary: PRIMARY,\n background: '#111827',\n surface: '#1f2937',\n text: TEXT,\n textSecondary: TEXT,\n hover: PRIMARY,\n pressed: PRIMARY,\n disabled: '#6b7280',\n error: '#ef4444',\n },\n typography: {\n fontFamily: 'sans-serif',\n fontSize: 18,\n fontWeight: 400,\n lineHeight: 1.2,\n },\n shape: {\n borderRadius: 0,\n },\n spacing: 8,\n components: {\n button: {\n minWidth: 120,\n paddingX: 12,\n paddingY: 8,\n textTransform: 'none',\n },\n modal: {\n backdropOpacity: 0.5,\n borderRadius: 8,\n gap: 16,\n maxWidth: 480,\n minWidth: 280,\n paddingX: 24,\n paddingY: 20,\n },\n tooltip: {\n background: '#111827',\n textColor: TEXT,\n delay: 300,\n },\n },\n};\n\n/** Resolve a partial theme into a fully resolved theme. */\nexport function resolveTheme(input?: DeepPartial<Theme>): Theme {\n return {\n palette: { ...theme.palette, ...input?.palette },\n typography: { ...theme.typography, ...input?.typography },\n shape: { ...theme.shape, ...input?.shape },\n spacing: input?.spacing ?? theme.spacing,\n components: {\n button: {\n ...theme.components.button,\n ...input?.components?.button,\n },\n modal: {\n ...theme.components.modal,\n ...input?.components?.modal,\n },\n tooltip: {\n ...theme.components.tooltip,\n ...input?.components?.tooltip,\n },\n },\n };\n}\n","import type { KAPLAYCtx } from 'kaplay';\n\nimport { createButton } from './button';\nimport { createModal, createModalManager } from './modal';\nimport { resolveTheme } from './theme';\nimport type {\n AddButton,\n AddModal,\n DeepPartial,\n GetTheme,\n SetTheme,\n Theme,\n} from './types';\n\n/**\n * Kaplay UI plugin.\n *\n * @param k - The Kaplay context.\n * @param theme - The theme config.\n * @returns The plugin methods.\n */\nexport function uiPlugin(k: KAPLAYCtx, theme: DeepPartial<Theme> = {}) {\n let userTheme: DeepPartial<Theme> = theme;\n let currentTheme: Theme = resolveTheme(userTheme);\n\n const getTheme: GetTheme = () => currentTheme;\n\n const setTheme: SetTheme = (next) => {\n userTheme = { ...userTheme, ...next };\n currentTheme = resolveTheme(userTheme);\n };\n\n const addButton: AddButton = (text = '', opt = {}) => {\n return createButton(k, currentTheme, text, opt);\n };\n\n const modalManager = createModalManager(k);\n\n const addModal: AddModal = (opt = {}) => {\n return createModal(k, currentTheme, opt, {\n isTopmost: modalManager.isTopmost,\n onClose: modalManager.closeModal,\n onOpen: modalManager.openModal,\n });\n };\n\n return {\n addButton,\n addModal,\n setTheme,\n getTheme,\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"plugin.mjs","names":[],"sources":["../src/modal.ts","../src/utils.ts","../src/button.ts","../src/theme.ts","../src/plugin.ts"],"sourcesContent":["import type { GameObj, KAPLAYCtx } from 'kaplay';\n\nimport { createButton } from './button';\nimport type { ModalOpt, Theme } from './types';\nimport { getLast, isModal, setPaused, toColor, uniqueTag } from './utils';\n\n/** Tag applied to modal game objects. */\nexport const MODAL_TAG = uniqueTag('modal');\n\ntype SizedGameObj = GameObj & {\n height: number;\n width: number;\n};\n\nexport interface ModalHooks {\n isTopmost: (modal: GameObj) => boolean;\n onClose: (modal: GameObj) => void;\n onOpen: (modal: GameObj) => void;\n}\n\nfunction isSized(obj: GameObj): obj is SizedGameObj {\n const sized = obj as Partial<SizedGameObj>;\n return typeof sized.width === 'number' && typeof sized.height === 'number';\n}\n\nexport function createModal(\n k: KAPLAYCtx,\n theme: Theme,\n opt: ModalOpt,\n hooks: ModalHooks,\n) {\n const {\n backdropOpacity,\n borderRadius,\n gap,\n maxWidth,\n minWidth,\n paddingX,\n paddingY,\n } = theme.components.modal;\n const fontSize = theme.typography.fontSize;\n const titleSize = Math.round(fontSize * 1.25);\n const font = theme.typography.fontFamily;\n const dismissible = opt.dismissible ?? true;\n\n const modal = k.add([\n k.pos(k.center()),\n k.fixed(),\n k.z(opt.z ?? 1000),\n MODAL_TAG,\n ]);\n\n modal.onDestroy(() => {\n hooks.onClose(modal);\n });\n\n hooks.onOpen(modal);\n\n const backdrop = modal.add([\n k.rect(k.width(), k.height()),\n k.pos(),\n k.color(0, 0, 0),\n k.opacity(backdropOpacity),\n k.anchor('center'),\n k.area(),\n ]);\n\n const content = modal.add([\n k.rect(0, 0, borderRadius > 0 ? { radius: borderRadius } : undefined),\n k.pos(),\n k.color(toColor(k, theme.palette.surface)),\n k.anchor('center'),\n k.area(),\n ]);\n\n const actions = content.add([k.pos()]);\n\n for (const action of opt.actions ?? []) {\n createButton(k, theme, action.text, {\n parent: actions,\n onClick: () => {\n if (action.close !== false) {\n modal.destroy();\n }\n\n action.onClick?.();\n },\n });\n }\n\n const titleMeasure = opt.title\n ? k.text(opt.title, { size: titleSize, font })\n : undefined;\n const bodyMeasure = opt.text\n ? k.text(opt.text, { size: fontSize, font })\n : undefined;\n const textWrapWidth = Math.max(0, (opt.width ?? maxWidth) - paddingX * 2);\n\n const title = opt.title\n ? content.add([\n k.text(opt.title, {\n size: titleSize,\n font,\n width: textWrapWidth > 0 ? textWrapWidth : undefined,\n align: 'center',\n }),\n k.pos(),\n k.color(toColor(k, theme.palette.text)),\n k.anchor('center'),\n ])\n : undefined;\n\n const body = opt.text\n ? content.add([\n k.text(opt.text, {\n size: fontSize,\n font,\n width: textWrapWidth > 0 ? textWrapWidth : undefined,\n align: 'center',\n }),\n k.pos(),\n k.color(toColor(k, theme.palette.textSecondary)),\n k.anchor('center'),\n ])\n : undefined;\n\n /**\n * Update modal layout:\n *\n * - fits the backdrop to the screen,\n * - sizes the content to its title, text, and actions,\n * - repositions the title, text, and actions.\n */\n function layout() {\n const screenWidth = k.width();\n const screenHeight = k.height();\n\n if (backdrop.width !== screenWidth) {\n backdrop.width = screenWidth;\n }\n\n if (backdrop.height !== screenHeight) {\n backdrop.height = screenHeight;\n }\n\n modal.pos = k.center();\n\n const buttons = actions.children.filter(isSized);\n const actionsWidth =\n buttons.reduce((width, button) => width + button.width, 0) +\n gap * Math.max(0, buttons.length - 1);\n const actionsHeight = Math.max(\n 0,\n ...buttons.map((button) => button.height),\n );\n\n const textContentWidth = Math.max(\n titleMeasure?.width ?? 0,\n bodyMeasure?.width ?? 0,\n );\n const nextWidth =\n opt.width ??\n Math.max(\n Math.min(textContentWidth, maxWidth - paddingX * 2) + paddingX * 2,\n actionsWidth + paddingX * 2,\n minWidth,\n );\n\n const titleHeight = title?.height ?? 0;\n const bodyHeight = body?.height ?? 0;\n const hasTitle = title !== undefined;\n const hasBody = body !== undefined;\n const hasActions = buttons.length > 0;\n const blockCount = Number(hasTitle) + Number(hasBody) + Number(hasActions);\n const contentHeight =\n titleHeight +\n bodyHeight +\n actionsHeight +\n gap * Math.max(0, blockCount - 1);\n const nextHeight = opt.height ?? contentHeight + paddingY * 2;\n\n if (content.width !== nextWidth) {\n content.width = nextWidth;\n }\n\n if (content.height !== nextHeight) {\n content.height = nextHeight;\n }\n\n let y = -nextHeight / 2 + (nextHeight - contentHeight) / 2;\n\n if (title) {\n title.pos = k.vec2(0, y + title.height / 2);\n y += title.height + gap;\n }\n\n if (body) {\n body.pos = k.vec2(0, y + body.height / 2);\n y += body.height + gap;\n }\n\n if (hasActions) {\n let x = -actionsWidth / 2;\n\n for (const button of buttons) {\n button.pos = k.vec2(x + button.width / 2, y + actionsHeight / 2);\n x += button.width + gap;\n }\n }\n }\n\n layout();\n modal.onUpdate(layout);\n\n if (dismissible) {\n // prevent click-through so modal isn't dismissed right after creation\n k.wait(0, () => {\n backdrop.onClick(() => {\n if (!hooks.isTopmost(modal)) {\n return;\n }\n\n if (content.hasPoint(k.mousePos())) {\n return;\n }\n\n modal.destroy();\n });\n });\n\n modal.onKeyPress('escape', () => {\n if (hooks.isTopmost(modal)) {\n modal.destroy();\n }\n });\n }\n\n return Object.assign(modal, {\n actions,\n content,\n });\n}\n\nexport interface ModalManager {\n openModal: ModalHooks['onOpen'];\n closeModal: ModalHooks['onClose'];\n isTopmost: ModalHooks['isTopmost'];\n isInsideModal: (gameObj: GameObj) => boolean;\n}\n\nexport function createModalManager(k: KAPLAYCtx): ModalManager {\n const modals: GameObj[] = [];\n let pausedScene: Map<GameObj, boolean> | undefined;\n let cancelPauseOnAdd: (() => void) | undefined;\n\n const isInsideModal = (gameObj: GameObj) =>\n modals.some((modal) => modal === gameObj || modal.isAncestorOf(gameObj));\n\n function pauseSceneTree(gameObj: GameObj) {\n if (isModal(gameObj) || isInsideModal(gameObj)) {\n return;\n }\n\n pausedScene ??= new Map();\n\n // v8 ignore if\n if (!pausedScene.has(gameObj)) {\n pausedScene.set(gameObj, gameObj.paused);\n }\n\n setPaused(gameObj);\n gameObj.children.forEach(pauseSceneTree);\n }\n\n function openModal(modal: GameObj) {\n k.setCursor('default');\n\n if (modals.length > 0) {\n setPaused(getLast(modals), true, true);\n }\n\n modals.push(modal);\n\n if (modals.length === 1) {\n pausedScene = new Map();\n k.getTreeRoot().children.forEach(pauseSceneTree);\n\n const controller = k.onAdd((gameObj) => {\n if (!isModal(gameObj) && !isInsideModal(gameObj)) {\n pauseSceneTree(gameObj);\n }\n });\n\n cancelPauseOnAdd = () => {\n controller.cancel();\n };\n }\n }\n\n function closeModal(modal: GameObj) {\n const index = modals.indexOf(modal);\n\n if (index === -1) {\n return;\n }\n\n const wasTopmost = index === modals.length - 1;\n\n modals.splice(index, 1);\n\n if (!modals.length) {\n cancelPauseOnAdd?.();\n cancelPauseOnAdd = undefined;\n\n // v8 ignore if\n if (pausedScene) {\n for (const [gameObj, paused] of pausedScene) {\n if (gameObj.exists()) {\n setPaused(gameObj, paused);\n }\n }\n\n pausedScene = undefined;\n }\n } else if (wasTopmost) {\n setPaused(getLast(modals), false, true);\n }\n }\n\n const isTopmost = (modal: GameObj) => getLast(modals) === modal;\n\n return { openModal, closeModal, isTopmost, isInsideModal };\n}\n","import type { Color, GameObj, KAPLAYCtx } from 'kaplay';\n\nimport { MODAL_TAG } from './modal';\n\n/**\n * Get the last item in an array.\n *\n * @param items - The items.\n * @returns The last item.\n */\nexport function getLast<T>(items: T[]): T {\n return items[items.length - 1];\n}\n\n/**\n * Check if the game object is a modal.\n *\n * @param gameObj - The game object.\n * @returns Whether game object is a modal.\n */\nexport function isModal(gameObj: GameObj): boolean {\n return gameObj.is(MODAL_TAG);\n}\n\n/**\n * Pause or unpause game object(s).\n *\n * @param gameObj - The root game object.\n * @param isPaused - Whether it should be paused.\n * @param includeTree - Whether to pause all game objects in tree.\n */\nexport function setPaused(\n gameObj: GameObj,\n isPaused = true,\n includeTree = false,\n): void {\n gameObj.paused = isPaused;\n\n if (includeTree) {\n gameObj.children.forEach((childGameObj) => {\n setPaused(childGameObj, isPaused, includeTree);\n });\n }\n}\n\n/**\n * Convert a hex string or color to color.\n *\n * @param k - The Kaplay context.\n * @param color - The hex string or color.\n * @returns The color.\n */\nexport function toColor(k: KAPLAYCtx, color: string | Color): Color {\n return typeof color === 'string' ? k.rgb(color) : color;\n}\n\n/**\n * Generate a unique tag.\n *\n * @param id - The identifier.\n * @returns The tag.\n */\nexport function uniqueTag(id: string): string {\n return [\n 'kaplay-plugin-ui',\n id,\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n crypto?.randomUUID?.(),\n ].join(':');\n}\n","import type { Color, KAPLAYCtx, Vec2 } from 'kaplay';\n\nimport type { ButtonOpt, Theme } from './types';\nimport { toColor } from './utils';\n\nexport function createButton(\n k: KAPLAYCtx,\n theme: Theme,\n text: string,\n opt: ButtonOpt & {\n pos?: Vec2;\n onClick?: () => void;\n color?: string | Color;\n hoverColor?: string | Color;\n pressedColor?: string | Color;\n textColor?: string | Color;\n },\n) {\n const radius = opt.radius ?? theme.shape.borderRadius;\n const { minWidth, paddingX, paddingY } = theme.components.button;\n const baseColor = toColor(k, opt.color ?? opt.fill ?? theme.palette.primary);\n let disabled = opt.disabled ?? false;\n const hoverColor = toColor(k, opt.hoverColor ?? theme.palette.hover);\n const pressedColor = toColor(k, opt.pressedColor ?? theme.palette.pressed);\n const textColor = toColor(k, opt.textColor ?? theme.palette.text);\n const size = opt.size ?? theme.typography.fontSize;\n const font = opt.font ?? theme.typography.fontFamily;\n const pos = opt.pos ?? k.vec2();\n\n const textComp = k.text(text, { size, font });\n const isIconOnly = opt.icon !== undefined && !text.length;\n const hasBackground = opt.background !== undefined;\n const showFill = !hasBackground && !isIconOnly;\n const rectOpt = showFill\n ? radius > 0\n ? { radius }\n : undefined\n : { fill: false };\n\n const components = [\n k.rect(0, 0, rectOpt),\n k.pos(pos),\n k.area(),\n k.scale(),\n k.color(disabled ? toColor(k, theme.palette.disabled) : baseColor),\n k.anchor('center'),\n ];\n const button = opt.parent ? opt.parent.add(components) : k.add(components);\n\n const background = opt.background\n ? button.add([k.sprite(opt.background), k.pos(), k.anchor('center')])\n : undefined;\n\n const icon = opt.icon\n ? button.add([k.sprite(opt.icon), k.pos(), k.anchor('center')])\n : undefined;\n\n const label = button.add([\n textComp,\n k.color(textColor as never),\n k.pos(),\n k.anchor('center'),\n ]);\n\n let iconWidth = 0;\n let iconHeight = 0;\n let iconState: 'base' | 'hover' | 'pressed' = 'base';\n let backgroundWidth = 0;\n let backgroundHeight = 0;\n let backgroundMeasured = false;\n let backgroundState: 'base' | 'hover' | 'pressed' = 'base';\n\n /**\n * Update button layout:\n *\n * - caches the base icon/background dimensions,\n * - calculates content width/height,\n * - updates `button.width` / `button.height`,\n * - stretches the background and repositions the icon and label.\n */\n function layout() {\n if (icon && iconState === 'base') {\n iconWidth = icon.width;\n iconHeight = icon.height;\n }\n\n if (\n background &&\n backgroundState === 'base' &&\n !backgroundMeasured &&\n (background.width > 0 || background.height > 0)\n ) {\n backgroundWidth = background.width;\n backgroundHeight = background.height;\n backgroundMeasured = true;\n }\n\n const gap = icon && iconWidth > 0 && label.width > 0 ? theme.spacing : 0;\n const contentWidth = iconWidth + gap + label.width;\n const contentHeight = Math.max(iconHeight, label.height);\n const contentBoxWidth = contentWidth + paddingX * 2;\n const contentBoxHeight = contentHeight + paddingY * 2;\n\n const nextWidth =\n opt.width ??\n (background\n ? Math.max(contentBoxWidth, backgroundWidth)\n : Math.max(contentBoxWidth, minWidth));\n const nextHeight =\n opt.height ??\n (background\n ? Math.max(contentBoxHeight, backgroundHeight)\n : contentBoxHeight);\n\n if (button.width !== nextWidth) {\n button.width = nextWidth;\n }\n\n if (button.height !== nextHeight) {\n button.height = nextHeight;\n }\n\n if (background && backgroundMeasured) {\n background.width = nextWidth;\n background.height = nextHeight;\n background.pos = k.vec2();\n }\n\n if (icon) {\n icon.pos = k.vec2(-contentWidth / 2 + iconWidth / 2, 0);\n }\n\n label.pos = k.vec2(\n -contentWidth / 2 + iconWidth + gap + label.width / 2,\n 0,\n );\n }\n\n layout();\n\n if (\n opt.width === undefined ||\n opt.height === undefined ||\n opt.icon !== undefined ||\n opt.background !== undefined\n ) {\n button.onUpdate(layout);\n }\n\n button.onClick(() => {\n if (disabled) {\n return;\n }\n\n // prevent click-through\n k.wait(0, () => {\n button.color = toColor(k, pressedColor);\n\n if (icon && opt.pressedIcon) {\n iconState = 'pressed';\n icon.sprite = opt.pressedIcon as string;\n }\n\n if (background && opt.pressedBackground) {\n backgroundState = 'pressed';\n background.sprite = opt.pressedBackground as string;\n }\n\n opt.onClick?.();\n\n // v8 ignore if\n if (disabled) {\n k.setCursor('not-allowed');\n } else if (!button.isHovering()) {\n k.setCursor('default');\n }\n });\n });\n\n button.onHover(() => {\n if (disabled) {\n k.setCursor('not-allowed');\n return;\n } else {\n k.setCursor('pointer');\n }\n\n button.color = toColor(k, hoverColor);\n button.scale = k.vec2(1.05, 1.05);\n\n if (icon && opt.hoverIcon) {\n iconState = 'hover';\n icon.sprite = opt.hoverIcon as string;\n }\n\n if (background && opt.hoverBackground) {\n backgroundState = 'hover';\n background.sprite = opt.hoverBackground as string;\n }\n });\n\n button.onHoverEnd(() => {\n k.setCursor('default');\n\n if (disabled) {\n return;\n }\n\n button.color = baseColor;\n button.scale = k.vec2(1, 1);\n\n if (icon && opt.icon) {\n iconState = 'base';\n icon.sprite = opt.icon as string;\n }\n\n if (background && opt.background) {\n backgroundState = 'base';\n background.sprite = opt.background as string;\n }\n\n if (icon || background) {\n layout();\n }\n });\n\n button.onDestroy(() => {\n if (button.isHovering()) {\n k.setCursor('default');\n }\n });\n\n function applyDisabledState() {\n button.color = disabled ? toColor(k, theme.palette.disabled) : baseColor;\n }\n\n applyDisabledState();\n\n const assignedButton = Object.assign(button, {\n background,\n icon,\n label,\n });\n\n const definedButton = Object.defineProperty(assignedButton, 'disabled', {\n get() {\n return disabled;\n },\n set(value: boolean) {\n disabled = value;\n applyDisabledState();\n },\n enumerable: true,\n configurable: true,\n }) as typeof assignedButton & { disabled: boolean };\n\n return definedButton;\n}\n","import type { DeepPartial, Theme } from './types';\n\nconst PRIMARY = '#4f8cff';\nconst TEXT = '#ffffff';\n\n/** Default theme. */\nconst theme: Theme = {\n palette: {\n primary: PRIMARY,\n secondary: PRIMARY,\n background: '#111827',\n surface: '#1f2937',\n text: TEXT,\n textSecondary: TEXT,\n hover: PRIMARY,\n pressed: PRIMARY,\n disabled: '#6b7280',\n error: '#ef4444',\n },\n typography: {\n fontFamily: 'sans-serif',\n fontSize: 18,\n fontWeight: 400,\n lineHeight: 1.2,\n },\n shape: {\n borderRadius: 0,\n },\n spacing: 8,\n components: {\n button: {\n minWidth: 120,\n paddingX: 12,\n paddingY: 8,\n textTransform: 'none',\n },\n modal: {\n backdropOpacity: 0.5,\n borderRadius: 8,\n gap: 16,\n maxWidth: 480,\n minWidth: 280,\n paddingX: 24,\n paddingY: 20,\n },\n tooltip: {\n background: '#111827',\n textColor: TEXT,\n delay: 300,\n },\n },\n};\n\n/** Resolve a partial theme into a fully resolved theme. */\nexport function resolveTheme(input?: DeepPartial<Theme>): Theme {\n return {\n palette: { ...theme.palette, ...input?.palette },\n typography: { ...theme.typography, ...input?.typography },\n shape: { ...theme.shape, ...input?.shape },\n spacing: input?.spacing ?? theme.spacing,\n components: {\n button: {\n ...theme.components.button,\n ...input?.components?.button,\n },\n modal: {\n ...theme.components.modal,\n ...input?.components?.modal,\n },\n tooltip: {\n ...theme.components.tooltip,\n ...input?.components?.tooltip,\n },\n },\n };\n}\n","import type { KAPLAYCtx } from 'kaplay';\n\nimport { createButton } from './button';\nimport { createModal, createModalManager } from './modal';\nimport { resolveTheme } from './theme';\nimport type {\n AddButton,\n AddModal,\n DeepPartial,\n GetTheme,\n SetTheme,\n Theme,\n} from './types';\n\n/**\n * Kaplay UI plugin.\n *\n * @param k - The Kaplay context.\n * @param theme - The theme config.\n * @returns The plugin methods.\n */\nexport function uiPlugin(k: KAPLAYCtx, theme: DeepPartial<Theme> = {}) {\n let userTheme: DeepPartial<Theme> = theme;\n let currentTheme: Theme = resolveTheme(userTheme);\n\n const getTheme: GetTheme = () => currentTheme;\n\n const setTheme: SetTheme = (next) => {\n userTheme = { ...userTheme, ...next };\n currentTheme = resolveTheme(userTheme);\n };\n\n const addButton: AddButton = (text = '', opt = {}) => {\n return createButton(k, currentTheme, text, opt);\n };\n\n const modalManager = createModalManager(k);\n\n const addModal: AddModal = (opt = {}) => {\n return createModal(k, currentTheme, opt, {\n isTopmost: modalManager.isTopmost,\n onClose: modalManager.closeModal,\n onOpen: modalManager.openModal,\n });\n };\n\n return {\n addButton,\n addModal,\n setTheme,\n getTheme,\n };\n}\n"],"mappings":"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"}
@@ -151,12 +151,13 @@
151
151
  function pauseSceneTree(gameObj) {
152
152
  if (isModal(gameObj) || isInsideModal(gameObj)) return;
153
153
  pausedScene ??= /* @__PURE__ */ new Map();
154
- // v8 ignore next
154
+ // v8 ignore if
155
155
  if (!pausedScene.has(gameObj)) pausedScene.set(gameObj, gameObj.paused);
156
156
  setPaused(gameObj);
157
157
  gameObj.children.forEach(pauseSceneTree);
158
158
  }
159
159
  function openModal(modal) {
160
+ k.setCursor("default");
160
161
  if (modals.length > 0) setPaused(getLast(modals), true, true);
161
162
  modals.push(modal);
162
163
  if (modals.length === 1) {
@@ -178,7 +179,7 @@
178
179
  if (!modals.length) {
179
180
  cancelPauseOnAdd?.();
180
181
  cancelPauseOnAdd = void 0;
181
- // v8 ignore next
182
+ // v8 ignore if
182
183
  if (pausedScene) {
183
184
  for (const [gameObj, paused] of pausedScene) if (gameObj.exists()) setPaused(gameObj, paused);
184
185
  pausedScene = void 0;
@@ -253,6 +254,7 @@
253
254
  const radius = opt.radius ?? theme.shape.borderRadius;
254
255
  const { minWidth, paddingX, paddingY } = theme.components.button;
255
256
  const baseColor = toColor(k, opt.color ?? opt.fill ?? theme.palette.primary);
257
+ let disabled = opt.disabled ?? false;
256
258
  const hoverColor = toColor(k, opt.hoverColor ?? theme.palette.hover);
257
259
  const pressedColor = toColor(k, opt.pressedColor ?? theme.palette.pressed);
258
260
  const textColor = toColor(k, opt.textColor ?? theme.palette.text);
@@ -270,7 +272,7 @@
270
272
  k.pos(pos),
271
273
  k.area(),
272
274
  k.scale(),
273
- k.color(baseColor),
275
+ k.color(disabled ? toColor(k, theme.palette.disabled) : baseColor),
274
276
  k.anchor("center")
275
277
  ];
276
278
  const button = opt.parent ? opt.parent.add(components) : k.add(components);
@@ -335,6 +337,7 @@
335
337
  layout();
336
338
  if (opt.width === void 0 || opt.height === void 0 || opt.icon !== void 0 || opt.background !== void 0) button.onUpdate(layout);
337
339
  button.onClick(() => {
340
+ if (disabled) return;
338
341
  k.wait(0, () => {
339
342
  button.color = toColor(k, pressedColor);
340
343
  if (icon && opt.pressedIcon) {
@@ -346,12 +349,18 @@
346
349
  background.sprite = opt.pressedBackground;
347
350
  }
348
351
  opt.onClick?.();
352
+ // v8 ignore if
353
+ if (disabled) k.setCursor("not-allowed");
354
+ else if (!button.isHovering()) k.setCursor("default");
349
355
  });
350
356
  });
351
357
  button.onHover(() => {
358
+ if (disabled) {
359
+ k.setCursor("not-allowed");
360
+ return;
361
+ } else k.setCursor("pointer");
352
362
  button.color = toColor(k, hoverColor);
353
363
  button.scale = k.vec2(1.05, 1.05);
354
- k.setCursor("pointer");
355
364
  if (icon && opt.hoverIcon) {
356
365
  iconState = "hover";
357
366
  icon.sprite = opt.hoverIcon;
@@ -362,9 +371,10 @@
362
371
  }
363
372
  });
364
373
  button.onHoverEnd(() => {
374
+ k.setCursor("default");
375
+ if (disabled) return;
365
376
  button.color = baseColor;
366
377
  button.scale = k.vec2(1, 1);
367
- k.setCursor("default");
368
378
  if (icon && opt.icon) {
369
379
  iconState = "base";
370
380
  icon.sprite = opt.icon;
@@ -375,11 +385,29 @@
375
385
  }
376
386
  if (icon || background) layout();
377
387
  });
378
- return Object.assign(button, {
388
+ button.onDestroy(() => {
389
+ if (button.isHovering()) k.setCursor("default");
390
+ });
391
+ function applyDisabledState() {
392
+ button.color = disabled ? toColor(k, theme.palette.disabled) : baseColor;
393
+ }
394
+ applyDisabledState();
395
+ const assignedButton = Object.assign(button, {
379
396
  background,
380
397
  icon,
381
398
  label
382
399
  });
400
+ return Object.defineProperty(assignedButton, "disabled", {
401
+ get() {
402
+ return disabled;
403
+ },
404
+ set(value) {
405
+ disabled = value;
406
+ applyDisabledState();
407
+ },
408
+ enumerable: true,
409
+ configurable: true
410
+ });
383
411
  }
384
412
  // src/theme.ts
385
413
  const PRIMARY = "#4f8cff";
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.umd.js","names":[],"sources":["../src/modal.ts","../src/utils.ts","../src/button.ts","../src/theme.ts","../src/plugin.ts"],"sourcesContent":["import type { GameObj, KAPLAYCtx } from 'kaplay';\n\nimport { createButton } from './button';\nimport type { ModalOpt, Theme } from './types';\nimport { getLast, isModal, setPaused, toColor, uniqueTag } from './utils';\n\n/** Tag applied to modal game objects. */\nexport const MODAL_TAG = uniqueTag('modal');\n\ntype SizedGameObj = GameObj & {\n height: number;\n width: number;\n};\n\nexport interface ModalHooks {\n isTopmost: (modal: GameObj) => boolean;\n onClose: (modal: GameObj) => void;\n onOpen: (modal: GameObj) => void;\n}\n\nfunction isSized(obj: GameObj): obj is SizedGameObj {\n const sized = obj as Partial<SizedGameObj>;\n return typeof sized.width === 'number' && typeof sized.height === 'number';\n}\n\nexport function createModal(\n k: KAPLAYCtx,\n theme: Theme,\n opt: ModalOpt,\n hooks: ModalHooks,\n) {\n const {\n backdropOpacity,\n borderRadius,\n gap,\n maxWidth,\n minWidth,\n paddingX,\n paddingY,\n } = theme.components.modal;\n const fontSize = theme.typography.fontSize;\n const titleSize = Math.round(fontSize * 1.25);\n const font = theme.typography.fontFamily;\n const dismissible = opt.dismissible ?? true;\n\n const modal = k.add([\n k.pos(k.center()),\n k.fixed(),\n k.z(opt.z ?? 1000),\n MODAL_TAG,\n ]);\n\n modal.onDestroy(() => {\n hooks.onClose(modal);\n });\n\n hooks.onOpen(modal);\n\n const backdrop = modal.add([\n k.rect(k.width(), k.height()),\n k.pos(),\n k.color(0, 0, 0),\n k.opacity(backdropOpacity),\n k.anchor('center'),\n k.area(),\n ]);\n\n const content = modal.add([\n k.rect(0, 0, borderRadius > 0 ? { radius: borderRadius } : undefined),\n k.pos(),\n k.color(toColor(k, theme.palette.surface)),\n k.anchor('center'),\n k.area(),\n ]);\n\n const actions = content.add([k.pos()]);\n\n for (const action of opt.actions ?? []) {\n createButton(k, theme, action.text, {\n parent: actions,\n onClick: () => {\n if (action.close !== false) {\n modal.destroy();\n }\n\n action.onClick?.();\n },\n });\n }\n\n const titleMeasure = opt.title\n ? k.text(opt.title, { size: titleSize, font })\n : undefined;\n const bodyMeasure = opt.text\n ? k.text(opt.text, { size: fontSize, font })\n : undefined;\n const textWrapWidth = Math.max(0, (opt.width ?? maxWidth) - paddingX * 2);\n\n const title = opt.title\n ? content.add([\n k.text(opt.title, {\n size: titleSize,\n font,\n width: textWrapWidth > 0 ? textWrapWidth : undefined,\n align: 'center',\n }),\n k.pos(),\n k.color(toColor(k, theme.palette.text)),\n k.anchor('center'),\n ])\n : undefined;\n\n const body = opt.text\n ? content.add([\n k.text(opt.text, {\n size: fontSize,\n font,\n width: textWrapWidth > 0 ? textWrapWidth : undefined,\n align: 'center',\n }),\n k.pos(),\n k.color(toColor(k, theme.palette.textSecondary)),\n k.anchor('center'),\n ])\n : undefined;\n\n /**\n * Update modal layout:\n *\n * - fits the backdrop to the screen,\n * - sizes the content to its title, text, and actions,\n * - repositions the title, text, and actions.\n */\n function layout() {\n const screenWidth = k.width();\n const screenHeight = k.height();\n\n if (backdrop.width !== screenWidth) {\n backdrop.width = screenWidth;\n }\n\n if (backdrop.height !== screenHeight) {\n backdrop.height = screenHeight;\n }\n\n modal.pos = k.center();\n\n const buttons = actions.children.filter(isSized);\n const actionsWidth =\n buttons.reduce((width, button) => width + button.width, 0) +\n gap * Math.max(0, buttons.length - 1);\n const actionsHeight = Math.max(\n 0,\n ...buttons.map((button) => button.height),\n );\n\n const textContentWidth = Math.max(\n titleMeasure?.width ?? 0,\n bodyMeasure?.width ?? 0,\n );\n const nextWidth =\n opt.width ??\n Math.max(\n Math.min(textContentWidth, maxWidth - paddingX * 2) + paddingX * 2,\n actionsWidth + paddingX * 2,\n minWidth,\n );\n\n const titleHeight = title?.height ?? 0;\n const bodyHeight = body?.height ?? 0;\n const hasTitle = title !== undefined;\n const hasBody = body !== undefined;\n const hasActions = buttons.length > 0;\n const blockCount = Number(hasTitle) + Number(hasBody) + Number(hasActions);\n const contentHeight =\n titleHeight +\n bodyHeight +\n actionsHeight +\n gap * Math.max(0, blockCount - 1);\n const nextHeight = opt.height ?? contentHeight + paddingY * 2;\n\n if (content.width !== nextWidth) {\n content.width = nextWidth;\n }\n\n if (content.height !== nextHeight) {\n content.height = nextHeight;\n }\n\n let y = -nextHeight / 2 + (nextHeight - contentHeight) / 2;\n\n if (title) {\n title.pos = k.vec2(0, y + title.height / 2);\n y += title.height + gap;\n }\n\n if (body) {\n body.pos = k.vec2(0, y + body.height / 2);\n y += body.height + gap;\n }\n\n if (hasActions) {\n let x = -actionsWidth / 2;\n\n for (const button of buttons) {\n button.pos = k.vec2(x + button.width / 2, y + actionsHeight / 2);\n x += button.width + gap;\n }\n }\n }\n\n layout();\n modal.onUpdate(layout);\n\n if (dismissible) {\n // prevent click-through so modal isn't dismissed right after creation\n k.wait(0, () => {\n backdrop.onClick(() => {\n if (!hooks.isTopmost(modal)) {\n return;\n }\n\n if (content.hasPoint(k.mousePos())) {\n return;\n }\n\n modal.destroy();\n });\n });\n\n modal.onKeyPress('escape', () => {\n if (hooks.isTopmost(modal)) {\n modal.destroy();\n }\n });\n }\n\n return Object.assign(modal, {\n actions,\n content,\n });\n}\n\nexport interface ModalManager {\n openModal: ModalHooks['onOpen'];\n closeModal: ModalHooks['onClose'];\n isTopmost: ModalHooks['isTopmost'];\n isInsideModal: (gameObj: GameObj) => boolean;\n}\n\nexport function createModalManager(k: KAPLAYCtx): ModalManager {\n const modals: GameObj[] = [];\n let pausedScene: Map<GameObj, boolean> | undefined;\n let cancelPauseOnAdd: (() => void) | undefined;\n\n const isInsideModal = (gameObj: GameObj) =>\n modals.some((modal) => modal === gameObj || modal.isAncestorOf(gameObj));\n\n function pauseSceneTree(gameObj: GameObj) {\n if (isModal(gameObj) || isInsideModal(gameObj)) {\n return;\n }\n\n pausedScene ??= new Map();\n\n // v8 ignore next\n if (!pausedScene.has(gameObj)) {\n pausedScene.set(gameObj, gameObj.paused);\n }\n\n setPaused(gameObj);\n gameObj.children.forEach(pauseSceneTree);\n }\n\n function openModal(modal: GameObj) {\n if (modals.length > 0) {\n setPaused(getLast(modals), true, true);\n }\n\n modals.push(modal);\n\n if (modals.length === 1) {\n pausedScene = new Map();\n k.getTreeRoot().children.forEach(pauseSceneTree);\n\n const controller = k.onAdd((gameObj) => {\n if (!isModal(gameObj) && !isInsideModal(gameObj)) {\n pauseSceneTree(gameObj);\n }\n });\n\n cancelPauseOnAdd = () => {\n controller.cancel();\n };\n }\n }\n\n function closeModal(modal: GameObj) {\n const index = modals.indexOf(modal);\n\n if (index === -1) {\n return;\n }\n\n const wasTopmost = index === modals.length - 1;\n\n modals.splice(index, 1);\n\n if (!modals.length) {\n cancelPauseOnAdd?.();\n cancelPauseOnAdd = undefined;\n\n // v8 ignore next\n if (pausedScene) {\n for (const [gameObj, paused] of pausedScene) {\n if (gameObj.exists()) {\n setPaused(gameObj, paused);\n }\n }\n\n pausedScene = undefined;\n }\n } else if (wasTopmost) {\n setPaused(getLast(modals), false, true);\n }\n }\n\n const isTopmost = (modal: GameObj) => getLast(modals) === modal;\n\n return { openModal, closeModal, isTopmost, isInsideModal };\n}\n","import type { Color, GameObj, KAPLAYCtx } from 'kaplay';\n\nimport { MODAL_TAG } from './modal';\n\n/**\n * Get the last item in an array.\n *\n * @param items - The items.\n * @returns The last item.\n */\nexport function getLast<T>(items: T[]): T {\n return items[items.length - 1];\n}\n\n/**\n * Check if the game object is a modal.\n *\n * @param gameObj - The game object.\n * @returns Whether game object is a modal.\n */\nexport function isModal(gameObj: GameObj): boolean {\n return gameObj.is(MODAL_TAG);\n}\n\n/**\n * Pause or unpause game object(s).\n *\n * @param gameObj - The root game object.\n * @param isPaused - Whether it should be paused.\n * @param includeTree - Whether to pause all game objects in tree.\n */\nexport function setPaused(\n gameObj: GameObj,\n isPaused = true,\n includeTree = false,\n): void {\n gameObj.paused = isPaused;\n\n if (includeTree) {\n gameObj.children.forEach((childGameObj) => {\n setPaused(childGameObj, isPaused, includeTree);\n });\n }\n}\n\n/**\n * Convert a hex string or color to color.\n *\n * @param k - The Kaplay context.\n * @param color - The hex string or color.\n * @returns The color.\n */\nexport function toColor(k: KAPLAYCtx, color: string | Color): Color {\n return typeof color === 'string' ? k.rgb(color) : color;\n}\n\n/**\n * Generate a unique tag.\n *\n * @param id - The identifier.\n * @returns The tag.\n */\nexport function uniqueTag(id: string): string {\n return [\n 'kaplay-plugin-ui',\n id,\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n crypto?.randomUUID?.(),\n ].join(':');\n}\n","import type { Color, KAPLAYCtx, Vec2 } from 'kaplay';\n\nimport type { ButtonOpt, Theme } from './types';\nimport { toColor } from './utils';\n\nexport function createButton(\n k: KAPLAYCtx,\n theme: Theme,\n text: string,\n opt: ButtonOpt & {\n pos?: Vec2;\n onClick?: () => void;\n color?: string | Color;\n hoverColor?: string | Color;\n pressedColor?: string | Color;\n textColor?: string | Color;\n },\n) {\n const radius = opt.radius ?? theme.shape.borderRadius;\n const { minWidth, paddingX, paddingY } = theme.components.button;\n const baseColor = toColor(k, opt.color ?? opt.fill ?? theme.palette.primary);\n const hoverColor = toColor(k, opt.hoverColor ?? theme.palette.hover);\n const pressedColor = toColor(k, opt.pressedColor ?? theme.palette.pressed);\n const textColor = toColor(k, opt.textColor ?? theme.palette.text);\n const size = opt.size ?? theme.typography.fontSize;\n const font = opt.font ?? theme.typography.fontFamily;\n const pos = opt.pos ?? k.vec2();\n\n const textComp = k.text(text, { size, font });\n const isIconOnly = opt.icon !== undefined && !text.length;\n const hasBackground = opt.background !== undefined;\n const showFill = !hasBackground && !isIconOnly;\n const rectOpt = showFill\n ? radius > 0\n ? { radius }\n : undefined\n : { fill: false };\n\n const components = [\n k.rect(0, 0, rectOpt),\n k.pos(pos),\n k.area(),\n k.scale(),\n k.color(baseColor),\n k.anchor('center'),\n ];\n const button = opt.parent ? opt.parent.add(components) : k.add(components);\n\n const background = opt.background\n ? button.add([k.sprite(opt.background), k.pos(), k.anchor('center')])\n : undefined;\n\n const icon = opt.icon\n ? button.add([k.sprite(opt.icon), k.pos(), k.anchor('center')])\n : undefined;\n\n const label = button.add([\n textComp,\n k.color(textColor as never),\n k.pos(),\n k.anchor('center'),\n ]);\n\n let iconWidth = 0;\n let iconHeight = 0;\n let iconState: 'base' | 'hover' | 'pressed' = 'base';\n let backgroundWidth = 0;\n let backgroundHeight = 0;\n let backgroundMeasured = false;\n let backgroundState: 'base' | 'hover' | 'pressed' = 'base';\n\n /**\n * Update button layout:\n *\n * - caches the base icon/background dimensions,\n * - calculates content width/height,\n * - updates `button.width` / `button.height`,\n * - stretches the background and repositions the icon and label.\n */\n function layout() {\n if (icon && iconState === 'base') {\n iconWidth = icon.width;\n iconHeight = icon.height;\n }\n\n if (\n background &&\n backgroundState === 'base' &&\n !backgroundMeasured &&\n (background.width > 0 || background.height > 0)\n ) {\n backgroundWidth = background.width;\n backgroundHeight = background.height;\n backgroundMeasured = true;\n }\n\n const gap = icon && iconWidth > 0 && label.width > 0 ? theme.spacing : 0;\n const contentWidth = iconWidth + gap + label.width;\n const contentHeight = Math.max(iconHeight, label.height);\n const contentBoxWidth = contentWidth + paddingX * 2;\n const contentBoxHeight = contentHeight + paddingY * 2;\n\n const nextWidth =\n opt.width ??\n (background\n ? Math.max(contentBoxWidth, backgroundWidth)\n : Math.max(contentBoxWidth, minWidth));\n const nextHeight =\n opt.height ??\n (background\n ? Math.max(contentBoxHeight, backgroundHeight)\n : contentBoxHeight);\n\n if (button.width !== nextWidth) {\n button.width = nextWidth;\n }\n\n if (button.height !== nextHeight) {\n button.height = nextHeight;\n }\n\n if (background && backgroundMeasured) {\n background.width = nextWidth;\n background.height = nextHeight;\n background.pos = k.vec2();\n }\n\n if (icon) {\n icon.pos = k.vec2(-contentWidth / 2 + iconWidth / 2, 0);\n }\n\n label.pos = k.vec2(\n -contentWidth / 2 + iconWidth + gap + label.width / 2,\n 0,\n );\n }\n\n layout();\n\n if (\n opt.width === undefined ||\n opt.height === undefined ||\n opt.icon !== undefined ||\n opt.background !== undefined\n ) {\n button.onUpdate(layout);\n }\n\n button.onClick(() => {\n // prevent stray click from click-through\n k.wait(0, () => {\n button.color = toColor(k, pressedColor);\n\n if (icon && opt.pressedIcon) {\n iconState = 'pressed';\n icon.sprite = opt.pressedIcon as string;\n }\n\n if (background && opt.pressedBackground) {\n backgroundState = 'pressed';\n background.sprite = opt.pressedBackground as string;\n }\n\n opt.onClick?.();\n });\n });\n\n button.onHover(() => {\n button.color = toColor(k, hoverColor);\n button.scale = k.vec2(1.05, 1.05);\n k.setCursor('pointer');\n\n if (icon && opt.hoverIcon) {\n iconState = 'hover';\n icon.sprite = opt.hoverIcon as string;\n }\n\n if (background && opt.hoverBackground) {\n backgroundState = 'hover';\n background.sprite = opt.hoverBackground as string;\n }\n });\n\n button.onHoverEnd(() => {\n button.color = baseColor;\n button.scale = k.vec2(1, 1);\n k.setCursor('default');\n\n if (icon && opt.icon) {\n iconState = 'base';\n icon.sprite = opt.icon as string;\n }\n\n if (background && opt.background) {\n backgroundState = 'base';\n background.sprite = opt.background as string;\n }\n\n if (icon || background) {\n layout();\n }\n });\n\n return Object.assign(button, {\n background,\n icon,\n label,\n });\n}\n","import type { DeepPartial, Theme } from './types';\n\nconst PRIMARY = '#4f8cff';\nconst TEXT = '#ffffff';\n\n/** Default theme. */\nconst theme: Theme = {\n palette: {\n primary: PRIMARY,\n secondary: PRIMARY,\n background: '#111827',\n surface: '#1f2937',\n text: TEXT,\n textSecondary: TEXT,\n hover: PRIMARY,\n pressed: PRIMARY,\n disabled: '#6b7280',\n error: '#ef4444',\n },\n typography: {\n fontFamily: 'sans-serif',\n fontSize: 18,\n fontWeight: 400,\n lineHeight: 1.2,\n },\n shape: {\n borderRadius: 0,\n },\n spacing: 8,\n components: {\n button: {\n minWidth: 120,\n paddingX: 12,\n paddingY: 8,\n textTransform: 'none',\n },\n modal: {\n backdropOpacity: 0.5,\n borderRadius: 8,\n gap: 16,\n maxWidth: 480,\n minWidth: 280,\n paddingX: 24,\n paddingY: 20,\n },\n tooltip: {\n background: '#111827',\n textColor: TEXT,\n delay: 300,\n },\n },\n};\n\n/** Resolve a partial theme into a fully resolved theme. */\nexport function resolveTheme(input?: DeepPartial<Theme>): Theme {\n return {\n palette: { ...theme.palette, ...input?.palette },\n typography: { ...theme.typography, ...input?.typography },\n shape: { ...theme.shape, ...input?.shape },\n spacing: input?.spacing ?? theme.spacing,\n components: {\n button: {\n ...theme.components.button,\n ...input?.components?.button,\n },\n modal: {\n ...theme.components.modal,\n ...input?.components?.modal,\n },\n tooltip: {\n ...theme.components.tooltip,\n ...input?.components?.tooltip,\n },\n },\n };\n}\n","import type { KAPLAYCtx } from 'kaplay';\n\nimport { createButton } from './button';\nimport { createModal, createModalManager } from './modal';\nimport { resolveTheme } from './theme';\nimport type {\n AddButton,\n AddModal,\n DeepPartial,\n GetTheme,\n SetTheme,\n Theme,\n} from './types';\n\n/**\n * Kaplay UI plugin.\n *\n * @param k - The Kaplay context.\n * @param theme - The theme config.\n * @returns The plugin methods.\n */\nexport function uiPlugin(k: KAPLAYCtx, theme: DeepPartial<Theme> = {}) {\n let userTheme: DeepPartial<Theme> = theme;\n let currentTheme: Theme = resolveTheme(userTheme);\n\n const getTheme: GetTheme = () => currentTheme;\n\n const setTheme: SetTheme = (next) => {\n userTheme = { ...userTheme, ...next };\n currentTheme = resolveTheme(userTheme);\n };\n\n const addButton: AddButton = (text = '', opt = {}) => {\n return createButton(k, currentTheme, text, opt);\n };\n\n const modalManager = createModalManager(k);\n\n const addModal: AddModal = (opt = {}) => {\n return createModal(k, currentTheme, opt, {\n isTopmost: modalManager.isTopmost,\n onClose: modalManager.closeModal,\n onOpen: modalManager.openModal,\n });\n };\n\n return {\n addButton,\n addModal,\n setTheme,\n getTheme,\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"plugin.umd.js","names":[],"sources":["../src/modal.ts","../src/utils.ts","../src/button.ts","../src/theme.ts","../src/plugin.ts"],"sourcesContent":["import type { GameObj, KAPLAYCtx } from 'kaplay';\n\nimport { createButton } from './button';\nimport type { ModalOpt, Theme } from './types';\nimport { getLast, isModal, setPaused, toColor, uniqueTag } from './utils';\n\n/** Tag applied to modal game objects. */\nexport const MODAL_TAG = uniqueTag('modal');\n\ntype SizedGameObj = GameObj & {\n height: number;\n width: number;\n};\n\nexport interface ModalHooks {\n isTopmost: (modal: GameObj) => boolean;\n onClose: (modal: GameObj) => void;\n onOpen: (modal: GameObj) => void;\n}\n\nfunction isSized(obj: GameObj): obj is SizedGameObj {\n const sized = obj as Partial<SizedGameObj>;\n return typeof sized.width === 'number' && typeof sized.height === 'number';\n}\n\nexport function createModal(\n k: KAPLAYCtx,\n theme: Theme,\n opt: ModalOpt,\n hooks: ModalHooks,\n) {\n const {\n backdropOpacity,\n borderRadius,\n gap,\n maxWidth,\n minWidth,\n paddingX,\n paddingY,\n } = theme.components.modal;\n const fontSize = theme.typography.fontSize;\n const titleSize = Math.round(fontSize * 1.25);\n const font = theme.typography.fontFamily;\n const dismissible = opt.dismissible ?? true;\n\n const modal = k.add([\n k.pos(k.center()),\n k.fixed(),\n k.z(opt.z ?? 1000),\n MODAL_TAG,\n ]);\n\n modal.onDestroy(() => {\n hooks.onClose(modal);\n });\n\n hooks.onOpen(modal);\n\n const backdrop = modal.add([\n k.rect(k.width(), k.height()),\n k.pos(),\n k.color(0, 0, 0),\n k.opacity(backdropOpacity),\n k.anchor('center'),\n k.area(),\n ]);\n\n const content = modal.add([\n k.rect(0, 0, borderRadius > 0 ? { radius: borderRadius } : undefined),\n k.pos(),\n k.color(toColor(k, theme.palette.surface)),\n k.anchor('center'),\n k.area(),\n ]);\n\n const actions = content.add([k.pos()]);\n\n for (const action of opt.actions ?? []) {\n createButton(k, theme, action.text, {\n parent: actions,\n onClick: () => {\n if (action.close !== false) {\n modal.destroy();\n }\n\n action.onClick?.();\n },\n });\n }\n\n const titleMeasure = opt.title\n ? k.text(opt.title, { size: titleSize, font })\n : undefined;\n const bodyMeasure = opt.text\n ? k.text(opt.text, { size: fontSize, font })\n : undefined;\n const textWrapWidth = Math.max(0, (opt.width ?? maxWidth) - paddingX * 2);\n\n const title = opt.title\n ? content.add([\n k.text(opt.title, {\n size: titleSize,\n font,\n width: textWrapWidth > 0 ? textWrapWidth : undefined,\n align: 'center',\n }),\n k.pos(),\n k.color(toColor(k, theme.palette.text)),\n k.anchor('center'),\n ])\n : undefined;\n\n const body = opt.text\n ? content.add([\n k.text(opt.text, {\n size: fontSize,\n font,\n width: textWrapWidth > 0 ? textWrapWidth : undefined,\n align: 'center',\n }),\n k.pos(),\n k.color(toColor(k, theme.palette.textSecondary)),\n k.anchor('center'),\n ])\n : undefined;\n\n /**\n * Update modal layout:\n *\n * - fits the backdrop to the screen,\n * - sizes the content to its title, text, and actions,\n * - repositions the title, text, and actions.\n */\n function layout() {\n const screenWidth = k.width();\n const screenHeight = k.height();\n\n if (backdrop.width !== screenWidth) {\n backdrop.width = screenWidth;\n }\n\n if (backdrop.height !== screenHeight) {\n backdrop.height = screenHeight;\n }\n\n modal.pos = k.center();\n\n const buttons = actions.children.filter(isSized);\n const actionsWidth =\n buttons.reduce((width, button) => width + button.width, 0) +\n gap * Math.max(0, buttons.length - 1);\n const actionsHeight = Math.max(\n 0,\n ...buttons.map((button) => button.height),\n );\n\n const textContentWidth = Math.max(\n titleMeasure?.width ?? 0,\n bodyMeasure?.width ?? 0,\n );\n const nextWidth =\n opt.width ??\n Math.max(\n Math.min(textContentWidth, maxWidth - paddingX * 2) + paddingX * 2,\n actionsWidth + paddingX * 2,\n minWidth,\n );\n\n const titleHeight = title?.height ?? 0;\n const bodyHeight = body?.height ?? 0;\n const hasTitle = title !== undefined;\n const hasBody = body !== undefined;\n const hasActions = buttons.length > 0;\n const blockCount = Number(hasTitle) + Number(hasBody) + Number(hasActions);\n const contentHeight =\n titleHeight +\n bodyHeight +\n actionsHeight +\n gap * Math.max(0, blockCount - 1);\n const nextHeight = opt.height ?? contentHeight + paddingY * 2;\n\n if (content.width !== nextWidth) {\n content.width = nextWidth;\n }\n\n if (content.height !== nextHeight) {\n content.height = nextHeight;\n }\n\n let y = -nextHeight / 2 + (nextHeight - contentHeight) / 2;\n\n if (title) {\n title.pos = k.vec2(0, y + title.height / 2);\n y += title.height + gap;\n }\n\n if (body) {\n body.pos = k.vec2(0, y + body.height / 2);\n y += body.height + gap;\n }\n\n if (hasActions) {\n let x = -actionsWidth / 2;\n\n for (const button of buttons) {\n button.pos = k.vec2(x + button.width / 2, y + actionsHeight / 2);\n x += button.width + gap;\n }\n }\n }\n\n layout();\n modal.onUpdate(layout);\n\n if (dismissible) {\n // prevent click-through so modal isn't dismissed right after creation\n k.wait(0, () => {\n backdrop.onClick(() => {\n if (!hooks.isTopmost(modal)) {\n return;\n }\n\n if (content.hasPoint(k.mousePos())) {\n return;\n }\n\n modal.destroy();\n });\n });\n\n modal.onKeyPress('escape', () => {\n if (hooks.isTopmost(modal)) {\n modal.destroy();\n }\n });\n }\n\n return Object.assign(modal, {\n actions,\n content,\n });\n}\n\nexport interface ModalManager {\n openModal: ModalHooks['onOpen'];\n closeModal: ModalHooks['onClose'];\n isTopmost: ModalHooks['isTopmost'];\n isInsideModal: (gameObj: GameObj) => boolean;\n}\n\nexport function createModalManager(k: KAPLAYCtx): ModalManager {\n const modals: GameObj[] = [];\n let pausedScene: Map<GameObj, boolean> | undefined;\n let cancelPauseOnAdd: (() => void) | undefined;\n\n const isInsideModal = (gameObj: GameObj) =>\n modals.some((modal) => modal === gameObj || modal.isAncestorOf(gameObj));\n\n function pauseSceneTree(gameObj: GameObj) {\n if (isModal(gameObj) || isInsideModal(gameObj)) {\n return;\n }\n\n pausedScene ??= new Map();\n\n // v8 ignore if\n if (!pausedScene.has(gameObj)) {\n pausedScene.set(gameObj, gameObj.paused);\n }\n\n setPaused(gameObj);\n gameObj.children.forEach(pauseSceneTree);\n }\n\n function openModal(modal: GameObj) {\n k.setCursor('default');\n\n if (modals.length > 0) {\n setPaused(getLast(modals), true, true);\n }\n\n modals.push(modal);\n\n if (modals.length === 1) {\n pausedScene = new Map();\n k.getTreeRoot().children.forEach(pauseSceneTree);\n\n const controller = k.onAdd((gameObj) => {\n if (!isModal(gameObj) && !isInsideModal(gameObj)) {\n pauseSceneTree(gameObj);\n }\n });\n\n cancelPauseOnAdd = () => {\n controller.cancel();\n };\n }\n }\n\n function closeModal(modal: GameObj) {\n const index = modals.indexOf(modal);\n\n if (index === -1) {\n return;\n }\n\n const wasTopmost = index === modals.length - 1;\n\n modals.splice(index, 1);\n\n if (!modals.length) {\n cancelPauseOnAdd?.();\n cancelPauseOnAdd = undefined;\n\n // v8 ignore if\n if (pausedScene) {\n for (const [gameObj, paused] of pausedScene) {\n if (gameObj.exists()) {\n setPaused(gameObj, paused);\n }\n }\n\n pausedScene = undefined;\n }\n } else if (wasTopmost) {\n setPaused(getLast(modals), false, true);\n }\n }\n\n const isTopmost = (modal: GameObj) => getLast(modals) === modal;\n\n return { openModal, closeModal, isTopmost, isInsideModal };\n}\n","import type { Color, GameObj, KAPLAYCtx } from 'kaplay';\n\nimport { MODAL_TAG } from './modal';\n\n/**\n * Get the last item in an array.\n *\n * @param items - The items.\n * @returns The last item.\n */\nexport function getLast<T>(items: T[]): T {\n return items[items.length - 1];\n}\n\n/**\n * Check if the game object is a modal.\n *\n * @param gameObj - The game object.\n * @returns Whether game object is a modal.\n */\nexport function isModal(gameObj: GameObj): boolean {\n return gameObj.is(MODAL_TAG);\n}\n\n/**\n * Pause or unpause game object(s).\n *\n * @param gameObj - The root game object.\n * @param isPaused - Whether it should be paused.\n * @param includeTree - Whether to pause all game objects in tree.\n */\nexport function setPaused(\n gameObj: GameObj,\n isPaused = true,\n includeTree = false,\n): void {\n gameObj.paused = isPaused;\n\n if (includeTree) {\n gameObj.children.forEach((childGameObj) => {\n setPaused(childGameObj, isPaused, includeTree);\n });\n }\n}\n\n/**\n * Convert a hex string or color to color.\n *\n * @param k - The Kaplay context.\n * @param color - The hex string or color.\n * @returns The color.\n */\nexport function toColor(k: KAPLAYCtx, color: string | Color): Color {\n return typeof color === 'string' ? k.rgb(color) : color;\n}\n\n/**\n * Generate a unique tag.\n *\n * @param id - The identifier.\n * @returns The tag.\n */\nexport function uniqueTag(id: string): string {\n return [\n 'kaplay-plugin-ui',\n id,\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n crypto?.randomUUID?.(),\n ].join(':');\n}\n","import type { Color, KAPLAYCtx, Vec2 } from 'kaplay';\n\nimport type { ButtonOpt, Theme } from './types';\nimport { toColor } from './utils';\n\nexport function createButton(\n k: KAPLAYCtx,\n theme: Theme,\n text: string,\n opt: ButtonOpt & {\n pos?: Vec2;\n onClick?: () => void;\n color?: string | Color;\n hoverColor?: string | Color;\n pressedColor?: string | Color;\n textColor?: string | Color;\n },\n) {\n const radius = opt.radius ?? theme.shape.borderRadius;\n const { minWidth, paddingX, paddingY } = theme.components.button;\n const baseColor = toColor(k, opt.color ?? opt.fill ?? theme.palette.primary);\n let disabled = opt.disabled ?? false;\n const hoverColor = toColor(k, opt.hoverColor ?? theme.palette.hover);\n const pressedColor = toColor(k, opt.pressedColor ?? theme.palette.pressed);\n const textColor = toColor(k, opt.textColor ?? theme.palette.text);\n const size = opt.size ?? theme.typography.fontSize;\n const font = opt.font ?? theme.typography.fontFamily;\n const pos = opt.pos ?? k.vec2();\n\n const textComp = k.text(text, { size, font });\n const isIconOnly = opt.icon !== undefined && !text.length;\n const hasBackground = opt.background !== undefined;\n const showFill = !hasBackground && !isIconOnly;\n const rectOpt = showFill\n ? radius > 0\n ? { radius }\n : undefined\n : { fill: false };\n\n const components = [\n k.rect(0, 0, rectOpt),\n k.pos(pos),\n k.area(),\n k.scale(),\n k.color(disabled ? toColor(k, theme.palette.disabled) : baseColor),\n k.anchor('center'),\n ];\n const button = opt.parent ? opt.parent.add(components) : k.add(components);\n\n const background = opt.background\n ? button.add([k.sprite(opt.background), k.pos(), k.anchor('center')])\n : undefined;\n\n const icon = opt.icon\n ? button.add([k.sprite(opt.icon), k.pos(), k.anchor('center')])\n : undefined;\n\n const label = button.add([\n textComp,\n k.color(textColor as never),\n k.pos(),\n k.anchor('center'),\n ]);\n\n let iconWidth = 0;\n let iconHeight = 0;\n let iconState: 'base' | 'hover' | 'pressed' = 'base';\n let backgroundWidth = 0;\n let backgroundHeight = 0;\n let backgroundMeasured = false;\n let backgroundState: 'base' | 'hover' | 'pressed' = 'base';\n\n /**\n * Update button layout:\n *\n * - caches the base icon/background dimensions,\n * - calculates content width/height,\n * - updates `button.width` / `button.height`,\n * - stretches the background and repositions the icon and label.\n */\n function layout() {\n if (icon && iconState === 'base') {\n iconWidth = icon.width;\n iconHeight = icon.height;\n }\n\n if (\n background &&\n backgroundState === 'base' &&\n !backgroundMeasured &&\n (background.width > 0 || background.height > 0)\n ) {\n backgroundWidth = background.width;\n backgroundHeight = background.height;\n backgroundMeasured = true;\n }\n\n const gap = icon && iconWidth > 0 && label.width > 0 ? theme.spacing : 0;\n const contentWidth = iconWidth + gap + label.width;\n const contentHeight = Math.max(iconHeight, label.height);\n const contentBoxWidth = contentWidth + paddingX * 2;\n const contentBoxHeight = contentHeight + paddingY * 2;\n\n const nextWidth =\n opt.width ??\n (background\n ? Math.max(contentBoxWidth, backgroundWidth)\n : Math.max(contentBoxWidth, minWidth));\n const nextHeight =\n opt.height ??\n (background\n ? Math.max(contentBoxHeight, backgroundHeight)\n : contentBoxHeight);\n\n if (button.width !== nextWidth) {\n button.width = nextWidth;\n }\n\n if (button.height !== nextHeight) {\n button.height = nextHeight;\n }\n\n if (background && backgroundMeasured) {\n background.width = nextWidth;\n background.height = nextHeight;\n background.pos = k.vec2();\n }\n\n if (icon) {\n icon.pos = k.vec2(-contentWidth / 2 + iconWidth / 2, 0);\n }\n\n label.pos = k.vec2(\n -contentWidth / 2 + iconWidth + gap + label.width / 2,\n 0,\n );\n }\n\n layout();\n\n if (\n opt.width === undefined ||\n opt.height === undefined ||\n opt.icon !== undefined ||\n opt.background !== undefined\n ) {\n button.onUpdate(layout);\n }\n\n button.onClick(() => {\n if (disabled) {\n return;\n }\n\n // prevent click-through\n k.wait(0, () => {\n button.color = toColor(k, pressedColor);\n\n if (icon && opt.pressedIcon) {\n iconState = 'pressed';\n icon.sprite = opt.pressedIcon as string;\n }\n\n if (background && opt.pressedBackground) {\n backgroundState = 'pressed';\n background.sprite = opt.pressedBackground as string;\n }\n\n opt.onClick?.();\n\n // v8 ignore if\n if (disabled) {\n k.setCursor('not-allowed');\n } else if (!button.isHovering()) {\n k.setCursor('default');\n }\n });\n });\n\n button.onHover(() => {\n if (disabled) {\n k.setCursor('not-allowed');\n return;\n } else {\n k.setCursor('pointer');\n }\n\n button.color = toColor(k, hoverColor);\n button.scale = k.vec2(1.05, 1.05);\n\n if (icon && opt.hoverIcon) {\n iconState = 'hover';\n icon.sprite = opt.hoverIcon as string;\n }\n\n if (background && opt.hoverBackground) {\n backgroundState = 'hover';\n background.sprite = opt.hoverBackground as string;\n }\n });\n\n button.onHoverEnd(() => {\n k.setCursor('default');\n\n if (disabled) {\n return;\n }\n\n button.color = baseColor;\n button.scale = k.vec2(1, 1);\n\n if (icon && opt.icon) {\n iconState = 'base';\n icon.sprite = opt.icon as string;\n }\n\n if (background && opt.background) {\n backgroundState = 'base';\n background.sprite = opt.background as string;\n }\n\n if (icon || background) {\n layout();\n }\n });\n\n button.onDestroy(() => {\n if (button.isHovering()) {\n k.setCursor('default');\n }\n });\n\n function applyDisabledState() {\n button.color = disabled ? toColor(k, theme.palette.disabled) : baseColor;\n }\n\n applyDisabledState();\n\n const assignedButton = Object.assign(button, {\n background,\n icon,\n label,\n });\n\n const definedButton = Object.defineProperty(assignedButton, 'disabled', {\n get() {\n return disabled;\n },\n set(value: boolean) {\n disabled = value;\n applyDisabledState();\n },\n enumerable: true,\n configurable: true,\n }) as typeof assignedButton & { disabled: boolean };\n\n return definedButton;\n}\n","import type { DeepPartial, Theme } from './types';\n\nconst PRIMARY = '#4f8cff';\nconst TEXT = '#ffffff';\n\n/** Default theme. */\nconst theme: Theme = {\n palette: {\n primary: PRIMARY,\n secondary: PRIMARY,\n background: '#111827',\n surface: '#1f2937',\n text: TEXT,\n textSecondary: TEXT,\n hover: PRIMARY,\n pressed: PRIMARY,\n disabled: '#6b7280',\n error: '#ef4444',\n },\n typography: {\n fontFamily: 'sans-serif',\n fontSize: 18,\n fontWeight: 400,\n lineHeight: 1.2,\n },\n shape: {\n borderRadius: 0,\n },\n spacing: 8,\n components: {\n button: {\n minWidth: 120,\n paddingX: 12,\n paddingY: 8,\n textTransform: 'none',\n },\n modal: {\n backdropOpacity: 0.5,\n borderRadius: 8,\n gap: 16,\n maxWidth: 480,\n minWidth: 280,\n paddingX: 24,\n paddingY: 20,\n },\n tooltip: {\n background: '#111827',\n textColor: TEXT,\n delay: 300,\n },\n },\n};\n\n/** Resolve a partial theme into a fully resolved theme. */\nexport function resolveTheme(input?: DeepPartial<Theme>): Theme {\n return {\n palette: { ...theme.palette, ...input?.palette },\n typography: { ...theme.typography, ...input?.typography },\n shape: { ...theme.shape, ...input?.shape },\n spacing: input?.spacing ?? theme.spacing,\n components: {\n button: {\n ...theme.components.button,\n ...input?.components?.button,\n },\n modal: {\n ...theme.components.modal,\n ...input?.components?.modal,\n },\n tooltip: {\n ...theme.components.tooltip,\n ...input?.components?.tooltip,\n },\n },\n };\n}\n","import type { KAPLAYCtx } from 'kaplay';\n\nimport { createButton } from './button';\nimport { createModal, createModalManager } from './modal';\nimport { resolveTheme } from './theme';\nimport type {\n AddButton,\n AddModal,\n DeepPartial,\n GetTheme,\n SetTheme,\n Theme,\n} from './types';\n\n/**\n * Kaplay UI plugin.\n *\n * @param k - The Kaplay context.\n * @param theme - The theme config.\n * @returns The plugin methods.\n */\nexport function uiPlugin(k: KAPLAYCtx, theme: DeepPartial<Theme> = {}) {\n let userTheme: DeepPartial<Theme> = theme;\n let currentTheme: Theme = resolveTheme(userTheme);\n\n const getTheme: GetTheme = () => currentTheme;\n\n const setTheme: SetTheme = (next) => {\n userTheme = { ...userTheme, ...next };\n currentTheme = resolveTheme(userTheme);\n };\n\n const addButton: AddButton = (text = '', opt = {}) => {\n return createButton(k, currentTheme, text, opt);\n };\n\n const modalManager = createModalManager(k);\n\n const addModal: AddModal = (opt = {}) => {\n return createModal(k, currentTheme, opt, {\n isTopmost: modalManager.isTopmost,\n onClose: modalManager.closeModal,\n onOpen: modalManager.openModal,\n });\n };\n\n return {\n addButton,\n addModal,\n setTheme,\n getTheme,\n };\n}\n"],"mappings":";;;;;;CAOA,MAAa,YAAY,UAAU,OAAO;CAa1C,SAAS,QAAQ,KAAmC;EAClD,MAAM,QAAQ;EACd,OAAO,OAAO,MAAM,UAAU,YAAY,OAAO,MAAM,WAAW;CACpE;CAEA,SAAgB,YACd,GACA,OACA,KACA,OACA;EACA,MAAM,EACJ,iBACA,cACA,KACA,UACA,UACA,UACA,aACE,MAAM,WAAW;EACrB,MAAM,WAAW,MAAM,WAAW;EAClC,MAAM,YAAY,KAAK,MAAM,WAAW,IAAI;EAC5C,MAAM,OAAO,MAAM,WAAW;EAC9B,MAAM,cAAc,IAAI,eAAe;EAEvC,MAAM,QAAQ,EAAE,IAAI;GAClB,EAAE,IAAI,EAAE,OAAO,CAAC;GAChB,EAAE,MAAM;GACR,EAAE,EAAE,IAAI,KAAK,GAAI;GACjB;EACF,CAAC;EAED,MAAM,gBAAgB;GACpB,MAAM,QAAQ,KAAK;EACrB,CAAC;EAED,MAAM,OAAO,KAAK;EAElB,MAAM,WAAW,MAAM,IAAI;GACzB,EAAE,KAAK,EAAE,MAAM,GAAG,EAAE,OAAO,CAAC;GAC5B,EAAE,IAAI;GACN,EAAE,MAAM,GAAG,GAAG,CAAC;GACf,EAAE,QAAQ,eAAe;GACzB,EAAE,OAAO,QAAQ;GACjB,EAAE,KAAK;EACT,CAAC;EAED,MAAM,UAAU,MAAM,IAAI;GACxB,EAAE,KAAK,GAAG,GAAG,eAAe,IAAI,EAAE,QAAQ,aAAa,IAAI,KAAA,CAAS;GACpE,EAAE,IAAI;GACN,EAAE,MAAM,QAAQ,GAAG,MAAM,QAAQ,OAAO,CAAC;GACzC,EAAE,OAAO,QAAQ;GACjB,EAAE,KAAK;EACT,CAAC;EAED,MAAM,UAAU,QAAQ,IAAI,CAAC,EAAE,IAAI,CAAC,CAAC;EAErC,KAAK,MAAM,UAAU,IAAI,WAAW,CAAC,GACnC,aAAa,GAAG,OAAO,OAAO,MAAM;GAClC,QAAQ;GACR,eAAe;IACb,IAAI,OAAO,UAAU,OACnB,MAAM,QAAQ;IAGhB,OAAO,UAAU;GACnB;EACF,CAAC;EAGH,MAAM,eAAe,IAAI,QACrB,EAAE,KAAK,IAAI,OAAO;GAAE,MAAM;GAAW;EAAK,CAAC,IAC3C,KAAA;EACJ,MAAM,cAAc,IAAI,OACpB,EAAE,KAAK,IAAI,MAAM;GAAE,MAAM;GAAU;EAAK,CAAC,IACzC,KAAA;EACJ,MAAM,gBAAgB,KAAK,IAAI,IAAI,IAAI,SAAS,YAAY,WAAW,CAAC;EAExE,MAAM,QAAQ,IAAI,QACd,QAAQ,IAAI;GACV,EAAE,KAAK,IAAI,OAAO;IAChB,MAAM;IACN;IACA,OAAO,gBAAgB,IAAI,gBAAgB,KAAA;IAC3C,OAAO;GACT,CAAC;GACD,EAAE,IAAI;GACN,EAAE,MAAM,QAAQ,GAAG,MAAM,QAAQ,IAAI,CAAC;GACtC,EAAE,OAAO,QAAQ;EACnB,CAAC,IACD,KAAA;EAEJ,MAAM,OAAO,IAAI,OACb,QAAQ,IAAI;GACV,EAAE,KAAK,IAAI,MAAM;IACf,MAAM;IACN;IACA,OAAO,gBAAgB,IAAI,gBAAgB,KAAA;IAC3C,OAAO;GACT,CAAC;GACD,EAAE,IAAI;GACN,EAAE,MAAM,QAAQ,GAAG,MAAM,QAAQ,aAAa,CAAC;GAC/C,EAAE,OAAO,QAAQ;EACnB,CAAC,IACD,KAAA;;;;;;;;EASJ,SAAS,SAAS;GAChB,MAAM,cAAc,EAAE,MAAM;GAC5B,MAAM,eAAe,EAAE,OAAO;GAE9B,IAAI,SAAS,UAAU,aACrB,SAAS,QAAQ;GAGnB,IAAI,SAAS,WAAW,cACtB,SAAS,SAAS;GAGpB,MAAM,MAAM,EAAE,OAAO;GAErB,MAAM,UAAU,QAAQ,SAAS,OAAO,OAAO;GAC/C,MAAM,eACJ,QAAQ,QAAQ,OAAO,WAAW,QAAQ,OAAO,OAAO,CAAC,IACzD,MAAM,KAAK,IAAI,GAAG,QAAQ,SAAS,CAAC;GACtC,MAAM,gBAAgB,KAAK,IACzB,GACA,GAAG,QAAQ,KAAK,WAAW,OAAO,MAAM,CAC1C;GAEA,MAAM,mBAAmB,KAAK,IAC5B,cAAc,SAAS,GACvB,aAAa,SAAS,CACxB;GACA,MAAM,YACJ,IAAI,SACJ,KAAK,IACH,KAAK,IAAI,kBAAkB,WAAW,WAAW,CAAC,IAAI,WAAW,GACjE,eAAe,WAAW,GAC1B,QACF;GAEF,MAAM,cAAc,OAAO,UAAU;GACrC,MAAM,aAAa,MAAM,UAAU;GACnC,MAAM,WAAW,UAAU,KAAA;GAC3B,MAAM,UAAU,SAAS,KAAA;GACzB,MAAM,aAAa,QAAQ,SAAS;GACpC,MAAM,aAAa,OAAO,QAAQ,IAAI,OAAO,OAAO,IAAI,OAAO,UAAU;GACzE,MAAM,gBACJ,cACA,aACA,gBACA,MAAM,KAAK,IAAI,GAAG,aAAa,CAAC;GAClC,MAAM,aAAa,IAAI,UAAU,gBAAgB,WAAW;GAE5D,IAAI,QAAQ,UAAU,WACpB,QAAQ,QAAQ;GAGlB,IAAI,QAAQ,WAAW,YACrB,QAAQ,SAAS;GAGnB,IAAI,IAAI,CAAC,aAAa,KAAK,aAAa,iBAAiB;GAEzD,IAAI,OAAO;IACT,MAAM,MAAM,EAAE,KAAK,GAAG,IAAI,MAAM,SAAS,CAAC;IAC1C,KAAK,MAAM,SAAS;GACtB;GAEA,IAAI,MAAM;IACR,KAAK,MAAM,EAAE,KAAK,GAAG,IAAI,KAAK,SAAS,CAAC;IACxC,KAAK,KAAK,SAAS;GACrB;GAEA,IAAI,YAAY;IACd,IAAI,IAAI,CAAC,eAAe;IAExB,KAAK,MAAM,UAAU,SAAS;KAC5B,OAAO,MAAM,EAAE,KAAK,IAAI,OAAO,QAAQ,GAAG,IAAI,gBAAgB,CAAC;KAC/D,KAAK,OAAO,QAAQ;IACtB;GACF;EACF;EAEA,OAAO;EACP,MAAM,SAAS,MAAM;EAErB,IAAI,aAAa;GAEf,EAAE,KAAK,SAAS;IACd,SAAS,cAAc;KACrB,IAAI,CAAC,MAAM,UAAU,KAAK,GACxB;KAGF,IAAI,QAAQ,SAAS,EAAE,SAAS,CAAC,GAC/B;KAGF,MAAM,QAAQ;IAChB,CAAC;GACH,CAAC;GAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,MAAM,UAAU,KAAK,GACvB,MAAM,QAAQ;GAElB,CAAC;EACH;EAEA,OAAO,OAAO,OAAO,OAAO;GAC1B;GACA;EACF,CAAC;CACH;CASA,SAAgB,mBAAmB,GAA4B;EAC7D,MAAM,SAAoB,CAAC;EAC3B,IAAI;EACJ,IAAI;EAEJ,MAAM,iBAAiB,YACrB,OAAO,MAAM,UAAU,UAAU,WAAW,MAAM,aAAa,OAAO,CAAC;EAEzE,SAAS,eAAe,SAAkB;GACxC,IAAI,QAAQ,OAAO,KAAK,cAAc,OAAO,GAC3C;GAGF,gCAAgB,IAAI,IAAI;;GAGxB,IAAI,CAAC,YAAY,IAAI,OAAO,GAC1B,YAAY,IAAI,SAAS,QAAQ,MAAM;GAGzC,UAAU,OAAO;GACjB,QAAQ,SAAS,QAAQ,cAAc;EACzC;EAEA,SAAS,UAAU,OAAgB;GACjC,EAAE,UAAU,SAAS;GAErB,IAAI,OAAO,SAAS,GAClB,UAAU,QAAQ,MAAM,GAAG,MAAM,IAAI;GAGvC,OAAO,KAAK,KAAK;GAEjB,IAAI,OAAO,WAAW,GAAG;IACvB,8BAAc,IAAI,IAAI;IACtB,EAAE,YAAY,CAAC,CAAC,SAAS,QAAQ,cAAc;IAE/C,MAAM,aAAa,EAAE,OAAO,YAAY;KACtC,IAAI,CAAC,QAAQ,OAAO,KAAK,CAAC,cAAc,OAAO,GAC7C,eAAe,OAAO;IAE1B,CAAC;IAED,yBAAyB;KACvB,WAAW,OAAO;IACpB;GACF;EACF;EAEA,SAAS,WAAW,OAAgB;GAClC,MAAM,QAAQ,OAAO,QAAQ,KAAK;GAElC,IAAI,UAAU,IACZ;GAGF,MAAM,aAAa,UAAU,OAAO,SAAS;GAE7C,OAAO,OAAO,OAAO,CAAC;GAEtB,IAAI,CAAC,OAAO,QAAQ;IAClB,mBAAmB;IACnB,mBAAmB,KAAA;;IAGnB,IAAI,aAAa;KACf,KAAK,MAAM,CAAC,SAAS,WAAW,aAC9B,IAAI,QAAQ,OAAO,GACjB,UAAU,SAAS,MAAM;KAI7B,cAAc,KAAA;IAChB;GACF,OAAO,IAAI,YACT,UAAU,QAAQ,MAAM,GAAG,OAAO,IAAI;EAE1C;EAEA,MAAM,aAAa,UAAmB,QAAQ,MAAM,MAAM;EAE1D,OAAO;GAAE;GAAW;GAAY;GAAW;EAAc;CAC3D;;;;;;;;CClUA,SAAgB,QAAW,OAAe;EACxC,OAAO,MAAM,MAAM,SAAS;CAC9B;;;;;;;CAQA,SAAgB,QAAQ,SAA2B;EACjD,OAAO,QAAQ,GAAG,SAAS;CAC7B;;;;;;;;CASA,SAAgB,UACd,SACA,WAAW,MACX,cAAc,OACR;EACN,QAAQ,SAAS;EAEjB,IAAI,aACF,QAAQ,SAAS,SAAS,iBAAiB;GACzC,UAAU,cAAc,UAAU,WAAW;EAC/C,CAAC;CAEL;;;;;;;;CASA,SAAgB,QAAQ,GAAc,OAA8B;EAClE,OAAO,OAAO,UAAU,WAAW,EAAE,IAAI,KAAK,IAAI;CACpD;;;;;;;CAQA,SAAgB,UAAU,IAAoB;EAC5C,OAAO;GACL;GACA;GAEA,QAAQ,aAAa;EACvB,CAAC,CAAC,KAAK,GAAG;CACZ;;CChEA,SAAgB,aACd,GACA,OACA,MACA,KAQA;EACA,MAAM,SAAS,IAAI,UAAU,MAAM,MAAM;EACzC,MAAM,EAAE,UAAU,UAAU,aAAa,MAAM,WAAW;EAC1D,MAAM,YAAY,QAAQ,GAAG,IAAI,SAAS,IAAI,QAAQ,MAAM,QAAQ,OAAO;EAC3E,IAAI,WAAW,IAAI,YAAY;EAC/B,MAAM,aAAa,QAAQ,GAAG,IAAI,cAAc,MAAM,QAAQ,KAAK;EACnE,MAAM,eAAe,QAAQ,GAAG,IAAI,gBAAgB,MAAM,QAAQ,OAAO;EACzE,MAAM,YAAY,QAAQ,GAAG,IAAI,aAAa,MAAM,QAAQ,IAAI;EAChE,MAAM,OAAO,IAAI,QAAQ,MAAM,WAAW;EAC1C,MAAM,OAAO,IAAI,QAAQ,MAAM,WAAW;EAC1C,MAAM,MAAM,IAAI,OAAO,EAAE,KAAK;EAE9B,MAAM,WAAW,EAAE,KAAK,MAAM;GAAE;GAAM;EAAK,CAAC;EAC5C,MAAM,aAAa,IAAI,SAAS,KAAA,KAAa,CAAC,KAAK;EAGnD,MAAM,UADW,EADK,IAAI,eAAe,KAAA,MACN,CAAC,aAEhC,SAAS,IACP,EAAE,OAAO,IACT,KAAA,IACF,EAAE,MAAM,MAAM;EAElB,MAAM,aAAa;GACjB,EAAE,KAAK,GAAG,GAAG,OAAO;GACpB,EAAE,IAAI,GAAG;GACT,EAAE,KAAK;GACP,EAAE,MAAM;GACR,EAAE,MAAM,WAAW,QAAQ,GAAG,MAAM,QAAQ,QAAQ,IAAI,SAAS;GACjE,EAAE,OAAO,QAAQ;EACnB;EACA,MAAM,SAAS,IAAI,SAAS,IAAI,OAAO,IAAI,UAAU,IAAI,EAAE,IAAI,UAAU;EAEzE,MAAM,aAAa,IAAI,aACnB,OAAO,IAAI;GAAC,EAAE,OAAO,IAAI,UAAU;GAAG,EAAE,IAAI;GAAG,EAAE,OAAO,QAAQ;EAAC,CAAC,IAClE,KAAA;EAEJ,MAAM,OAAO,IAAI,OACb,OAAO,IAAI;GAAC,EAAE,OAAO,IAAI,IAAI;GAAG,EAAE,IAAI;GAAG,EAAE,OAAO,QAAQ;EAAC,CAAC,IAC5D,KAAA;EAEJ,MAAM,QAAQ,OAAO,IAAI;GACvB;GACA,EAAE,MAAM,SAAkB;GAC1B,EAAE,IAAI;GACN,EAAE,OAAO,QAAQ;EACnB,CAAC;EAED,IAAI,YAAY;EAChB,IAAI,aAAa;EACjB,IAAI,YAA0C;EAC9C,IAAI,kBAAkB;EACtB,IAAI,mBAAmB;EACvB,IAAI,qBAAqB;EACzB,IAAI,kBAAgD;;;;;;;;;EAUpD,SAAS,SAAS;GAChB,IAAI,QAAQ,cAAc,QAAQ;IAChC,YAAY,KAAK;IACjB,aAAa,KAAK;GACpB;GAEA,IACE,cACA,oBAAoB,UACpB,CAAC,uBACA,WAAW,QAAQ,KAAK,WAAW,SAAS,IAC7C;IACA,kBAAkB,WAAW;IAC7B,mBAAmB,WAAW;IAC9B,qBAAqB;GACvB;GAEA,MAAM,MAAM,QAAQ,YAAY,KAAK,MAAM,QAAQ,IAAI,MAAM,UAAU;GACvE,MAAM,eAAe,YAAY,MAAM,MAAM;GAC7C,MAAM,gBAAgB,KAAK,IAAI,YAAY,MAAM,MAAM;GACvD,MAAM,kBAAkB,eAAe,WAAW;GAClD,MAAM,mBAAmB,gBAAgB,WAAW;GAEpD,MAAM,YACJ,IAAI,UACH,aACG,KAAK,IAAI,iBAAiB,eAAe,IACzC,KAAK,IAAI,iBAAiB,QAAQ;GACxC,MAAM,aACJ,IAAI,WACH,aACG,KAAK,IAAI,kBAAkB,gBAAgB,IAC3C;GAEN,IAAI,OAAO,UAAU,WACnB,OAAO,QAAQ;GAGjB,IAAI,OAAO,WAAW,YACpB,OAAO,SAAS;GAGlB,IAAI,cAAc,oBAAoB;IACpC,WAAW,QAAQ;IACnB,WAAW,SAAS;IACpB,WAAW,MAAM,EAAE,KAAK;GAC1B;GAEA,IAAI,MACF,KAAK,MAAM,EAAE,KAAK,CAAC,eAAe,IAAI,YAAY,GAAG,CAAC;GAGxD,MAAM,MAAM,EAAE,KACZ,CAAC,eAAe,IAAI,YAAY,MAAM,MAAM,QAAQ,GACpD,CACF;EACF;EAEA,OAAO;EAEP,IACE,IAAI,UAAU,KAAA,KACd,IAAI,WAAW,KAAA,KACf,IAAI,SAAS,KAAA,KACb,IAAI,eAAe,KAAA,GAEnB,OAAO,SAAS,MAAM;EAGxB,OAAO,cAAc;GACnB,IAAI,UACF;GAIF,EAAE,KAAK,SAAS;IACd,OAAO,QAAQ,QAAQ,GAAG,YAAY;IAEtC,IAAI,QAAQ,IAAI,aAAa;KAC3B,YAAY;KACZ,KAAK,SAAS,IAAI;IACpB;IAEA,IAAI,cAAc,IAAI,mBAAmB;KACvC,kBAAkB;KAClB,WAAW,SAAS,IAAI;IAC1B;IAEA,IAAI,UAAU;;IAGd,IAAI,UACF,EAAE,UAAU,aAAa;SACpB,IAAI,CAAC,OAAO,WAAW,GAC5B,EAAE,UAAU,SAAS;GAEzB,CAAC;EACH,CAAC;EAED,OAAO,cAAc;GACnB,IAAI,UAAU;IACZ,EAAE,UAAU,aAAa;IACzB;GACF,OACE,EAAE,UAAU,SAAS;GAGvB,OAAO,QAAQ,QAAQ,GAAG,UAAU;GACpC,OAAO,QAAQ,EAAE,KAAK,MAAM,IAAI;GAEhC,IAAI,QAAQ,IAAI,WAAW;IACzB,YAAY;IACZ,KAAK,SAAS,IAAI;GACpB;GAEA,IAAI,cAAc,IAAI,iBAAiB;IACrC,kBAAkB;IAClB,WAAW,SAAS,IAAI;GAC1B;EACF,CAAC;EAED,OAAO,iBAAiB;GACtB,EAAE,UAAU,SAAS;GAErB,IAAI,UACF;GAGF,OAAO,QAAQ;GACf,OAAO,QAAQ,EAAE,KAAK,GAAG,CAAC;GAE1B,IAAI,QAAQ,IAAI,MAAM;IACpB,YAAY;IACZ,KAAK,SAAS,IAAI;GACpB;GAEA,IAAI,cAAc,IAAI,YAAY;IAChC,kBAAkB;IAClB,WAAW,SAAS,IAAI;GAC1B;GAEA,IAAI,QAAQ,YACV,OAAO;EAEX,CAAC;EAED,OAAO,gBAAgB;GACrB,IAAI,OAAO,WAAW,GACpB,EAAE,UAAU,SAAS;EAEzB,CAAC;EAED,SAAS,qBAAqB;GAC5B,OAAO,QAAQ,WAAW,QAAQ,GAAG,MAAM,QAAQ,QAAQ,IAAI;EACjE;EAEA,mBAAmB;EAEnB,MAAM,iBAAiB,OAAO,OAAO,QAAQ;GAC3C;GACA;GACA;EACF,CAAC;EAcD,OAZsB,OAAO,eAAe,gBAAgB,YAAY;GACtE,MAAM;IACJ,OAAO;GACT;GACA,IAAI,OAAgB;IAClB,WAAW;IACX,mBAAmB;GACrB;GACA,YAAY;GACZ,cAAc;EAChB,CAEmB;CACrB;;CC/PA,MAAM,UAAU;CAChB,MAAM,OAAO;;CAGb,MAAM,QAAe;EACnB,SAAS;GACP,SAAS;GACT,WAAW;GACX,YAAY;GACZ,SAAS;GACT,MAAM;GACN,eAAe;GACf,OAAO;GACP,SAAS;GACT,UAAU;GACV,OAAO;EACT;EACA,YAAY;GACV,YAAY;GACZ,UAAU;GACV,YAAY;GACZ,YAAY;EACd;EACA,OAAO,EACL,cAAc,EAChB;EACA,SAAS;EACT,YAAY;GACV,QAAQ;IACN,UAAU;IACV,UAAU;IACV,UAAU;IACV,eAAe;GACjB;GACA,OAAO;IACL,iBAAiB;IACjB,cAAc;IACd,KAAK;IACL,UAAU;IACV,UAAU;IACV,UAAU;IACV,UAAU;GACZ;GACA,SAAS;IACP,YAAY;IACZ,WAAW;IACX,OAAO;GACT;EACF;CACF;;CAGA,SAAgB,aAAa,OAAmC;EAC9D,OAAO;GACL,SAAS;IAAE,GAAG,MAAM;IAAS,GAAG,OAAO;GAAQ;GAC/C,YAAY;IAAE,GAAG,MAAM;IAAY,GAAG,OAAO;GAAW;GACxD,OAAO;IAAE,GAAG,MAAM;IAAO,GAAG,OAAO;GAAM;GACzC,SAAS,OAAO,WAAW,MAAM;GACjC,YAAY;IACV,QAAQ;KACN,GAAG,MAAM,WAAW;KACpB,GAAG,OAAO,YAAY;IACxB;IACA,OAAO;KACL,GAAG,MAAM,WAAW;KACpB,GAAG,OAAO,YAAY;IACxB;IACA,SAAS;KACP,GAAG,MAAM,WAAW;KACpB,GAAG,OAAO,YAAY;IACxB;GACF;EACF;CACF;;;;;;;;;CCtDA,SAAgB,SAAS,GAAc,QAA4B,CAAC,GAAG;EACrE,IAAI,YAAgC;EACpC,IAAI,eAAsB,aAAa,SAAS;EAEhD,MAAM,iBAA2B;EAEjC,MAAM,YAAsB,SAAS;GACnC,YAAY;IAAE,GAAG;IAAW,GAAG;GAAK;GACpC,eAAe,aAAa,SAAS;EACvC;EAEA,MAAM,aAAwB,OAAO,IAAI,MAAM,CAAC,MAAM;GACpD,OAAO,aAAa,GAAG,cAAc,MAAM,GAAG;EAChD;EAEA,MAAM,eAAe,mBAAmB,CAAC;EAEzC,MAAM,YAAsB,MAAM,CAAC,MAAM;GACvC,OAAO,YAAY,GAAG,cAAc,KAAK;IACvC,WAAW,aAAa;IACxB,SAAS,aAAa;IACtB,QAAQ,aAAa;GACvB,CAAC;EACH;EAEA,OAAO;GACL;GACA;GACA;GACA;EACF;CACF"}
@@ -1,2 +1,4 @@
1
- (function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports):typeof define==`function`&&define.amd?define([`exports`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e.KaplayPluginUI={}))})(this,function(e){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});let t=l(`modal`);function n(e){let t=e;return typeof t.width==`number`&&typeof t.height==`number`}function r(e,r,i,a){let{backdropOpacity:o,borderRadius:s,gap:l,maxWidth:d,minWidth:f,paddingX:p,paddingY:m}=r.components.modal,h=r.typography.fontSize,g=Math.round(h*1.25),_=r.typography.fontFamily,v=i.dismissible??!0,y=e.add([e.pos(e.center()),e.fixed(),e.z(i.z??1e3),t]);y.onDestroy(()=>{a.onClose(y)}),a.onOpen(y);let b=y.add([e.rect(e.width(),e.height()),e.pos(),e.color(0,0,0),e.opacity(o),e.anchor(`center`),e.area()]),x=y.add([e.rect(0,0,s>0?{radius:s}:void 0),e.pos(),e.color(c(e,r.palette.surface)),e.anchor(`center`),e.area()]),S=x.add([e.pos()]);for(let t of i.actions??[])u(e,r,t.text,{parent:S,onClick:()=>{t.close!==!1&&y.destroy(),t.onClick?.()}});let C=i.title?e.text(i.title,{size:g,font:_}):void 0,w=i.text?e.text(i.text,{size:h,font:_}):void 0,T=Math.max(0,(i.width??d)-p*2),E=i.title?x.add([e.text(i.title,{size:g,font:_,width:T>0?T:void 0,align:`center`}),e.pos(),e.color(c(e,r.palette.text)),e.anchor(`center`)]):void 0,D=i.text?x.add([e.text(i.text,{size:h,font:_,width:T>0?T:void 0,align:`center`}),e.pos(),e.color(c(e,r.palette.textSecondary)),e.anchor(`center`)]):void 0;function O(){let t=e.width(),r=e.height();b.width!==t&&(b.width=t),b.height!==r&&(b.height=r),y.pos=e.center();let a=S.children.filter(n),o=a.reduce((e,t)=>e+t.width,0)+l*Math.max(0,a.length-1),s=Math.max(0,...a.map(e=>e.height)),c=Math.max(C?.width??0,w?.width??0),u=i.width??Math.max(Math.min(c,d-p*2)+p*2,o+p*2,f),h=E?.height??0,g=D?.height??0,_=E!==void 0,v=D!==void 0,T=a.length>0,O=Number(_)+Number(v)+Number(T),k=h+g+s+l*Math.max(0,O-1),A=i.height??k+m*2;x.width!==u&&(x.width=u),x.height!==A&&(x.height=A);let j=-A/2+(A-k)/2;if(E&&(E.pos=e.vec2(0,j+E.height/2),j+=E.height+l),D&&(D.pos=e.vec2(0,j+D.height/2),j+=D.height+l),T){let t=-o/2;for(let n of a)n.pos=e.vec2(t+n.width/2,j+s/2),t+=n.width+l}}return O(),y.onUpdate(O),v&&(e.wait(0,()=>{b.onClick(()=>{a.isTopmost(y)&&(x.hasPoint(e.mousePos())||y.destroy())})}),y.onKeyPress(`escape`,()=>{a.isTopmost(y)&&y.destroy()})),Object.assign(y,{actions:S,content:x})}function i(e){let t=[],n,r,i=e=>t.some(t=>t===e||t.isAncestorOf(e));function c(e){o(e)||i(e)||(n??=/* @__PURE__ */ new Map,n.has(e)||n.set(e,e.paused),s(e),e.children.forEach(c))}function l(l){if(t.length>0&&s(a(t),!0,!0),t.push(l),t.length===1){n=/* @__PURE__ */ new Map,e.getTreeRoot().children.forEach(c);let t=e.onAdd(e=>{!o(e)&&!i(e)&&c(e)});r=()=>{t.cancel()}}}function u(e){let i=t.indexOf(e);if(i===-1)return;let o=i===t.length-1;if(t.splice(i,1),t.length)o&&s(a(t),!1,!0);else if(r?.(),r=void 0,n){for(let[e,t]of n)e.exists()&&s(e,t);n=void 0}}return{openModal:l,closeModal:u,isTopmost:e=>a(t)===e,isInsideModal:i}}function a(e){return e[e.length-1]}function o(e){return e.is(t)}function s(e,t=!0,n=!1){e.paused=t,n&&e.children.forEach(e=>{s(e,t,n)})}function c(e,t){return typeof t==`string`?e.rgb(t):t}function l(e){return[`kaplay-plugin-ui`,e,crypto?.randomUUID?.()].join(`:`)}function u(e,t,n,r){let i=r.radius??t.shape.borderRadius,{minWidth:a,paddingX:o,paddingY:s}=t.components.button,l=c(e,r.color??r.fill??t.palette.primary),u=c(e,r.hoverColor??t.palette.hover),d=c(e,r.pressedColor??t.palette.pressed),f=c(e,r.textColor??t.palette.text),p=r.size??t.typography.fontSize,m=r.font??t.typography.fontFamily,h=r.pos??e.vec2(),g=e.text(n,{size:p,font:m}),_=r.icon!==void 0&&!n.length,v=r.background===void 0&&!_?i>0?{radius:i}:void 0:{fill:!1},y=[e.rect(0,0,v),e.pos(h),e.area(),e.scale(),e.color(l),e.anchor(`center`)],b=r.parent?r.parent.add(y):e.add(y),x=r.background?b.add([e.sprite(r.background),e.pos(),e.anchor(`center`)]):void 0,S=r.icon?b.add([e.sprite(r.icon),e.pos(),e.anchor(`center`)]):void 0,C=b.add([g,e.color(f),e.pos(),e.anchor(`center`)]),w=0,T=0,E=`base`,D=0,O=0,k=!1,A=`base`;function j(){S&&E===`base`&&(w=S.width,T=S.height),x&&A===`base`&&!k&&(x.width>0||x.height>0)&&(D=x.width,O=x.height,k=!0);let n=S&&w>0&&C.width>0?t.spacing:0,i=w+n+C.width,c=Math.max(T,C.height),l=i+o*2,u=c+s*2,d=r.width??(x?Math.max(l,D):Math.max(l,a)),f=r.height??(x?Math.max(u,O):u);b.width!==d&&(b.width=d),b.height!==f&&(b.height=f),x&&k&&(x.width=d,x.height=f,x.pos=e.vec2()),S&&(S.pos=e.vec2(-i/2+w/2,0)),C.pos=e.vec2(-i/2+w+n+C.width/2,0)}return j(),(r.width===void 0||r.height===void 0||r.icon!==void 0||r.background!==void 0)&&b.onUpdate(j),b.onClick(()=>{e.wait(0,()=>{b.color=c(e,d),S&&r.pressedIcon&&(E=`pressed`,S.sprite=r.pressedIcon),x&&r.pressedBackground&&(A=`pressed`,x.sprite=r.pressedBackground),r.onClick?.()})}),b.onHover(()=>{b.color=c(e,u),b.scale=e.vec2(1.05,1.05),e.setCursor(`pointer`),S&&r.hoverIcon&&(E=`hover`,S.sprite=r.hoverIcon),x&&r.hoverBackground&&(A=`hover`,x.sprite=r.hoverBackground)}),b.onHoverEnd(()=>{b.color=l,b.scale=e.vec2(1,1),e.setCursor(`default`),S&&r.icon&&(E=`base`,S.sprite=r.icon),x&&r.background&&(A=`base`,x.sprite=r.background),(S||x)&&j()}),Object.assign(b,{background:x,icon:S,label:C})}let d=`#4f8cff`,f=`#ffffff`,p={palette:{primary:d,secondary:d,background:`#111827`,surface:`#1f2937`,text:f,textSecondary:f,hover:d,pressed:d,disabled:`#6b7280`,error:`#ef4444`},typography:{fontFamily:`sans-serif`,fontSize:18,fontWeight:400,lineHeight:1.2},shape:{borderRadius:0},spacing:8,components:{button:{minWidth:120,paddingX:12,paddingY:8,textTransform:`none`},modal:{backdropOpacity:.5,borderRadius:8,gap:16,maxWidth:480,minWidth:280,paddingX:24,paddingY:20},tooltip:{background:`#111827`,textColor:f,delay:300}}};function m(e){return{palette:{...p.palette,...e?.palette},typography:{...p.typography,...e?.typography},shape:{...p.shape,...e?.shape},spacing:e?.spacing??p.spacing,components:{button:{...p.components.button,...e?.components?.button},modal:{...p.components.modal,...e?.components?.modal},tooltip:{...p.components.tooltip,...e?.components?.tooltip}}}}function h(e,t={}){let n=t,a=m(n),o=()=>a,s=e=>{n={...n,...e},a=m(n)},c=(t=``,n={})=>u(e,a,t,n),l=i(e);return{addButton:c,addModal:(t={})=>r(e,a,t,{isTopmost:l.isTopmost,onClose:l.closeModal,onOpen:l.openModal}),setTheme:s,getTheme:o}}e.uiPlugin=h});
1
+ (function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports):typeof define==`function`&&define.amd?define([`exports`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e.KaplayPluginUI={}))})(this,function(e){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});let t=l(`modal`);function n(e){let t=e;return typeof t.width==`number`&&typeof t.height==`number`}function r(e,r,i,a){let{backdropOpacity:o,borderRadius:s,gap:l,maxWidth:d,minWidth:f,paddingX:p,paddingY:m}=r.components.modal,h=r.typography.fontSize,g=Math.round(h*1.25),_=r.typography.fontFamily,v=i.dismissible??!0,y=e.add([e.pos(e.center()),e.fixed(),e.z(i.z??1e3),t]);y.onDestroy(()=>{a.onClose(y)}),a.onOpen(y);let b=y.add([e.rect(e.width(),e.height()),e.pos(),e.color(0,0,0),e.opacity(o),e.anchor(`center`),e.area()]),x=y.add([e.rect(0,0,s>0?{radius:s}:void 0),e.pos(),e.color(c(e,r.palette.surface)),e.anchor(`center`),e.area()]),S=x.add([e.pos()]);for(let t of i.actions??[])u(e,r,t.text,{parent:S,onClick:()=>{t.close!==!1&&y.destroy(),t.onClick?.()}});let C=i.title?e.text(i.title,{size:g,font:_}):void 0,w=i.text?e.text(i.text,{size:h,font:_}):void 0,T=Math.max(0,(i.width??d)-p*2),E=i.title?x.add([e.text(i.title,{size:g,font:_,width:T>0?T:void 0,align:`center`}),e.pos(),e.color(c(e,r.palette.text)),e.anchor(`center`)]):void 0,D=i.text?x.add([e.text(i.text,{size:h,font:_,width:T>0?T:void 0,align:`center`}),e.pos(),e.color(c(e,r.palette.textSecondary)),e.anchor(`center`)]):void 0;function O(){let t=e.width(),r=e.height();b.width!==t&&(b.width=t),b.height!==r&&(b.height=r),y.pos=e.center();let a=S.children.filter(n),o=a.reduce((e,t)=>e+t.width,0)+l*Math.max(0,a.length-1),s=Math.max(0,...a.map(e=>e.height)),c=Math.max(C?.width??0,w?.width??0),u=i.width??Math.max(Math.min(c,d-p*2)+p*2,o+p*2,f),h=E?.height??0,g=D?.height??0,_=E!==void 0,v=D!==void 0,T=a.length>0,O=Number(_)+Number(v)+Number(T),k=h+g+s+l*Math.max(0,O-1),A=i.height??k+m*2;x.width!==u&&(x.width=u),x.height!==A&&(x.height=A);let j=-A/2+(A-k)/2;if(E&&(E.pos=e.vec2(0,j+E.height/2),j+=E.height+l),D&&(D.pos=e.vec2(0,j+D.height/2),j+=D.height+l),T){let t=-o/2;for(let n of a)n.pos=e.vec2(t+n.width/2,j+s/2),t+=n.width+l}}return O(),y.onUpdate(O),v&&(e.wait(0,()=>{b.onClick(()=>{a.isTopmost(y)&&(x.hasPoint(e.mousePos())||y.destroy())})}),y.onKeyPress(`escape`,()=>{a.isTopmost(y)&&y.destroy()})),Object.assign(y,{actions:S,content:x})}function i(e){let t=[],n,r,i=e=>t.some(t=>t===e||t.isAncestorOf(e));function c(e){o(e)||i(e)||(n??=/* @__PURE__ */ new Map,n.has(e)||n.set(e,e.paused),s(e),e.children.forEach(c))}function l(l){if(e.setCursor(`default`),t.length>0&&s(a(t),!0,!0),t.push(l),t.length===1){n=/* @__PURE__ */ new Map,e.getTreeRoot().children.forEach(c);let t=e.onAdd(e=>{!o(e)&&!i(e)&&c(e)});r=()=>{t.cancel()}}}function u(e){let i=t.indexOf(e);if(i===-1)return;let o=i===t.length-1;if(t.splice(i,1),t.length)o&&s(a(t),!1,!0);else if(r?.(),r=void 0,n){for(let[e,t]of n)e.exists()&&s(e,t);n=void 0}}return{openModal:l,closeModal:u,isTopmost:e=>a(t)===e,isInsideModal:i}}function a(e){return e[e.length-1]}function o(e){return e.is(t)}function s(e,t=!0,n=!1){e.paused=t,n&&e.children.forEach(e=>{s(e,t,n)})}function c(e,t){return typeof t==`string`?e.rgb(t):t}function l(e){return[`kaplay-plugin-ui`,e,crypto?.randomUUID?.()].join(`:`)}function u(e,t,n,r){let i=r.radius??t.shape.borderRadius,{minWidth:a,paddingX:o,paddingY:s}=t.components.button,l=c(e,r.color??r.fill??t.palette.primary),u=r.disabled??!1,d=c(e,r.hoverColor??t.palette.hover),f=c(e,r.pressedColor??t.palette.pressed),p=c(e,r.textColor??t.palette.text),m=r.size??t.typography.fontSize,h=r.font??t.typography.fontFamily,g=r.pos??e.vec2(),_=e.text(n,{size:m,font:h}),v=r.icon!==void 0&&!n.length,y=r.background===void 0&&!v?i>0?{radius:i}:void 0:{fill:!1},b=[e.rect(0,0,y),e.pos(g),e.area(),e.scale(),e.color(u?c(e,t.palette.disabled):l),e.anchor(`center`)],x=r.parent?r.parent.add(b):e.add(b),S=r.background?x.add([e.sprite(r.background),e.pos(),e.anchor(`center`)]):void 0,C=r.icon?x.add([e.sprite(r.icon),e.pos(),e.anchor(`center`)]):void 0,w=x.add([_,e.color(p),e.pos(),e.anchor(`center`)]),T=0,E=0,D=`base`,O=0,k=0,A=!1,j=`base`;function M(){C&&D===`base`&&(T=C.width,E=C.height),S&&j===`base`&&!A&&(S.width>0||S.height>0)&&(O=S.width,k=S.height,A=!0);let n=C&&T>0&&w.width>0?t.spacing:0,i=T+n+w.width,c=Math.max(E,w.height),l=i+o*2,u=c+s*2,d=r.width??(S?Math.max(l,O):Math.max(l,a)),f=r.height??(S?Math.max(u,k):u);x.width!==d&&(x.width=d),x.height!==f&&(x.height=f),S&&A&&(S.width=d,S.height=f,S.pos=e.vec2()),C&&(C.pos=e.vec2(-i/2+T/2,0)),w.pos=e.vec2(-i/2+T+n+w.width/2,0)}M(),(r.width===void 0||r.height===void 0||r.icon!==void 0||r.background!==void 0)&&x.onUpdate(M),x.onClick(()=>{u||e.wait(0,()=>{
2
+ // v8 ignore if
3
+ x.color=c(e,f),C&&r.pressedIcon&&(D=`pressed`,C.sprite=r.pressedIcon),S&&r.pressedBackground&&(j=`pressed`,S.sprite=r.pressedBackground),r.onClick?.(),u?e.setCursor(`not-allowed`):x.isHovering()||e.setCursor(`default`)})}),x.onHover(()=>{u?e.setCursor(`not-allowed`):(e.setCursor(`pointer`),x.color=c(e,d),x.scale=e.vec2(1.05,1.05),C&&r.hoverIcon&&(D=`hover`,C.sprite=r.hoverIcon),S&&r.hoverBackground&&(j=`hover`,S.sprite=r.hoverBackground))}),x.onHoverEnd(()=>{e.setCursor(`default`),!u&&(x.color=l,x.scale=e.vec2(1,1),C&&r.icon&&(D=`base`,C.sprite=r.icon),S&&r.background&&(j=`base`,S.sprite=r.background),(C||S)&&M())}),x.onDestroy(()=>{x.isHovering()&&e.setCursor(`default`)});function N(){x.color=u?c(e,t.palette.disabled):l}N();let P=Object.assign(x,{background:S,icon:C,label:w});return Object.defineProperty(P,"disabled",{get(){return u},set(e){u=e,N()},enumerable:!0,configurable:!0})}let d=`#4f8cff`,f=`#ffffff`,p={palette:{primary:d,secondary:d,background:`#111827`,surface:`#1f2937`,text:f,textSecondary:f,hover:d,pressed:d,disabled:`#6b7280`,error:`#ef4444`},typography:{fontFamily:`sans-serif`,fontSize:18,fontWeight:400,lineHeight:1.2},shape:{borderRadius:0},spacing:8,components:{button:{minWidth:120,paddingX:12,paddingY:8,textTransform:`none`},modal:{backdropOpacity:.5,borderRadius:8,gap:16,maxWidth:480,minWidth:280,paddingX:24,paddingY:20},tooltip:{background:`#111827`,textColor:f,delay:300}}};function m(e){return{palette:{...p.palette,...e?.palette},typography:{...p.typography,...e?.typography},shape:{...p.shape,...e?.shape},spacing:e?.spacing??p.spacing,components:{button:{...p.components.button,...e?.components?.button},modal:{...p.components.modal,...e?.components?.modal},tooltip:{...p.components.tooltip,...e?.components?.tooltip}}}}function h(e,t={}){let n=t,a=m(n),o=()=>a,s=e=>{n={...n,...e},a=m(n)},c=(t=``,n={})=>u(e,a,t,n),l=i(e);return{addButton:c,addModal:(t={})=>r(e,a,t,{isTopmost:l.isTopmost,onClose:l.closeModal,onOpen:l.openModal}),setTheme:s,getTheme:o}}e.uiPlugin=h});
2
4
  //# sourceMappingURL=plugin.umd.min.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.umd.min.js","names":["factory","global","exports","MODAL_TAG","uniqueTag","isSized","sized","obj","createModal","theme","fontSize","titleSize","font","dismissible","opt","modal","k","hooks","backdrop","backdropOpacity","content","borderRadius","toColor","actions","action","createButton","titleMeasure","bodyMeasure","textWrapWidth","maxWidth","paddingX","title","body","layout","screenWidth","screenHeight","buttons","actionsWidth","width","button","gap","actionsHeight","textContentWidth","nextWidth","minWidth","titleHeight","bodyHeight","hasTitle","hasBody","hasActions","blockCount","contentHeight","nextHeight","paddingY","y","x","createModalManager","modals","pausedScene","cancelPauseOnAdd","isInsideModal","gameObj","pauseSceneTree","isModal","setPaused","openModal","getLast","controller","closeModal","index","wasTopmost","paused","items","isPaused","includeTree","childGameObj","color","id","radius","baseColor","hoverColor","pressedColor","textColor","size","pos","textComp","text","isIconOnly","rectOpt","components","background","icon","label","iconWidth","iconHeight","iconState","backgroundWidth","backgroundHeight","backgroundMeasured","backgroundState","contentWidth","contentBoxWidth","contentBoxHeight","PRIMARY","TEXT","resolveTheme","input","uiPlugin","userTheme","currentTheme","getTheme","setTheme","next","addButton","modalManager"],"sources":["../src/modal.ts","../src/utils.ts","../src/button.ts","../src/theme.ts","../src/plugin.ts"],"sourcesContent":["import type { GameObj, KAPLAYCtx } from 'kaplay';\n\nimport { createButton } from './button';\nimport type { ModalOpt, Theme } from './types';\nimport { getLast, isModal, setPaused, toColor, uniqueTag } from './utils';\n\n/** Tag applied to modal game objects. */\nexport const MODAL_TAG = uniqueTag('modal');\n\ntype SizedGameObj = GameObj & {\n height: number;\n width: number;\n};\n\nexport interface ModalHooks {\n isTopmost: (modal: GameObj) => boolean;\n onClose: (modal: GameObj) => void;\n onOpen: (modal: GameObj) => void;\n}\n\nfunction isSized(obj: GameObj): obj is SizedGameObj {\n const sized = obj as Partial<SizedGameObj>;\n return typeof sized.width === 'number' && typeof sized.height === 'number';\n}\n\nexport function createModal(\n k: KAPLAYCtx,\n theme: Theme,\n opt: ModalOpt,\n hooks: ModalHooks,\n) {\n const {\n backdropOpacity,\n borderRadius,\n gap,\n maxWidth,\n minWidth,\n paddingX,\n paddingY,\n } = theme.components.modal;\n const fontSize = theme.typography.fontSize;\n const titleSize = Math.round(fontSize * 1.25);\n const font = theme.typography.fontFamily;\n const dismissible = opt.dismissible ?? true;\n\n const modal = k.add([\n k.pos(k.center()),\n k.fixed(),\n k.z(opt.z ?? 1000),\n MODAL_TAG,\n ]);\n\n modal.onDestroy(() => {\n hooks.onClose(modal);\n });\n\n hooks.onOpen(modal);\n\n const backdrop = modal.add([\n k.rect(k.width(), k.height()),\n k.pos(),\n k.color(0, 0, 0),\n k.opacity(backdropOpacity),\n k.anchor('center'),\n k.area(),\n ]);\n\n const content = modal.add([\n k.rect(0, 0, borderRadius > 0 ? { radius: borderRadius } : undefined),\n k.pos(),\n k.color(toColor(k, theme.palette.surface)),\n k.anchor('center'),\n k.area(),\n ]);\n\n const actions = content.add([k.pos()]);\n\n for (const action of opt.actions ?? []) {\n createButton(k, theme, action.text, {\n parent: actions,\n onClick: () => {\n if (action.close !== false) {\n modal.destroy();\n }\n\n action.onClick?.();\n },\n });\n }\n\n const titleMeasure = opt.title\n ? k.text(opt.title, { size: titleSize, font })\n : undefined;\n const bodyMeasure = opt.text\n ? k.text(opt.text, { size: fontSize, font })\n : undefined;\n const textWrapWidth = Math.max(0, (opt.width ?? maxWidth) - paddingX * 2);\n\n const title = opt.title\n ? content.add([\n k.text(opt.title, {\n size: titleSize,\n font,\n width: textWrapWidth > 0 ? textWrapWidth : undefined,\n align: 'center',\n }),\n k.pos(),\n k.color(toColor(k, theme.palette.text)),\n k.anchor('center'),\n ])\n : undefined;\n\n const body = opt.text\n ? content.add([\n k.text(opt.text, {\n size: fontSize,\n font,\n width: textWrapWidth > 0 ? textWrapWidth : undefined,\n align: 'center',\n }),\n k.pos(),\n k.color(toColor(k, theme.palette.textSecondary)),\n k.anchor('center'),\n ])\n : undefined;\n\n /**\n * Update modal layout:\n *\n * - fits the backdrop to the screen,\n * - sizes the content to its title, text, and actions,\n * - repositions the title, text, and actions.\n */\n function layout() {\n const screenWidth = k.width();\n const screenHeight = k.height();\n\n if (backdrop.width !== screenWidth) {\n backdrop.width = screenWidth;\n }\n\n if (backdrop.height !== screenHeight) {\n backdrop.height = screenHeight;\n }\n\n modal.pos = k.center();\n\n const buttons = actions.children.filter(isSized);\n const actionsWidth =\n buttons.reduce((width, button) => width + button.width, 0) +\n gap * Math.max(0, buttons.length - 1);\n const actionsHeight = Math.max(\n 0,\n ...buttons.map((button) => button.height),\n );\n\n const textContentWidth = Math.max(\n titleMeasure?.width ?? 0,\n bodyMeasure?.width ?? 0,\n );\n const nextWidth =\n opt.width ??\n Math.max(\n Math.min(textContentWidth, maxWidth - paddingX * 2) + paddingX * 2,\n actionsWidth + paddingX * 2,\n minWidth,\n );\n\n const titleHeight = title?.height ?? 0;\n const bodyHeight = body?.height ?? 0;\n const hasTitle = title !== undefined;\n const hasBody = body !== undefined;\n const hasActions = buttons.length > 0;\n const blockCount = Number(hasTitle) + Number(hasBody) + Number(hasActions);\n const contentHeight =\n titleHeight +\n bodyHeight +\n actionsHeight +\n gap * Math.max(0, blockCount - 1);\n const nextHeight = opt.height ?? contentHeight + paddingY * 2;\n\n if (content.width !== nextWidth) {\n content.width = nextWidth;\n }\n\n if (content.height !== nextHeight) {\n content.height = nextHeight;\n }\n\n let y = -nextHeight / 2 + (nextHeight - contentHeight) / 2;\n\n if (title) {\n title.pos = k.vec2(0, y + title.height / 2);\n y += title.height + gap;\n }\n\n if (body) {\n body.pos = k.vec2(0, y + body.height / 2);\n y += body.height + gap;\n }\n\n if (hasActions) {\n let x = -actionsWidth / 2;\n\n for (const button of buttons) {\n button.pos = k.vec2(x + button.width / 2, y + actionsHeight / 2);\n x += button.width + gap;\n }\n }\n }\n\n layout();\n modal.onUpdate(layout);\n\n if (dismissible) {\n // prevent click-through so modal isn't dismissed right after creation\n k.wait(0, () => {\n backdrop.onClick(() => {\n if (!hooks.isTopmost(modal)) {\n return;\n }\n\n if (content.hasPoint(k.mousePos())) {\n return;\n }\n\n modal.destroy();\n });\n });\n\n modal.onKeyPress('escape', () => {\n if (hooks.isTopmost(modal)) {\n modal.destroy();\n }\n });\n }\n\n return Object.assign(modal, {\n actions,\n content,\n });\n}\n\nexport interface ModalManager {\n openModal: ModalHooks['onOpen'];\n closeModal: ModalHooks['onClose'];\n isTopmost: ModalHooks['isTopmost'];\n isInsideModal: (gameObj: GameObj) => boolean;\n}\n\nexport function createModalManager(k: KAPLAYCtx): ModalManager {\n const modals: GameObj[] = [];\n let pausedScene: Map<GameObj, boolean> | undefined;\n let cancelPauseOnAdd: (() => void) | undefined;\n\n const isInsideModal = (gameObj: GameObj) =>\n modals.some((modal) => modal === gameObj || modal.isAncestorOf(gameObj));\n\n function pauseSceneTree(gameObj: GameObj) {\n if (isModal(gameObj) || isInsideModal(gameObj)) {\n return;\n }\n\n pausedScene ??= new Map();\n\n // v8 ignore next\n if (!pausedScene.has(gameObj)) {\n pausedScene.set(gameObj, gameObj.paused);\n }\n\n setPaused(gameObj);\n gameObj.children.forEach(pauseSceneTree);\n }\n\n function openModal(modal: GameObj) {\n if (modals.length > 0) {\n setPaused(getLast(modals), true, true);\n }\n\n modals.push(modal);\n\n if (modals.length === 1) {\n pausedScene = new Map();\n k.getTreeRoot().children.forEach(pauseSceneTree);\n\n const controller = k.onAdd((gameObj) => {\n if (!isModal(gameObj) && !isInsideModal(gameObj)) {\n pauseSceneTree(gameObj);\n }\n });\n\n cancelPauseOnAdd = () => {\n controller.cancel();\n };\n }\n }\n\n function closeModal(modal: GameObj) {\n const index = modals.indexOf(modal);\n\n if (index === -1) {\n return;\n }\n\n const wasTopmost = index === modals.length - 1;\n\n modals.splice(index, 1);\n\n if (!modals.length) {\n cancelPauseOnAdd?.();\n cancelPauseOnAdd = undefined;\n\n // v8 ignore next\n if (pausedScene) {\n for (const [gameObj, paused] of pausedScene) {\n if (gameObj.exists()) {\n setPaused(gameObj, paused);\n }\n }\n\n pausedScene = undefined;\n }\n } else if (wasTopmost) {\n setPaused(getLast(modals), false, true);\n }\n }\n\n const isTopmost = (modal: GameObj) => getLast(modals) === modal;\n\n return { openModal, closeModal, isTopmost, isInsideModal };\n}\n","import type { Color, GameObj, KAPLAYCtx } from 'kaplay';\n\nimport { MODAL_TAG } from './modal';\n\n/**\n * Get the last item in an array.\n *\n * @param items - The items.\n * @returns The last item.\n */\nexport function getLast<T>(items: T[]): T {\n return items[items.length - 1];\n}\n\n/**\n * Check if the game object is a modal.\n *\n * @param gameObj - The game object.\n * @returns Whether game object is a modal.\n */\nexport function isModal(gameObj: GameObj): boolean {\n return gameObj.is(MODAL_TAG);\n}\n\n/**\n * Pause or unpause game object(s).\n *\n * @param gameObj - The root game object.\n * @param isPaused - Whether it should be paused.\n * @param includeTree - Whether to pause all game objects in tree.\n */\nexport function setPaused(\n gameObj: GameObj,\n isPaused = true,\n includeTree = false,\n): void {\n gameObj.paused = isPaused;\n\n if (includeTree) {\n gameObj.children.forEach((childGameObj) => {\n setPaused(childGameObj, isPaused, includeTree);\n });\n }\n}\n\n/**\n * Convert a hex string or color to color.\n *\n * @param k - The Kaplay context.\n * @param color - The hex string or color.\n * @returns The color.\n */\nexport function toColor(k: KAPLAYCtx, color: string | Color): Color {\n return typeof color === 'string' ? k.rgb(color) : color;\n}\n\n/**\n * Generate a unique tag.\n *\n * @param id - The identifier.\n * @returns The tag.\n */\nexport function uniqueTag(id: string): string {\n return [\n 'kaplay-plugin-ui',\n id,\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n crypto?.randomUUID?.(),\n ].join(':');\n}\n","import type { Color, KAPLAYCtx, Vec2 } from 'kaplay';\n\nimport type { ButtonOpt, Theme } from './types';\nimport { toColor } from './utils';\n\nexport function createButton(\n k: KAPLAYCtx,\n theme: Theme,\n text: string,\n opt: ButtonOpt & {\n pos?: Vec2;\n onClick?: () => void;\n color?: string | Color;\n hoverColor?: string | Color;\n pressedColor?: string | Color;\n textColor?: string | Color;\n },\n) {\n const radius = opt.radius ?? theme.shape.borderRadius;\n const { minWidth, paddingX, paddingY } = theme.components.button;\n const baseColor = toColor(k, opt.color ?? opt.fill ?? theme.palette.primary);\n const hoverColor = toColor(k, opt.hoverColor ?? theme.palette.hover);\n const pressedColor = toColor(k, opt.pressedColor ?? theme.palette.pressed);\n const textColor = toColor(k, opt.textColor ?? theme.palette.text);\n const size = opt.size ?? theme.typography.fontSize;\n const font = opt.font ?? theme.typography.fontFamily;\n const pos = opt.pos ?? k.vec2();\n\n const textComp = k.text(text, { size, font });\n const isIconOnly = opt.icon !== undefined && !text.length;\n const hasBackground = opt.background !== undefined;\n const showFill = !hasBackground && !isIconOnly;\n const rectOpt = showFill\n ? radius > 0\n ? { radius }\n : undefined\n : { fill: false };\n\n const components = [\n k.rect(0, 0, rectOpt),\n k.pos(pos),\n k.area(),\n k.scale(),\n k.color(baseColor),\n k.anchor('center'),\n ];\n const button = opt.parent ? opt.parent.add(components) : k.add(components);\n\n const background = opt.background\n ? button.add([k.sprite(opt.background), k.pos(), k.anchor('center')])\n : undefined;\n\n const icon = opt.icon\n ? button.add([k.sprite(opt.icon), k.pos(), k.anchor('center')])\n : undefined;\n\n const label = button.add([\n textComp,\n k.color(textColor as never),\n k.pos(),\n k.anchor('center'),\n ]);\n\n let iconWidth = 0;\n let iconHeight = 0;\n let iconState: 'base' | 'hover' | 'pressed' = 'base';\n let backgroundWidth = 0;\n let backgroundHeight = 0;\n let backgroundMeasured = false;\n let backgroundState: 'base' | 'hover' | 'pressed' = 'base';\n\n /**\n * Update button layout:\n *\n * - caches the base icon/background dimensions,\n * - calculates content width/height,\n * - updates `button.width` / `button.height`,\n * - stretches the background and repositions the icon and label.\n */\n function layout() {\n if (icon && iconState === 'base') {\n iconWidth = icon.width;\n iconHeight = icon.height;\n }\n\n if (\n background &&\n backgroundState === 'base' &&\n !backgroundMeasured &&\n (background.width > 0 || background.height > 0)\n ) {\n backgroundWidth = background.width;\n backgroundHeight = background.height;\n backgroundMeasured = true;\n }\n\n const gap = icon && iconWidth > 0 && label.width > 0 ? theme.spacing : 0;\n const contentWidth = iconWidth + gap + label.width;\n const contentHeight = Math.max(iconHeight, label.height);\n const contentBoxWidth = contentWidth + paddingX * 2;\n const contentBoxHeight = contentHeight + paddingY * 2;\n\n const nextWidth =\n opt.width ??\n (background\n ? Math.max(contentBoxWidth, backgroundWidth)\n : Math.max(contentBoxWidth, minWidth));\n const nextHeight =\n opt.height ??\n (background\n ? Math.max(contentBoxHeight, backgroundHeight)\n : contentBoxHeight);\n\n if (button.width !== nextWidth) {\n button.width = nextWidth;\n }\n\n if (button.height !== nextHeight) {\n button.height = nextHeight;\n }\n\n if (background && backgroundMeasured) {\n background.width = nextWidth;\n background.height = nextHeight;\n background.pos = k.vec2();\n }\n\n if (icon) {\n icon.pos = k.vec2(-contentWidth / 2 + iconWidth / 2, 0);\n }\n\n label.pos = k.vec2(\n -contentWidth / 2 + iconWidth + gap + label.width / 2,\n 0,\n );\n }\n\n layout();\n\n if (\n opt.width === undefined ||\n opt.height === undefined ||\n opt.icon !== undefined ||\n opt.background !== undefined\n ) {\n button.onUpdate(layout);\n }\n\n button.onClick(() => {\n // prevent stray click from click-through\n k.wait(0, () => {\n button.color = toColor(k, pressedColor);\n\n if (icon && opt.pressedIcon) {\n iconState = 'pressed';\n icon.sprite = opt.pressedIcon as string;\n }\n\n if (background && opt.pressedBackground) {\n backgroundState = 'pressed';\n background.sprite = opt.pressedBackground as string;\n }\n\n opt.onClick?.();\n });\n });\n\n button.onHover(() => {\n button.color = toColor(k, hoverColor);\n button.scale = k.vec2(1.05, 1.05);\n k.setCursor('pointer');\n\n if (icon && opt.hoverIcon) {\n iconState = 'hover';\n icon.sprite = opt.hoverIcon as string;\n }\n\n if (background && opt.hoverBackground) {\n backgroundState = 'hover';\n background.sprite = opt.hoverBackground as string;\n }\n });\n\n button.onHoverEnd(() => {\n button.color = baseColor;\n button.scale = k.vec2(1, 1);\n k.setCursor('default');\n\n if (icon && opt.icon) {\n iconState = 'base';\n icon.sprite = opt.icon as string;\n }\n\n if (background && opt.background) {\n backgroundState = 'base';\n background.sprite = opt.background as string;\n }\n\n if (icon || background) {\n layout();\n }\n });\n\n return Object.assign(button, {\n background,\n icon,\n label,\n });\n}\n","import type { DeepPartial, Theme } from './types';\n\nconst PRIMARY = '#4f8cff';\nconst TEXT = '#ffffff';\n\n/** Default theme. */\nconst theme: Theme = {\n palette: {\n primary: PRIMARY,\n secondary: PRIMARY,\n background: '#111827',\n surface: '#1f2937',\n text: TEXT,\n textSecondary: TEXT,\n hover: PRIMARY,\n pressed: PRIMARY,\n disabled: '#6b7280',\n error: '#ef4444',\n },\n typography: {\n fontFamily: 'sans-serif',\n fontSize: 18,\n fontWeight: 400,\n lineHeight: 1.2,\n },\n shape: {\n borderRadius: 0,\n },\n spacing: 8,\n components: {\n button: {\n minWidth: 120,\n paddingX: 12,\n paddingY: 8,\n textTransform: 'none',\n },\n modal: {\n backdropOpacity: 0.5,\n borderRadius: 8,\n gap: 16,\n maxWidth: 480,\n minWidth: 280,\n paddingX: 24,\n paddingY: 20,\n },\n tooltip: {\n background: '#111827',\n textColor: TEXT,\n delay: 300,\n },\n },\n};\n\n/** Resolve a partial theme into a fully resolved theme. */\nexport function resolveTheme(input?: DeepPartial<Theme>): Theme {\n return {\n palette: { ...theme.palette, ...input?.palette },\n typography: { ...theme.typography, ...input?.typography },\n shape: { ...theme.shape, ...input?.shape },\n spacing: input?.spacing ?? theme.spacing,\n components: {\n button: {\n ...theme.components.button,\n ...input?.components?.button,\n },\n modal: {\n ...theme.components.modal,\n ...input?.components?.modal,\n },\n tooltip: {\n ...theme.components.tooltip,\n ...input?.components?.tooltip,\n },\n },\n };\n}\n","import type { KAPLAYCtx } from 'kaplay';\n\nimport { createButton } from './button';\nimport { createModal, createModalManager } from './modal';\nimport { resolveTheme } from './theme';\nimport type {\n AddButton,\n AddModal,\n DeepPartial,\n GetTheme,\n SetTheme,\n Theme,\n} from './types';\n\n/**\n * Kaplay UI plugin.\n *\n * @param k - The Kaplay context.\n * @param theme - The theme config.\n * @returns The plugin methods.\n */\nexport function uiPlugin(k: KAPLAYCtx, theme: DeepPartial<Theme> = {}) {\n let userTheme: DeepPartial<Theme> = theme;\n let currentTheme: Theme = resolveTheme(userTheme);\n\n const getTheme: GetTheme = () => currentTheme;\n\n const setTheme: SetTheme = (next) => {\n userTheme = { ...userTheme, ...next };\n currentTheme = resolveTheme(userTheme);\n };\n\n const addButton: AddButton = (text = '', opt = {}) => {\n return createButton(k, currentTheme, text, opt);\n };\n\n const modalManager = createModalManager(k);\n\n const addModal: AddModal = (opt = {}) => {\n return createModal(k, currentTheme, opt, {\n isTopmost: modalManager.isTopmost,\n onClose: modalManager.closeModal,\n onOpen: modalManager.openModal,\n });\n };\n\n return {\n addButton,\n addModal,\n setTheme,\n getTheme,\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"plugin.umd.min.js","names":["factory","global","exports","MODAL_TAG","uniqueTag","isSized","sized","obj","createModal","theme","fontSize","titleSize","font","dismissible","opt","modal","k","hooks","backdrop","backdropOpacity","content","borderRadius","toColor","actions","action","createButton","titleMeasure","bodyMeasure","textWrapWidth","maxWidth","paddingX","title","body","layout","screenWidth","screenHeight","buttons","actionsWidth","width","button","gap","actionsHeight","textContentWidth","nextWidth","minWidth","titleHeight","bodyHeight","hasTitle","hasBody","hasActions","blockCount","contentHeight","nextHeight","paddingY","y","x","createModalManager","modals","pausedScene","cancelPauseOnAdd","isInsideModal","gameObj","pauseSceneTree","isModal","setPaused","openModal","getLast","controller","closeModal","index","wasTopmost","paused","items","isPaused","includeTree","childGameObj","color","id","radius","baseColor","disabled","hoverColor","pressedColor","textColor","size","pos","textComp","text","isIconOnly","rectOpt","components","background","icon","label","iconWidth","iconHeight","iconState","backgroundWidth","backgroundHeight","backgroundMeasured","backgroundState","contentWidth","contentBoxWidth","contentBoxHeight","applyDisabledState","assignedButton","value","PRIMARY","TEXT","resolveTheme","input","uiPlugin","userTheme","currentTheme","getTheme","setTheme","next","addButton","modalManager"],"sources":["../src/modal.ts","../src/utils.ts","../src/button.ts","../src/theme.ts","../src/plugin.ts"],"sourcesContent":["import type { GameObj, KAPLAYCtx } from 'kaplay';\n\nimport { createButton } from './button';\nimport type { ModalOpt, Theme } from './types';\nimport { getLast, isModal, setPaused, toColor, uniqueTag } from './utils';\n\n/** Tag applied to modal game objects. */\nexport const MODAL_TAG = uniqueTag('modal');\n\ntype SizedGameObj = GameObj & {\n height: number;\n width: number;\n};\n\nexport interface ModalHooks {\n isTopmost: (modal: GameObj) => boolean;\n onClose: (modal: GameObj) => void;\n onOpen: (modal: GameObj) => void;\n}\n\nfunction isSized(obj: GameObj): obj is SizedGameObj {\n const sized = obj as Partial<SizedGameObj>;\n return typeof sized.width === 'number' && typeof sized.height === 'number';\n}\n\nexport function createModal(\n k: KAPLAYCtx,\n theme: Theme,\n opt: ModalOpt,\n hooks: ModalHooks,\n) {\n const {\n backdropOpacity,\n borderRadius,\n gap,\n maxWidth,\n minWidth,\n paddingX,\n paddingY,\n } = theme.components.modal;\n const fontSize = theme.typography.fontSize;\n const titleSize = Math.round(fontSize * 1.25);\n const font = theme.typography.fontFamily;\n const dismissible = opt.dismissible ?? true;\n\n const modal = k.add([\n k.pos(k.center()),\n k.fixed(),\n k.z(opt.z ?? 1000),\n MODAL_TAG,\n ]);\n\n modal.onDestroy(() => {\n hooks.onClose(modal);\n });\n\n hooks.onOpen(modal);\n\n const backdrop = modal.add([\n k.rect(k.width(), k.height()),\n k.pos(),\n k.color(0, 0, 0),\n k.opacity(backdropOpacity),\n k.anchor('center'),\n k.area(),\n ]);\n\n const content = modal.add([\n k.rect(0, 0, borderRadius > 0 ? { radius: borderRadius } : undefined),\n k.pos(),\n k.color(toColor(k, theme.palette.surface)),\n k.anchor('center'),\n k.area(),\n ]);\n\n const actions = content.add([k.pos()]);\n\n for (const action of opt.actions ?? []) {\n createButton(k, theme, action.text, {\n parent: actions,\n onClick: () => {\n if (action.close !== false) {\n modal.destroy();\n }\n\n action.onClick?.();\n },\n });\n }\n\n const titleMeasure = opt.title\n ? k.text(opt.title, { size: titleSize, font })\n : undefined;\n const bodyMeasure = opt.text\n ? k.text(opt.text, { size: fontSize, font })\n : undefined;\n const textWrapWidth = Math.max(0, (opt.width ?? maxWidth) - paddingX * 2);\n\n const title = opt.title\n ? content.add([\n k.text(opt.title, {\n size: titleSize,\n font,\n width: textWrapWidth > 0 ? textWrapWidth : undefined,\n align: 'center',\n }),\n k.pos(),\n k.color(toColor(k, theme.palette.text)),\n k.anchor('center'),\n ])\n : undefined;\n\n const body = opt.text\n ? content.add([\n k.text(opt.text, {\n size: fontSize,\n font,\n width: textWrapWidth > 0 ? textWrapWidth : undefined,\n align: 'center',\n }),\n k.pos(),\n k.color(toColor(k, theme.palette.textSecondary)),\n k.anchor('center'),\n ])\n : undefined;\n\n /**\n * Update modal layout:\n *\n * - fits the backdrop to the screen,\n * - sizes the content to its title, text, and actions,\n * - repositions the title, text, and actions.\n */\n function layout() {\n const screenWidth = k.width();\n const screenHeight = k.height();\n\n if (backdrop.width !== screenWidth) {\n backdrop.width = screenWidth;\n }\n\n if (backdrop.height !== screenHeight) {\n backdrop.height = screenHeight;\n }\n\n modal.pos = k.center();\n\n const buttons = actions.children.filter(isSized);\n const actionsWidth =\n buttons.reduce((width, button) => width + button.width, 0) +\n gap * Math.max(0, buttons.length - 1);\n const actionsHeight = Math.max(\n 0,\n ...buttons.map((button) => button.height),\n );\n\n const textContentWidth = Math.max(\n titleMeasure?.width ?? 0,\n bodyMeasure?.width ?? 0,\n );\n const nextWidth =\n opt.width ??\n Math.max(\n Math.min(textContentWidth, maxWidth - paddingX * 2) + paddingX * 2,\n actionsWidth + paddingX * 2,\n minWidth,\n );\n\n const titleHeight = title?.height ?? 0;\n const bodyHeight = body?.height ?? 0;\n const hasTitle = title !== undefined;\n const hasBody = body !== undefined;\n const hasActions = buttons.length > 0;\n const blockCount = Number(hasTitle) + Number(hasBody) + Number(hasActions);\n const contentHeight =\n titleHeight +\n bodyHeight +\n actionsHeight +\n gap * Math.max(0, blockCount - 1);\n const nextHeight = opt.height ?? contentHeight + paddingY * 2;\n\n if (content.width !== nextWidth) {\n content.width = nextWidth;\n }\n\n if (content.height !== nextHeight) {\n content.height = nextHeight;\n }\n\n let y = -nextHeight / 2 + (nextHeight - contentHeight) / 2;\n\n if (title) {\n title.pos = k.vec2(0, y + title.height / 2);\n y += title.height + gap;\n }\n\n if (body) {\n body.pos = k.vec2(0, y + body.height / 2);\n y += body.height + gap;\n }\n\n if (hasActions) {\n let x = -actionsWidth / 2;\n\n for (const button of buttons) {\n button.pos = k.vec2(x + button.width / 2, y + actionsHeight / 2);\n x += button.width + gap;\n }\n }\n }\n\n layout();\n modal.onUpdate(layout);\n\n if (dismissible) {\n // prevent click-through so modal isn't dismissed right after creation\n k.wait(0, () => {\n backdrop.onClick(() => {\n if (!hooks.isTopmost(modal)) {\n return;\n }\n\n if (content.hasPoint(k.mousePos())) {\n return;\n }\n\n modal.destroy();\n });\n });\n\n modal.onKeyPress('escape', () => {\n if (hooks.isTopmost(modal)) {\n modal.destroy();\n }\n });\n }\n\n return Object.assign(modal, {\n actions,\n content,\n });\n}\n\nexport interface ModalManager {\n openModal: ModalHooks['onOpen'];\n closeModal: ModalHooks['onClose'];\n isTopmost: ModalHooks['isTopmost'];\n isInsideModal: (gameObj: GameObj) => boolean;\n}\n\nexport function createModalManager(k: KAPLAYCtx): ModalManager {\n const modals: GameObj[] = [];\n let pausedScene: Map<GameObj, boolean> | undefined;\n let cancelPauseOnAdd: (() => void) | undefined;\n\n const isInsideModal = (gameObj: GameObj) =>\n modals.some((modal) => modal === gameObj || modal.isAncestorOf(gameObj));\n\n function pauseSceneTree(gameObj: GameObj) {\n if (isModal(gameObj) || isInsideModal(gameObj)) {\n return;\n }\n\n pausedScene ??= new Map();\n\n // v8 ignore if\n if (!pausedScene.has(gameObj)) {\n pausedScene.set(gameObj, gameObj.paused);\n }\n\n setPaused(gameObj);\n gameObj.children.forEach(pauseSceneTree);\n }\n\n function openModal(modal: GameObj) {\n k.setCursor('default');\n\n if (modals.length > 0) {\n setPaused(getLast(modals), true, true);\n }\n\n modals.push(modal);\n\n if (modals.length === 1) {\n pausedScene = new Map();\n k.getTreeRoot().children.forEach(pauseSceneTree);\n\n const controller = k.onAdd((gameObj) => {\n if (!isModal(gameObj) && !isInsideModal(gameObj)) {\n pauseSceneTree(gameObj);\n }\n });\n\n cancelPauseOnAdd = () => {\n controller.cancel();\n };\n }\n }\n\n function closeModal(modal: GameObj) {\n const index = modals.indexOf(modal);\n\n if (index === -1) {\n return;\n }\n\n const wasTopmost = index === modals.length - 1;\n\n modals.splice(index, 1);\n\n if (!modals.length) {\n cancelPauseOnAdd?.();\n cancelPauseOnAdd = undefined;\n\n // v8 ignore if\n if (pausedScene) {\n for (const [gameObj, paused] of pausedScene) {\n if (gameObj.exists()) {\n setPaused(gameObj, paused);\n }\n }\n\n pausedScene = undefined;\n }\n } else if (wasTopmost) {\n setPaused(getLast(modals), false, true);\n }\n }\n\n const isTopmost = (modal: GameObj) => getLast(modals) === modal;\n\n return { openModal, closeModal, isTopmost, isInsideModal };\n}\n","import type { Color, GameObj, KAPLAYCtx } from 'kaplay';\n\nimport { MODAL_TAG } from './modal';\n\n/**\n * Get the last item in an array.\n *\n * @param items - The items.\n * @returns The last item.\n */\nexport function getLast<T>(items: T[]): T {\n return items[items.length - 1];\n}\n\n/**\n * Check if the game object is a modal.\n *\n * @param gameObj - The game object.\n * @returns Whether game object is a modal.\n */\nexport function isModal(gameObj: GameObj): boolean {\n return gameObj.is(MODAL_TAG);\n}\n\n/**\n * Pause or unpause game object(s).\n *\n * @param gameObj - The root game object.\n * @param isPaused - Whether it should be paused.\n * @param includeTree - Whether to pause all game objects in tree.\n */\nexport function setPaused(\n gameObj: GameObj,\n isPaused = true,\n includeTree = false,\n): void {\n gameObj.paused = isPaused;\n\n if (includeTree) {\n gameObj.children.forEach((childGameObj) => {\n setPaused(childGameObj, isPaused, includeTree);\n });\n }\n}\n\n/**\n * Convert a hex string or color to color.\n *\n * @param k - The Kaplay context.\n * @param color - The hex string or color.\n * @returns The color.\n */\nexport function toColor(k: KAPLAYCtx, color: string | Color): Color {\n return typeof color === 'string' ? k.rgb(color) : color;\n}\n\n/**\n * Generate a unique tag.\n *\n * @param id - The identifier.\n * @returns The tag.\n */\nexport function uniqueTag(id: string): string {\n return [\n 'kaplay-plugin-ui',\n id,\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n crypto?.randomUUID?.(),\n ].join(':');\n}\n","import type { Color, KAPLAYCtx, Vec2 } from 'kaplay';\n\nimport type { ButtonOpt, Theme } from './types';\nimport { toColor } from './utils';\n\nexport function createButton(\n k: KAPLAYCtx,\n theme: Theme,\n text: string,\n opt: ButtonOpt & {\n pos?: Vec2;\n onClick?: () => void;\n color?: string | Color;\n hoverColor?: string | Color;\n pressedColor?: string | Color;\n textColor?: string | Color;\n },\n) {\n const radius = opt.radius ?? theme.shape.borderRadius;\n const { minWidth, paddingX, paddingY } = theme.components.button;\n const baseColor = toColor(k, opt.color ?? opt.fill ?? theme.palette.primary);\n let disabled = opt.disabled ?? false;\n const hoverColor = toColor(k, opt.hoverColor ?? theme.palette.hover);\n const pressedColor = toColor(k, opt.pressedColor ?? theme.palette.pressed);\n const textColor = toColor(k, opt.textColor ?? theme.palette.text);\n const size = opt.size ?? theme.typography.fontSize;\n const font = opt.font ?? theme.typography.fontFamily;\n const pos = opt.pos ?? k.vec2();\n\n const textComp = k.text(text, { size, font });\n const isIconOnly = opt.icon !== undefined && !text.length;\n const hasBackground = opt.background !== undefined;\n const showFill = !hasBackground && !isIconOnly;\n const rectOpt = showFill\n ? radius > 0\n ? { radius }\n : undefined\n : { fill: false };\n\n const components = [\n k.rect(0, 0, rectOpt),\n k.pos(pos),\n k.area(),\n k.scale(),\n k.color(disabled ? toColor(k, theme.palette.disabled) : baseColor),\n k.anchor('center'),\n ];\n const button = opt.parent ? opt.parent.add(components) : k.add(components);\n\n const background = opt.background\n ? button.add([k.sprite(opt.background), k.pos(), k.anchor('center')])\n : undefined;\n\n const icon = opt.icon\n ? button.add([k.sprite(opt.icon), k.pos(), k.anchor('center')])\n : undefined;\n\n const label = button.add([\n textComp,\n k.color(textColor as never),\n k.pos(),\n k.anchor('center'),\n ]);\n\n let iconWidth = 0;\n let iconHeight = 0;\n let iconState: 'base' | 'hover' | 'pressed' = 'base';\n let backgroundWidth = 0;\n let backgroundHeight = 0;\n let backgroundMeasured = false;\n let backgroundState: 'base' | 'hover' | 'pressed' = 'base';\n\n /**\n * Update button layout:\n *\n * - caches the base icon/background dimensions,\n * - calculates content width/height,\n * - updates `button.width` / `button.height`,\n * - stretches the background and repositions the icon and label.\n */\n function layout() {\n if (icon && iconState === 'base') {\n iconWidth = icon.width;\n iconHeight = icon.height;\n }\n\n if (\n background &&\n backgroundState === 'base' &&\n !backgroundMeasured &&\n (background.width > 0 || background.height > 0)\n ) {\n backgroundWidth = background.width;\n backgroundHeight = background.height;\n backgroundMeasured = true;\n }\n\n const gap = icon && iconWidth > 0 && label.width > 0 ? theme.spacing : 0;\n const contentWidth = iconWidth + gap + label.width;\n const contentHeight = Math.max(iconHeight, label.height);\n const contentBoxWidth = contentWidth + paddingX * 2;\n const contentBoxHeight = contentHeight + paddingY * 2;\n\n const nextWidth =\n opt.width ??\n (background\n ? Math.max(contentBoxWidth, backgroundWidth)\n : Math.max(contentBoxWidth, minWidth));\n const nextHeight =\n opt.height ??\n (background\n ? Math.max(contentBoxHeight, backgroundHeight)\n : contentBoxHeight);\n\n if (button.width !== nextWidth) {\n button.width = nextWidth;\n }\n\n if (button.height !== nextHeight) {\n button.height = nextHeight;\n }\n\n if (background && backgroundMeasured) {\n background.width = nextWidth;\n background.height = nextHeight;\n background.pos = k.vec2();\n }\n\n if (icon) {\n icon.pos = k.vec2(-contentWidth / 2 + iconWidth / 2, 0);\n }\n\n label.pos = k.vec2(\n -contentWidth / 2 + iconWidth + gap + label.width / 2,\n 0,\n );\n }\n\n layout();\n\n if (\n opt.width === undefined ||\n opt.height === undefined ||\n opt.icon !== undefined ||\n opt.background !== undefined\n ) {\n button.onUpdate(layout);\n }\n\n button.onClick(() => {\n if (disabled) {\n return;\n }\n\n // prevent click-through\n k.wait(0, () => {\n button.color = toColor(k, pressedColor);\n\n if (icon && opt.pressedIcon) {\n iconState = 'pressed';\n icon.sprite = opt.pressedIcon as string;\n }\n\n if (background && opt.pressedBackground) {\n backgroundState = 'pressed';\n background.sprite = opt.pressedBackground as string;\n }\n\n opt.onClick?.();\n\n // v8 ignore if\n if (disabled) {\n k.setCursor('not-allowed');\n } else if (!button.isHovering()) {\n k.setCursor('default');\n }\n });\n });\n\n button.onHover(() => {\n if (disabled) {\n k.setCursor('not-allowed');\n return;\n } else {\n k.setCursor('pointer');\n }\n\n button.color = toColor(k, hoverColor);\n button.scale = k.vec2(1.05, 1.05);\n\n if (icon && opt.hoverIcon) {\n iconState = 'hover';\n icon.sprite = opt.hoverIcon as string;\n }\n\n if (background && opt.hoverBackground) {\n backgroundState = 'hover';\n background.sprite = opt.hoverBackground as string;\n }\n });\n\n button.onHoverEnd(() => {\n k.setCursor('default');\n\n if (disabled) {\n return;\n }\n\n button.color = baseColor;\n button.scale = k.vec2(1, 1);\n\n if (icon && opt.icon) {\n iconState = 'base';\n icon.sprite = opt.icon as string;\n }\n\n if (background && opt.background) {\n backgroundState = 'base';\n background.sprite = opt.background as string;\n }\n\n if (icon || background) {\n layout();\n }\n });\n\n button.onDestroy(() => {\n if (button.isHovering()) {\n k.setCursor('default');\n }\n });\n\n function applyDisabledState() {\n button.color = disabled ? toColor(k, theme.palette.disabled) : baseColor;\n }\n\n applyDisabledState();\n\n const assignedButton = Object.assign(button, {\n background,\n icon,\n label,\n });\n\n const definedButton = Object.defineProperty(assignedButton, 'disabled', {\n get() {\n return disabled;\n },\n set(value: boolean) {\n disabled = value;\n applyDisabledState();\n },\n enumerable: true,\n configurable: true,\n }) as typeof assignedButton & { disabled: boolean };\n\n return definedButton;\n}\n","import type { DeepPartial, Theme } from './types';\n\nconst PRIMARY = '#4f8cff';\nconst TEXT = '#ffffff';\n\n/** Default theme. */\nconst theme: Theme = {\n palette: {\n primary: PRIMARY,\n secondary: PRIMARY,\n background: '#111827',\n surface: '#1f2937',\n text: TEXT,\n textSecondary: TEXT,\n hover: PRIMARY,\n pressed: PRIMARY,\n disabled: '#6b7280',\n error: '#ef4444',\n },\n typography: {\n fontFamily: 'sans-serif',\n fontSize: 18,\n fontWeight: 400,\n lineHeight: 1.2,\n },\n shape: {\n borderRadius: 0,\n },\n spacing: 8,\n components: {\n button: {\n minWidth: 120,\n paddingX: 12,\n paddingY: 8,\n textTransform: 'none',\n },\n modal: {\n backdropOpacity: 0.5,\n borderRadius: 8,\n gap: 16,\n maxWidth: 480,\n minWidth: 280,\n paddingX: 24,\n paddingY: 20,\n },\n tooltip: {\n background: '#111827',\n textColor: TEXT,\n delay: 300,\n },\n },\n};\n\n/** Resolve a partial theme into a fully resolved theme. */\nexport function resolveTheme(input?: DeepPartial<Theme>): Theme {\n return {\n palette: { ...theme.palette, ...input?.palette },\n typography: { ...theme.typography, ...input?.typography },\n shape: { ...theme.shape, ...input?.shape },\n spacing: input?.spacing ?? theme.spacing,\n components: {\n button: {\n ...theme.components.button,\n ...input?.components?.button,\n },\n modal: {\n ...theme.components.modal,\n ...input?.components?.modal,\n },\n tooltip: {\n ...theme.components.tooltip,\n ...input?.components?.tooltip,\n },\n },\n };\n}\n","import type { KAPLAYCtx } from 'kaplay';\n\nimport { createButton } from './button';\nimport { createModal, createModalManager } from './modal';\nimport { resolveTheme } from './theme';\nimport type {\n AddButton,\n AddModal,\n DeepPartial,\n GetTheme,\n SetTheme,\n Theme,\n} from './types';\n\n/**\n * Kaplay UI plugin.\n *\n * @param k - The Kaplay context.\n * @param theme - The theme config.\n * @returns The plugin methods.\n */\nexport function uiPlugin(k: KAPLAYCtx, theme: DeepPartial<Theme> = {}) {\n let userTheme: DeepPartial<Theme> = theme;\n let currentTheme: Theme = resolveTheme(userTheme);\n\n const getTheme: GetTheme = () => currentTheme;\n\n const setTheme: SetTheme = (next) => {\n userTheme = { ...userTheme, ...next };\n currentTheme = resolveTheme(userTheme);\n };\n\n const addButton: AddButton = (text = '', opt = {}) => {\n return createButton(k, currentTheme, text, opt);\n };\n\n const modalManager = createModalManager(k);\n\n const addModal: AddModal = (opt = {}) => {\n return createModal(k, currentTheme, opt, {\n isTopmost: modalManager.isTopmost,\n onClose: modalManager.closeModal,\n onOpen: modalManager.openModal,\n });\n };\n\n return {\n addButton,\n addModal,\n setTheme,\n getTheme,\n };\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kaplay-plugin-ui",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "Kaplay plugin UI",
5
5
  "author": "Mark <mark@remarkablemark.org> (https://remarkablemark.org)",
6
6
  "main": "./dist/plugin.cjs",