green-screen-react 1.1.3 → 1.2.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/index.js CHANGED
@@ -102,6 +102,14 @@ var RestAdapter = class {
102
102
  async setCursor(row, col) {
103
103
  return this.request("POST", "/set-cursor", { row, col });
104
104
  }
105
+ async readMdt(modifiedOnly = true) {
106
+ const qs = modifiedOnly ? "" : "?includeUnmodified=1";
107
+ const resp = await this.request(
108
+ "GET",
109
+ `/read-mdt${qs}`
110
+ );
111
+ return resp?.fields ?? [];
112
+ }
105
113
  async connect(config) {
106
114
  return this.request("POST", "/connect", config);
107
115
  }
@@ -122,9 +130,12 @@ var WebSocketAdapter = class _WebSocketAdapter {
122
130
  this.status = { connected: false, status: "disconnected" };
123
131
  this.pendingScreenResolver = null;
124
132
  this.pendingConnectResolver = null;
133
+ this.pendingMdtResolver = null;
125
134
  this.connectingPromise = null;
126
135
  this.screenListeners = /* @__PURE__ */ new Set();
127
136
  this.statusListeners = /* @__PURE__ */ new Set();
137
+ this.sessionLostListeners = /* @__PURE__ */ new Set();
138
+ this.sessionResumedListeners = /* @__PURE__ */ new Set();
128
139
  this._sessionId = null;
129
140
  this.workerUrl = (options.workerUrl || _WebSocketAdapter.detectEnvUrl() || "http://localhost:3001").replace(/\/+$/, "");
130
141
  }
@@ -159,6 +170,22 @@ var WebSocketAdapter = class _WebSocketAdapter {
159
170
  this.statusListeners.add(listener);
160
171
  return () => this.statusListeners.delete(listener);
161
172
  }
173
+ /**
174
+ * Subscribe to session-lost notifications. Fires when the proxy detects
175
+ * that the server-side session has terminated (host TCP drop, idle
176
+ * timeout, explicit destroy). Integrators use this to prompt a reconnect
177
+ * or swap to a "session expired" UI without relying on error-string
178
+ * matching.
179
+ */
180
+ onSessionLost(listener) {
181
+ this.sessionLostListeners.add(listener);
182
+ return () => this.sessionLostListeners.delete(listener);
183
+ }
184
+ /** Subscribe to session-resumed notifications (fires after a successful `reattach`). */
185
+ onSessionResumed(listener) {
186
+ this.sessionResumedListeners.add(listener);
187
+ return () => this.sessionResumedListeners.delete(listener);
188
+ }
162
189
  async getScreen() {
163
190
  return this.screen;
164
191
  }
@@ -174,6 +201,25 @@ var WebSocketAdapter = class _WebSocketAdapter {
174
201
  async setCursor(row, col) {
175
202
  return this.sendAndWaitForScreen({ type: "setCursor", row, col });
176
203
  }
204
+ async readMdt(modifiedOnly = true) {
205
+ await this.ensureWebSocket();
206
+ return new Promise((resolve) => {
207
+ if (this.pendingMdtResolver) {
208
+ const old = this.pendingMdtResolver;
209
+ this.pendingMdtResolver = null;
210
+ old([]);
211
+ }
212
+ const timeout = setTimeout(() => {
213
+ this.pendingMdtResolver = null;
214
+ resolve([]);
215
+ }, 5e3);
216
+ this.pendingMdtResolver = (fields) => {
217
+ clearTimeout(timeout);
218
+ resolve(fields);
219
+ };
220
+ this.wsSend({ type: "readMdt", modifiedOnly });
221
+ });
222
+ }
177
223
  async connect(config) {
178
224
  await this.ensureWebSocket();
179
225
  if (!config) {
@@ -287,6 +333,22 @@ var WebSocketAdapter = class _WebSocketAdapter {
287
333
  }
288
334
  break;
289
335
  }
336
+ case "mdt": {
337
+ if (this.pendingMdtResolver) {
338
+ const resolver = this.pendingMdtResolver;
339
+ this.pendingMdtResolver = null;
340
+ resolver(msg.data?.fields ?? []);
341
+ }
342
+ break;
343
+ }
344
+ case "session.lost": {
345
+ for (const listener of this.sessionLostListeners) listener(msg.sessionId, msg.status);
346
+ break;
347
+ }
348
+ case "session.resumed": {
349
+ for (const listener of this.sessionResumedListeners) listener(msg.sessionId);
350
+ break;
351
+ }
290
352
  case "status":
291
353
  this.status = msg.data;
292
354
  for (const listener of this.statusListeners) listener(msg.data);
@@ -957,10 +1019,15 @@ var RefreshIcon = ({ size = 12, className }) => /* @__PURE__ */ (0, import_jsx_r
957
1019
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15" })
958
1020
  ] });
959
1021
  var KeyIcon = ({ size = 12, style: s }) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", style: s, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M21 2l-2 2m-7.61 7.61a5.5 5.5 0 1 1-7.778 7.778 5.5 5.5 0 0 1 7.777-7.777zm0 0L15.5 7.5m0 0l3 3L22 7l-3-3m-3.5 3.5L19 4" }) });
960
- var HelpIcon = ({ size = 14 }) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
961
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: "12", cy: "12", r: "10" }),
962
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3" }),
963
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "12", y1: "17", x2: "12.01", y2: "17" })
1022
+ var KeyboardIcon = ({ size = 14 }) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
1023
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { x: "2", y: "6", width: "20", height: "12", rx: "2", ry: "2" }),
1024
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "6", y1: "10", x2: "6", y2: "10" }),
1025
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "10", y1: "10", x2: "10", y2: "10" }),
1026
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "14", y1: "10", x2: "14", y2: "10" }),
1027
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "18", y1: "10", x2: "18", y2: "10" }),
1028
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "6", y1: "14", x2: "6", y2: "14" }),
1029
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "18", y1: "14", x2: "18", y2: "14" }),
1030
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "10", y1: "14", x2: "14", y2: "14" })
964
1031
  ] });
