pome-ui 2.0.0-preview50 → 2.0.0-preview52
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 +1 -1
- package/pome-skeleton.css +136 -0
- package/pome-ui.dev.js +468 -32
- package/pome-ui.dev.min.js +23 -23
- package/pome-ui.js +468 -32
- package/pome-ui.min.js +4 -4
package/pome-ui.dev.js
CHANGED
|
@@ -18196,6 +18196,274 @@ 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
|
+
// Registry: kebab-name → pre-rendered skeleton HTML string
|
|
18205
|
+
var _componentSkeletonRegistry = {};
|
|
18206
|
+
|
|
18207
|
+
function _toKebabCase(name) {
|
|
18208
|
+
// ScalableSidebar → scalable-sidebar, menuBar → menu-bar
|
|
18209
|
+
return name.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase();
|
|
18210
|
+
}
|
|
18211
|
+
|
|
18212
|
+
function _registerComponentSkeleton(name, opt, rawHtml, skeletonFileHtml) {
|
|
18213
|
+
// skeletonFileHtml: content of <component>.skeleton.html (takes top priority)
|
|
18214
|
+
// opt.skeleton === string: inline HTML declared in the JS
|
|
18215
|
+
// opt.skeleton === true: auto-generate from rawHtml
|
|
18216
|
+
var hasFile = !!skeletonFileHtml;
|
|
18217
|
+
var hasInline = opt && typeof opt.skeleton === 'string';
|
|
18218
|
+
var hasAuto = opt && opt.skeleton === true;
|
|
18219
|
+
if (!hasFile && !hasInline && !hasAuto) return;
|
|
18220
|
+
var kebab = _toKebabCase(name);
|
|
18221
|
+
var skHtml = hasFile ? skeletonFileHtml
|
|
18222
|
+
: hasInline ? opt.skeleton
|
|
18223
|
+
: _generateSkeletonHtml(rawHtml || (opt && opt.template) || '');
|
|
18224
|
+
_componentSkeletonRegistry[kebab] = skHtml;
|
|
18225
|
+
}
|
|
18226
|
+
|
|
18227
|
+
// --- Extract a plain numeric value from a bound attribute like :default-width="230" ---
|
|
18228
|
+
function _extractBoundNumber(el, attr) {
|
|
18229
|
+
var val = el.getAttribute(':' + attr);
|
|
18230
|
+
if (!val) return null;
|
|
18231
|
+
var m = val.match(/^(\d+(?:\.\d+)?)/);
|
|
18232
|
+
return m ? m[1] : null;
|
|
18233
|
+
}
|
|
18234
|
+
|
|
18235
|
+
// --- Known HTML tags for skeleton (custom components will be unwrapped) ---
|
|
18236
|
+
var _knownHtmlTags = null;
|
|
18237
|
+
function _isKnownHtmlTag(tag) {
|
|
18238
|
+
if (!_knownHtmlTags) {
|
|
18239
|
+
_knownHtmlTags = {};
|
|
18240
|
+
'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; });
|
|
18241
|
+
}
|
|
18242
|
+
return !!_knownHtmlTags[tag];
|
|
18243
|
+
}
|
|
18244
|
+
|
|
18245
|
+
function _generateSkeletonHtml(templateHtml) {
|
|
18246
|
+
if (!templateHtml) return '';
|
|
18247
|
+
var html = templateHtml;
|
|
18248
|
+
// Remove Vue mustache expressions – replace with non-breaking space
|
|
18249
|
+
html = html.replace(/\{\{[\s\S]*?\}\}/g, '\u00A0');
|
|
18250
|
+
// Parse as DOM
|
|
18251
|
+
var doc = new DOMParser().parseFromString(
|
|
18252
|
+
'<div id="_pome-sk-wrap">' + html + '</div>', 'text/html'
|
|
18253
|
+
);
|
|
18254
|
+
var root = doc.getElementById('_pome-sk-wrap');
|
|
18255
|
+
if (!root) return '';
|
|
18256
|
+
// Pass 1 – structural: unwrap <template> slots, replace custom elements,
|
|
18257
|
+
// clone v-for rows, remove v-else branches
|
|
18258
|
+
_skeletonStructurePass(root, doc);
|
|
18259
|
+
// Pass 2 – cleanup: strip Vue directives, replace text with skeleton blocks
|
|
18260
|
+
_skeletonCleanupPass(root);
|
|
18261
|
+
return root.innerHTML;
|
|
18262
|
+
}
|
|
18263
|
+
|
|
18264
|
+
function _wrapTemplateWithSkeleton(opt) {
|
|
18265
|
+
if (!opt.skeleton || !opt.template) return;
|
|
18266
|
+
_ensureSkeletonStyle();
|
|
18267
|
+
var skeletonHtml = typeof opt.skeleton === 'string'
|
|
18268
|
+
? opt.skeleton
|
|
18269
|
+
: _generateSkeletonHtml(opt.template);
|
|
18270
|
+
|
|
18271
|
+
var inner = '<template v-if="!pomeSkeletonLoading">' + opt.template
|
|
18272
|
+
+ '</template><div v-if="pomeSkeletonLoading" class="_pome-skeleton">'
|
|
18273
|
+
+ skeletonHtml + '</div>';
|
|
18274
|
+
|
|
18275
|
+
if (opt.delayOpen) {
|
|
18276
|
+
// Wrap everything in a single div with reactive delayOpen classes
|
|
18277
|
+
// so the delayOpen animation applies to both skeleton and real content.
|
|
18278
|
+
opt.template = '<div :class="{\'_pome-ui-opened\': pomeDelayOpened, \'_pome-ui-opening\': pomeDelayOpening}">' + inner + '</div>';
|
|
18279
|
+
} else {
|
|
18280
|
+
opt.template = inner;
|
|
18281
|
+
}
|
|
18282
|
+
}
|
|
18283
|
+
|
|
18284
|
+
// --- Pass 1: structural transformations ---
|
|
18285
|
+
function _skeletonStructurePass(node, doc) {
|
|
18286
|
+
if (!node || node.nodeType !== 1) return;
|
|
18287
|
+
var i = 0;
|
|
18288
|
+
while (i < node.childNodes.length) {
|
|
18289
|
+
var child = node.childNodes[i];
|
|
18290
|
+
if (child.nodeType !== 1) { i++; continue; }
|
|
18291
|
+
var tag = child.tagName.toLowerCase();
|
|
18292
|
+
|
|
18293
|
+
// 1. Handle <template> tags (Vue slots)
|
|
18294
|
+
if (tag === 'template') {
|
|
18295
|
+
// Determine slot name from #xxx or v-slot:xxx
|
|
18296
|
+
var slotName = null;
|
|
18297
|
+
for (var a = 0; a < child.attributes.length; a++) {
|
|
18298
|
+
var attr = child.attributes[a].name;
|
|
18299
|
+
if (attr[0] === '#') { slotName = attr.substring(1); break; }
|
|
18300
|
+
if (attr.indexOf('v-slot:') === 0) { slotName = attr.substring(7); break; }
|
|
18301
|
+
}
|
|
18302
|
+
// Only keep the default slot (or unnamed templates); discard others
|
|
18303
|
+
if (slotName && slotName !== 'default') {
|
|
18304
|
+
node.removeChild(child);
|
|
18305
|
+
continue;
|
|
18306
|
+
}
|
|
18307
|
+
// Unwrap template content into parent
|
|
18308
|
+
if (child.content) {
|
|
18309
|
+
var tplNodes = Array.from(child.content.childNodes);
|
|
18310
|
+
for (var j = 0; j < tplNodes.length; j++) {
|
|
18311
|
+
node.insertBefore(doc.importNode(tplNodes[j], true), child);
|
|
18312
|
+
}
|
|
18313
|
+
}
|
|
18314
|
+
node.removeChild(child);
|
|
18315
|
+
continue; // re-examine index (new children inserted)
|
|
18316
|
+
}
|
|
18317
|
+
|
|
18318
|
+
// 2. Remove v-else / v-else-if branches entirely
|
|
18319
|
+
if (child.getAttribute('v-else') !== null || child.getAttribute('v-else-if') !== null) {
|
|
18320
|
+
node.removeChild(child);
|
|
18321
|
+
continue;
|
|
18322
|
+
}
|
|
18323
|
+
|
|
18324
|
+
// 3. Handle v-for – clone element to simulate repeated rows
|
|
18325
|
+
if (child.getAttribute('v-for')) {
|
|
18326
|
+
child.removeAttribute('v-for');
|
|
18327
|
+
var cloneCount = (tag === 'tr') ? 4 : (tag === 'li') ? 3 : 2;
|
|
18328
|
+
var refNode = child.nextSibling;
|
|
18329
|
+
for (var j = 0; j < cloneCount; j++) {
|
|
18330
|
+
var clone = child.cloneNode(true);
|
|
18331
|
+
node.insertBefore(clone, refNode);
|
|
18332
|
+
}
|
|
18333
|
+
// fall through to continue processing this element
|
|
18334
|
+
}
|
|
18335
|
+
|
|
18336
|
+
// 4. Replace custom/unknown elements with <div>, keep children
|
|
18337
|
+
if (!_isKnownHtmlTag(tag)) {
|
|
18338
|
+
// Check if this component has declared its own skeleton
|
|
18339
|
+
var registeredSkeleton = _componentSkeletonRegistry[tag];
|
|
18340
|
+
if (registeredSkeleton) {
|
|
18341
|
+
// Parse the registered skeleton HTML and replace the element with it
|
|
18342
|
+
var skWrap = doc.createElement('div');
|
|
18343
|
+
skWrap.innerHTML = registeredSkeleton;
|
|
18344
|
+
if (child.getAttribute('class')) skWrap.setAttribute('class', child.getAttribute('class'));
|
|
18345
|
+
var existingStyleReg = child.getAttribute('style') || '';
|
|
18346
|
+
var styleExtraReg = '';
|
|
18347
|
+
var defWReg = _extractBoundNumber(child, 'default-width') || child.getAttribute('default-width');
|
|
18348
|
+
var minWReg = _extractBoundNumber(child, 'min-width') || child.getAttribute('min-width');
|
|
18349
|
+
if (defWReg) styleExtraReg += ';width:' + defWReg + 'px;flex-shrink:0';
|
|
18350
|
+
if (minWReg) styleExtraReg += ';min-width:' + minWReg + 'px';
|
|
18351
|
+
var combinedStyleReg = (existingStyleReg + styleExtraReg).replace(/^;+/, '');
|
|
18352
|
+
if (combinedStyleReg) skWrap.setAttribute('style', combinedStyleReg);
|
|
18353
|
+
// Replace the custom element with a wrapper containing the skeleton.
|
|
18354
|
+
// Use a fragment-like approach: move skWrap's children directly
|
|
18355
|
+
// so we don't add an extra div when the registered HTML already has one.
|
|
18356
|
+
var skNodes = Array.from(skWrap.childNodes);
|
|
18357
|
+
if (skNodes.length === 1 && skNodes[0].nodeType === 1) {
|
|
18358
|
+
var skRoot = skNodes[0];
|
|
18359
|
+
// Merge class / style from host element onto the single root node
|
|
18360
|
+
if (child.getAttribute('class')) skRoot.classList && skRoot.setAttribute('class', (skRoot.getAttribute('class') || '') + ' ' + child.getAttribute('class'));
|
|
18361
|
+
if (combinedStyleReg) skRoot.setAttribute('style', (skRoot.getAttribute('style') ? skRoot.getAttribute('style') + ';' : '') + combinedStyleReg);
|
|
18362
|
+
node.replaceChild(skRoot, child);
|
|
18363
|
+
i++;
|
|
18364
|
+
} else {
|
|
18365
|
+
while (skWrap.firstChild) node.insertBefore(skWrap.firstChild, child);
|
|
18366
|
+
node.removeChild(child);
|
|
18367
|
+
// i stays the same since we removed child and inserted nodes before it
|
|
18368
|
+
}
|
|
18369
|
+
continue;
|
|
18370
|
+
}
|
|
18371
|
+
|
|
18372
|
+
var div = doc.createElement('div');
|
|
18373
|
+
if (child.getAttribute('class')) div.setAttribute('class', child.getAttribute('class'));
|
|
18374
|
+
|
|
18375
|
+
// Reconstruct inline style, preserving explicit style + any bound sizing attrs
|
|
18376
|
+
var existingStyle = child.getAttribute('style') || '';
|
|
18377
|
+
var styleExtra = '';
|
|
18378
|
+
// :default-width / default-width → width + flex-shrink:0 so sidebar-like
|
|
18379
|
+
// components keep their width in the skeleton
|
|
18380
|
+
var defW = _extractBoundNumber(child, 'default-width') || child.getAttribute('default-width');
|
|
18381
|
+
var minW = _extractBoundNumber(child, 'min-width') || child.getAttribute('min-width');
|
|
18382
|
+
var maxW = _extractBoundNumber(child, 'max-width') || child.getAttribute('max-width');
|
|
18383
|
+
if (defW) styleExtra += ';width:' + defW + 'px;flex-shrink:0';
|
|
18384
|
+
if (minW) styleExtra += ';min-width:' + minW + 'px';
|
|
18385
|
+
if (maxW) styleExtra += ';max-width:' + maxW + 'px';
|
|
18386
|
+
var combinedStyle = (existingStyle + styleExtra).replace(/^;+/, '');
|
|
18387
|
+
if (combinedStyle) div.setAttribute('style', combinedStyle);
|
|
18388
|
+
|
|
18389
|
+
while (child.firstChild) div.appendChild(child.firstChild);
|
|
18390
|
+
node.replaceChild(div, child);
|
|
18391
|
+
_skeletonStructurePass(div, doc); // recurse into replacement
|
|
18392
|
+
i++;
|
|
18393
|
+
continue;
|
|
18394
|
+
}
|
|
18395
|
+
|
|
18396
|
+
// 5. Recurse into known elements
|
|
18397
|
+
_skeletonStructurePass(child, doc);
|
|
18398
|
+
i++;
|
|
18399
|
+
}
|
|
18400
|
+
}
|
|
18401
|
+
|
|
18402
|
+
// --- Pass 2: cleanup directives, replace text, handle media ---
|
|
18403
|
+
function _skeletonCleanupPass(node) {
|
|
18404
|
+
if (!node) return;
|
|
18405
|
+
if (node.nodeType === 1) {
|
|
18406
|
+
// Strip Vue directives / bindings / event listeners
|
|
18407
|
+
var attrsToRemove = [];
|
|
18408
|
+
for (var i = 0; i < node.attributes.length; i++) {
|
|
18409
|
+
var attrName = node.attributes[i].name;
|
|
18410
|
+
if (attrName.indexOf('v-') === 0 || attrName[0] === '@' || attrName[0] === ':' || attrName[0] === '#') {
|
|
18411
|
+
attrsToRemove.push(attrName);
|
|
18412
|
+
}
|
|
18413
|
+
}
|
|
18414
|
+
for (var i = 0; i < attrsToRemove.length; i++) {
|
|
18415
|
+
node.removeAttribute(attrsToRemove[i]);
|
|
18416
|
+
}
|
|
18417
|
+
// Remove delayOpen classes
|
|
18418
|
+
node.classList.remove('_pome-ui-opened');
|
|
18419
|
+
node.classList.remove('_pome-ui-opening');
|
|
18420
|
+
// Add shimmer class to leaf-level text containers
|
|
18421
|
+
var tag = node.tagName.toLowerCase();
|
|
18422
|
+
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};
|
|
18423
|
+
if (shimmerTags[tag]) {
|
|
18424
|
+
node.classList.add('_pome-sk-shimmer');
|
|
18425
|
+
}
|
|
18426
|
+
if (tag === 'img') {
|
|
18427
|
+
// Replace img with a shimmer skeleton block that preserves dimensions
|
|
18428
|
+
var imgBlock = node.ownerDocument.createElement('span');
|
|
18429
|
+
imgBlock.className = '_pome-sk-img';
|
|
18430
|
+
// Preserve sizing from class or explicit attributes
|
|
18431
|
+
if (node.getAttribute('class')) imgBlock.className += ' ' + node.getAttribute('class');
|
|
18432
|
+
if (node.getAttribute('width')) imgBlock.style.width = node.getAttribute('width') + (/\d$/.test(node.getAttribute('width')) ? 'px' : '');
|
|
18433
|
+
else imgBlock.style.width = '24px';
|
|
18434
|
+
if (node.getAttribute('height')) imgBlock.style.height = node.getAttribute('height') + (/\d$/.test(node.getAttribute('height')) ? 'px' : '');
|
|
18435
|
+
else imgBlock.style.height = '24px';
|
|
18436
|
+
node.parentNode.replaceChild(imgBlock, node);
|
|
18437
|
+
return; // node replaced, skip children
|
|
18438
|
+
}
|
|
18439
|
+
if (tag === 'video' || tag === 'canvas' || tag === 'iframe') {
|
|
18440
|
+
node.removeAttribute('src');
|
|
18441
|
+
node.removeAttribute('srcset');
|
|
18442
|
+
node.style.backgroundColor = '#e8e8e8';
|
|
18443
|
+
node.style.borderRadius = '4px';
|
|
18444
|
+
}
|
|
18445
|
+
}
|
|
18446
|
+
var children = Array.from(node.childNodes);
|
|
18447
|
+
for (var i = 0; i < children.length; i++) {
|
|
18448
|
+
var child = children[i];
|
|
18449
|
+
if (child.nodeType === 3) { // text node
|
|
18450
|
+
// \u00A0 comes from mustache replacement – treat it as content
|
|
18451
|
+
var text = child.textContent.replace(/[\s\u00A0]+/g, '');
|
|
18452
|
+
var hasNbsp = child.textContent.indexOf('\u00A0') >= 0;
|
|
18453
|
+
if (text || hasNbsp) {
|
|
18454
|
+
var parentTag = child.parentNode && child.parentNode.tagName
|
|
18455
|
+
? child.parentNode.tagName.toLowerCase() : '';
|
|
18456
|
+
var block = child.ownerDocument.createElement('span');
|
|
18457
|
+
block.className = '_pome-sk-block';
|
|
18458
|
+
block.innerHTML = '\u00A0';
|
|
18459
|
+
child.parentNode.replaceChild(block, child);
|
|
18460
|
+
}
|
|
18461
|
+
} else if (child.nodeType === 1) {
|
|
18462
|
+
_skeletonCleanupPass(child);
|
|
18463
|
+
}
|
|
18464
|
+
}
|
|
18465
|
+
}
|
|
18466
|
+
|
|
18199
18467
|
function _httpCached(url) {
|
|
18200
18468
|
return !!_cache[url];
|
|
18201
18469
|
}
|
|
@@ -18238,6 +18506,24 @@ function build(options, exports) {
|
|
|
18238
18506
|
});
|
|
18239
18507
|
};
|
|
18240
18508
|
|
|
18509
|
+
// Fetch a .skeleton.html file, returning null when the file does not
|
|
18510
|
+
// physically exist. Unlike _httpGet, this guards against SPA catch-all
|
|
18511
|
+
// routes that return the app entry page (status 200) for any URL:
|
|
18512
|
+
// if the response body starts with "<!DOCTYPE" or "<html" it is treated
|
|
18513
|
+
// as a missing file and null is returned.
|
|
18514
|
+
function _httpGetSkeletonFile(url) {
|
|
18515
|
+
return _httpGet(url).then(function (text) {
|
|
18516
|
+
if (!text) return null;
|
|
18517
|
+
var trimmed = text.replace(/^[\s\uFEFF]+/, ''); // strip BOM / whitespace
|
|
18518
|
+
if (/^<!DOCTYPE/i.test(trimmed) || /^<html/i.test(trimmed)) {
|
|
18519
|
+
return null; // SPA fallback page, not a real skeleton file
|
|
18520
|
+
}
|
|
18521
|
+
return text;
|
|
18522
|
+
}).catch(function () {
|
|
18523
|
+
return null;
|
|
18524
|
+
});
|
|
18525
|
+
};
|
|
18526
|
+
|
|
18241
18527
|
function _serializeOptionsToUrl(options) {
|
|
18242
18528
|
var fields = Object.getOwnPropertyNames(options);
|
|
18243
18529
|
var str = '';
|
|
@@ -18458,19 +18744,27 @@ function build(options, exports) {
|
|
|
18458
18744
|
});
|
|
18459
18745
|
})
|
|
18460
18746
|
.then(function (component) {
|
|
18461
|
-
var
|
|
18747
|
+
var htmlPromise;
|
|
18462
18748
|
if (mobile) {
|
|
18463
|
-
|
|
18749
|
+
htmlPromise = _httpExist(url + '.m.html').then(function (res) {
|
|
18464
18750
|
return _httpGet(url + (res ? '.m.html' : '.html'));
|
|
18465
18751
|
});
|
|
18466
18752
|
} else {
|
|
18467
|
-
|
|
18753
|
+
htmlPromise = _httpGet(url + '.html');
|
|
18468
18754
|
}
|
|
18755
|
+
// Load .skeleton.html in parallel; null if not found
|
|
18756
|
+
var skeletonFilePromise = _httpGetSkeletonFile(url + '.skeleton.html');
|
|
18469
18757
|
|
|
18470
|
-
return
|
|
18758
|
+
return Promise.all([htmlPromise, skeletonFilePromise]).then(function (results) {
|
|
18759
|
+
var template = results[0];
|
|
18760
|
+
var skeletonFileHtml = results[1];
|
|
18471
18761
|
if (!component.template) {
|
|
18472
18762
|
component.template = _sanitizeTemplate(template);
|
|
18473
18763
|
}
|
|
18764
|
+
// .skeleton.html file takes top priority over skeleton: true / inline string
|
|
18765
|
+
if (skeletonFileHtml && component.skeleton) {
|
|
18766
|
+
component.skeleton = skeletonFileHtml;
|
|
18767
|
+
}
|
|
18474
18768
|
if (component.delayOpen) {
|
|
18475
18769
|
var dom = new DOMParser().parseFromString(component.template, 'text/html');
|
|
18476
18770
|
var children = dom.querySelector('body').children;
|
|
@@ -18481,14 +18775,22 @@ function build(options, exports) {
|
|
|
18481
18775
|
if (children[i].getAttribute('v-if') != null || children[i].getAttribute('v-else-if') || children[i].getAttribute('v-else')) {
|
|
18482
18776
|
continue;
|
|
18483
18777
|
}
|
|
18484
|
-
|
|
18485
|
-
|
|
18778
|
+
if (component.skeleton) {
|
|
18779
|
+
// Skeleton mode: delayOpen classes will be added by
|
|
18780
|
+
// _wrapTemplateWithSkeleton on an outer wrapper div.
|
|
18781
|
+
// No need to add them to individual children here.
|
|
18782
|
+
} else {
|
|
18783
|
+
children[i].classList.add('_pome-ui-opened');
|
|
18784
|
+
children[i].classList.add('_pome-ui-opening');
|
|
18785
|
+
}
|
|
18486
18786
|
}
|
|
18487
18787
|
|
|
18488
18788
|
var ret = template.indexOf('<html') >= 0 ? new XMLSerializer().serializeToString(dom) : dom.querySelector('body').innerHTML;
|
|
18489
18789
|
component.template = ret;
|
|
18490
18790
|
}
|
|
18491
18791
|
_patchComponent(url, component);
|
|
18792
|
+
// _wrapTemplateWithSkeleton is deferred to after _loadComponents
|
|
18793
|
+
// so the component skeleton registry is fully populated.
|
|
18492
18794
|
return Promise.resolve(component);
|
|
18493
18795
|
});
|
|
18494
18796
|
})
|
|
@@ -18522,6 +18824,8 @@ function build(options, exports) {
|
|
|
18522
18824
|
|
|
18523
18825
|
return Promise.all([pageCssPromise, _loadComponents(components, url)]).then(function (results) {
|
|
18524
18826
|
var components = results[1];
|
|
18827
|
+
// Wrap skeleton template now that component registry is fully populated.
|
|
18828
|
+
_wrapTemplateWithSkeleton(component);
|
|
18525
18829
|
var ret = Vue.createApp(component);
|
|
18526
18830
|
|
|
18527
18831
|
for (var i = 0; i < components.length; ++i) {
|
|
@@ -18751,6 +19055,48 @@ function build(options, exports) {
|
|
|
18751
19055
|
|
|
18752
19056
|
return _loadComponents(options.components || [], layout)
|
|
18753
19057
|
.then(function (components) {
|
|
19058
|
+
// Inject layout skeleton overlay now that component registry is populated.
|
|
19059
|
+
if (options._pomeLayoutSkeleton) {
|
|
19060
|
+
var ls = options._pomeLayoutSkeleton;
|
|
19061
|
+
var skHtml = ls.skeletonOverride
|
|
19062
|
+
? ls.skeletonOverride
|
|
19063
|
+
: (function () {
|
|
19064
|
+
var html = _generateSkeletonHtml(ls.skeletonBody);
|
|
19065
|
+
// Post-process: remove empty top-level nodes (modals etc.),
|
|
19066
|
+
// set flex:1 on the main content area next to the sidebar.
|
|
19067
|
+
var tmp = document.createElement('div');
|
|
19068
|
+
tmp.innerHTML = html;
|
|
19069
|
+
var topChildren = Array.from(tmp.children);
|
|
19070
|
+
var sidebarIdx = -1;
|
|
19071
|
+
for (var ci = 0; ci < topChildren.length; ci++) {
|
|
19072
|
+
var ch = topChildren[ci];
|
|
19073
|
+
if (!ch.innerHTML.trim()) {
|
|
19074
|
+
tmp.removeChild(ch);
|
|
19075
|
+
topChildren = Array.from(tmp.children);
|
|
19076
|
+
ci--;
|
|
19077
|
+
continue;
|
|
19078
|
+
}
|
|
19079
|
+
if (sidebarIdx < 0 && /\bwidth\s*:/.test(ch.getAttribute('style') || '')) {
|
|
19080
|
+
sidebarIdx = ci;
|
|
19081
|
+
} else if (sidebarIdx >= 0 && ci === sidebarIdx + 1) {
|
|
19082
|
+
ch.style.flex = '1';
|
|
19083
|
+
ch.style.minWidth = '0';
|
|
19084
|
+
ch.style.overflow = 'hidden';
|
|
19085
|
+
}
|
|
19086
|
+
}
|
|
19087
|
+
return tmp.innerHTML;
|
|
19088
|
+
})();
|
|
19089
|
+
var appEl = document.querySelector(ls.appId);
|
|
19090
|
+
if (appEl) {
|
|
19091
|
+
var skDiv = document.createElement('div');
|
|
19092
|
+
skDiv.setAttribute('v-if', 'pomeSkeletonLoading');
|
|
19093
|
+
skDiv.className = '_pome-skeleton _pome-layout-skeleton ' + ls.bodyClass;
|
|
19094
|
+
if (ls.overlayStyle) skDiv.setAttribute('style', ls.overlayStyle);
|
|
19095
|
+
skDiv.innerHTML = skHtml;
|
|
19096
|
+
appEl.insertBefore(skDiv, appEl.firstChild);
|
|
19097
|
+
}
|
|
19098
|
+
delete options._pomeLayoutSkeleton;
|
|
19099
|
+
}
|
|
18754
19100
|
var app = Vue.createApp(options || {});
|
|
18755
19101
|
for (var i = 0; i < components.length; ++i) {
|
|
18756
19102
|
var com = components[i];
|
|
@@ -19229,24 +19575,51 @@ function build(options, exports) {
|
|
|
19229
19575
|
options.created = function () { };
|
|
19230
19576
|
}
|
|
19231
19577
|
|
|
19232
|
-
if (options.style) {
|
|
19578
|
+
if (options.style || options.skeleton) {
|
|
19233
19579
|
var originalCreated = options.created;
|
|
19234
19580
|
options.created = function () {
|
|
19235
|
-
if (
|
|
19236
|
-
_css[view]
|
|
19237
|
-
|
|
19238
|
-
if (_css[view] == 0) {
|
|
19239
|
-
if (_options.preloadCss) {
|
|
19240
|
-
this._cssLoadPromise = appendCssReferenceAsync(view, options.style);
|
|
19241
|
-
} else {
|
|
19242
|
-
appendCssReference(view, options.style);
|
|
19581
|
+
if (options.style) {
|
|
19582
|
+
if (!_css[view]) {
|
|
19583
|
+
_css[view] = 0;
|
|
19243
19584
|
}
|
|
19585
|
+
if (_css[view] == 0) {
|
|
19586
|
+
if (_options.preloadCss) {
|
|
19587
|
+
this._cssLoadPromise = appendCssReferenceAsync(view, options.style);
|
|
19588
|
+
} else {
|
|
19589
|
+
appendCssReference(view, options.style);
|
|
19590
|
+
}
|
|
19591
|
+
}
|
|
19592
|
+
++_css[view];
|
|
19244
19593
|
}
|
|
19245
|
-
|
|
19594
|
+
|
|
19246
19595
|
this.createdPromise = Promise.resolve();
|
|
19247
19596
|
var ret = originalCreated.call(this);
|
|
19248
19597
|
if (ret instanceof Promise) {
|
|
19249
19598
|
this.createdPromise = ret;
|
|
19599
|
+
if (options.skeleton) {
|
|
19600
|
+
var self = this;
|
|
19601
|
+
// For skeleton + delayOpen: start the delayOpen animation
|
|
19602
|
+
// IMMEDIATELY so the skeleton fades in right away.
|
|
19603
|
+
if (options.delayOpen) {
|
|
19604
|
+
self.$nextTick(function () {
|
|
19605
|
+
requestAnimationFrame(function () {
|
|
19606
|
+
requestAnimationFrame(function () {
|
|
19607
|
+
self.pomeDelayOpening = false;
|
|
19608
|
+
});
|
|
19609
|
+
});
|
|
19610
|
+
setTimeout(function () {
|
|
19611
|
+
self.pomeDelayOpened = false;
|
|
19612
|
+
}, options.delayOpen);
|
|
19613
|
+
});
|
|
19614
|
+
}
|
|
19615
|
+
ret.then(function () {
|
|
19616
|
+
self.pomeSkeletonLoading = false;
|
|
19617
|
+
}).catch(function (err) {
|
|
19618
|
+
console.error('[SKELETON] created Promise REJECTED:', err);
|
|
19619
|
+
});
|
|
19620
|
+
}
|
|
19621
|
+
} else if (options.skeleton) {
|
|
19622
|
+
this.pomeSkeletonLoading = false;
|
|
19250
19623
|
}
|
|
19251
19624
|
|
|
19252
19625
|
return ret;
|
|
@@ -19268,11 +19641,12 @@ function build(options, exports) {
|
|
|
19268
19641
|
}
|
|
19269
19642
|
|
|
19270
19643
|
// Remove opening class if needed
|
|
19271
|
-
if (options.delayOpen) {
|
|
19644
|
+
if (options.delayOpen && !options.skeleton) {
|
|
19272
19645
|
var self = this;
|
|
19273
|
-
|
|
19646
|
+
var delayOpenFunc = function () {
|
|
19274
19647
|
requestAnimationFrame(function () {
|
|
19275
19648
|
requestAnimationFrame(function () {
|
|
19649
|
+
if (!self.$el || !self.$el.parentNode) return;
|
|
19276
19650
|
var doms = self.$el.parentNode.querySelectorAll('._pome-ui-opening');
|
|
19277
19651
|
for (var i = 0; i < doms.length; ++i) {
|
|
19278
19652
|
doms[i].classList.remove('_pome-ui-opening');
|
|
@@ -19281,11 +19655,16 @@ function build(options, exports) {
|
|
|
19281
19655
|
});
|
|
19282
19656
|
|
|
19283
19657
|
setTimeout(function () {
|
|
19658
|
+
if (!self.$el || !self.$el.parentNode) return;
|
|
19284
19659
|
var doms = self.$el.parentNode.querySelectorAll('._pome-ui-opened');
|
|
19285
19660
|
for (var i = 0; i < doms.length; ++i) {
|
|
19286
19661
|
doms[i].classList.remove('_pome-ui-opened');
|
|
19287
19662
|
}
|
|
19288
19663
|
}, options.delayOpen);
|
|
19664
|
+
};
|
|
19665
|
+
|
|
19666
|
+
(self.createdPromise || Promise.resolve()).then(function () {
|
|
19667
|
+
delayOpenFunc();
|
|
19289
19668
|
});
|
|
19290
19669
|
}
|
|
19291
19670
|
|
|
@@ -19294,14 +19673,16 @@ function build(options, exports) {
|
|
|
19294
19673
|
|
|
19295
19674
|
var originalUnmounted = options.unmounted;
|
|
19296
19675
|
options.unmounted = function () {
|
|
19297
|
-
if (
|
|
19298
|
-
|
|
19299
|
-
|
|
19676
|
+
if (options.style) {
|
|
19677
|
+
if (!_css[view]) {
|
|
19678
|
+
return originalUnmounted.call(this);
|
|
19679
|
+
}
|
|
19300
19680
|
|
|
19301
|
-
|
|
19302
|
-
|
|
19303
|
-
|
|
19304
|
-
|
|
19681
|
+
--_css[view];
|
|
19682
|
+
if (_css[view] <= 0) {
|
|
19683
|
+
removeCssReference(view);
|
|
19684
|
+
delete _css[view];
|
|
19685
|
+
}
|
|
19305
19686
|
}
|
|
19306
19687
|
return originalUnmounted.call(this);
|
|
19307
19688
|
};
|
|
@@ -19377,9 +19758,17 @@ function build(options, exports) {
|
|
|
19377
19758
|
if (layout) {
|
|
19378
19759
|
var layoutName = layout + (mobile ? '.m' : '');
|
|
19379
19760
|
var _layoutHtml;
|
|
19380
|
-
|
|
19381
|
-
|
|
19382
|
-
|
|
19761
|
+
var _layoutSkeletonFileHtml; // hoisted so LayoutNext closure can access it
|
|
19762
|
+
// Load layout .html, .js and optional .skeleton.html all in parallel
|
|
19763
|
+
return Promise.all([
|
|
19764
|
+
_httpGet(layoutName + '.html'),
|
|
19765
|
+
_httpGet(layout + '.js'),
|
|
19766
|
+
_httpGetSkeletonFile(layoutName + '.skeleton.html')
|
|
19767
|
+
]).then(function (layoutResults) {
|
|
19768
|
+
_layoutHtml = layoutResults[0];
|
|
19769
|
+
var js = layoutResults[1];
|
|
19770
|
+
_layoutSkeletonFileHtml = layoutResults[2]; // null when absent
|
|
19771
|
+
return js;
|
|
19383
19772
|
}).then(function (js) {
|
|
19384
19773
|
var _opt = null;
|
|
19385
19774
|
var Layout = function (options) {
|
|
@@ -19403,6 +19792,37 @@ function build(options, exports) {
|
|
|
19403
19792
|
var appId = 'pomelo-' + ticks;
|
|
19404
19793
|
var containerId = 'container-' + ticks;
|
|
19405
19794
|
var body = document.querySelector('body').innerHTML.replace('<render-body></render-body>', '<div id="' + containerId + '"></div>');
|
|
19795
|
+
// Layout skeleton: defer generation to Root→_loadComponents so the
|
|
19796
|
+
// component skeleton registry is fully populated before we generate HTML.
|
|
19797
|
+
if (options.skeleton) {
|
|
19798
|
+
_ensureSkeletonStyle();
|
|
19799
|
+
var bodyEl = document.querySelector('body');
|
|
19800
|
+
var bodyClass = bodyEl.getAttribute('class') || '';
|
|
19801
|
+
var bodyInlineStyle = bodyEl.getAttribute('style') || '';
|
|
19802
|
+
var bodyComputed = window.getComputedStyle(bodyEl);
|
|
19803
|
+
var overlayStyle = bodyInlineStyle;
|
|
19804
|
+
if (bodyComputed.display && bodyComputed.display.indexOf('flex') >= 0) {
|
|
19805
|
+
if (overlayStyle) overlayStyle += ';';
|
|
19806
|
+
overlayStyle += 'display:' + bodyComputed.display
|
|
19807
|
+
+ ';flex-direction:' + bodyComputed.flexDirection
|
|
19808
|
+
+ ';align-items:' + bodyComputed.alignItems;
|
|
19809
|
+
} else {
|
|
19810
|
+
if (overlayStyle) overlayStyle += ';';
|
|
19811
|
+
overlayStyle += 'display:flex;flex-direction:row;align-items:stretch';
|
|
19812
|
+
}
|
|
19813
|
+
var skeletonBody = body.replace('<div id="' + containerId + '"></div>', '<div style="flex:1;min-width:0"></div>');
|
|
19814
|
+
// Store pending data; Root will inject the overlay div after
|
|
19815
|
+
// _loadComponents so registered component skeletons are available.
|
|
19816
|
+
options._pomeLayoutSkeleton = {
|
|
19817
|
+
appId: '#' + appId,
|
|
19818
|
+
bodyClass: bodyClass,
|
|
19819
|
+
overlayStyle: overlayStyle,
|
|
19820
|
+
skeletonBody: skeletonBody,
|
|
19821
|
+
// Priority: .skeleton.html file > inline string in JS
|
|
19822
|
+
skeletonOverride: _layoutSkeletonFileHtml
|
|
19823
|
+
|| (typeof options.skeleton === 'string' ? options.skeleton : null)
|
|
19824
|
+
};
|
|
19825
|
+
}
|
|
19406
19826
|
_ensureVueCloakStyle();
|
|
19407
19827
|
document.querySelector('body').innerHTML = '<div id="' + appId + '" class="_pome-vue-cloak">' + body + '</div>';
|
|
19408
19828
|
options.template = null;
|
|
@@ -19576,10 +19996,15 @@ function build(options, exports) {
|
|
|
19576
19996
|
var _html;
|
|
19577
19997
|
var _name;
|
|
19578
19998
|
var _opt;
|
|
19579
|
-
|
|
19580
|
-
|
|
19581
|
-
|
|
19582
|
-
|
|
19999
|
+
// Load .html, .js and optional .skeleton.html in parallel
|
|
20000
|
+
return Promise.all([
|
|
20001
|
+
_httpGet(c + '.html'),
|
|
20002
|
+
_httpGet(c + '.js'),
|
|
20003
|
+
_httpGetSkeletonFile(c + '.skeleton.html')
|
|
20004
|
+
]).then(function (results) {
|
|
20005
|
+
_html = results[0];
|
|
20006
|
+
var comJs = results[1];
|
|
20007
|
+
var skeletonFileHtml = results[2]; // null when file does not exist
|
|
19583
20008
|
var Component = function (name, options) {
|
|
19584
20009
|
_opt = options;
|
|
19585
20010
|
_name = name;
|
|
@@ -19593,6 +20018,10 @@ function build(options, exports) {
|
|
|
19593
20018
|
}
|
|
19594
20019
|
_hookSetup(_opt);
|
|
19595
20020
|
_patchComponent(c, _opt);
|
|
20021
|
+
// Register the component's skeleton BEFORE wrapping the template,
|
|
20022
|
+
// so _generateSkeletonHtml still sees the clean original template.
|
|
20023
|
+
_registerComponentSkeleton(_name, _opt, _opt.template, skeletonFileHtml);
|
|
20024
|
+
_wrapTemplateWithSkeleton(_opt);
|
|
19596
20025
|
_hookData(_opt);
|
|
19597
20026
|
var cssPreloadPromise = Promise.resolve();
|
|
19598
20027
|
if (_options.preloadCss && _opt.style) {
|
|
@@ -19700,6 +20129,13 @@ function build(options, exports) {
|
|
|
19700
20129
|
opt.data = function (app) {
|
|
19701
20130
|
var data = func1.call(this, app);
|
|
19702
20131
|
var data2 = { pomeUiSubTitles: [] };
|
|
20132
|
+
if (opt.skeleton) {
|
|
20133
|
+
data2.pomeSkeletonLoading = true;
|
|
20134
|
+
}
|
|
20135
|
+
if (opt.skeleton && opt.delayOpen) {
|
|
20136
|
+
data2.pomeDelayOpened = true;
|
|
20137
|
+
data2.pomeDelayOpening = true;
|
|
20138
|
+
}
|
|
19703
20139
|
_combineObject(data2, data);
|
|
19704
20140
|
return data;
|
|
19705
20141
|
}
|