@lwc/engine-core 2.33.0 → 2.34.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.
@@ -309,6 +309,7 @@ function log(method, message, vm) {
309
309
  if (!shared.isUndefined(vm)) {
310
310
  msg = `${msg}\n${getComponentStack(vm)}`;
311
311
  }
312
+ // In Jest tests, reduce the warning and error verbosity by not printing the callstack
312
313
  if (process.env.NODE_ENV === 'test') {
313
314
  /* eslint-disable-next-line no-console */
314
315
  console[method](msg);
@@ -378,6 +379,9 @@ function offsetPropertyErrorMessage(name) {
378
379
  // Global HTML Attributes & Properties
379
380
  // https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes
380
381
  // https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement
382
+ //
383
+ // If you update this list, check for test files that recapitulate the same list. Searching the codebase
384
+ // for e.g. "dropzone" should suffice.
381
385
  const globalHTMLProperties = shared.assign(shared.create(null), {
382
386
  accessKey: {
383
387
  attribute: 'accesskey',
@@ -2972,6 +2976,9 @@ function updateStylesheetToken(vm, template) {
2972
2976
  stylesheets: newStylesheets,
2973
2977
  stylesheetToken: newStylesheetToken
2974
2978
  } = template;
2979
+ const {
2980
+ stylesheets: newVmStylesheets
2981
+ } = vm;
2975
2982
  const isSyntheticShadow = renderMode === 1 /* RenderMode.Shadow */ && shadowMode === 1 /* ShadowMode.Synthetic */;
2976
2983
  const {
2977
2984
  hasScopedStyles
@@ -2995,7 +3002,9 @@ function updateStylesheetToken(vm, template) {
2995
3002
  }
2996
3003
  // Apply the new template styling token to the host element, if the new template has any
2997
3004
  // associated stylesheets. In the case of light DOM, also ensure there is at least one scoped stylesheet.
2998
- if (!shared.isUndefined(newStylesheets) && newStylesheets.length !== 0) {
3005
+ const hasNewStylesheets = hasStyles(newStylesheets);
3006
+ const hasNewVmStylesheets = hasStyles(newVmStylesheets);
3007
+ if (hasNewStylesheets || hasNewVmStylesheets) {
2999
3008
  newToken = newStylesheetToken;
3000
3009
  }
3001
3010
  // Set the new styling token on the host element
@@ -3067,10 +3076,17 @@ function getStylesheetsContent(vm, template) {
3067
3076
  stylesheets,
3068
3077
  stylesheetToken
3069
3078
  } = template;
3079
+ const {
3080
+ stylesheets: vmStylesheets
3081
+ } = vm;
3070
3082
  let content = [];
3071
- if (!shared.isUndefined(stylesheets) && stylesheets.length !== 0) {
3083
+ if (hasStyles(stylesheets)) {
3072
3084
  content = evaluateStylesheetsContent(stylesheets, stylesheetToken, vm);
3073
3085
  }
3086
+ // VM (component) stylesheets apply after template stylesheets
3087
+ if (hasStyles(vmStylesheets)) {
3088
+ shared.ArrayPush.apply(content, evaluateStylesheetsContent(vmStylesheets, stylesheetToken, vm));
3089
+ }
3074
3090
  return content;
3075
3091
  }
3076
3092
  // It might be worth caching this to avoid doing the lookup repeatedly, but
@@ -3108,10 +3124,13 @@ function getStylesheetTokenHost(vnode) {
3108
3124
  const {
3109
3125
  template
3110
3126
  } = getComponentInternalDef(vnode.ctor);
3127
+ const {
3128
+ vm
3129
+ } = vnode;
3111
3130
  const {
3112
3131
  stylesheetToken
3113
3132
  } = template;
3114
- return !shared.isUndefined(stylesheetToken) && computeHasScopedStyles(template) ? makeHostToken(stylesheetToken) : null;
3133
+ return !shared.isUndefined(stylesheetToken) && computeHasScopedStyles(template, vm) ? makeHostToken(stylesheetToken) : null;
3115
3134
  }
3116
3135
  function getNearestNativeShadowComponent(vm) {
3117
3136
  const owner = getNearestShadowComponent(vm);
@@ -3728,6 +3747,28 @@ function patchCustomElement(n1, n2, parent, renderer) {
3728
3747
  // in fallback mode, the allocation will always set children to
3729
3748
  // empty and delegate the real allocation to the slot elements
3730
3749
  allocateChildren(n2, vm);
3750
+ // Solves an edge case with slotted VFragments in native shadow mode.
3751
+ //
3752
+ // During allocation, in native shadow, slotted VFragment nodes are flattened and their text delimiters are removed
3753
+ // to avoid interfering with native slot behavior. When this happens, if any of the fragments
3754
+ // were not stable, the children must go through the dynamic diffing algo.
3755
+ //
3756
+ // If the new children (n2.children) contain no VFragments, but the previous children (n1.children) were dynamic,
3757
+ // the new nodes must be marked dynamic so that all nodes are properly updated. The only indicator that the new
3758
+ // nodes need to be dynamic comes from the previous children, so we check that to determine whether we need to
3759
+ // mark the new children dynamic.
3760
+ //
3761
+ // Example:
3762
+ // n1.children: [div, VFragment('', div, null, ''), div] => [div, div, null, div]; // marked dynamic
3763
+ // n2.children: [div, null, div] => [div, null, div] // marked ???
3764
+ const {
3765
+ shadowMode,
3766
+ renderMode
3767
+ } = vm;
3768
+ if (shadowMode == 0 /* ShadowMode.Native */ && renderMode !== 0 /* RenderMode.Light */ && hasDynamicChildren(n1.children)) {
3769
+ // No-op if children has already been marked dynamic by 'allocateChildren()'.
3770
+ markAsDynamicChildren(n2.children);
3771
+ }
3731
3772
  }
3732
3773
  // in fallback mode, the children will be always empty, so, nothing
3733
3774
  // will happen, but in native, it does allocate the light dom
@@ -3920,7 +3961,6 @@ function allocateChildren(vnode, vm) {
3920
3961
  //
3921
3962
  // In case #2, we will always get a fresh VCustomElement.
3922
3963
  const children = vnode.aChildren || vnode.children;
3923
- vm.aChildren = children;
3924
3964
  const {
3925
3965
  renderMode,
3926
3966
  shadowMode
@@ -3933,15 +3973,61 @@ function allocateChildren(vnode, vm) {
3933
3973
  logError(`Invalid usage of 'lwc:slot-data' on ${getComponentTag(vm)} tag. Scoped slot content can only be passed to a light dom child.`);
3934
3974
  }
3935
3975
  }
3976
+ // If any of the children being allocated are VFragments, we remove the text delimiters and flatten all immediate
3977
+ // children VFragments to avoid them interfering with default slot behavior.
3978
+ const allocatedChildren = flattenFragmentsInChildren(children);
3979
+ vnode.children = allocatedChildren;
3980
+ vm.aChildren = allocatedChildren;
3936
3981
  if (shadowMode === 1 /* ShadowMode.Synthetic */ || renderMode === 0 /* RenderMode.Light */) {
3937
3982
  // slow path
3938
- allocateInSlot(vm, children, vnode.owner);
3983
+ allocateInSlot(vm, allocatedChildren, vnode.owner);
3939
3984
  // save the allocated children in case this vnode is reused.
3940
- vnode.aChildren = children;
3985
+ vnode.aChildren = allocatedChildren;
3941
3986
  // every child vnode is now allocated, and the host should receive none directly, it receives them via the shadow!
3942
3987
  vnode.children = EmptyArray;
3943
3988
  }
3944
3989
  }
3990
+ /**
3991
+ * Flattens the contents of all VFragments in an array of VNodes, removes the text delimiters on those VFragments, and
3992
+ * marks the resulting children array as dynamic. Uses a stack (array) to iteratively traverse the nested VFragments
3993
+ * and avoid the perf overhead of creating/destroying throwaway arrays/objects in a recursive approach.
3994
+ *
3995
+ * With the delimiters removed, the contents are marked dynamic so they are diffed correctly.
3996
+ *
3997
+ * This function is used for slotted VFragments to avoid the text delimiters interfering with slotting functionality.
3998
+ */
3999
+ function flattenFragmentsInChildren(children) {
4000
+ const flattenedChildren = [];
4001
+ // Initialize our stack with the direct children of the custom component and check whether we have a VFragment.
4002
+ // If no VFragment is found in children, we don't need to traverse anything or mark the children dynamic and can return early.
4003
+ const nodeStack = [];
4004
+ let fragmentFound = false;
4005
+ for (let i = children.length - 1; i > -1; i -= 1) {
4006
+ const child = children[i];
4007
+ shared.ArrayPush.call(nodeStack, child);
4008
+ fragmentFound = fragmentFound || !!(child && isVFragment(child));
4009
+ }
4010
+ if (!fragmentFound) {
4011
+ return children;
4012
+ }
4013
+ let currentNode;
4014
+ while (!shared.isUndefined(currentNode = shared.ArrayPop.call(nodeStack))) {
4015
+ if (!shared.isNull(currentNode) && isVFragment(currentNode)) {
4016
+ const fChildren = currentNode.children;
4017
+ // Ignore the start and end text node delimiters
4018
+ for (let i = fChildren.length - 2; i > 0; i -= 1) {
4019
+ shared.ArrayPush.call(nodeStack, fChildren[i]);
4020
+ }
4021
+ } else {
4022
+ shared.ArrayPush.call(flattenedChildren, currentNode);
4023
+ }
4024
+ }
4025
+ // We always mark the children as dynamic because nothing generates stable VFragments yet.
4026
+ // If/when stable VFragments are generated by the compiler, this code should be updated to
4027
+ // not mark dynamic if all flattened VFragments were stable.
4028
+ markAsDynamicChildren(flattenedChildren);
4029
+ return flattenedChildren;
4030
+ }
3945
4031
  function createViewModelHook(elm, vnode, renderer) {
3946
4032
  let vm = getAssociatedVMIfPresent(elm);
3947
4033
  // There is a possibility that a custom element is registered under tagName, in which case, the
@@ -3966,22 +4052,20 @@ function createViewModelHook(elm, vnode, renderer) {
3966
4052
  }
3967
4053
  return vm;
3968
4054
  }
3969
- /**
3970
- * Collects all slots into a SlotSet, traversing through VFragment Nodes
3971
- */
3972
- function collectSlots(vm, children, cmpSlotsMapping) {
4055
+ function allocateInSlot(vm, children, owner) {
3973
4056
  var _a, _b;
4057
+ const {
4058
+ cmpSlots: {
4059
+ slotAssignments: oldSlotsMapping
4060
+ }
4061
+ } = vm;
4062
+ const cmpSlotsMapping = shared.create(null);
4063
+ // Collect all slots into cmpSlotsMapping
3974
4064
  for (let i = 0, len = children.length; i < len; i += 1) {
3975
4065
  const vnode = children[i];
3976
4066
  if (shared.isNull(vnode)) {
3977
4067
  continue;
3978
4068
  }
3979
- // Dive further iff the content is wrapped in a VFragment
3980
- if (isVFragment(vnode)) {
3981
- // Remove the text delimiter nodes to avoid overriding default slot content
3982
- collectSlots(vm, vnode.children.slice(1, -1), cmpSlotsMapping);
3983
- continue;
3984
- }
3985
4069
  let slotName = '';
3986
4070
  if (isVBaseElement(vnode)) {
3987
4071
  slotName = (_b = (_a = vnode.data.attrs) === null || _a === void 0 ? void 0 : _a.slot) !== null && _b !== void 0 ? _b : '';
@@ -3991,15 +4075,6 @@ function collectSlots(vm, children, cmpSlotsMapping) {
3991
4075
  const vnodes = cmpSlotsMapping[slotName] = cmpSlotsMapping[slotName] || [];
3992
4076
  shared.ArrayPush.call(vnodes, vnode);
3993
4077
  }
3994
- }
3995
- function allocateInSlot(vm, children, owner) {
3996
- const {
3997
- cmpSlots: {
3998
- slotAssignments: oldSlotsMapping
3999
- }
4000
- } = vm;
4001
- const cmpSlotsMapping = shared.create(null);
4002
- collectSlots(vm, children, cmpSlotsMapping);
4003
4078
  vm.cmpSlots = {
4004
4079
  owner,
4005
4080
  slotAssignments: cmpSlotsMapping
@@ -4030,14 +4105,14 @@ function allocateInSlot(vm, children, owner) {
4030
4105
  }
4031
4106
  }
4032
4107
  // Using a WeakMap instead of a WeakSet because this one works in IE11 :(
4033
- const FromIteration = new WeakMap();
4034
- // dynamic children means it was generated by an iteration
4035
- // in a template, and will require a more complex diffing algo.
4108
+ const DynamicChildren = new WeakMap();
4109
+ // dynamic children means it was either generated by an iteration in a template
4110
+ // or part of an unstable fragment, and will require a more complex diffing algo.
4036
4111
  function markAsDynamicChildren(children) {
4037
- FromIteration.set(children, 1);
4112
+ DynamicChildren.set(children, 1);
4038
4113
  }
4039
4114
  function hasDynamicChildren(children) {
4040
- return FromIteration.has(children);
4115
+ return DynamicChildren.has(children);
4041
4116
  }
4042
4117
  function createKeyToOldIdx(children, beginIdx, endIdx) {
4043
4118
  const map = {};
@@ -4903,7 +4978,7 @@ function evaluateTemplate(vm, html) {
4903
4978
  // Create a brand new template cache for the swapped templated.
4904
4979
  context.tplCache = shared.create(null);
4905
4980
  // Set the computeHasScopedStyles property in the context, to avoid recomputing it repeatedly.
4906
- context.hasScopedStyles = computeHasScopedStyles(html);
4981
+ context.hasScopedStyles = computeHasScopedStyles(html, vm);
4907
4982
  // Update the scoping token on the host element.
4908
4983
  updateStylesheetToken(vm, html);
4909
4984
  // Evaluate, create stylesheet and cache the produced VNode for future
@@ -4946,9 +5021,8 @@ function evaluateTemplate(vm, html) {
4946
5021
  }
4947
5022
  return vnodes;
4948
5023
  }
4949
- function computeHasScopedStyles(template) {
4950
- const { stylesheets } = template;
4951
- if (!shared.isUndefined(stylesheets)) {
5024
+ function computeHasScopedStylesInStylesheets(stylesheets) {
5025
+ if (hasStyles(stylesheets)) {
4952
5026
  for (let i = 0; i < stylesheets.length; i++) {
4953
5027
  if (shared.isTrue(stylesheets[i][shared.KEY__SCOPED_CSS])) {
4954
5028
  return true;
@@ -4957,6 +5031,15 @@ function computeHasScopedStyles(template) {
4957
5031
  }
4958
5032
  return false;
4959
5033
  }
5034
+ function computeHasScopedStyles(template, vm) {
5035
+ const { stylesheets } = template;
5036
+ const vmStylesheets = !shared.isUndefined(vm) ? vm.stylesheets : null;
5037
+ return (computeHasScopedStylesInStylesheets(stylesheets) ||
5038
+ computeHasScopedStylesInStylesheets(vmStylesheets));
5039
+ }
5040
+ function hasStyles(stylesheets) {
5041
+ return !shared.isUndefined(stylesheets) && !shared.isNull(stylesheets) && stylesheets.length > 0;
5042
+ }
4960
5043
 
4961
5044
  /*
4962
5045
  * Copyright (c) 2018, salesforce.com, inc.
@@ -5272,6 +5355,7 @@ function createVM(elm, ctor, renderer, options) {
5272
5355
  // Properties set right after VM creation.
5273
5356
  tro: null,
5274
5357
  shadowMode: null,
5358
+ stylesheets: null,
5275
5359
  // Properties set by the LightningElement constructor.
5276
5360
  component: null,
5277
5361
  shadowRoot: null,
@@ -5284,6 +5368,7 @@ function createVM(elm, ctor, renderer, options) {
5284
5368
  if (process.env.NODE_ENV !== 'production') {
5285
5369
  vm.debugInfo = shared.create(null);
5286
5370
  }
5371
+ vm.stylesheets = computeStylesheets(vm, def.ctor);
5287
5372
  vm.shadowMode = computeShadowMode(vm, renderer);
5288
5373
  vm.tro = getTemplateReactiveObserver(vm);
5289
5374
  if (process.env.NODE_ENV !== 'production') {
@@ -5302,6 +5387,42 @@ function createVM(elm, ctor, renderer, options) {
5302
5387
  }
5303
5388
  return vm;
5304
5389
  }
5390
+ function validateComponentStylesheets(vm, stylesheets) {
5391
+ let valid = true;
5392
+ const validate = arrayOrStylesheet => {
5393
+ if (shared.isArray(arrayOrStylesheet)) {
5394
+ for (let i = 0; i < arrayOrStylesheet.length; i++) {
5395
+ validate(arrayOrStylesheet[i]);
5396
+ }
5397
+ } else if (!shared.isFunction(arrayOrStylesheet)) {
5398
+ // function assumed to be a stylesheet factory
5399
+ valid = false;
5400
+ }
5401
+ };
5402
+ if (!shared.isArray(stylesheets)) {
5403
+ valid = false;
5404
+ } else {
5405
+ validate(stylesheets);
5406
+ }
5407
+ return valid;
5408
+ }
5409
+ // Validate and flatten any stylesheets defined as `static stylesheets`
5410
+ function computeStylesheets(vm, ctor) {
5411
+ if (features.lwcRuntimeFlags.ENABLE_PROGRAMMATIC_STYLESHEETS) {
5412
+ const {
5413
+ stylesheets
5414
+ } = ctor;
5415
+ if (!shared.isUndefined(stylesheets)) {
5416
+ const valid = validateComponentStylesheets(vm, stylesheets);
5417
+ if (valid) {
5418
+ return flattenStylesheets(stylesheets);
5419
+ } else if (process.env.NODE_ENV !== 'production') {
5420
+ logError(`static stylesheets must be an array of CSS stylesheets. Found invalid stylesheets on <${vm.tagName}>`, vm);
5421
+ }
5422
+ }
5423
+ }
5424
+ return null;
5425
+ }
5305
5426
  function computeShadowMode(vm, renderer) {
5306
5427
  const {
5307
5428
  def
@@ -6661,4 +6782,4 @@ exports.swapTemplate = swapTemplate;
6661
6782
  exports.track = track;
6662
6783
  exports.unwrap = unwrap;
6663
6784
  exports.wire = wire;
6664
- /* version: 2.33.0 */
6785
+ /* version: 2.34.0 */
@@ -1,7 +1,7 @@
1
1
  /* proxy-compat-disable */
2
2
  import { lwcRuntimeFlags } from '@lwc/features';
3
3
  export { setFeatureFlag, setFeatureFlagForTest } from '@lwc/features';
4
- import { seal, create, isUndefined as isUndefined$1, isFunction as isFunction$1, ArrayPush as ArrayPush$1, ArrayIndexOf, ArraySplice, StringToLowerCase, isNull, ArrayJoin, isFrozen, defineProperty, hasOwnProperty as hasOwnProperty$1, assign, forEach, keys, AriaPropNameToAttrNameMap, getPropertyDescriptor, defineProperties, getOwnPropertyNames as getOwnPropertyNames$1, getPrototypeOf as getPrototypeOf$1, setPrototypeOf, isObject, freeze, assert, KEY__SYNTHETIC_MODE, toString as toString$1, getOwnPropertyDescriptor as getOwnPropertyDescriptor$1, isFalse, LWC_VERSION_COMMENT_REGEX, LWC_VERSION, htmlPropertyToAttribute, ArraySlice, ArrayMap, isArray as isArray$1, KEY__SCOPED_CSS, StringCharCodeAt, XML_NAMESPACE, XLINK_NAMESPACE, htmlAttributeToProperty, isString, StringSlice, isTrue, SVG_NAMESPACE, KEY__SHADOW_STATIC, KEY__SHADOW_RESOLVER, ArraySome, isNumber, StringReplace, noop, ArrayUnshift, ArrayFilter, ArrayCopyWithin, ArrayFill, ArraySort, ArrayReverse, ArrayShift, ArrayPop } from '@lwc/shared';
4
+ import { seal, create, isUndefined as isUndefined$1, isFunction as isFunction$1, ArrayPush as ArrayPush$1, ArrayIndexOf, ArraySplice, StringToLowerCase, isNull, ArrayJoin, isFrozen, defineProperty, hasOwnProperty as hasOwnProperty$1, assign, forEach, keys, AriaPropNameToAttrNameMap, getPropertyDescriptor, defineProperties, getOwnPropertyNames as getOwnPropertyNames$1, getPrototypeOf as getPrototypeOf$1, setPrototypeOf, isObject, freeze, assert, KEY__SYNTHETIC_MODE, toString as toString$1, getOwnPropertyDescriptor as getOwnPropertyDescriptor$1, isFalse, LWC_VERSION_COMMENT_REGEX, LWC_VERSION, htmlPropertyToAttribute, ArraySlice, ArrayMap, isArray as isArray$1, KEY__SCOPED_CSS, StringCharCodeAt, XML_NAMESPACE, XLINK_NAMESPACE, htmlAttributeToProperty, isString, StringSlice, isTrue, SVG_NAMESPACE, KEY__SHADOW_STATIC, KEY__SHADOW_RESOLVER, ArraySome, ArrayPop, isNumber, StringReplace, noop, ArrayUnshift, ArrayFilter, ArrayCopyWithin, ArrayFill, ArraySort, ArrayReverse, ArrayShift } from '@lwc/shared';
5
5
  import { applyAriaReflection } from '@lwc/aria-reflection';
6
6
 
7
7
  /*
@@ -308,6 +308,7 @@ function log(method, message, vm) {
308
308
  if (!isUndefined$1(vm)) {
309
309
  msg = `${msg}\n${getComponentStack(vm)}`;
310
310
  }
311
+ // In Jest tests, reduce the warning and error verbosity by not printing the callstack
311
312
  if (process.env.NODE_ENV === 'test') {
312
313
  /* eslint-disable-next-line no-console */
313
314
  console[method](msg);
@@ -377,6 +378,9 @@ function offsetPropertyErrorMessage(name) {
377
378
  // Global HTML Attributes & Properties
378
379
  // https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes
379
380
  // https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement
381
+ //
382
+ // If you update this list, check for test files that recapitulate the same list. Searching the codebase
383
+ // for e.g. "dropzone" should suffice.
380
384
  const globalHTMLProperties = assign(create(null), {
381
385
  accessKey: {
382
386
  attribute: 'accesskey',
@@ -2971,6 +2975,9 @@ function updateStylesheetToken(vm, template) {
2971
2975
  stylesheets: newStylesheets,
2972
2976
  stylesheetToken: newStylesheetToken
2973
2977
  } = template;
2978
+ const {
2979
+ stylesheets: newVmStylesheets
2980
+ } = vm;
2974
2981
  const isSyntheticShadow = renderMode === 1 /* RenderMode.Shadow */ && shadowMode === 1 /* ShadowMode.Synthetic */;
2975
2982
  const {
2976
2983
  hasScopedStyles
@@ -2994,7 +3001,9 @@ function updateStylesheetToken(vm, template) {
2994
3001
  }
2995
3002
  // Apply the new template styling token to the host element, if the new template has any
2996
3003
  // associated stylesheets. In the case of light DOM, also ensure there is at least one scoped stylesheet.
2997
- if (!isUndefined$1(newStylesheets) && newStylesheets.length !== 0) {
3004
+ const hasNewStylesheets = hasStyles(newStylesheets);
3005
+ const hasNewVmStylesheets = hasStyles(newVmStylesheets);
3006
+ if (hasNewStylesheets || hasNewVmStylesheets) {
2998
3007
  newToken = newStylesheetToken;
2999
3008
  }
3000
3009
  // Set the new styling token on the host element
@@ -3066,10 +3075,17 @@ function getStylesheetsContent(vm, template) {
3066
3075
  stylesheets,
3067
3076
  stylesheetToken
3068
3077
  } = template;
3078
+ const {
3079
+ stylesheets: vmStylesheets
3080
+ } = vm;
3069
3081
  let content = [];
3070
- if (!isUndefined$1(stylesheets) && stylesheets.length !== 0) {
3082
+ if (hasStyles(stylesheets)) {
3071
3083
  content = evaluateStylesheetsContent(stylesheets, stylesheetToken, vm);
3072
3084
  }
3085
+ // VM (component) stylesheets apply after template stylesheets
3086
+ if (hasStyles(vmStylesheets)) {
3087
+ ArrayPush$1.apply(content, evaluateStylesheetsContent(vmStylesheets, stylesheetToken, vm));
3088
+ }
3073
3089
  return content;
3074
3090
  }
3075
3091
  // It might be worth caching this to avoid doing the lookup repeatedly, but
@@ -3107,10 +3123,13 @@ function getStylesheetTokenHost(vnode) {
3107
3123
  const {
3108
3124
  template
3109
3125
  } = getComponentInternalDef(vnode.ctor);
3126
+ const {
3127
+ vm
3128
+ } = vnode;
3110
3129
  const {
3111
3130
  stylesheetToken
3112
3131
  } = template;
3113
- return !isUndefined$1(stylesheetToken) && computeHasScopedStyles(template) ? makeHostToken(stylesheetToken) : null;
3132
+ return !isUndefined$1(stylesheetToken) && computeHasScopedStyles(template, vm) ? makeHostToken(stylesheetToken) : null;
3114
3133
  }
3115
3134
  function getNearestNativeShadowComponent(vm) {
3116
3135
  const owner = getNearestShadowComponent(vm);
@@ -3727,6 +3746,28 @@ function patchCustomElement(n1, n2, parent, renderer) {
3727
3746
  // in fallback mode, the allocation will always set children to
3728
3747
  // empty and delegate the real allocation to the slot elements
3729
3748
  allocateChildren(n2, vm);
3749
+ // Solves an edge case with slotted VFragments in native shadow mode.
3750
+ //
3751
+ // During allocation, in native shadow, slotted VFragment nodes are flattened and their text delimiters are removed
3752
+ // to avoid interfering with native slot behavior. When this happens, if any of the fragments
3753
+ // were not stable, the children must go through the dynamic diffing algo.
3754
+ //
3755
+ // If the new children (n2.children) contain no VFragments, but the previous children (n1.children) were dynamic,
3756
+ // the new nodes must be marked dynamic so that all nodes are properly updated. The only indicator that the new
3757
+ // nodes need to be dynamic comes from the previous children, so we check that to determine whether we need to
3758
+ // mark the new children dynamic.
3759
+ //
3760
+ // Example:
3761
+ // n1.children: [div, VFragment('', div, null, ''), div] => [div, div, null, div]; // marked dynamic
3762
+ // n2.children: [div, null, div] => [div, null, div] // marked ???
3763
+ const {
3764
+ shadowMode,
3765
+ renderMode
3766
+ } = vm;
3767
+ if (shadowMode == 0 /* ShadowMode.Native */ && renderMode !== 0 /* RenderMode.Light */ && hasDynamicChildren(n1.children)) {
3768
+ // No-op if children has already been marked dynamic by 'allocateChildren()'.
3769
+ markAsDynamicChildren(n2.children);
3770
+ }
3730
3771
  }
3731
3772
  // in fallback mode, the children will be always empty, so, nothing
3732
3773
  // will happen, but in native, it does allocate the light dom
@@ -3919,7 +3960,6 @@ function allocateChildren(vnode, vm) {
3919
3960
  //
3920
3961
  // In case #2, we will always get a fresh VCustomElement.
3921
3962
  const children = vnode.aChildren || vnode.children;
3922
- vm.aChildren = children;
3923
3963
  const {
3924
3964
  renderMode,
3925
3965
  shadowMode
@@ -3932,15 +3972,61 @@ function allocateChildren(vnode, vm) {
3932
3972
  logError(`Invalid usage of 'lwc:slot-data' on ${getComponentTag(vm)} tag. Scoped slot content can only be passed to a light dom child.`);
3933
3973
  }
3934
3974
  }
3975
+ // If any of the children being allocated are VFragments, we remove the text delimiters and flatten all immediate
3976
+ // children VFragments to avoid them interfering with default slot behavior.
3977
+ const allocatedChildren = flattenFragmentsInChildren(children);
3978
+ vnode.children = allocatedChildren;
3979
+ vm.aChildren = allocatedChildren;
3935
3980
  if (shadowMode === 1 /* ShadowMode.Synthetic */ || renderMode === 0 /* RenderMode.Light */) {
3936
3981
  // slow path
3937
- allocateInSlot(vm, children, vnode.owner);
3982
+ allocateInSlot(vm, allocatedChildren, vnode.owner);
3938
3983
  // save the allocated children in case this vnode is reused.
3939
- vnode.aChildren = children;
3984
+ vnode.aChildren = allocatedChildren;
3940
3985
  // every child vnode is now allocated, and the host should receive none directly, it receives them via the shadow!
3941
3986
  vnode.children = EmptyArray;
3942
3987
  }
3943
3988
  }
3989
+ /**
3990
+ * Flattens the contents of all VFragments in an array of VNodes, removes the text delimiters on those VFragments, and
3991
+ * marks the resulting children array as dynamic. Uses a stack (array) to iteratively traverse the nested VFragments
3992
+ * and avoid the perf overhead of creating/destroying throwaway arrays/objects in a recursive approach.
3993
+ *
3994
+ * With the delimiters removed, the contents are marked dynamic so they are diffed correctly.
3995
+ *
3996
+ * This function is used for slotted VFragments to avoid the text delimiters interfering with slotting functionality.
3997
+ */
3998
+ function flattenFragmentsInChildren(children) {
3999
+ const flattenedChildren = [];
4000
+ // Initialize our stack with the direct children of the custom component and check whether we have a VFragment.
4001
+ // If no VFragment is found in children, we don't need to traverse anything or mark the children dynamic and can return early.
4002
+ const nodeStack = [];
4003
+ let fragmentFound = false;
4004
+ for (let i = children.length - 1; i > -1; i -= 1) {
4005
+ const child = children[i];
4006
+ ArrayPush$1.call(nodeStack, child);
4007
+ fragmentFound = fragmentFound || !!(child && isVFragment(child));
4008
+ }
4009
+ if (!fragmentFound) {
4010
+ return children;
4011
+ }
4012
+ let currentNode;
4013
+ while (!isUndefined$1(currentNode = ArrayPop.call(nodeStack))) {
4014
+ if (!isNull(currentNode) && isVFragment(currentNode)) {
4015
+ const fChildren = currentNode.children;
4016
+ // Ignore the start and end text node delimiters
4017
+ for (let i = fChildren.length - 2; i > 0; i -= 1) {
4018
+ ArrayPush$1.call(nodeStack, fChildren[i]);
4019
+ }
4020
+ } else {
4021
+ ArrayPush$1.call(flattenedChildren, currentNode);
4022
+ }
4023
+ }
4024
+ // We always mark the children as dynamic because nothing generates stable VFragments yet.
4025
+ // If/when stable VFragments are generated by the compiler, this code should be updated to
4026
+ // not mark dynamic if all flattened VFragments were stable.
4027
+ markAsDynamicChildren(flattenedChildren);
4028
+ return flattenedChildren;
4029
+ }
3944
4030
  function createViewModelHook(elm, vnode, renderer) {
3945
4031
  let vm = getAssociatedVMIfPresent(elm);
3946
4032
  // There is a possibility that a custom element is registered under tagName, in which case, the
@@ -3965,22 +4051,20 @@ function createViewModelHook(elm, vnode, renderer) {
3965
4051
  }
3966
4052
  return vm;
3967
4053
  }
3968
- /**
3969
- * Collects all slots into a SlotSet, traversing through VFragment Nodes
3970
- */
3971
- function collectSlots(vm, children, cmpSlotsMapping) {
4054
+ function allocateInSlot(vm, children, owner) {
3972
4055
  var _a, _b;
4056
+ const {
4057
+ cmpSlots: {
4058
+ slotAssignments: oldSlotsMapping
4059
+ }
4060
+ } = vm;
4061
+ const cmpSlotsMapping = create(null);
4062
+ // Collect all slots into cmpSlotsMapping
3973
4063
  for (let i = 0, len = children.length; i < len; i += 1) {
3974
4064
  const vnode = children[i];
3975
4065
  if (isNull(vnode)) {
3976
4066
  continue;
3977
4067
  }
3978
- // Dive further iff the content is wrapped in a VFragment
3979
- if (isVFragment(vnode)) {
3980
- // Remove the text delimiter nodes to avoid overriding default slot content
3981
- collectSlots(vm, vnode.children.slice(1, -1), cmpSlotsMapping);
3982
- continue;
3983
- }
3984
4068
  let slotName = '';
3985
4069
  if (isVBaseElement(vnode)) {
3986
4070
  slotName = (_b = (_a = vnode.data.attrs) === null || _a === void 0 ? void 0 : _a.slot) !== null && _b !== void 0 ? _b : '';
@@ -3990,15 +4074,6 @@ function collectSlots(vm, children, cmpSlotsMapping) {
3990
4074
  const vnodes = cmpSlotsMapping[slotName] = cmpSlotsMapping[slotName] || [];
3991
4075
  ArrayPush$1.call(vnodes, vnode);
3992
4076
  }
3993
- }
3994
- function allocateInSlot(vm, children, owner) {
3995
- const {
3996
- cmpSlots: {
3997
- slotAssignments: oldSlotsMapping
3998
- }
3999
- } = vm;
4000
- const cmpSlotsMapping = create(null);
4001
- collectSlots(vm, children, cmpSlotsMapping);
4002
4077
  vm.cmpSlots = {
4003
4078
  owner,
4004
4079
  slotAssignments: cmpSlotsMapping
@@ -4029,14 +4104,14 @@ function allocateInSlot(vm, children, owner) {
4029
4104
  }
4030
4105
  }
4031
4106
  // Using a WeakMap instead of a WeakSet because this one works in IE11 :(
4032
- const FromIteration = new WeakMap();
4033
- // dynamic children means it was generated by an iteration
4034
- // in a template, and will require a more complex diffing algo.
4107
+ const DynamicChildren = new WeakMap();
4108
+ // dynamic children means it was either generated by an iteration in a template
4109
+ // or part of an unstable fragment, and will require a more complex diffing algo.
4035
4110
  function markAsDynamicChildren(children) {
4036
- FromIteration.set(children, 1);
4111
+ DynamicChildren.set(children, 1);
4037
4112
  }
4038
4113
  function hasDynamicChildren(children) {
4039
- return FromIteration.has(children);
4114
+ return DynamicChildren.has(children);
4040
4115
  }
4041
4116
  function createKeyToOldIdx(children, beginIdx, endIdx) {
4042
4117
  const map = {};
@@ -4902,7 +4977,7 @@ function evaluateTemplate(vm, html) {
4902
4977
  // Create a brand new template cache for the swapped templated.
4903
4978
  context.tplCache = create(null);
4904
4979
  // Set the computeHasScopedStyles property in the context, to avoid recomputing it repeatedly.
4905
- context.hasScopedStyles = computeHasScopedStyles(html);
4980
+ context.hasScopedStyles = computeHasScopedStyles(html, vm);
4906
4981
  // Update the scoping token on the host element.
4907
4982
  updateStylesheetToken(vm, html);
4908
4983
  // Evaluate, create stylesheet and cache the produced VNode for future
@@ -4945,9 +5020,8 @@ function evaluateTemplate(vm, html) {
4945
5020
  }
4946
5021
  return vnodes;
4947
5022
  }
4948
- function computeHasScopedStyles(template) {
4949
- const { stylesheets } = template;
4950
- if (!isUndefined$1(stylesheets)) {
5023
+ function computeHasScopedStylesInStylesheets(stylesheets) {
5024
+ if (hasStyles(stylesheets)) {
4951
5025
  for (let i = 0; i < stylesheets.length; i++) {
4952
5026
  if (isTrue(stylesheets[i][KEY__SCOPED_CSS])) {
4953
5027
  return true;
@@ -4956,6 +5030,15 @@ function computeHasScopedStyles(template) {
4956
5030
  }
4957
5031
  return false;
4958
5032
  }
5033
+ function computeHasScopedStyles(template, vm) {
5034
+ const { stylesheets } = template;
5035
+ const vmStylesheets = !isUndefined$1(vm) ? vm.stylesheets : null;
5036
+ return (computeHasScopedStylesInStylesheets(stylesheets) ||
5037
+ computeHasScopedStylesInStylesheets(vmStylesheets));
5038
+ }
5039
+ function hasStyles(stylesheets) {
5040
+ return !isUndefined$1(stylesheets) && !isNull(stylesheets) && stylesheets.length > 0;
5041
+ }
4959
5042
 
4960
5043
  /*
4961
5044
  * Copyright (c) 2018, salesforce.com, inc.
@@ -5271,6 +5354,7 @@ function createVM(elm, ctor, renderer, options) {
5271
5354
  // Properties set right after VM creation.
5272
5355
  tro: null,
5273
5356
  shadowMode: null,
5357
+ stylesheets: null,
5274
5358
  // Properties set by the LightningElement constructor.
5275
5359
  component: null,
5276
5360
  shadowRoot: null,
@@ -5283,6 +5367,7 @@ function createVM(elm, ctor, renderer, options) {
5283
5367
  if (process.env.NODE_ENV !== 'production') {
5284
5368
  vm.debugInfo = create(null);
5285
5369
  }
5370
+ vm.stylesheets = computeStylesheets(vm, def.ctor);
5286
5371
  vm.shadowMode = computeShadowMode(vm, renderer);
5287
5372
  vm.tro = getTemplateReactiveObserver(vm);
5288
5373
  if (process.env.NODE_ENV !== 'production') {
@@ -5301,6 +5386,42 @@ function createVM(elm, ctor, renderer, options) {
5301
5386
  }
5302
5387
  return vm;
5303
5388
  }
5389
+ function validateComponentStylesheets(vm, stylesheets) {
5390
+ let valid = true;
5391
+ const validate = arrayOrStylesheet => {
5392
+ if (isArray$1(arrayOrStylesheet)) {
5393
+ for (let i = 0; i < arrayOrStylesheet.length; i++) {
5394
+ validate(arrayOrStylesheet[i]);
5395
+ }
5396
+ } else if (!isFunction$1(arrayOrStylesheet)) {
5397
+ // function assumed to be a stylesheet factory
5398
+ valid = false;
5399
+ }
5400
+ };
5401
+ if (!isArray$1(stylesheets)) {
5402
+ valid = false;
5403
+ } else {
5404
+ validate(stylesheets);
5405
+ }
5406
+ return valid;
5407
+ }
5408
+ // Validate and flatten any stylesheets defined as `static stylesheets`
5409
+ function computeStylesheets(vm, ctor) {
5410
+ if (lwcRuntimeFlags.ENABLE_PROGRAMMATIC_STYLESHEETS) {
5411
+ const {
5412
+ stylesheets
5413
+ } = ctor;
5414
+ if (!isUndefined$1(stylesheets)) {
5415
+ const valid = validateComponentStylesheets(vm, stylesheets);
5416
+ if (valid) {
5417
+ return flattenStylesheets(stylesheets);
5418
+ } else if (process.env.NODE_ENV !== 'production') {
5419
+ logError(`static stylesheets must be an array of CSS stylesheets. Found invalid stylesheets on <${vm.tagName}>`, vm);
5420
+ }
5421
+ }
5422
+ }
5423
+ return null;
5424
+ }
5304
5425
  function computeShadowMode(vm, renderer) {
5305
5426
  const {
5306
5427
  def
@@ -6624,4 +6745,4 @@ function getComponentConstructor(elm) {
6624
6745
  }
6625
6746
 
6626
6747
  export { LightningElement, profilerControl as __unstable__ProfilerControl, api$1 as api, connectRootElement, createContextProvider, createVM, disconnectRootElement, freezeTemplate, getAssociatedVMIfPresent, getComponentConstructor, getComponentDef, getComponentHtmlPrototype, hydrateRoot, isComponentConstructor, parseFragment, parseSVGFragment, readonly, register, registerComponent, registerDecorators, registerTemplate, sanitizeAttribute, setHooks, swapComponent, swapStyle, swapTemplate, track, unwrap, wire };
6627
- /* version: 2.33.0 */
6748
+ /* version: 2.34.0 */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lwc/engine-core",
3
- "version": "2.33.0",
3
+ "version": "2.34.0",
4
4
  "description": "Core LWC engine APIs.",
5
5
  "homepage": "https://lwc.dev/",
6
6
  "repository": {
@@ -24,9 +24,9 @@
24
24
  "types/"
25
25
  ],
26
26
  "dependencies": {
27
- "@lwc/aria-reflection": "2.33.0",
28
- "@lwc/features": "2.33.0",
29
- "@lwc/shared": "2.33.0"
27
+ "@lwc/aria-reflection": "2.34.0",
28
+ "@lwc/features": "2.34.0",
29
+ "@lwc/shared": "2.34.0"
30
30
  },
31
31
  "devDependencies": {
32
32
  "observable-membrane": "2.0.0"
@@ -10,6 +10,7 @@ import { AccessibleElementProperties } from '@lwc/shared';
10
10
  import { ShadowSupportMode } from './vm';
11
11
  import { Template } from './template';
12
12
  import { HTMLElementConstructor } from './base-bridge-element';
13
+ import { TemplateStylesheetFactories } from './stylesheet';
13
14
  export interface LightningElementConstructor {
14
15
  new (): LightningElement;
15
16
  readonly prototype: LightningElement;
@@ -17,6 +18,7 @@ export interface LightningElementConstructor {
17
18
  delegatesFocus?: boolean;
18
19
  renderMode?: 'light' | 'shadow';
19
20
  shadowSupportMode?: ShadowSupportMode;
21
+ stylesheets: TemplateStylesheetFactories;
20
22
  }
21
23
  type HTMLElementTheGoodParts = Pick<Object, 'toString'> & Pick<HTMLElement, 'accessKey' | 'addEventListener' | 'children' | 'childNodes' | 'classList' | 'dir' | 'dispatchEvent' | 'draggable' | 'firstChild' | 'firstElementChild' | 'getAttribute' | 'getAttributeNS' | 'getBoundingClientRect' | 'getElementsByClassName' | 'getElementsByTagName' | 'hasAttribute' | 'hasAttributeNS' | 'hidden' | 'id' | 'isConnected' | 'lang' | 'lastChild' | 'lastElementChild' | 'querySelector' | 'querySelectorAll' | 'removeAttribute' | 'removeAttributeNS' | 'removeEventListener' | 'setAttribute' | 'setAttributeNS' | 'spellcheck' | 'tabIndex' | 'title'>;
22
24
  type RefNodes = {
@@ -21,4 +21,5 @@ export declare function setVMBeingRendered(vm: VM | null): void;
21
21
  export declare const parseFragment: (strings: string[], ...keys: number[]) => () => Element;
22
22
  export declare const parseSVGFragment: (strings: string[], ...keys: number[]) => () => Element;
23
23
  export declare function evaluateTemplate(vm: VM, html: Template): VNodes;
24
- export declare function computeHasScopedStyles(template: Template): boolean;
24
+ export declare function computeHasScopedStyles(template: Template, vm: VM | undefined): boolean;
25
+ export declare function hasStyles(stylesheets: TemplateStylesheetFactories | undefined | null): stylesheets is TemplateStylesheetFactories;
@@ -4,6 +4,7 @@ import { ComponentDef } from './def';
4
4
  import { LightningElement, LightningElementConstructor } from './base-lightning-element';
5
5
  import { ReactiveObserver } from './mutation-tracker';
6
6
  import { VNodes, VCustomElement, VNode, VBaseElement } from './vnodes';
7
+ import { TemplateStylesheetFactories } from './stylesheet';
7
8
  type ShadowRootMode = 'open' | 'closed';
8
9
  export interface TemplateCache {
9
10
  [key: string]: any;
@@ -132,6 +133,9 @@ export interface VM<N = HostNode, E = HostElement> {
132
133
  /**
133
134
  * Debug info bag. Stores useful debug information about the component. */
134
135
  debugInfo?: Record<string, any>;
136
+ /**
137
+ * Any stylesheets associated with the component */
138
+ stylesheets: TemplateStylesheetFactories | null;
135
139
  }
136
140
  type VMAssociable = HostNode | LightningElement;
137
141
  export declare function rerenderVM(vm: VM): void;