965
1032
  var MinimizeIcon = () => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { width: 14, height: 14, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M4 14h6v6M3 21l7-7M20 10h-6V4M21 3l-7 7" }) });
966
1033
 
@@ -1085,8 +1152,201 @@ function InlineSignIn({ defaultProtocol, loading: externalLoading, error, onConn
1085
1152
  ] });
1086
1153
  }
1087
1154
 
1155
+ // src/utils/attribute.ts
1156
+ function decodeAttrByte(byte) {
1157
+ const type = byte & 7;
1158
+ const colorGroup = byte & 24;
1159
+ const highIntensity = (byte & 2) !== 0;
1160
+ let color = "green";
1161
+ switch (colorGroup) {
1162
+ case 0:
1163
+ color = highIntensity ? "white" : "green";
1164
+ break;
1165
+ case 8:
1166
+ color = "red";
1167
+ break;
1168
+ // red stays red at HI
1169
+ case 16:
1170
+ color = highIntensity ? "yellow" : "turquoise";
1171
+ break;
1172
+ case 24:
1173
+ color = highIntensity ? "blue" : "pink";
1174
+ break;
1175
+ }
1176
+ const nonDisplay = type === 7;
1177
+ const underscore = type === 4 || type === 5 || type === 6;
1178
+ const reverse = type === 5;
1179
+ const columnSeparator = type === 1 || type === 3;
1180
+ const hiTypeBit = type === 2 || type === 3 || type === 6;
1181
+ return {
1182
+ color,
1183
+ highIntensity: highIntensity || hiTypeBit,
1184
+ underscore,
1185
+ reverse,
1186
+ blink: false,
1187
+ // 5250 has no base-attr blink; blink comes from WEA highlight
1188
+ nonDisplay,
1189
+ columnSeparator
1190
+ };
1191
+ }
1192
+ function decodeExtColor(byte) {
1193
+ switch (byte & 15) {
1194
+ case 0:
1195
+ return "green";
1196
+ case 1:
1197
+ return "blue";
1198
+ case 2:
1199
+ return "red";
1200
+ case 3:
1201
+ return "pink";
1202
+ case 4:
1203
+ return "turquoise";
1204
+ case 5:
1205
+ return "yellow";
1206
+ case 6:
1207
+ return "white";
1208
+ case 7:
1209
+ return void 0;
1210
+ // default — inherit
1211
+ // Reverse-image pairs: distinct tokens so themes can override
1212
+ case 8:
1213
+ return "green";
1214
+ // reverse green → typically same color, background flip
1215
+ case 9:
1216
+ return "blue";
1217
+ case 10:
1218
+ return "red";
1219
+ case 11:
1220
+ return "pink";
1221
+ case 12:
1222
+ return "turquoise";
1223
+ case 13:
1224
+ return "yellow";
1225
+ case 14:
1226
+ return "white";
1227
+ case 15:
1228
+ return void 0;
1229
+ default:
1230
+ return void 0;
1231
+ }
1232
+ }
1233
+ function extColorIsReverse(byte) {
1234
+ const v = byte & 15;
1235
+ return v >= 8 && v <= 14;
1236
+ }
1237
+ function decodeExtHighlight(byte) {
1238
+ return {
1239
+ underscore: (byte & 1) !== 0,
1240
+ reverse: (byte & 2) !== 0,
1241
+ blink: (byte & 4) !== 0,
1242
+ columnSeparator: (byte & 8) !== 0
1243
+ };
1244
+ }
1245
+ function cssVarForColor(color) {
1246
+ if (!color) return "var(--gs-green, #10b981)";
1247
+ return `var(--gs-${color}, var(--gs-green, #10b981))`;
1248
+ }
1249
+
1250
+ // src/utils/validation.ts
1251
+ function filterFieldChar(field, ch, isLastPosition = false) {
1252
+ let out = ch;
1253
+ if (field.monocase) {
1254
+ out = out.toUpperCase();
1255
+ }
1256
+ const cp = out.charCodeAt(0);
1257
+ const isAsciiLetter = cp >= 65 && cp <= 90 || cp >= 97 && cp <= 122;
1258
+ const isAsciiDigit = cp >= 48 && cp <= 57;
1259
+ switch (field.shift_type) {
1260
+ case "alpha_only": {
1261
+ if (isAsciiLetter || out === "," || out === "-" || out === "." || out === " ") return out;
1262
+ return null;
1263
+ }
1264
+ case "numeric_only": {
1265
+ if (isAsciiDigit || out === "-" || out === "+" || out === "," || out === "." || out === " ") return out;
1266
+ return null;
1267
+ }
1268
+ case "digits_only": {
1269
+ if (isAsciiDigit) return out;
1270
+ return null;
1271
+ }
1272
+ case "signed_num": {
1273
+ if (isAsciiDigit) return out;
1274
+ if (isLastPosition && (out === "-" || out === "+")) return out;
1275
+ return null;
1276
+ }
1277
+ case "katakana": {
1278
+ if (cp >= 65377 && cp <= 65439 || isAsciiDigit || out === " ") return out;
1279
+ return null;
1280
+ }
1281
+ case "alpha":
1282
+ case "numeric_shift":
1283
+ case "io":
1284
+ case void 0:
1285
+ default:
1286
+ return out;
1287
+ }
1288
+ }
1289
+ function filterFieldInput(field, input, startOffset) {
1290
+ let out = "";
1291
+ let rejected = false;
1292
+ for (let i = 0; i < input.length; i++) {
1293
+ const pos = startOffset + i;
1294
+ const isLast = pos === field.length - 1;
1295
+ const transformed = filterFieldChar(field, input[i], isLast);
1296
+ if (transformed === null) {
1297
+ rejected = true;
1298
+ } else {
1299
+ out += transformed;
1300
+ }
1301
+ }
1302
+ return { out, rejected };
1303
+ }
1304
+ function validateMod10(value) {
1305
+ const digits = value.replace(/\D/g, "");
1306
+ if (digits.length < 2) return true;
1307
+ let sum = 0;
1308
+ let alt = false;
1309
+ for (let i = digits.length - 1; i >= 0; i--) {
1310
+ let d = digits.charCodeAt(i) - 48;
1311
+ if (alt) {
1312
+ d *= 2;
1313
+ if (d > 9) d -= 9;
1314
+ }
1315
+ sum += d;
1316
+ alt = !alt;
1317
+ }
1318
+ return sum % 10 === 0;
1319
+ }
1320
+ function validateMod11(value) {
1321
+ const digits = value.replace(/\D/g, "");
1322
+ if (digits.length < 2) return true;
1323
+ const body = digits.slice(0, -1);
1324
+ const check = digits.charCodeAt(digits.length - 1) - 48;
1325
+ let sum = 0;
1326
+ let weight = 2;
1327
+ for (let i = body.length - 1; i >= 0; i--) {
1328
+ sum += (body.charCodeAt(i) - 48) * weight;
1329
+ weight++;
1330
+ if (weight > 7) weight = 2;
1331
+ }
1332
+ const remainder = sum % 11;
1333
+ const expected = remainder === 0 ? 0 : 11 - remainder;
1334
+ return expected === check;
1335
+ }
1336
+
1088
1337
  // src/components/GreenScreenTerminal.tsx
