pome-ui 2.0.0-preview50 → 2.0.0-preview51

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pome-ui",
3
- "version": "2.0.0-preview50",
3
+ "version": "2.0.0-preview51",
4
4
  "description": "Front-end MVC library",
5
5
  "main": "pome-ui.js",
6
6
  "bin": {
@@ -0,0 +1,121 @@
1
+ /* Pomelo Vue Skeleton Styles
2
+ * These styles control the skeleton loading placeholder appearance.
3
+ * You can override any of these rules in your own CSS to customize the skeleton effect.
4
+ */
5
+
6
+ /* Skeleton container */
7
+ ._pome-skeleton {
8
+ pointer-events: none !important;
9
+ user-select: none !important;
10
+ }
11
+
12
+ /* Hide all text and visual decorations inside skeleton */
13
+ ._pome-skeleton * {
14
+ color: transparent !important;
15
+ -webkit-text-fill-color: transparent !important;
16
+ border-color: transparent !important;
17
+ box-shadow: none !important;
18
+ text-shadow: none !important;
19
+ }
20
+
21
+ /* Hide media elements */
22
+ ._pome-skeleton svg,
23
+ ._pome-skeleton video,
24
+ ._pome-skeleton canvas,
25
+ ._pome-skeleton iframe {
26
+ visibility: hidden !important;
27
+ }
28
+
29
+ ._pome-skeleton img {
30
+ visibility: hidden !important;
31
+ }
32
+
33
+ /* Shimmer animation keyframes */
34
+ @keyframes _pome-sk-shimmer {
35
+ 0% { background-position: 100% 50%; }
36
+ 100% { background-position: 0 50%; }
37
+ }
38
+
39
+ /* Shimmer class – applied to elements that should show the loading animation */
40
+ ._pome-sk-shimmer {
41
+ background-image: linear-gradient(90deg, #e8e8e8 25%, #d8d8d8 37%, #e8e8e8 63%) !important;
42
+ background-size: 400% 100% !important;
43
+ animation: _pome-sk-shimmer 1.4s ease infinite !important;
44
+ }
45
+
46
+ /* Inline elements sizing */
47
+ ._pome-skeleton span._pome-sk-shimmer,
48
+ ._pome-skeleton a._pome-sk-shimmer,
49
+ ._pome-skeleton label._pome-sk-shimmer {
50
+ display: inline-block !important;
51
+ min-width: 100px !important;
52
+ min-height: 1em !important;
53
+ border-radius: 4px !important;
54
+ }
55
+
56
+ /* Block elements sizing */
57
+ ._pome-skeleton p._pome-sk-shimmer,
58
+ ._pome-skeleton h1._pome-sk-shimmer,
59
+ ._pome-skeleton h2._pome-sk-shimmer,
60
+ ._pome-skeleton h3._pome-sk-shimmer,
61
+ ._pome-skeleton h4._pome-sk-shimmer,
62
+ ._pome-skeleton h5._pome-sk-shimmer,
63
+ ._pome-skeleton h6._pome-sk-shimmer,
64
+ ._pome-skeleton li._pome-sk-shimmer,
65
+ ._pome-skeleton blockquote._pome-sk-shimmer,
66
+ ._pome-skeleton figcaption._pome-sk-shimmer {
67
+ display: block !important;
68
+ min-height: 1em !important;
69
+ border-radius: 4px !important;
70
+ }
71
+
72
+ /* Table layout */
73
+ ._pome-skeleton table {
74
+ table-layout: fixed !important;
75
+ width: 100% !important;
76
+ }
77
+
78
+ ._pome-skeleton td,
79
+ ._pome-skeleton th {
80
+ color: transparent !important;
81
+ overflow: hidden !important;
82
+ }
83
+
84
+ ._pome-skeleton td ._pome-sk-block,
85
+ ._pome-skeleton th ._pome-sk-block {
86
+ min-width: 0 !important;
87
+ width: 80% !important;
88
+ }
89
+
90
+ /* Button / input sizing */
91
+ ._pome-skeleton button._pome-sk-shimmer,
92
+ ._pome-skeleton input._pome-sk-shimmer,
93
+ ._pome-skeleton textarea._pome-sk-shimmer,
94
+ ._pome-skeleton select._pome-sk-shimmer {
95
+ display: inline-block !important;
96
+ min-width: 80px !important;
97
+ min-height: 1em !important;
98
+ border-radius: 4px !important;
99
+ }
100
+
101
+ /* Skeleton block – replaces text nodes */
102
+ ._pome-sk-block {
103
+ display: inline-block;
104
+ min-width: 100px;
105
+ height: 1em;
106
+ border-radius: 4px;
107
+ vertical-align: middle;
108
+ background-image: linear-gradient(90deg, #e8e8e8 25%, #d8d8d8 37%, #e8e8e8 63%);
109
+ background-size: 400% 100%;
110
+ animation: _pome-sk-shimmer 1.4s ease infinite;
111
+ }
112
+
113
+ /* Skeleton image – replaces img elements */
114
+ ._pome-sk-img {
115
+ display: inline-block;
116
+ border-radius: 4px;
117
+ vertical-align: middle;
118
+ background-image: linear-gradient(90deg, #e8e8e8 25%, #d8d8d8 37%, #e8e8e8 63%);
119
+ background-size: 400% 100%;
120
+ animation: _pome-sk-shimmer 1.4s ease infinite;
121
+ }
package/pome-ui.dev.js CHANGED
@@ -18196,6 +18196,195 @@ function build(options, exports) {
18196
18196
  document.head.appendChild(style);
18197
18197
  }
18198
18198
 
18199
+ function _ensureSkeletonStyle() {
18200
+ // pome-skeleton.css must be referenced by the host page manually.
18201
+ // This function is kept as a hook point for future use.
18202
+ }
18203
+
18204
+ // --- Known HTML tags for skeleton (custom components will be unwrapped) ---
18205
+ var _knownHtmlTags = null;
18206
+ function _isKnownHtmlTag(tag) {
18207
+ if (!_knownHtmlTags) {
18208
+ _knownHtmlTags = {};
18209
+ 'div,span,p,a,h1,h2,h3,h4,h5,h6,ul,ol,li,table,thead,tbody,tfoot,tr,td,th,colgroup,col,img,video,canvas,iframe,svg,form,input,textarea,select,option,button,label,section,article,aside,header,footer,nav,main,figure,figcaption,blockquote,pre,code,em,strong,i,b,u,small,sub,sup,br,hr,dl,dt,dd,details,summary,fieldset,legend,caption,abbr,cite,mark,time,address'.split(',').forEach(function(t) { _knownHtmlTags[t] = true; });
18210
+ }
18211
+ return !!_knownHtmlTags[tag];
18212
+ }
18213
+
18214
+ function _generateSkeletonHtml(templateHtml) {
18215
+ if (!templateHtml) return '';
18216
+ var html = templateHtml;
18217
+ // Remove Vue mustache expressions – replace with non-breaking space
18218
+ html = html.replace(/\{\{[\s\S]*?\}\}/g, '\u00A0');
18219
+ // Parse as DOM
18220
+ var doc = new DOMParser().parseFromString(
18221
+ '<div id="_pome-sk-wrap">' + html + '</div>', 'text/html'
18222
+ );
18223
+ var root = doc.getElementById('_pome-sk-wrap');
18224
+ if (!root) return '';
18225
+ // Pass 1 – structural: unwrap <template> slots, replace custom elements,
18226
+ // clone v-for rows, remove v-else branches
18227
+ _skeletonStructurePass(root, doc);
18228
+ // Pass 2 – cleanup: strip Vue directives, replace text with skeleton blocks
18229
+ _skeletonCleanupPass(root);
18230
+ return root.innerHTML;
18231
+ }
18232
+
18233
+ function _wrapTemplateWithSkeleton(opt) {
18234
+ if (!opt.skeleton || !opt.template) return;
18235
+ _ensureSkeletonStyle();
18236
+ var skeletonHtml = typeof opt.skeleton === 'string'
18237
+ ? opt.skeleton
18238
+ : _generateSkeletonHtml(opt.template);
18239
+
18240
+ var inner = '<template v-if="!pomeSkeletonLoading">' + opt.template
18241
+ + '</template><div v-if="pomeSkeletonLoading" class="_pome-skeleton">'
18242
+ + skeletonHtml + '</div>';
18243
+
18244
+ if (opt.delayOpen) {
18245
+ // Wrap everything in a single div with reactive delayOpen classes
18246
+ // so the delayOpen animation applies to both skeleton and real content.
18247
+ opt.template = '<div :class="{\'_pome-ui-opened\': pomeDelayOpened, \'_pome-ui-opening\': pomeDelayOpening}">' + inner + '</div>';
18248
+ } else {
18249
+ opt.template = inner;
18250
+ }
18251
+ }
18252
+
18253
+ // --- Pass 1: structural transformations ---
18254
+ function _skeletonStructurePass(node, doc) {
18255
+ if (!node || node.nodeType !== 1) return;
18256
+ var i = 0;
18257
+ while (i < node.childNodes.length) {
18258
+ var child = node.childNodes[i];
18259
+ if (child.nodeType !== 1) { i++; continue; }
18260
+ var tag = child.tagName.toLowerCase();
18261
+
18262
+ // 1. Handle <template> tags (Vue slots)
18263
+ if (tag === 'template') {
18264
+ // Determine slot name from #xxx or v-slot:xxx
18265
+ var slotName = null;
18266
+ for (var a = 0; a < child.attributes.length; a++) {
18267
+ var attr = child.attributes[a].name;
18268
+ if (attr[0] === '#') { slotName = attr.substring(1); break; }
18269
+ if (attr.indexOf('v-slot:') === 0) { slotName = attr.substring(7); break; }
18270
+ }
18271
+ // Only keep the default slot (or unnamed templates); discard others
18272
+ if (slotName && slotName !== 'default') {
18273
+ node.removeChild(child);
18274
+ continue;
18275
+ }
18276
+ // Unwrap template content into parent
18277
+ if (child.content) {
18278
+ var tplNodes = Array.from(child.content.childNodes);
18279
+ for (var j = 0; j < tplNodes.length; j++) {
18280
+ node.insertBefore(doc.importNode(tplNodes[j], true), child);
18281
+ }
18282
+ }
18283
+ node.removeChild(child);
18284
+ continue; // re-examine index (new children inserted)
18285
+ }
18286
+
18287
+ // 2. Remove v-else / v-else-if branches entirely
18288
+ if (child.getAttribute('v-else') !== null || child.getAttribute('v-else-if') !== null) {
18289
+ node.removeChild(child);
18290
+ continue;
18291
+ }
18292
+
18293
+ // 3. Handle v-for – clone element to simulate repeated rows
18294
+ if (child.getAttribute('v-for')) {
18295
+ child.removeAttribute('v-for');
18296
+ var cloneCount = (tag === 'tr') ? 4 : (tag === 'li') ? 3 : 2;
18297
+ var refNode = child.nextSibling;
18298
+ for (var j = 0; j < cloneCount; j++) {
18299
+ var clone = child.cloneNode(true);
18300
+ node.insertBefore(clone, refNode);
18301
+ }
18302
+ // fall through to continue processing this element
18303
+ }
18304
+
18305
+ // 4. Replace custom/unknown elements with <div>, keep children
18306
+ if (!_isKnownHtmlTag(tag)) {
18307
+ var div = doc.createElement('div');
18308
+ if (child.getAttribute('class')) div.setAttribute('class', child.getAttribute('class'));
18309
+ if (child.getAttribute('style')) div.setAttribute('style', child.getAttribute('style'));
18310
+ while (child.firstChild) div.appendChild(child.firstChild);
18311
+ node.replaceChild(div, child);
18312
+ _skeletonStructurePass(div, doc); // recurse into replacement
18313
+ i++;
18314
+ continue;
18315
+ }
18316
+
18317
+ // 5. Recurse into known elements
18318
+ _skeletonStructurePass(child, doc);
18319
+ i++;
18320
+ }
18321
+ }
18322
+
18323
+ // --- Pass 2: cleanup directives, replace text, handle media ---
18324
+ function _skeletonCleanupPass(node) {
18325
+ if (!node) return;
18326
+ if (node.nodeType === 1) {
18327
+ // Strip Vue directives / bindings / event listeners
18328
+ var attrsToRemove = [];
18329
+ for (var i = 0; i < node.attributes.length; i++) {
18330
+ var attrName = node.attributes[i].name;
18331
+ if (attrName.indexOf('v-') === 0 || attrName[0] === '@' || attrName[0] === ':' || attrName[0] === '#') {
18332
+ attrsToRemove.push(attrName);
18333
+ }
18334
+ }
18335
+ for (var i = 0; i < attrsToRemove.length; i++) {
18336
+ node.removeAttribute(attrsToRemove[i]);
18337
+ }
18338
+ // Remove delayOpen classes
18339
+ node.classList.remove('_pome-ui-opened');
18340
+ node.classList.remove('_pome-ui-opening');
18341
+ // Add shimmer class to leaf-level text containers
18342
+ var tag = node.tagName.toLowerCase();
18343
+ var shimmerTags = {span:1,a:1,p:1,h1:1,h2:1,h3:1,h4:1,h5:1,h6:1,li:1,label:1,button:1,input:1,textarea:1,select:1,dt:1,dd:1,figcaption:1,blockquote:1};
18344
+ if (shimmerTags[tag]) {
18345
+ node.classList.add('_pome-sk-shimmer');
18346
+ }
18347
+ if (tag === 'img') {
18348
+ // Replace img with a shimmer skeleton block that preserves dimensions
18349
+ var imgBlock = node.ownerDocument.createElement('span');
18350
+ imgBlock.className = '_pome-sk-img';
18351
+ // Preserve sizing from class or explicit attributes
18352
+ if (node.getAttribute('class')) imgBlock.className += ' ' + node.getAttribute('class');
18353
+ if (node.getAttribute('width')) imgBlock.style.width = node.getAttribute('width') + (/\d$/.test(node.getAttribute('width')) ? 'px' : '');
18354
+ else imgBlock.style.width = '24px';
18355
+ if (node.getAttribute('height')) imgBlock.style.height = node.getAttribute('height') + (/\d$/.test(node.getAttribute('height')) ? 'px' : '');
18356
+ else imgBlock.style.height = '24px';
18357
+ node.parentNode.replaceChild(imgBlock, node);
18358
+ return; // node replaced, skip children
18359
+ }
18360
+ if (tag === 'video' || tag === 'canvas' || tag === 'iframe') {
18361
+ node.removeAttribute('src');
18362
+ node.removeAttribute('srcset');
18363
+ node.style.backgroundColor = '#e8e8e8';
18364
+ node.style.borderRadius = '4px';
18365
+ }
18366
+ }
18367
+ var children = Array.from(node.childNodes);
18368
+ for (var i = 0; i < children.length; i++) {
18369
+ var child = children[i];
18370
+ if (child.nodeType === 3) { // text node
18371
+ // \u00A0 comes from mustache replacement – treat it as content
18372
+ var text = child.textContent.replace(/[\s\u00A0]+/g, '');
18373
+ var hasNbsp = child.textContent.indexOf('\u00A0') >= 0;
18374
+ if (text || hasNbsp) {
18375
+ var parentTag = child.parentNode && child.parentNode.tagName
18376
+ ? child.parentNode.tagName.toLowerCase() : '';
18377
+ var block = child.ownerDocument.createElement('span');
18378
+ block.className = '_pome-sk-block';
18379
+ block.innerHTML = '\u00A0';
18380
+ child.parentNode.replaceChild(block, child);
18381
+ }
18382
+ } else if (child.nodeType === 1) {
18383
+ _skeletonCleanupPass(child);
18384
+ }
18385
+ }
18386
+ }
18387
+
18199
18388
  function _httpCached(url) {
18200
18389
  return !!_cache[url];
18201
18390
  }
@@ -18481,14 +18670,21 @@ function build(options, exports) {
18481
18670
  if (children[i].getAttribute('v-if') != null || children[i].getAttribute('v-else-if') || children[i].getAttribute('v-else')) {
18482
18671
  continue;
18483
18672
  }
18484
- children[i].classList.add('_pome-ui-opened');
18485
- children[i].classList.add('_pome-ui-opening');
18673
+ if (component.skeleton) {
18674
+ // Skeleton mode: delayOpen classes will be added by
18675
+ // _wrapTemplateWithSkeleton on an outer wrapper div.
18676
+ // No need to add them to individual children here.
18677
+ } else {
18678
+ children[i].classList.add('_pome-ui-opened');
18679
+ children[i].classList.add('_pome-ui-opening');
18680
+ }
18486
18681
  }
18487
18682
 
18488
18683
  var ret = template.indexOf('<html') >= 0 ? new XMLSerializer().serializeToString(dom) : dom.querySelector('body').innerHTML;
18489
18684
  component.template = ret;
18490
18685
  }
18491
18686
  _patchComponent(url, component);
18687
+ _wrapTemplateWithSkeleton(component);
18492
18688
  return Promise.resolve(component);
18493
18689
  });
18494
18690
  })
