druxt 0.24.1-dev.20260920213131 → 0.25.0-dev.20260921034536

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.
@@ -5,6 +5,66 @@ import Vue from 'vue'
5
5
 
6
6
  import DruxtWrapper from './DruxtWrapper.vue'
7
7
 
8
+ /**
9
+ * Whether JSON holds a value well enough to compare two of them.
10
+ *
11
+ * A function or a symbol writes as `undefined`, a Map or a Set as `{}`, and
12
+ * NaN as `null`, so two different values can write the same text. Anything
13
+ * JSON does not hold exactly is compared by identity instead.
14
+ *
15
+ * @private
16
+ *
17
+ * @param {*} value - The value to check.
18
+ * @param {WeakSet} [seen] - Objects already visited, so a cycle ends the walk.
19
+ *
20
+ * @returns {boolean} Whether JSON.stringify represents the value exactly.
21
+ */
22
+ const isComparable = (value, seen = new WeakSet()) => {
23
+ if (value === null) return true
24
+
25
+ const type = typeof value
26
+ if (type === 'string' || type === 'boolean') return true
27
+ if (type === 'number') return Number.isFinite(value)
28
+ if (type !== 'object') return false
29
+
30
+ if (seen.has(value)) return false
31
+ seen.add(value)
32
+
33
+ if (Array.isArray(value)) return value.every((item) => isComparable(item, seen))
34
+
35
+ // A Map, a Set, a Date or any class instance writes as something other than itself.
36
+ const prototype = Object.getPrototypeOf(value)
37
+ if (prototype !== Object.prototype && prototype !== null) return false
38
+
39
+ return Object.values(value).every((item) => isComparable(item, seen))
40
+ }
41
+
42
+ /**
43
+ * Whether rebuilt propsData differs from what the wrapper already has.
44
+ *
45
+ * Values that cannot be compared count as changed, so a rebuild is skipped
46
+ * only when the two are known to match.
47
+ *
48
+ * @private
49
+ *
50
+ * @param {object} current - The propsData the wrapper holds.
51
+ * @param {object} next - The propsData the module's callback just produced.
52
+ *
53
+ * @returns {boolean} Whether the wrapper needs the new propsData.
54
+ */
55
+ const hasChanged = (current = {}, next = {}) => {
56
+ const keys = [...new Set([...Object.keys(current), ...Object.keys(next)])]
57
+ return keys.some((key) => {
58
+ if (current[key] === next[key]) return false
59
+ if (!isComparable(current[key]) || !isComparable(next[key])) return true
60
+ try {
61
+ return JSON.stringify(current[key]) !== JSON.stringify(next[key])
62
+ } catch (err) {
63
+ return true
64
+ }
65
+ })
66
+ }
67
+
8
68
  /**
9
69
  * The DruxtModule component renders a Druxt module; import and extend the
10
70
  * component to build your own module.
@@ -168,6 +228,9 @@ export default {
168
228
 
169
229
  // Get wrapper data.
170
230
  const wrapperData = await this.getWrapperData(component.is)
231
+ // On the instance rather than in data(), so it is not copied into the
232
+ // server rendered payload once per module.
233
+ this.wrapperProps = wrapperData.props || {}
171
234
 
172
235
  // Build module settings.
173
236
  component.settings = wrapperData.druxt || {}
@@ -206,7 +269,20 @@ export default {
206
269
  * @param {object} vm.$route - The current route.
207
270
  * @return {string} The current language code.
208
271
  */
209
- lang: ({ langcode, $route }) => langcode || ($route.meta || {}).langcode
272
+ lang: ({ langcode, $route }) => langcode || ($route.meta || {}).langcode,
273
+
274
+ /**
275
+ * The module's own propsData, tracked so a prop change rebuilds it.
276
+ *
277
+ * Reading it here registers whatever the module's `propsData()` callback
278
+ * reads, so each module declares its own dependencies by using them.
279
+ *
280
+ * @type {object}
281
+ */
282
+ modulePropsData() {
283
+ if (!(this.$options.druxt || {}).propsData) return null
284
+ return { langcode: this.lang, ...this.$options.druxt.propsData.call(this, this) }
285
+ }
210
286
  },
211
287
 
212
288
  watch: {
@@ -216,6 +292,27 @@ export default {
216
292
  }
217
293
  },
218
294
 