1089
1338
  var import_jsx_runtime4 = require("react/jsx-runtime");
1339
+ function aidByteToKeyName(aid) {
1340
+ if (aid === 241) return "ENTER";
1341
+ if (aid === 243) return "HELP";
1342
+ if (aid === 244) return "PAGEUP";
1343
+ if (aid === 245) return "PAGEDOWN";
1344
+ if (aid === 246) return "PRINT";
1345
+ if (aid === 189) return "CLEAR";
1346
+ if (aid >= 49 && aid <= 60) return `F${aid - 48}`;
1347
+ if (aid >= 177 && aid <= 188) return `F${aid - 176 + 12}`;
1348
+ return null;
1349
+ }
1090
1350
  var noopResult = { success: false, error: "No adapter configured" };
1091
1351
  var noopAdapter = {
1092
1352
  getScreen: async () => null,
@@ -1117,7 +1377,6 @@ function GreenScreenTerminal({
1117
1377
  inlineSignIn = true,
1118
1378
  defaultProtocol: signInDefaultProtocol,
1119
1379
  onSignIn,
1120
- autoSignedIn,
1121
1380
  autoFocusDisabled = false,
1122
1381
  bootLoader,
1123
1382
  bootLoaderReady,
@@ -1127,6 +1386,7 @@ function GreenScreenTerminal({
1127
1386
  onNotification,
1128
1387
  onScreenChange,
1129
1388
  onMinimize,
1389
+ showShortcutsButton = true,
1130
1390
  className,
1131
1391
  style
1132
1392
  }) {
@@ -1181,13 +1441,7 @@ function GreenScreenTerminal({
1181
1441
  }, [rawScreenData?.content]);
1182
1442
  const [inputText, setInputText] = (0, import_react5.useState)("");
1183
1443
  const [isFocused, setIsFocused] = (0, import_react5.useState)(false);
1184
- const [showSignInHint, setShowSignInHint] = (0, import_react5.useState)(false);
1185
1444
  const [showShortcuts, setShowShortcuts] = (0, import_react5.useState)(false);
1186
- const prevAutoSignedIn = (0, import_react5.useRef)(false);
1187
- (0, import_react5.useEffect)(() => {
1188
- if (autoSignedIn && !prevAutoSignedIn.current) setShowSignInHint(true);
1189
- prevAutoSignedIn.current = !!autoSignedIn;
1190
- }, [autoSignedIn]);
1191
1445
  const terminalRef = (0, import_react5.useRef)(null);
1192
1446
  const inputRef = (0, import_react5.useRef)(null);
1193
1447
  const [syncedCursor, setSyncedCursor] = (0, import_react5.useState)(null);
@@ -1197,7 +1451,6 @@ function GreenScreenTerminal({
1197
1451
  if (prevRawContentRef.current && newContent && newContent !== prevRawContentRef.current) {
1198
1452
  setSyncedCursor(null);
1199
1453
  setInputText("");
1200
- if (showSignInHint) setShowSignInHint(false);
1201
1454
  }
1202
1455
  prevRawContentRef.current = newContent;
1203
1456
  }, [rawScreenData?.content]);
@@ -1260,11 +1513,9 @@ function GreenScreenTerminal({
1260
1513
  const newAdapter = new RestAdapter({ baseUrl: `http://${config.host}${port}` });
1261
1514
  setInternalAdapter(newAdapter);
1262
1515
  await newAdapter.connect(config);
1263
- if (config.username && config.password) setShowSignInHint(true);
1264
1516
  return;
1265
1517
  }
1266
1518
  await connect(config);
1267
- if (config.username && config.password) setShowSignInHint(true);
1268
1519
  } catch (err) {
1269
1520
  setSignInError(err instanceof Error ? err.message : String(err));
1270
1521
  setConnecting(false);
@@ -1352,13 +1603,25 @@ function GreenScreenTerminal({
1352
1603
  const clickedRow = Math.floor(y / ROW_HEIGHT);
1353
1604
  const clickedCol = Math.floor(x / charWidth);
1354
1605
  if (clickedRow < 0 || clickedRow >= (screenData.rows || 24) || clickedCol < 0 || clickedCol >= (screenData.cols || 80)) return;
1606
+ const clickedField = screenData.fields.find(
1607
+ (f) => f.row === clickedRow && clickedCol >= f.col && clickedCol < f.col + f.length
1608
+ );
1609
+ const ptrAid = clickedField && clickedField.pointer_aid;
1610
+ if (ptrAid) {
1611
+ const keyName = aidByteToKeyName(ptrAid);
1612
+ if (keyName) {
1613
+ setSyncedCursor({ row: clickedRow, col: clickedCol });
1614
+ adapter.setCursor?.(clickedRow, clickedCol).then(() => sendKey(keyName));
1615
+ return;
1616
+ }
1617
+ }
1355
1618
  setSyncedCursor({ row: clickedRow, col: clickedCol });
1356
1619
  adapter.setCursor?.(clickedRow, clickedCol).then((r) => {
1357
1620
  if (r?.cursor_row !== void 0) {
1358
1621
  setSyncedCursor({ row: r.cursor_row, col: r.cursor_col });
1359
1622
  }
1360
1623
  });
1361
- }, [readOnly, screenData, adapter]);
1624
+ }, [readOnly, screenData, adapter, sendKey]);
1362
1625
  const getCurrentField = (0, import_react5.useCallback)(() => {
1363
1626
  const fields = screenData?.fields || [];
1364
1627
  const cursorRow = syncedCursor?.row ?? screenData?.cursor_row ?? 0;
@@ -1368,6 +1631,31 @@ function GreenScreenTerminal({
1368
1631
  }
1369
1632
  return null;
1370
1633
  }, [screenData, syncedCursor]);
1634
+ const readFieldValue = (0, import_react5.useCallback)((field) => {
1635
+ if (!screenData?.content) return "";
1636
+ const lines = screenData.content.split("\n");
1637
+ const line = lines[field.row] || "";
1638
+ return line.substring(field.col, field.col + field.length).replace(/\s+$/, "");
1639
+ }, [screenData?.content]);
1640
+ const [validationError, setValidationError] = (0, import_react5.useState)(null);
1641
+ const runSelfCheck = (0, import_react5.useCallback)(() => {
1642
+ const fields = screenData?.fields || [];
1643
+ for (const f of fields) {
1644
+ if (!f.is_input) continue;
1645
+ const val = readFieldValue(f);
1646
+ if (!val) continue;
1647
+ if (f.self_check_mod10 && !validateMod10(val)) {
1648
+ setValidationError(`Invalid check digit (MOD10) in field at row ${f.row + 1}, col ${f.col + 1}`);
1649
+ return false;
1650
+ }
1651
+ if (f.self_check_mod11 && !validateMod11(val)) {
1652
+ setValidationError(`Invalid check digit (MOD11) in field at row ${f.row + 1}, col ${f.col + 1}`);
1653
+ return false;
1654
+ }
1655
+ }
1656
+ setValidationError(null);
1657
+ return true;
1658
+ }, [screenData?.fields, readFieldValue]);
1371
1659
  const handleKeyDown = async (e) => {
1372
1660
  if (readOnly) {
1373
1661
  e.preventDefault();
@@ -1406,18 +1694,19 @@ function GreenScreenTerminal({
1406
1694
  End: "END",
1407
1695
  Insert: "INSERT"
1408
1696
  };
1409
- if (e.key.startsWith("F") && e.key.length <= 3) {
1697
+ if (/^F([1-9]|1[0-9]|2[0-4])$/.test(e.key)) {
1410
1698
  e.preventDefault();
1411
- const fKey = e.key.toUpperCase();
1412
- if (/^F([1-9]|1[0-9]|2[0-4])$/.test(fKey)) {
1413
- const kr = await sendKey(fKey);
1414
- if (kr.cursor_row !== void 0) setSyncedCursor({ row: kr.cursor_row, col: kr.cursor_col });
1415
- return;
1416
- }
1699
+ if (!runSelfCheck()) return;
1700
+ const kr = await sendKey(e.key);
1701
+ if (kr.cursor_row !== void 0) setSyncedCursor({ row: kr.cursor_row, col: kr.cursor_col });
1702
+ return;
1417
1703
  }
1418
1704
  if (keyMap[e.key]) {
1419
1705
  e.preventDefault();
1420
- const kr = await sendKey(keyMap[e.key]);
1706
+ const k = keyMap[e.key];
1707
+ const isSubmit = k === "ENTER" || k === "PAGEUP" || k === "PAGEDOWN";
1708
+ if (isSubmit && !runSelfCheck()) return;
1709
+ const kr = await sendKey(k);
1421
1710
  if (kr.cursor_row !== void 0) setSyncedCursor({ row: kr.cursor_row, col: kr.cursor_col });
1422
1711
  }
1423
1712
  };
@@ -1428,18 +1717,32 @@ function GreenScreenTerminal({
1428
1717
  }
1429
1718
  const newText = e.target.value;
1430
1719
  if (newText.length > inputText.length) {
1431
- const newChars = newText.substring(inputText.length);
1432
- const curRow = syncedCursor?.row ?? screenData?.cursor_row ?? 0;
1433
- const curCol = syncedCursor?.col ?? screenData?.cursor_col ?? 0;
1434
- const edits = [];
1435
- for (let i = 0; i < newChars.length; i++) {
1436
- edits.push({ row: curRow, col: curCol + i, ch: newChars[i] });
1720
+ let newChars = newText.substring(inputText.length);
1721
+ const curField = getCurrentField();
1722
+ if (curField && (curField.shift_type || curField.monocase)) {
1723
+ const curColAbs = syncedCursor?.col ?? screenData?.cursor_col ?? 0;
1724
+ const startOffset = Math.max(0, curColAbs - curField.col);
1725
+ const { out, rejected } = filterFieldInput(curField, newChars, startOffset);
1726
+ if (rejected) {
1727
+ const what = curField.shift_type === "digits_only" || curField.shift_type === "numeric_only" || curField.shift_type === "signed_num" ? "digits only" : curField.shift_type === "alpha_only" ? "letters only" : curField.shift_type === "katakana" ? "katakana only" : "character not allowed";
1728
+ setValidationError(`Field accepts ${what}`);
1729
+ setTimeout(() => setValidationError(null), 1500);
1730
+ }
1731
+ newChars = out;
1732
+ }
1733
+ if (newChars.length > 0) {
1734
+ const curRow = syncedCursor?.row ?? screenData?.cursor_row ?? 0;
1735
+ const curCol = syncedCursor?.col ?? screenData?.cursor_col ?? 0;
1736
+ const edits = [];
1737
+ for (let i = 0; i < newChars.length; i++) {
1738
+ edits.push({ row: curRow, col: curCol + i, ch: newChars[i] });
1739
+ }
1740
+ setOptimisticEdits((prev) => [...prev, ...edits]);
1741
+ setSyncedCursor({ row: curRow, col: curCol + newChars.length });
1742
+ sendText(newChars).then((r) => {
1743
+ if (r.cursor_row !== void 0) setSyncedCursor({ row: r.cursor_row, col: r.cursor_col });
1744
+ });
1437
1745
  }
1438
- setOptimisticEdits((prev) => [...prev, ...edits]);
1439
- setSyncedCursor({ row: curRow, col: curCol + newChars.length });
1440
- sendText(newChars).then((r) => {
1441
- if (r.cursor_row !== void 0) setSyncedCursor({ row: r.cursor_row, col: r.cursor_col });
1442
- });
1443
1746
  }
1444
1747
  setInputText("");
1445
1748
  e.target.value = "";
@@ -1471,41 +1774,156 @@ function GreenScreenTerminal({
1471
1774
  if (lastIndex < text.length) segments.push(/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: text.substring(lastIndex) }, `${keyPrefix}-e`));
1472
1775
  return segments.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, { children: segments }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, { children: text });
1473
1776
  }, []);
1474
- const renderRowWithFields = (0, import_react5.useCallback)((line, rowIndex, fields) => {
1777
+ const renderRowWithFields = (0, import_react5.useCallback)((line, rowIndex, fields, cursorRow, cursorCol) => {
1475
1778
  const inputFields = fields.filter((f) => f.row === rowIndex && f.is_input);
1476
1779
  const highlightedFields = fields.filter((f) => f.row === rowIndex && f.is_protected && f.is_highlighted);
1477
1780
  const reverseFields = fields.filter((f) => f.row === rowIndex && f.is_protected && f.is_reverse);
1478
1781
  const colorFields = fields.filter((f) => f.row === rowIndex && f.is_protected && f.color && !f.is_highlighted && !f.is_reverse);
1479
1782
  const allRowFields = [...inputFields, ...highlightedFields, ...reverseFields, ...colorFields];
1480
- if (allRowFields.length === 0) return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: line });
1783
+ const cols = screenData?.cols || profile.defaultCols;
1784
+ const extAttrs = screenData?.ext_attrs;
1785
+ const dbcsCont = screenData?.dbcs_cont;
1786
+ const dbcsContSet = dbcsCont && dbcsCont.length > 0 ? new Set(dbcsCont) : null;
1787
+ const hasExtOnRow = !!extAttrs && (() => {
1788
+ for (let c = 0; c < cols; c++) {
1789
+ if (extAttrs[rowIndex * cols + c]) return true;
1790
+ }
1791
+ return false;
1792
+ })();
1793
+ const hasDbcsOnRow = !!dbcsContSet && (() => {
1794
+ for (let c = 0; c < cols; c++) {
1795
+ if (dbcsContSet.has(rowIndex * cols + c)) return true;
1796
+ }
1797
+ return false;
1798
+ })();
1799
+ if (allRowFields.length === 0 && !hasExtOnRow && !hasDbcsOnRow) return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: line });
1481
1800
  const sorted = [...allRowFields].sort((a, b) => a.col - b.col);
1482
1801
  const segs = [];
1483
1802
  let lastEnd = 0;
1484
- const cols = screenData?.cols || profile.defaultCols;
1803
+ const renderPlainRun = (runStart, runEnd, keyPrefix) => {
1804
+ if (runStart >= runEnd) return;
1805
+ if (!hasExtOnRow && !hasDbcsOnRow) {
1806
+ segs.push(/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: line.substring(runStart, runEnd) }, keyPrefix));
1807
+ return;
1808
+ }
1809
+ let pos = runStart;
1810
+ while (pos < runEnd) {
1811
+ const addr = rowIndex * cols + pos;
1812
+ const ext = extAttrs?.[addr];
1813
+ const isContCell = dbcsContSet?.has(addr);
1814
+ if (isContCell) {
1815
+ segs.push(
1816
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1817
+ "span",
1818
+ {
1819
+ style: { display: "inline-block", width: "1ch" },
1820
+ "aria-hidden": "true"
1821
+ },
1822
+ `${keyPrefix}-dc${pos}`
1823
+ )
1824
+ );
1825
+ pos++;
1826
+ continue;
1827
+ }
1828
+ if (ext) {
1829
+ const color = ext.color !== void 0 ? decodeExtColor(ext.color) : void 0;
1830
+ const colorReverse = ext.color !== void 0 && extColorIsReverse(ext.color);
1831
+ const hl = ext.highlight !== void 0 ? decodeExtHighlight(ext.highlight) : void 0;
1832
+ const style2 = {};
1833
+ if (color) style2.color = cssVarForColor(color);
1834
+ if (colorReverse) {
1835
+ style2.background = cssVarForColor(color);
1836
+ style2.color = "#000";
1837
+ }
1838
+ if (hl?.reverse) {
1839
+ style2.background = "currentColor";
1840
+ style2.color = "#000";
1841
+ }
1842
+ if (hl?.underscore) style2.textDecoration = "underline";
1843
+ if (hl?.blink) style2.animation = "gs-blink 1s steps(2, start) infinite";
1844
+ segs.push(
1845
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { style: style2, children: line[pos] }, `${keyPrefix}-x${pos}`)
1846
+ );
1847
+ pos++;
1848
+ continue;
1849
+ }
1850
+ let runEndPlain = pos + 1;
1851
+ while (runEndPlain < runEnd) {
1852
+ const a = rowIndex * cols + runEndPlain;
1853
+ if (extAttrs?.[a] || dbcsContSet?.has(a)) break;
1854
+ runEndPlain++;
1855
+ }
1856
+ segs.push(
1857
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: line.substring(pos, runEndPlain) }, `${keyPrefix}-p${pos}`)
1858
+ );
1859
+ pos = runEndPlain;
1860
+ }
1861
+ };
1485
1862
  sorted.forEach((field, idx) => {
1486
1863
  const fs = field.col;
1487
1864
  const fe = Math.min(field.col + field.length, cols);
1488
- if (fs > lastEnd) segs.push(/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: line.substring(lastEnd, fs) }, `t${idx}`));
1865
+ if (fs > lastEnd) renderPlainRun(lastEnd, fs, `t${idx}`);
1489
1866
  const fc = line.substring(fs, fe);