@@ -19229,24 +19425,51 @@ function build(options, exports) {
19229
19425
  options.created = function () { };
19230
19426
  }
19231
19427
 
19232
- if (options.style) {
19428
+ if (options.style || options.skeleton) {
19233
19429
  var originalCreated = options.created;
19234
19430
  options.created = function () {
19235
- if (!_css[view]) {
19236
- _css[view] = 0;
19237
- }
19238
- if (_css[view] == 0) {
19239
- if (_options.preloadCss) {
19240
- this._cssLoadPromise = appendCssReferenceAsync(view, options.style);
19241
- } else {
19242
- appendCssReference(view, options.style);
19431
+ if (options.style) {
19432
+ if (!_css[view]) {
19433
+ _css[view] = 0;
19243
19434
  }
19435
+ if (_css[view] == 0) {
19436
+ if (_options.preloadCss) {
19437
+ this._cssLoadPromise = appendCssReferenceAsync(view, options.style);
19438
+ } else {
19439
+ appendCssReference(view, options.style);
19440
+ }
19441
+ }
19442
+ ++_css[view];
19244
19443
  }
19245
- ++_css[view];
19444
+
19246
19445
  this.createdPromise = Promise.resolve();
19247
19446
  var ret = originalCreated.call(this);
19248
19447
  if (ret instanceof Promise) {
19249
19448
  this.createdPromise = ret;
19449
+ if (options.skeleton) {
19450
+ var self = this;
19451
+ // For skeleton + delayOpen: start the delayOpen animation
19452
+ // IMMEDIATELY so the skeleton fades in right away.
19453
+ if (options.delayOpen) {
19454
+ self.$nextTick(function () {
19455
+ requestAnimationFrame(function () {
19456
+ requestAnimationFrame(function () {
19457
+ self.pomeDelayOpening = false;
19458
+ });
19459
+ });
19460
+ setTimeout(function () {
19461
+ self.pomeDelayOpened = false;
19462
+ }, options.delayOpen);
19463
+ });
19464
+ }
19465
+ ret.then(function () {
19466
+ self.pomeSkeletonLoading = false;
19467
+ }).catch(function (err) {
19468
+ console.error('[SKELETON] created Promise REJECTED:', err);
19469
+ });
19470
+ }
19471
+ } else if (options.skeleton) {
19472
+ this.pomeSkeletonLoading = false;
19250
19473
  }
19251
19474
 
19252
19475
  return ret;
@@ -19268,11 +19491,12 @@ function build(options, exports) {
19268
19491
  }
19269
19492
 
19270
19493
  // Remove opening class if needed
19271
- if (options.delayOpen) {
19494
+ if (options.delayOpen && !options.skeleton) {
19272
19495
  var self = this;
19273
- (self.createdPromise || Promise.resolve()).then(function () {
19496
+ var delayOpenFunc = function () {
19274
19497
  requestAnimationFrame(function () {
19275
19498
  requestAnimationFrame(function () {
19499
+ if (!self.$el || !self.$el.parentNode) return;
19276
19500
  var doms = self.$el.parentNode.querySelectorAll('._pome-ui-opening');
19277
19501
  for (var i = 0; i < doms.length; ++i) {
19278
19502
  doms[i].classList.remove('_pome-ui-opening');
@@ -19281,11 +19505,16 @@ function build(options, exports) {
19281
19505
  });
19282
19506
 
19283
19507
  setTimeout(function () {
19508
+ if (!self.$el || !self.$el.parentNode) return;
19284
19509
  var doms = self.$el.parentNode.querySelectorAll('._pome-ui-opened');
19285
19510
  for (var i = 0; i < doms.length; ++i) {
19286
19511
  doms[i].classList.remove('_pome-ui-opened');
19287
19512
  }
19288
19513
  }, options.delayOpen);
19514
+ };
19515
+
19516
+ (self.createdPromise || Promise.resolve()).then(function () {
19517
+ delayOpenFunc();
19289
19518
  });
19290
19519
  }
19291
19520
 
@@ -19294,14 +19523,16 @@ function build(options, exports) {
19294
19523
 
19295
19524
  var originalUnmounted = options.unmounted;
19296
19525
  options.unmounted = function () {
19297
- if (!_css[view]) {
19298
- return;
19299
- }
19526
+ if (options.style) {
19527
+ if (!_css[view]) {
19528
+ return originalUnmounted.call(this);
19529
+ }
19300
19530
 
19301
- --_css[view];
19302
- if (_css[view] <= 0) {
19303
- removeCssReference(view);
19304
- delete _css[view];
19531
+ --_css[view];
19532
+ if (_css[view] <= 0) {
19533
+ removeCssReference(view);
19534
+ delete _css[view];
19535
+ }
19305
19536
  }
19306
19537
  return originalUnmounted.call(this);
19307
19538
  };
@@ -19593,6 +19824,7 @@ function build(options, exports) {
19593
19824
  }
19594
19825
  _hookSetup(_opt);
19595
19826
  _patchComponent(c, _opt);
19827
+ _wrapTemplateWithSkeleton(_opt);
19596
19828
  _hookData(_opt);
19597
19829
  var cssPreloadPromise = Promise.resolve();
19598
19830
  if (_options.preloadCss && _opt.style) {
@@ -19700,6 +19932,13 @@ function build(options, exports) {
19700
19932
  opt.data = function (app) {
19701
19933
  var data = func1.call(this, app);
19702
19934
  var data2 = { pomeUiSubTitles: [] };
19935
+ if (opt.skeleton) {
19936
+ data2.pomeSkeletonLoading = true;
19937
+ }
19938
+ if (opt.skeleton && opt.delayOpen) {
19939
+ data2.pomeDelayOpened = true;
19940
+ data2.pomeDelayOpening = true;
19941
+ }
19703
19942
  _combineObject(data2, data);
19704
19943
  return data;
19705
19944
  }