UNPKG

@mai3/phaser-sdk

Version:

A UI component library based on the Phaser game engine

535 lines (534 loc) 21.8 kB
var __extends = (this && this.__extends) || (function () { var extendStatics = function (d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; return extendStatics(d, b); }; return function (d, b) { if (typeof b !== "function" && b !== null) throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); var __assign = (this && this.__assign) || function () { __assign = Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; import Phaser from 'phaser'; import { Container } from './Container'; import { Label } from './Label'; /** * A text input box component that supports text selection, cursor movement, and IME input */ var TextBox = /** @class */ (function (_super) { __extends(TextBox, _super); function TextBox(scene, config) { var _a, _b; var _this = _super.call(this, scene, config, 'TextBox') || this; _this.isFocus = false; _this.charWidths = []; _this.isSelecting = false; _this.maxWidth = 100; _this.isComposing = false; _this.compositionText = ''; _this.placeholder = ''; _this.previousValue = ''; _this._config = config; _this.scene = scene; config.text = (_a = config.text) !== null && _a !== void 0 ? _a : ''; _this.previousValue = config.text; _this.placeholder = (_b = config.placeholder) !== null && _b !== void 0 ? _b : ''; _this.Type = 'TextBox'; _this.reDraw(config); return _this; } TextBox.prototype.reDraw = function (config) { if (!config) return; this._config = config; this.clearPreviousElements(); this.initializeState(config); this.createLabel(config); this.createCursor(config); this.createSelection(); this.setupEventHandlers(); this.createHiddenInput(); this.updateConfig(config); this.RefreshBounds(); this.setupKeyboardEvents(); this.updatePlaceholder(); }; TextBox.prototype.clearPreviousElements = function () { if (this.label) this.label.destroy(); if (this.cursor) this.cursor.destroy(); if (this.selection) this.selection.destroy(); if (this.hiddenInput) this.hiddenInput.remove(); }; TextBox.prototype.initializeState = function (config) { var _a, _b, _c; this.isFocus = false; this.maxWidth = (_a = config.width) !== null && _a !== void 0 ? _a : 100; this.isSelecting = false; this.selectionStart = undefined; this.selectionEnd = undefined; this.isComposing = false; this.compositionText = ''; this.charWidths = []; this.placeholder = (_b = config.placeholder) !== null && _b !== void 0 ? _b : ''; this.previousValue = (_c = config.text) !== null && _c !== void 0 ? _c : ''; }; TextBox.prototype.createLabel = function (config) { this.label = new Label(this.scene, config); if (config.text) { this.label.Text = config.text; if (config.textStyle) { this.label.Label.setStyle(config.textStyle); } } else { this.label.Text = this.placeholder; if (config.textStyle) { this.label.Label.setStyle(__assign(__assign({}, config.textStyle), { color: '#999' })); } } this.label.setPosition(0, 0); this.addChildAt(this.label, 0); }; TextBox.prototype.updatePlaceholder = function () { if (!this.label || !this._config) return; if (!this.label.Text && !this.isComposing) { this.label.Text = this.placeholder; if (this._config.textStyle) { this.label.Label.setStyle(__assign(__assign({}, this._config.textStyle), { color: '#999' })); } this.cursor.setVisible(false); } else if (this.label.Text && this.label.Text !== this.placeholder) { if (this._config.textStyle) { this.label.Label.setStyle(this._config.textStyle); } } }; TextBox.prototype.createCursor = function (config) { var _a; this.cursor = this.scene.make.text({ style: { color: '#383838', fontSize: (_a = config.textStyle) === null || _a === void 0 ? void 0 : _a.fontSize } }); this.cursor.text = "|"; this.cursor.setOrigin(0); this.cursor.x = this.label.TextWidth + 2; this.cursor.y = (this.label.RealHeight - this.cursor.displayHeight) / 2; this.cursor.setVisible(false); this.addChildAt(this.cursor, 1); }; TextBox.prototype.createSelection = function () { this.selection = this.scene.add.rectangle(0, 0, 0, 0, 0xEE6363, 0.5); this.selection.setOrigin(0); this.selection.y = this.cursor.y; this.selection.height = this.cursor.displayHeight; this.addChildAt(this.selection, 1); }; TextBox.prototype.setupEventHandlers = function () { this.setEventInteractive(); this.on('pointerover', this.handleOver, this); this.on('pointerout', this.handleOut, this); this.on('pointerup', this.handlePointerUp, this); this.on('pointerdown', this.handlePointerDown, this); this.on('pointermove', this.handlePointerMove, this); }; TextBox.prototype.setupKeyboardEvents = function () { var keyboard = this.scene.input.keyboard; if (!keyboard) return; keyboard.off('keyup', this.handleKeyup, this); keyboard.off('keydown', this.handleKeydown, this); keyboard.on('keyup', this.handleKeyup, this); keyboard.on('keydown', this.handleKeydown, this); }; TextBox.prototype.handleKeydown = function (event) { if (!this.isFocus || !this.hiddenInput || TextBox.activeTextBox !== this) return; this.updateCursorPosition(); }; TextBox.prototype.handleKeyup = function (event) { if (!this.isFocus || TextBox.activeTextBox !== this) return; if (event.key === 'ArrowLeft' || event.key === 'ArrowRight' || event.key === 'Backspace') { this.handleMoveCursor(); } }; TextBox.prototype.getCursorPosition = function () { var _a, _b; return (_b = (_a = this.hiddenInput) === null || _a === void 0 ? void 0 : _a.selectionStart) !== null && _b !== void 0 ? _b : 0; }; TextBox.prototype.updateCharWidths = function () { this.charWidths = this.getCharacterWidths(); }; TextBox.prototype.createHiddenInput = function () { this.hiddenInput = document.createElement('input'); this.setupHiddenInputStyles(); this.hiddenInput.value = this.label.Text === this.placeholder ? '' : this.label.Text; document.body.appendChild(this.hiddenInput); this.setupHiddenInputEvents(); }; TextBox.prototype.setupHiddenInputStyles = function () { if (!this.hiddenInput) return; this.hiddenInput.type = 'text'; this.hiddenInput.style.position = 'absolute'; this.hiddenInput.style.opacity = '0'; this.hiddenInput.style.pointerEvents = 'none'; this.hiddenInput.style.zIndex = '-1'; this.hiddenInput.style.top = '-1000px'; }; TextBox.prototype.setupHiddenInputEvents = function () { var _this = this; if (!this.hiddenInput) return; this.hiddenInput.addEventListener('input', function (e) { var inputEvent = e; if (!_this.isComposing) { _this.handleInput(); _this.updateCursorPosition(); } else if (inputEvent.inputType === 'insertCompositionText') { _this.compositionText = e.target.value; _this.label.Text = _this.compositionText; _this.updateCharWidths(); _this.updateCursorPosition(); } _this.updatePlaceholder(); }); this.hiddenInput.addEventListener('compositionstart', function () { var _a; _this.isComposing = true; _this.compositionText = ((_a = _this.hiddenInput) === null || _a === void 0 ? void 0 : _a.value) || ''; }); this.hiddenInput.addEventListener('compositionend', function () { _this.handleCompositionEnd(); _this.updateCursorPosition(); _this.updatePlaceholder(); }); this.hiddenInput.addEventListener('blur', function () { _this.isFocus = false; _this.cursor.setVisible(false); if (_this.timerEvent) { _this.timerEvent.remove(); _this.timerEvent = undefined; } _this.updatePlaceholder(); }); this.hiddenInput.addEventListener('focus', function () { _this.isFocus = true; if (_this.label.Text !== _this.placeholder) { _this.cursor.setVisible(true); _this.addTimerEvent(); } }); document.addEventListener('selectionchange', function () { if (TextBox.activeTextBox === _this && document.activeElement === _this.hiddenInput) { _this.updateCursorPosition(); } }); }; TextBox.prototype.handleInput = function () { var _a; if (!this.hiddenInput) return; var newValue = this.hiddenInput.value; this.label.Text = newValue; this.updateCharWidths(); this.updateSelectionAfterInput(); this.updateCursorPosition(); if (((_a = this._config) === null || _a === void 0 ? void 0 : _a.onChange) && newValue !== this.previousValue) { this._config.onChange(newValue); this.previousValue = newValue; } }; TextBox.prototype.handleCompositionEnd = function () { var _a; if (!this.hiddenInput) return; this.isComposing = false; this.compositionText = ''; var newValue = this.hiddenInput.value; this.label.Text = newValue; this.updateCharWidths(); this.updateSelectionAfterInput(); this.updateCursorPosition(); if (((_a = this._config) === null || _a === void 0 ? void 0 : _a.onChange) && newValue !== this.previousValue) { this._config.onChange(newValue); this.previousValue = newValue; } }; TextBox.prototype.getTextWidth = function (text) { var context = this.getOrCreateMeasureContext(); if (!context) return 0; var fontSize = this.label.Label.style.fontSize || '16px'; var fontFamily = this.label.Label.style.fontFamily || 'Arial'; context.font = "".concat(fontSize, " ").concat(fontFamily); return context.measureText(text).width; }; TextBox.prototype.getOrCreateMeasureContext = function () { if (!TextBox.measureCanvas) { TextBox.measureCanvas = document.createElement('canvas'); TextBox.measureContext = TextBox.measureCanvas.getContext('2d'); } return TextBox.measureContext; }; TextBox.prototype.updateSelectionAfterInput = function () { var _a; if (!this.hiddenInput) return; var cursorPosition = (_a = this.hiddenInput.selectionStart) !== null && _a !== void 0 ? _a : 0; this.selectionStart = cursorPosition; this.selectionEnd = cursorPosition; this.selection.width = 0; this.selection.setVisible(false); this.cursor.x = this.getCharacterXPosition(cursorPosition) + 2; if (this.label.Text !== this.placeholder) { this.cursor.setVisible(true); } }; TextBox.prototype.handleOver = function () { this.scene.input.setDefaultCursor('text'); }; TextBox.prototype.handleOut = function () { this.scene.input.setDefaultCursor('default'); }; TextBox.prototype.handlePointerDown = function (pointer) { var _this = this; var _a; if (TextBox.activeTextBox && TextBox.activeTextBox !== this) { this.deactivateTextBox(TextBox.activeTextBox); } TextBox.activeTextBox = this; this.isFocus = true; // Clear text if currently showing placeholder when clicked if (this.label.Text === this.placeholder) { this.label.Text = ''; if (this.hiddenInput) this.hiddenInput.value = ''; if ((_a = this._config) === null || _a === void 0 ? void 0 : _a.textStyle) { this.label.Label.setStyle(this._config.textStyle); } } var worldPoint = this.getLabelWorldPoint(); var cursorX = Math.min(pointer.x - worldPoint.x, this.label.TextWidth); this.cursor.x = cursorX + 2; if (this.label.Text !== this.placeholder) { this.cursor.setVisible(true); this.addTimerEvent(); } this.resetSelection(); this.setDomCursorPosition(); this.setNativeCursorPosition(); this.selection.x = this.cursor.x; this.selectionStart = this.getCursorPosition(); this.selectionEnd = this.selectionStart; this.isSelecting = true; // Focus the hidden input after a short delay setTimeout(function () { var _a; (_a = _this.hiddenInput) === null || _a === void 0 ? void 0 : _a.focus(); }, 10); }; TextBox.prototype.deactivateTextBox = function (textBox) { textBox.isFocus = false; textBox.cursor.setVisible(false); textBox.selection.setVisible(false); if (textBox.timerEvent) { textBox.timerEvent.remove(); textBox.timerEvent = undefined; } // Show placeholder when losing focus if there is no content textBox.updatePlaceholder(); }; TextBox.prototype.resetSelection = function () { this.selection.width = 0; this.selection.setVisible(false); }; TextBox.prototype.handlePointerMove = function (pointer) { if (!this.isSelecting || TextBox.activeTextBox !== this) return; var worldPoint = this.getLabelWorldPoint(); var cursorX = Math.max(0, Math.min(pointer.x - worldPoint.x, this.label.TextWidth)); this.selectionEnd = this.getCharacterIndexAtPosition(cursorX); this.updateSelection(); }; TextBox.prototype.handlePointerUp = function () { this.isSelecting = false; if (this.selectionStart === this.selectionEnd) { this.selection.setVisible(false); } }; TextBox.prototype.updateCursorPosition = function () { var _a, _b; if (!this.isFocus || TextBox.activeTextBox !== this) return; var cursorPosition = (_b = (_a = this.hiddenInput) === null || _a === void 0 ? void 0 : _a.selectionStart) !== null && _b !== void 0 ? _b : 0; this.updateCharWidths(); this.cursor.x = this.getCharacterXPosition(cursorPosition) + 2; if (this.label.Text !== this.placeholder) { this.cursor.setVisible(true); } this.resetCursorBlink(); }; TextBox.prototype.resetCursorBlink = function () { var _this = this; if (!this.timerEvent) return; this.timerEvent.reset({ delay: 800, callback: function () { if (_this.label.Text !== _this.placeholder) { _this.cursor.visible = !_this.cursor.visible; } }, loop: true }); }; TextBox.prototype.updateSelection = function () { var _a; if (this.selectionStart === undefined || this.selectionEnd === undefined) return; var _b = [ Math.min(this.selectionStart, this.selectionEnd), Math.max(this.selectionStart, this.selectionEnd) ], start = _b[0], end = _b[1]; var startX = this.getCharacterXPosition(start); var endX = this.getCharacterXPosition(end); this.updateSelectionVisuals(startX, endX); (_a = this.hiddenInput) === null || _a === void 0 ? void 0 : _a.setSelectionRange(start, end); }; TextBox.prototype.updateSelectionVisuals = function (startX, endX) { this.selection.x = startX; this.selection.width = endX - startX; this.selection.setVisible(true); this.cursor.x = endX + 2; if (this.label.Text !== this.placeholder) { this.cursor.setVisible(true); } }; TextBox.prototype.handleMoveCursor = function () { this.setNativeCursorPosition(); }; TextBox.prototype.setDomCursorPosition = function () { if (!this.hiddenInput) return; this.cursor.x = Math.min(this.cursor.x, this.label.TextWidth + 2); var characterIndex = this.getCharacterIndexAtPosition(this.cursor.x - 2); this.hiddenInput.setSelectionRange(characterIndex, characterIndex); }; TextBox.prototype.setNativeCursorPosition = function () { var characterIndex = this.getCursorPosition(); this.cursor.x = this.getCharacterXPosition(characterIndex) + 2; if (this.label.Text !== this.placeholder) { this.cursor.setVisible(true); } }; TextBox.prototype.getCharacterIndexAtPosition = function (x) { var _a; this.charWidths = this.getCharacterWidths(); var accumulatedWidth = 0; for (var i = 0; i < this.label.Text.length; i++) { accumulatedWidth += (_a = this.charWidths[i]) !== null && _a !== void 0 ? _a : 0; if (x < accumulatedWidth) { return i; } } return this.label.Text.length; }; TextBox.prototype.getCharacterWidths = function () { var _a, _b; var context = this.getOrCreateMeasureContext(); if (!context) return []; var fontSize = this.label.Label.style.fontSize || '16px'; var fontFamily = this.label.Label.style.fontFamily || 'Arial'; context.font = "".concat(fontSize, " ").concat(fontFamily); var text = this.isComposing ? this.compositionText : ((_b = (_a = this.hiddenInput) === null || _a === void 0 ? void 0 : _a.value) !== null && _b !== void 0 ? _b : ''); return Array.from(text).map(function (char) { return context.measureText(char).width; }); }; TextBox.prototype.getCharacterXPosition = function (index) { var charWidths = this.getCharacterWidths(); index = Math.min(index, charWidths.length); return charWidths.slice(0, index).reduce(function (sum, width) { return sum + (width !== null && width !== void 0 ? width : 0); }, 0); }; TextBox.prototype.addTimerEvent = function () { var _this = this; if (this.timerEvent) return; this.timerEvent = this.scene.time.addEvent({ delay: 800, callback: function () { if (_this.label.Text !== _this.placeholder) { _this.cursor.visible = !_this.cursor.visible; } }, callbackScope: this, loop: true }); }; TextBox.prototype.getLabelWorldPoint = function () { var worldPoint = new Phaser.Math.Vector2(); var transformMatrix = this.label.getWorldTransformMatrix(); transformMatrix.transformPoint(this.label.x, this.label.y, worldPoint); return worldPoint; }; TextBox.prototype.destroy = function (fromScene) { if (TextBox.activeTextBox === this) { TextBox.activeTextBox = null; } this.removeEventListeners(); this.destroyComponents(fromScene); _super.prototype.destroy.call(this, fromScene); }; TextBox.prototype.removeEventListeners = function () { this.off('pointerover', this.handleOver); this.off('pointerout', this.handleOut); this.off('pointerup', this.handlePointerUp); this.off('pointerdown', this.handlePointerDown); this.off('pointermove', this.handlePointerMove); var keyboard = this.scene.input.keyboard; if (keyboard) { keyboard.off('keyup', this.handleKeyup); keyboard.off('keydown', this.handleKeydown); } }; TextBox.prototype.destroyComponents = function (fromScene) { var _a; this.label.destroy(fromScene); this.cursor.destroy(fromScene); this.selection.destroy(fromScene); if (this.timerEvent) { this.timerEvent.remove(); this.timerEvent.destroy(); } (_a = this.hiddenInput) === null || _a === void 0 ? void 0 : _a.remove(); }; TextBox.measureCanvas = null; TextBox.measureContext = null; TextBox.activeTextBox = null; return TextBox; }(Container)); export { TextBox };