1490
- const colorVar = field.color ? `var(--gs-${field.color}, var(--gs-green))` : void 0;
1867
+ const cursorInField = cursorRow === field.row && cursorCol >= fs && cursorCol < field.col + field.length;
1868
+ const entryAttr = cursorInField && field.highlight_entry_attr !== void 0 ? decodeAttrByte(field.highlight_entry_attr) : null;
1869
+ const baseColor = entryAttr?.color ?? field.color;
1870
+ const colorVar = baseColor ? cssVarForColor(baseColor) : void 0;
1871
+ const fieldStyle = {};
1872
+ if (colorVar) fieldStyle.color = colorVar;
1873
+ if (entryAttr?.reverse) {
1874
+ fieldStyle.background = "currentColor";
1875
+ fieldStyle.color = "#000";
1876
+ }
1877
+ if (entryAttr?.underscore) fieldStyle.textDecoration = "underline";
1878
+ if (entryAttr?.highIntensity) fieldStyle.fontWeight = "bold";
1879
+ const isPassword = field.is_non_display;
1880
+ const displayText = isPassword ? " ".repeat(fc.length) : fc;
1491
1881
  if (field.is_input) {
1492
1882
  const fieldWidth = Math.min(field.length, cols - fs);
1493
- const fieldClass = showSignInHint ? "gs-confirmed-field" : field.is_underscored ? "gs-input-field" : void 0;
1494
- segs.push(/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: fieldClass || void 0, style: { display: "inline-block", width: `${fieldWidth}ch`, overflow: "hidden", color: colorVar }, children: fc }, `f${idx}`));
1883
+ const fieldClass = field.is_underscored ? "gs-input-field" : void 0;
1884
+ const extra = [];
1885
+ if (field.is_dbcs) extra.push("gs-dbcs-field");
1886
+ if (cursorInField) extra.push("gs-field-active");
1887
+ const composedClass = [fieldClass, ...extra].filter(Boolean).join(" ") || void 0;
1888
+ segs.push(
1889
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1890
+ "span",
1891
+ {
1892
+ className: composedClass,
1893
+ style: {
1894
+ display: "inline-block",
1895
+ width: `${fieldWidth}ch`,
1896
+ overflow: "hidden",
1897
+ ...fieldStyle
1898
+ },
1899
+ children: displayText
1900
+ },
1901
+ `f${idx}`
1902
+ )
1903
+ );
1495
1904
  } else if (field.is_reverse) {
1496
- segs.push(/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { style: { color: colorVar || "var(--gs-red, #FF5555)", fontWeight: "bold" }, children: fc }, `v${idx}`));
1497
- } else if (colorVar) {
1498
- segs.push(/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { style: { color: colorVar }, children: fc }, `h${idx}`));
1905
+ segs.push(
1906
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1907
+ "span",
1908
+ {
1909
+ style: { color: colorVar || "var(--gs-red, #FF5555)", fontWeight: "bold", ...fieldStyle },
1910
+ children: displayText
1911
+ },
1912
+ `v${idx}`
1913
+ )
1914
+ );
1915
+ } else if (colorVar || entryAttr) {
1916
+ segs.push(/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { style: fieldStyle, children: displayText }, `h${idx}`));
1499
1917
  } else if (field.is_highlighted) {
1500
- segs.push(/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { style: { color: "var(--gs-white, #FFFFFF)" }, children: fc }, `h${idx}`));
1918
+ segs.push(/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { style: { color: "var(--gs-white, #FFFFFF)" }, children: displayText }, `h${idx}`));
1501
1919
  } else {
1502
- segs.push(/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: fc }, `h${idx}`));
1920
+ segs.push(/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: displayText }, `h${idx}`));
1503
1921
  }
