@tn3w/openage 1.0.5 → 1.0.6

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.
@@ -547,6 +547,20 @@
547
547
  flex-direction: column;
548
548
  }
549
549
 
550
+ .oa-inline-shell {
551
+ background: var(--oa-bg);
552
+ border: 1px solid var(--oa-border);
553
+ border-radius: var(--oa-radius);
554
+ box-shadow: 0 20px 60px rgba(0,0,0,0.18),
555
+ 0 0 0 1px rgba(0,0,0,0.06);
556
+ width: min(100%, 360px);
557
+ max-width: 100%;
558
+ overflow: hidden;
559
+ display: flex;
560
+ flex-direction: column;
561
+ margin: 0 auto;
562
+ }
563
+
550
564
  .oa-modal-overlay {
551
565
  position: fixed;
552
566
  inset: 0;
@@ -1114,8 +1128,7 @@
1114
1128
  'face_landmarker/face_landmarker/float16/1/' +
1115
1129
  'face_landmarker.task';
1116
1130
 
1117
- const FACEAPI_CDN =
1118
- 'https://cdn.jsdelivr.net/npm/face-api.js@0.22.2/dist/face-api.min.js';
1131
+ const FACEAPI_CDN = 'https://cdn.jsdelivr.net/npm/face-api.js@0.22.2/dist/face-api.min.js';
1119
1132
 
1120
1133
  const FACEAPI_MODEL_CDN =
1121
1134
  'https://cdn.jsdelivr.net/gh/justadudewhohacks/face-api.js@master/weights';
@@ -1152,6 +1165,7 @@
1152
1165
  this.popup = null;
1153
1166
  this.shadow = null;
1154
1167
  this.elements = {};
1168
+ this.popupElements = null;
1155
1169
  this.onChallenge = null;
1156
1170
  this.onStartClick = null;
1157
1171
  this.popupFrame = 0;
@@ -1164,6 +1178,7 @@
1164
1178
  const host = document.createElement('div');
1165
1179
  host.id = this.id;
1166
1180
  this.shadow = host.attachShadow({ mode: 'open' });
1181
+ this.host = host;
1167
1182
 
1168
1183
  const style = document.createElement('style');
1169
1184
  style.textContent = STYLES;
@@ -1176,7 +1191,12 @@
1176
1191
  if (this.params.size === 'invisible') {
1177
1192
  host.style.display = 'none';
1178
1193
  this.container.appendChild(host);
1179
- this.host = host;
1194
+ return;
1195
+ }
1196
+
1197
+ if (this.isInlineLayout()) {
1198
+ this.renderInlineShell();
1199
+ this.container.appendChild(host);
1180
1200
  return;
1181
1201
  }
1182
1202
 
@@ -1212,7 +1232,32 @@
1212
1232
  this.elements.errorSlot = this.shadow.querySelector('.oa-error-slot');
1213
1233
 
1214
1234
  this.container.appendChild(host);
1215
- this.host = host;
1235
+ }
1236
+
1237
+ isInlineLayout() {
1238
+ return this.params.layout === 'inline';
1239
+ }
1240
+
1241
+ renderInlineShell() {
1242
+ const inlineShell = document.createElement('div');
1243
+ inlineShell.className = 'oa-inline-shell';
1244
+ inlineShell.innerHTML = this.buildPopupContent({ closeable: false });
1245
+ this.shadow.appendChild(inlineShell);
1246
+
1247
+ this.popup = {
1248
+ host: this.host,
1249
+ root: inlineShell,
1250
+ inline: true,
1251
+ };
1252
+
1253
+ this.bindPopupEvents(inlineShell);
1254
+ }
1255
+
1256
+ resetInlineShell() {
1257
+ if (!this.popup?.root) return;
1258
+
1259
+ this.popup.root.innerHTML = this.buildPopupContent({ closeable: false });
1260
+ this.bindPopupEvents(this.popup.root);
1216
1261
  }
1217
1262
 
1218
1263
  startChallenge() {
@@ -1245,6 +1290,14 @@
1245
1290
  }
