@mai3/phaser-sdk
Version:
A UI component library based on the Phaser game engine
535 lines (534 loc) • 21.8 kB
JavaScript
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 };