1504
1922
  lastEnd = fe;
1505
1923
  });
1506
- if (lastEnd < line.length) segs.push(/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: line.substring(lastEnd) }, "te"));
1924
+ if (lastEnd < line.length) renderPlainRun(lastEnd, line.length, "te");
1507
1925
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, { children: segs });
1508
- }, [renderTextWithUnderlines, screenData?.cols, profile.defaultCols, showSignInHint]);
1926
+ }, [renderTextWithUnderlines, screenData, profile.defaultCols]);
1509
1927
  const renderScreen = () => {
1510
1928
  if (showBootLoader && !screenData?.content) {
1511
1929
  if (bootLoader === false) return null;
@@ -1522,7 +1940,7 @@ function GreenScreenTerminal({
1522
1940
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { style: { fontFamily: "var(--gs-font)", fontSize: "12px", color: connStatus?.status === "connecting" ? "#f59e0b" : connStatus?.status === "loading" ? "#94a3b8" : "#808080" }, children: connStatus?.connected ? "Waiting for screen data..." : connStatus?.status === "connecting" ? "Connecting..." : connStatus?.status === "loading" ? "Loading..." : "Not connected" }),
1523
1941
  !connStatus?.connected && isUsingDefaultAdapter && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("p", { style: { fontFamily: "var(--gs-font)", fontSize: "11px", color: "#606060", marginTop: "8px" }, children: [
1524
1942
  "Start the proxy: ",
1525
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("code", { style: { color: "#10b981" }, children: "npx green-screen-proxy --mock" })
1943
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("code", { style: { color: "#10b981" }, children: "npx green-screen-proxy" })
1526
1944
  ] })
1527
1945
  ] }) });