1246
1291
 
1247
1292
  openPopup() {
1293
+ if (this.isInlineLayout()) {
1294
+ if (!this.popup) {
1295
+ this.renderInlineShell();
1296
+ }
1297
+
1298
+ return this.getVideo();
1299
+ }
1300
+
1248
1301
  if (this.popup) return this.getVideo();
1249
1302
 
1250
1303
  const anchor = this.getPopupAnchor();
@@ -1422,7 +1475,7 @@
1422
1475
  this.popup.root.style.pointerEvents = 'auto';
1423
1476
  }
1424
1477
 
1425
- buildPopupContent() {
1478
+ buildPopupContent({ closeable = true } = {}) {
1426
1479
  return `
1427
1480
  <div class="oa-header">
1428
1481
  <div class="oa-title">
@@ -1433,10 +1486,14 @@
1433
1486
  </a>
1434
1487
  <span class="oa-badge">on-device</span>
1435
1488
  </div>
1436
- <button class="oa-close-btn"
1437
- aria-label="Close">
1438
- ${CLOSE_SVG}
1439
- </button>
1489
+ ${
1490
+ closeable
1491
+ ? `<button class="oa-close-btn"
1492
+ aria-label="Close">
1493
+ ${CLOSE_SVG}
1494
+ </button>`
1495
+ : ''
1496
+ }
1440
1497
  </div>
1441
1498
  <div class="oa-body">
1442
1499
  ${heroTemplate('Initializing…')}
@@ -1449,7 +1506,7 @@
1449
1506
  `;
1450
1507
  }
1451
1508
 
1452
- bindPopupEvents(root, shadow) {
1509
+ bindPopupEvents(root) {
1453
1510
  const closeBtn = root.querySelector('.oa-close-btn');
1454
1511
  if (closeBtn) {
1455
1512
  closeBtn.addEventListener('click', () => {
@@ -1567,6 +1624,22 @@
1567
1624
  }
1568
1625
 
1569
1626
  showResult(outcome, message) {
1627
+ if (this.isInlineLayout()) {
1628
+ if (!this.popupElements?.body) return;
1629
+
1630
+ this.popupElements.body.innerHTML = resultTemplate(outcome, message);
1631
+ this.hideActions();
1632
+
1633
+ if (outcome === 'pass') {
1634
+ this.setState('verified');
1635
+ return;
1636
+ }
1637
+
1638
+ this.setState(outcome === 'fail' ? 'failed' : 'retry');
1639
+ this.showActions('Try Again');
1640
+ return;
1641
+ }
1642
+
1570
1643
  if (outcome === 'pass') {
1571
1644
  this.closePopup();
1572
1645
  this.setState('verified');
@@ -1606,8 +1679,9 @@
1606
1679
  if (!this.popupElements?.body) return;
1607
1680
 
1608
1681
  this.popupElements.body.innerHTML = errorStepTemplate(message);
1609
- this.popupElements.errorCountdown =
1610
- this.popupElements.body.querySelector('.oa-error-step-countdown');
1682
+ this.popupElements.errorCountdown = this.popupElements.body.querySelector(
1683
+ '.oa-error-step-countdown'
1684
+ );
1611
1685
  this.hideActions();
1612
1686
  }
1613
1687
 
@@ -1626,6 +1700,22 @@
1626
1700
 
1627
1701
  closePopup() {
1628
1702
  if (!this.popup) return;
1703
+
1704
+ if (this.popup.inline) {
1705
+ this.popup.cleanup?.();
1706
+ if (this.popupFrame) {
1707
+ cancelAnimationFrame(this.popupFrame);
1708
+ this.popupFrame = 0;
1709
+ }
1710
+ this.resetInlineShell();
1711
+
1712
+ if (this.state === 'loading') {
1713
+ this.setState('idle');
1714
+ }
1715
+
1716
+ return;
1717
+ }
1718
+
1629
1719
  this.popup.cleanup?.();
1630
1720
  this.popup.themeCleanup?.();
1631
1721
  this.popup.host.remove();
@@ -2825,9 +2915,18 @@
2825
2915
  return 'Verification failed. Please try again.';
2826
2916
  }
2827
2917
 
2918
+ function isInlineLayout(widget) {
2919
+ return widget?.params?.layout === 'inline';
2920
+ }
2921
+
2828
2922
  async function showErrorStep(widget, error) {
2829
2923
  const message = resolveChallengeErrorMessage(error);
2830
2924
 
2925
+ if (isInlineLayout(widget)) {
2926
+ widget.showResult?.('retry', message);
2927
+ return;
2928
+ }
2929
+
2831
2930
  if (!widget.popup) {
2832
2931
  widget.openPopup?.();
2833
2932
  }
@@ -2880,10 +2979,14 @@
2880
2979
  await Promise.all([loadVision(), initAgeEstimator()]);
2881
2980
 
2882
2981
  modelBuffer = await loadModel();
2883
- widget.showReady();
2884
2982
 
2885
- await waitForStart(widget);
2886
- await startCameraFlow(widget, modelBuffer);
2983
+ if (isInlineLayout(widget)) {
2984
+ await startCameraFlow(widget, modelBuffer);
2985
+ } else {
2986
+ widget.showReady();
2987
+ await waitForStart(widget);
2988
+ await startCameraFlow(widget, modelBuffer);
2989
+ }
2887
2990
 
2888
2991
  const transport = createTransport('serverless', params);
2889
2992
 
@@ -2972,10 +3075,16 @@
2972
3075
  captureFrame: () => (captureFrame() ? 'true' : 'null'),
2973
3076
  });
2974
3077
 
2975
- widget.showReady();
2976
- await waitForStart(widget);
3078
+ let video;
3079
+
3080
+ if (isInlineLayout(widget)) {
3081
+ video = widget.showCamera();
3082
+ } else {
3083
+ widget.showReady();
3084
+ await waitForStart(widget);
3085
+ video = widget.showCamera();
3086
+ }
2977
3087
 
2978
- const video = widget.showCamera();
2979
3088
  widget.setVideoStatus('Requesting camera…');
2980
3089
  await startCamera(video);
2981
3090
  exposeMirrorVideo(video);
@@ -3278,8 +3387,19 @@
3278
3387
  const emitter = new EventEmitter();
3279
3388
  const widgets = new Map();
3280
3389
 
3390
+ function normalizeLayout(layout) {
3391
+ if (layout === 'inline' || layout === 'embed' || layout === 'embedded') {
3392
+ return 'inline';
3393
+ }
3394
+
3395
+ return 'widget';
3396
+ }
3397
+
3281
3398
  function normalizeParams(params) {
3282
3399
  const globalConfig = typeof window !== 'undefined' ? window.openage || {} : {};
3400
+ const layout = normalizeLayout(
3401
+ params.layout ?? params.display ?? globalConfig.layout ?? globalConfig.display
3402
+ );
3283
3403
 
3284
3404
  return {
3285
3405
  mode: 'serverless',
@@ -3288,6 +3408,7 @@
3288
3408
  minAge: 18,
3289
3409
  ...globalConfig,
3290
3410
  ...params,
3411
+ layout,
3291
3412
  };
3292
3413
  }
3293
3414
 
@@ -3296,6 +3417,10 @@
3296
3417
  emitter.emit('opened', widget.id);
3297
3418
  runChallenge(widget, emitter);
3298
3419
  };
3420
+
3421
+ if (widget.params.layout === 'inline') {
3422
+ widget.startChallenge();
3423
+ }
3299
3424
  }
3300
3425
 
3301
3426
  function render(container, params = {}) {
@@ -3444,6 +3569,7 @@
3444
3569
  size: element.dataset.size,
3445
3570
  action: element.dataset.action,
3446
3571
  mode: element.dataset.mode,
3572
+ layout: element.dataset.layout || element.dataset.display,
3447
3573
  server: element.dataset.server,
3448
3574
  };
3449
3575