295
+ // Nuxt 2 doesn't re-run fetch() when a component's own props change, so a
296
+ // reused instance would keep the propsData its first fetch() built. Rebuild
297
+ // it in place instead of refetching: the data is unchanged, the props are not.
298
+ modulePropsData: {
299
+ deep: true,
300
+ handler(to) {
301
+ // An errored module renders DruxtDebug with its own props; leave them.
302
+ if (!to || this.component.is === 'DruxtDebug' || !Object.keys(this.component.propsData || {}).length) {
303
+ return
304
+ }
305
+ if (!hasChanged(this.component.propsData, to)) {
306
+ return
307
+ }
308
+
309
+ // After hydration this instance never ran fetch(), so read the resolved
310
+ // wrapper's props instead of the ones that fetch() put on the instance.
311
+ const resolved = ((this.$options.components || {})[this.component.is] || {}).options || {}
312
+ this.component = { ...this.component, ...this.getModulePropsData(this.wrapperProps || resolved.props || {}) }
313
+ }
314
+ },
315
+
219
316
  model() {
220
317
  if (this.component.props && this.component.props.value !== this.model) {
221
318
  this.component.props.value = this.model
@@ -1,6 +1,6 @@
1
1
  <template>
2
2
  <div>
3
- <slot v-bind="{ ...$parent.$attrs }" />
3
+ <slot v-bind="slotAttrs" />
4
4
  </div>
5
5
  </template>
6
6
 
@@ -10,6 +10,31 @@
10
10
  * Placeholder component for the Druxt Wrapper theming system.
11
11
  */
12
12
  export default {
13
- name: 'DruxtWrapper'
13
+ name: 'DruxtWrapper',
14
+
15
+ // A module hands its wrapper the module's own attributes and every propsData
16
+ // key the wrapper does not declare, so the wrapper can read them. Rendering
17
+ // them here as well would repeat the module's attributes on a second element
18
+ // and write objects into the markup as [object Object]. They stay readable
19
+ // in $attrs either way.
20
+ inheritAttrs: false,
21
+
22
+ computed: {
23
+ /**
24
+ * The module's attributes, for the default slot.
25
+ *
26
+ * A module renders its fields through this slot, and hands each one these
27
+ * attributes, so whatever is here reaches every module below. `id` names
28
+ * one element, so it is left out rather than repeated on each of them.
29
+ * DruxtModule withholds `data-fetch-key` for the same reason.
30
+ *
31
+ * @type {object}
32
+ */
33
+ slotAttrs() {
34
+ const attrs = { ...(this.$parent || {}).$attrs }
35
+ delete attrs.id
36
+ return attrs
37
+ }
38
+ }
14
39
  }
15
40
  </script>
package/dist/druxt.esm.js CHANGED
@@ -263,7 +263,7 @@ class DruxtClient {
263
263
  }
264
264
 
265
265
  var name = "druxt";
266
- var version = "0.24.1-dev.20260920213131";
266
+ var version = "0.25.0-dev.20260921034536";
267
267
  var description = "The Fully Decoupled Drupal Framework for Nuxt.js.";
268
268
  var keywords = [
269
269
  "cms",
@@ -322,7 +322,7 @@ var dependencies = {
322
322
  };
323
323
  var devDependencies = {
324
324
  "@storybook/addon-docs": "^6.5.16",
325
- "druxt-test-utils": "^0.2.15"
325
+ "druxt-test-utils": "1.0.0-dev.20260921034536"
326
326
  };
327
327
  var peerDependencies = {
328
328
  axios: "0.28.0",
package/dist/druxt.ssr.js CHANGED
@@ -276,7 +276,7 @@ class DruxtClient {
276
276
  }
277
277
 
278
278
  var name = "druxt";
279
- var version = "0.24.1-dev.20260920213131";
279
+ var version = "0.25.0-dev.20260921034536";
280
280
  var description = "The Fully Decoupled Drupal Framework for Nuxt.js.";
281
281
  var keywords = [
282
282
  "cms",
@@ -335,7 +335,7 @@ var dependencies = {
335
335
  };
336
336
  var devDependencies = {
337
337
  "@storybook/addon-docs": "^6.5.16",
338
- "druxt-test-utils": "^0.2.15"
338
+ "druxt-test-utils": "1.0.0-dev.20260921034536"
339
339
  };
340
340
  var peerDependencies = {
341
341
  axios: "0.28.0",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "druxt",
3
- "version": "0.24.1-dev.20260920213131",
3
+ "version": "0.25.0-dev.20260921034536",
4
4
  "description": "The Fully Decoupled Drupal Framework for Nuxt.js.",
5
5
  "keywords": [
6
6
  "cms",
@@ -59,7 +59,7 @@
59
59
  },
60
60
  "devDependencies": {
61
61
  "@storybook/addon-docs": "^6.5.16",
62
- "druxt-test-utils": "^0.2.15"
62
+ "druxt-test-utils": "1.0.0-dev.20260921034536"
63
63
  },
64
64
  "peerDependencies": {
65
65
  "axios": "0.28.0",