1528
1946
  }
@@ -1555,11 +1973,33 @@ function GreenScreenTerminal({
1555
1973
  }
1556
1974
  const headerSegments = index === 0 ? profile.colors.parseHeaderRow(displayLine) : null;
1557
1975
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: headerSegments ? "" : profile.colors.getRowColorClass(index, displayLine, termRows), style: { height: `${ROW_HEIGHT}px`, lineHeight: `${ROW_HEIGHT}px`, whiteSpace: "pre", position: "relative" }, children: [
1558
- headerSegments ? headerSegments.map((seg, i) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: seg.colorClass, children: seg.text }, i)) : renderRowWithFields(displayLine, index, fields),
1559
- cursorInInputField && !showSignInHint && index === cursor.row && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "gs-cursor", style: { position: "absolute", left: `${cursor.col}ch`, width: "1ch", height: `${ROW_HEIGHT}px`, top: 0, pointerEvents: "none" } })
1976
+ headerSegments ? headerSegments.map((seg, i) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: seg.colorClass, children: seg.text }, i)) : renderRowWithFields(displayLine, index, fields, cursor.row, cursor.col),
1977
+ cursorInInputField && index === cursor.row && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "gs-cursor", style: { position: "absolute", left: `${cursor.col}ch`, width: "1ch", height: `${ROW_HEIGHT}px`, top: 0, pointerEvents: "none" } })
1560
1978
  ] }, index);
1561
1979
  }),
1562
- showSignInHint && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "gs-signin-hint", children: "Signed in \u2014 press Enter to continue" }),
1980
+ validationError && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
1981
+ "div",
1982
+ {
1983
+ role: "alert",
1984
+ style: {
1985
+ position: "absolute",
1986
+ left: 0,
1987
+ right: 0,
1988
+ bottom: 0,
1989
+ padding: "2px 6px",
1990
+ background: "var(--gs-red, #FF5555)",
1991
+ color: "#000",
1992
+ fontFamily: "var(--gs-font)",
1993
+ fontSize: "12px",
1994
+ zIndex: 10
1995
+ },
1996
+ onClick: () => setValidationError(null),
1997
+ children: [
1998
+ validationError,
1999
+ " \u2014 click to dismiss"
2000
+ ]
2001
+ }
2002
+ ),
1563
2003
  screenData.cursor_row !== void 0 && screenData.cursor_col !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { style: { position: "absolute", bottom: 0, right: 0, fontFamily: "var(--gs-font)", fontSize: "10px", color: "var(--gs-green, #10b981)", pointerEvents: "none", opacity: 0.6 }, children: [
1564
2004
  String(screenData.cursor_row + 1).padStart(2, "0"),
1565
2005
  "/",
@@ -1590,6 +2030,15 @@ function GreenScreenTerminal({
1590
2030
  return "#64748b";
1591
2031
  }
1592
2032
  };
2033
+ const handleShortcutSend = (0, import_react5.useCallback)(async (key) => {
2034
+ if (readOnly) return;
2035
+ const isSubmit = key === "ENTER" || key === "PAGEUP" || key === "PAGEDOWN" || /^F([1-9]|1[0-9]|2[0-4])$/.test(key);
2036
+ if (isSubmit && !runSelfCheck()) return;
2037
+ setIsFocused(true);
2038
+ inputRef.current?.focus();
2039
+ const kr = await sendKey(key);
2040
+ if (kr.cursor_row !== void 0) setSyncedCursor({ row: kr.cursor_row, col: kr.cursor_col });
2041
+ }, [readOnly, runSelfCheck, sendKey]);
1593
2042
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: `gs-terminal ${isFocused ? "gs-terminal-focused" : ""} ${className || ""}`, style, children: [
1594
2043
  showHeader && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "gs-header", children: embedded ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
1595
2044
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: "gs-header-left", children: [
@@ -1609,10 +2058,10 @@ function GreenScreenTerminal({
1609
2058
  e.stopPropagation();
1610
2059
  onMinimize();
1611
2060
  }, className: "gs-btn-icon", title: "Minimize terminal", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(MinimizeIcon, {}) }),
1612
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { onClick: (e) => {
2061
+ showShortcutsButton && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { onClick: (e) => {
1613
2062
  e.stopPropagation();
1614
2063
  setShowShortcuts((s) => !s);
1615
- }, className: "gs-btn-icon", title: "Keyboard shortcuts", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(HelpIcon, { size: 12 }) }),
2064
+ }, className: "gs-btn-icon", title: "Keyboard shortcuts", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(KeyboardIcon, { size: 12 }) }),
1616
2065
  headerRight
1617
2066
  ] })
1618
2067
  ] }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
@@ -1639,10 +2088,10 @@ function GreenScreenTerminal({
1639
2088
  connStatus.username && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "gs-host", children: connStatus.username })
1640
2089
  ] }),
1641
2090
  statusActions,
1642
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { onClick: (e) => {
2091
+ showShortcutsButton && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { onClick: (e) => {
1643
2092
  e.stopPropagation();
1644
2093
  setShowShortcuts((s) => !s);
1645
- }, className: "gs-btn-icon", title: "Keyboard shortcuts", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(HelpIcon, { size: 12 }) }),
2094
+ }, className: "gs-btn-icon", title: "Keyboard shortcuts", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(KeyboardIcon, { size: 12 }) }),
1646
2095
  headerRight
1647
2096
  ] })
1648
2097
  ] }) }),
@@ -1663,29 +2112,47 @@ function GreenScreenTerminal({
1663
2112
  showShortcuts && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "gs-shortcuts-panel", children: [
1664
2113
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "gs-shortcuts-header", children: [
1665
2114
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: "Keyboard Shortcuts" }),
1666
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { className: "gs-btn-icon", onClick: () => setShowShortcuts(false), style: { pointerEvents: "auto" }, children: "\xD7" })
2115
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { className: "gs-btn-icon", onClick: () => setShowShortcuts(false), children: "\xD7" })
1667
2116
  ] }),
2117
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "gs-shortcuts-section-title", children: "Actions" }),
2118
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("table", { className: "gs-shortcuts-table", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("tbody", { children: [
2119
+ ["Enter", "Submit", "ENTER"],
2120
+ ["Tab", "Next field", "TAB"],
2121
+ ["Backspace", "Backspace", "BACKSPACE"],
2122
+ ["Delete", "Delete", "DELETE"],
2123
+ ["Insert", "Insert / Overwrite", "INSERT"],
2124
+ ["Home", "Home", "HOME"],
2125
+ ["End", "End", "END"],
2126
+ ["Page Up", "Roll Down", "PAGEUP"],
2127
+ ["Page Down", "Roll Up", "PAGEDOWN"],
2128
+ ["Ctrl+Enter", "Field Exit", "FIELD_EXIT"],
2129
+ ["Ctrl+R", "Reset", "RESET"],
2130
+ ["\u2014", "Help", "HELP"],
2131
+ ["\u2014", "Clear", "CLEAR"],
2132
+ ["\u2014", "Print", "PRINT"]
2133
+ ].map(([label, desc, key]) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("tr", { className: "gs-shortcut-row", onClick: (e) => {
2134
+ e.stopPropagation();
2135
+ handleShortcutSend(key);
2136
+ }, children: [
2137
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("td", { className: "gs-shortcut-key", children: label }),
2138
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("td", { children: desc })
2139
+ ] }, key)) }) }),
2140
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "gs-shortcuts-section-title", children: "Function keys" }),
2141
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "gs-shortcuts-fkeys", children: Array.from({ length: 24 }, (_, i) => `F${i + 1}`).map((fk) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2142
+ "button",
2143
+ {
2144
+ className: "gs-shortcut-fkey",
2145
+ onClick: (e) => {
2146
+ e.stopPropagation();
2147
+ handleShortcutSend(fk);
2148
+ },
2149
+ title: `Send ${fk}`,
2150
+ children: fk
2151
+ },
2152
+ fk
2153
+ )) }),
2154
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "gs-shortcuts-section-title", children: "Info" }),
1668
2155
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("table", { className: "gs-shortcuts-table", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("tbody", { children: [
1669
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("tr", { children: [
1670
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("td", { className: "gs-shortcut-key", children: "Ctrl+Enter" }),
1671
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("td", { children: "Field Exit" })
1672
- ] }),
1673
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("tr", { children: [
1674
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("td", { className: "gs-shortcut-key", children: "Ctrl+R" }),
1675
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("td", { children: "Reset" })
1676
- ] }),
1677
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("tr", { children: [
1678
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("td", { className: "gs-shortcut-key", children: "Insert" }),
1679
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("td", { children: "Insert / Overwrite" })
1680
- ] }),
1681
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("tr", { children: [
1682
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("td", { className: "gs-shortcut-key", children: "Page Up" }),
1683
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("td", { children: "Roll Down" })
1684
- ] }),
1685
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("tr", { children: [
1686
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("td", { className: "gs-shortcut-key", children: "Page Down" }),
1687
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("td", { children: "Roll Up" })
1688
- ] }),
1689
2156
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("tr", { children: [
1690
2157
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("td", { className: "gs-shortcut-key", children: "Click" }),
1691
2158
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("td", { children: "Focus / Position cursor" })
@@ -1713,7 +2180,19 @@ function GreenScreenTerminal({
1713
2180
  autoComplete: "off",
1714
2181
  autoCorrect: "off",
1715
2182
  autoCapitalize: "off",
1716
- spellCheck: false
2183
+ spellCheck: false,
2184
+ lang: (() => {
2185
+ const f = getCurrentField();
2186
+ if (!f) return void 0;
2187
+ if (f.is_dbcs || f.is_dbcs_either) return "ja";
2188
+ return void 0;
2189
+ })(),
2190
+ inputMode: (() => {
2191
+ const f = getCurrentField();
2192
+ if (!f) return void 0;
2193
+ if (f.is_dbcs) return "text";
2194
+ return "text";
2195
+ })()
1717
2196
  }
1718
2197
  )
1719
2198
  ]