@gitlab/ui 32.32.0 → 32.33.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.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ # [32.33.0](https://gitlab.com/gitlab-org/gitlab-ui/compare/v32.32.0...v32.33.0) (2021-11-04)
2
+
3
+
4
+ ### Features
5
+
6
+ * **GlTruncate:** show full text in a tooltip ([168c532](https://gitlab.com/gitlab-org/gitlab-ui/commit/168c5325c1fab5174cfa5ad092d829ad11c85363))
7
+
1
8
  # [32.32.0](https://gitlab.com/gitlab-org/gitlab-ui/compare/v32.31.0...v32.32.0) (2021-11-03)
2
9
 
3
10
 
@@ -12,6 +12,9 @@ var truncate_documentation = {
12
12
  position: {
13
13
  additionalInfo: 'Ellipsis position',
14
14
  enum: 'truncateOptions'
15
+ },
16
+ withTooltip: {
17
+ additionalInfo: 'Display the full text in a tooltip only if it is being truncated'
15
18
  }
16
19
  }
17
20
  };
@@ -1,7 +1,14 @@
1
+ import { GlTooltipDirective } from '../../../directives/tooltip';
2
+ import { GlResizeObserverDirective } from '../../../directives/resize_observer/resize_observer';
1
3
  import { POSITION } from './constants';
2
4
  import __vue_normalize__ from 'vue-runtime-helpers/dist/normalize-component.js';
3
5
 
4
6
  var script = {
7
+ POSITION,
8
+ directives: {
9
+ GlTooltip: GlTooltipDirective,
10
+ GlResizeObserver: GlResizeObserverDirective
11
+ },
5
12
  props: {
6
13
  text: {
7
14
  type: String,
@@ -12,12 +19,17 @@ var script = {
12
19
  required: false,
13
20
  default: POSITION.END,
14
21
  validator: value => Object.values(POSITION).includes(value)
22
+ },
23
+ withTooltip: {
24
+ type: Boolean,
25
+ required: false,
26
+ default: false
15
27
  }
16
28
  },
17
29
 
18
30
  data() {
19
31
  return {
20
- POSITION
32
+ isTruncated: false
21
33
  };
22
34
  },
23
35
 
@@ -32,6 +44,26 @@ var script = {
32
44
 
33
45
  last() {
34
46
  return this.text.slice(this.middleIndex);
47
+ },
48
+
49
+ isTooltipDisabled() {
50
+ return !this.withTooltip || !this.isTruncated;
51
+ }
52
+
53
+ },
54
+ watch: {
55
+ withTooltip(withTooltip) {
56
+ if (withTooltip) {
57
+ this.checkTruncationState();
58
+ }
59
+ }
60
+
61
+ },
62
+ methods: {
63
+ checkTruncationState() {
64
+ if (this.withTooltip) {
65
+ this.isTruncated = this.$refs.text.scrollWidth > this.$refs.text.offsetWidth;
66
+ }
35
67
  }
36
68
 
37
69
  }
@@ -41,7 +73,7 @@ var script = {
41
73
  const __vue_script__ = script;
42
74
 
43
75
  /* template */
44
- var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return (_vm.position === _vm.POSITION.START)?_c('span',{staticClass:"gl-truncate",attrs:{"title":_vm.text}},[_c('span',{staticClass:"gl-truncate-start gl-text-overflow-ellipsis!"},[_vm._v("‎"+_vm._s(_vm.text)+"‎")])]):(_vm.position === _vm.POSITION.MIDDLE)?_c('span',{staticClass:"gl-truncate",attrs:{"title":_vm.text}},[_c('span',{staticClass:"gl-truncate-end"},[_vm._v(_vm._s(_vm.first))]),_c('span',{staticClass:"gl-truncate-start"},[_vm._v("‎"+_vm._s(_vm.last)+"‎")])]):_c('span',{staticClass:"gl-truncate",attrs:{"title":_vm.text}},[_c('span',{staticClass:"gl-truncate-end"},[_vm._v(_vm._s(_vm.text))])])};
76
+ var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return (_vm.position === _vm.$options.POSITION.START)?_c('span',{directives:[{name:"gl-tooltip",rawName:"v-gl-tooltip",value:({ disabled: _vm.isTooltipDisabled }),expression:"{ disabled: isTooltipDisabled }"},{name:"gl-resize-observer",rawName:"v-gl-resize-observer:[withTooltip]",value:(_vm.checkTruncationState),expression:"checkTruncationState",arg:_vm.withTooltip}],staticClass:"gl-truncate",attrs:{"title":_vm.text}},[_c('span',{ref:"text",staticClass:"gl-truncate-start gl-text-overflow-ellipsis!"},[_vm._v("‎"+_vm._s(_vm.text)+"‎")])]):(_vm.position === _vm.$options.POSITION.MIDDLE)?_c('span',{directives:[{name:"gl-tooltip",rawName:"v-gl-tooltip",value:({ disabled: _vm.isTooltipDisabled }),expression:"{ disabled: isTooltipDisabled }"},{name:"gl-resize-observer",rawName:"v-gl-resize-observer:[withTooltip]",value:(_vm.checkTruncationState),expression:"checkTruncationState",arg:_vm.withTooltip}],staticClass:"gl-truncate",attrs:{"title":_vm.text}},[_c('span',{ref:"text",staticClass:"gl-truncate-end"},[_vm._v(_vm._s(_vm.first))]),_c('span',{staticClass:"gl-truncate-start"},[_vm._v("‎"+_vm._s(_vm.last)+"‎")])]):_c('span',{directives:[{name:"gl-tooltip",rawName:"v-gl-tooltip",value:({ disabled: _vm.isTooltipDisabled }),expression:"{ disabled: isTooltipDisabled }"},{name:"gl-resize-observer",rawName:"v-gl-resize-observer:[withTooltip]",value:(_vm.checkTruncationState),expression:"checkTruncationState",arg:_vm.withTooltip}],staticClass:"gl-truncate",attrs:{"data-testid":"truncate-end-container","title":_vm.text}},[_c('span',{ref:"text",staticClass:"gl-truncate-end"},[_vm._v(_vm._s(_vm.text))])])};
45
77
  var __vue_staticRenderFns__ = [];
46
78
 
47
79
  /* style */
@@ -1,6 +1,6 @@
1
1
  import examples from './examples';
2
2
 
3
- var description = "# Resize Observer\n\n<!-- STORY -->\n\n## Usage\n\nThis directive can be used to get notified whenever a given element's size (width or height) changes\nand to retrieve the updated dimensions.\n\nUnder the hood, it leverages the [Resize Observer API](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver).\nIf you use GitLab UI in an older browser which doesn't support the Resize Observer API,\nyou can use a [polyfill](https://github.com/que-etc/resize-observer-polyfill).\n\nThe directive accepts a callback as a value and passes on the received\n[contentRect](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserverEntry/contentRect)\nand the target element whenever a resize event gets triggered.\n\n```html\n<script>\nexport default {\n data() {\n return {\n width: 0,\n height: 0,\n };\n },\n methods: {\n handleResize({ contentRect: { width, height } }) {\n this.width = width;\n this.height = height;\n },\n },\n};\n</script>\n<template>\n <div v-gl-resize-observer-directive=\"handleResize\">\n <p>{{ width }} x {{ height }}</p>\n </div>\n</template>\n```\n";
3
+ var description = "# Resize Observer\n\n<!-- STORY -->\n\n## Usage\n\nThis directive can be used to get notified whenever a given element's size (width or height) changes\nand to retrieve the updated dimensions.\n\nUnder the hood, it leverages the [Resize Observer API](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver).\nIf you use GitLab UI in an older browser which doesn't support the Resize Observer API,\nyou can use a [polyfill](https://github.com/que-etc/resize-observer-polyfill).\n\nThe directive accepts a callback as a value and passes on the received\n[contentRect](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserverEntry/contentRect)\nand the target element whenever a resize event gets triggered.\n\n```html\n<script>\nexport default {\n data() {\n return {\n width: 0,\n height: 0,\n };\n },\n methods: {\n handleResize({ contentRect: { width, height } }) {\n this.width = width;\n this.height = height;\n },\n },\n};\n</script>\n<template>\n <div v-gl-resize-observer-directive=\"handleResize\">\n <p>{{ width }} x {{ height }}</p>\n </div>\n</template>\n```\n\nThe observer can be toggled on or off by passing a boolean argument to the directive:\n\n```html\n<script>\nexport default {\n data() {\n return {\n shouldObserve: true,\n };\n },\n methods: {\n handleResize() {},\n },\n};\n</script>\n<template>\n <div v-gl-resize-observer-directive[shouldObserve]=\"handleResize\"></div>\n</template>\n```\n";
4
4
 
5
5
  var resize_observer_documentation = {
6
6
  followsDesignSystem: false,
@@ -1,37 +1,57 @@
1
1
  import isFunction from 'lodash/isFunction';
2
2
 
3
3
  let observer = null;
4
+
5
+ const attachObserver = (el, resizeHandler) => {
6
+ if (!isFunction(resizeHandler)) {
7
+ throw TypeError('directive value must be a function');
8
+ }
9
+
10
+ if (!observer) {
11
+ // the observer instance is shared for performance reasons
12
+ // more information: https://github.com/WICG/ResizeObserver/issues/59
13
+ observer = new ResizeObserver(entries => {
14
+ entries.forEach(event => {
15
+ event.target.glResizeHandler(event);
16
+ });
17
+ });
18
+ }
19
+
20
+ el.glResizeHandler = resizeHandler;
21
+ observer.observe(el);
22
+ };
23
+
24
+ const detachObserver = el => {
25
+ if (el.glResizeHandler) {
26
+ var _observer;
27
+
28
+ delete el.glResizeHandler;
29
+ (_observer = observer) === null || _observer === void 0 ? void 0 : _observer.unobserve(el);
30
+ }
31
+ };
32
+
4
33
  const GlResizeObserverDirective = {
5
34
  bind(el, {
6
- value: resizeHandler
35
+ value: resizeHandler,
36
+ arg: enabled = true
7
37
  }) {
8
- if (!isFunction(resizeHandler)) {
9
- throw TypeError('directive value must be a function');
10
- }
11
-
12
- if (!observer) {
13
- // the observer instance is shared for performance reasons
14
- // more information: https://github.com/WICG/ResizeObserver/issues/59
15
- observer = new ResizeObserver(entries => {
16
- entries.forEach(event => {
17
- event.target.glResizeHandler(event);
18
- });
19
- });
38
+ if (enabled) {
39
+ attachObserver(el, resizeHandler);
20
40
  }
21
-
22
- el.glResizeHandler = resizeHandler;
23
- observer.observe(el);
24
41
  },
25
42
 
26
- unbind(el) {
27
- if (el.glResizeHandler) {
28
- var _observer;
29
-
30
- delete el.glResizeHandler;
31
- (_observer = observer) === null || _observer === void 0 ? void 0 : _observer.unobserve(el);
43
+ update(el, {
44
+ value: resizeHandler,
45
+ arg: enabled = true
46
+ }) {
47
+ if (enabled) {
48
+ attachObserver(el, resizeHandler);
49
+ } else {
50
+ detachObserver(el);
32
51
  }
33
- }
52
+ },
34
53
 
54
+ unbind: detachObserver
35
55
  };
36
56
 
37
57
  export { GlResizeObserverDirective };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gitlab/ui",
3
- "version": "32.32.0",
3
+ "version": "32.33.0",
4
4
  "description": "GitLab UI Components",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -84,7 +84,7 @@
84
84
  "@babel/preset-env": "^7.10.2",
85
85
  "@gitlab/eslint-plugin": "9.4.0",
86
86
  "@gitlab/stylelint-config": "2.6.0",
87
- "@gitlab/svgs": "1.218.0",
87
+ "@gitlab/svgs": "1.219.0",
88
88
  "@rollup/plugin-commonjs": "^11.1.0",
89
89
  "@rollup/plugin-node-resolve": "^7.1.3",
90
90
  "@rollup/plugin-replace": "^2.3.2",
@@ -12,5 +12,8 @@ export default {
12
12
  additionalInfo: 'Ellipsis position',
13
13
  enum: 'truncateOptions',
14
14
  },
15
+ withTooltip: {
16
+ additionalInfo: 'Display the full text in a tooltip only if it is being truncated',
17
+ },
15
18
  },
16
19
  };
@@ -1,6 +1,7 @@
1
1
  import { shallowMount } from '@vue/test-utils';
2
2
  import { POSITION } from './constants';
3
3
  import Truncate from './truncate.vue';
4
+ import { createMockDirective, getBinding } from '~helpers/vue_mock_directive';
4
5
 
5
6
  const removeSpecialChar = (text) => {
6
7
  return text.replace(/&lrm;|\u200E/gi, '');
@@ -18,6 +19,9 @@ describe('Truncate component', () => {
18
19
  const createComponent = (props) => {
19
20
  wrapper = shallowMount(Truncate, {
20
21
  propsData: { ...defaultProps, ...props },
22
+ directives: {
23
+ GlTooltip: createMockDirective(),
24
+ },
21
25
  });
22
26
  };
23
27
 
@@ -45,6 +49,10 @@ describe('Truncate component', () => {
45
49
  it('should have the default position', () => {
46
50
  expect(wrapper.props('position')).toBe('end');
47
51
  });
52
+
53
+ it('disables the tooltip by default', () => {
54
+ expect(getBinding(wrapper.element, 'gl-tooltip').value.disabled).toBe(true);
55
+ });
48
56
  });
49
57
 
50
58
  describe('start truncation', () => {
@@ -1,4 +1,4 @@
1
- import { withKnobs, text, select } from '@storybook/addon-knobs';
1
+ import { withKnobs, text, select, boolean } from '@storybook/addon-knobs';
2
2
  import { documentedStoriesOf } from '../../../../documentation/documented_stories';
3
3
  import { GlTruncate } from '../../../../index';
4
4
  import { POSITION } from './constants';
@@ -8,11 +8,12 @@ const components = {
8
8
  GlTruncate,
9
9
  };
10
10
 
11
- const template = '<gl-truncate :text="text" :position="position" />';
11
+ const template = '<gl-truncate :text="text" :position="position" :with-tooltip="withTooltip" />';
12
12
 
13
13
  function generateProps({
14
14
  longText = 'src/thisIs/AVeryLongFilePath/that/needs/to/be/smartly/truncated/from/the/middle/so/we/dont/lose/important/information/here.vue',
15
15
  position = 'middle',
16
+ withTooltip = false,
16
17
  } = {}) {
17
18
  return {
18
19
  text: {
@@ -23,6 +24,9 @@ function generateProps({
23
24
  type: String,
24
25
  default: select('position', Object.values(POSITION), position),
25
26
  },
27
+ withTooltip: {
28
+ default: boolean('withTooltip', withTooltip),
29
+ },
26
30
  };
27
31
  }
28
32
 
@@ -1,7 +1,14 @@
1
1
  <script>
2
+ import { GlTooltipDirective } from '../../../directives/tooltip';
3
+ import { GlResizeObserverDirective } from '../../../directives/resize_observer/resize_observer';
2
4
  import { POSITION } from './constants';
3
5
 
4
6
  export default {
7
+ POSITION,
8
+ directives: {
9
+ GlTooltip: GlTooltipDirective,
10
+ GlResizeObserver: GlResizeObserverDirective,
11
+ },
5
12
  props: {
6
13
  text: {
7
14
  type: String,
@@ -13,10 +20,15 @@ export default {
13
20
  default: POSITION.END,
14
21
  validator: (value) => Object.values(POSITION).includes(value),
15
22
  },
23
+ withTooltip: {
24
+ type: Boolean,
25
+ required: false,
26
+ default: false,
27
+ },
16
28
  },
17
29
  data() {
18
30
  return {
19
- POSITION,
31
+ isTruncated: false,
20
32
  };
21
33
  },
22
34
  computed: {
@@ -30,24 +42,62 @@ export default {
30
42
  last() {
31
43
  return this.text.slice(this.middleIndex);
32
44
  },
45
+ isTooltipDisabled() {
46
+ return !this.withTooltip || !this.isTruncated;
47
+ },
48
+ },
49
+ watch: {
50
+ withTooltip(withTooltip) {
51
+ if (withTooltip) {
52
+ this.checkTruncationState();
53
+ }
54
+ },
55
+ },
56
+ methods: {
57
+ checkTruncationState() {
58
+ if (this.withTooltip) {
59
+ this.isTruncated = this.$refs.text.scrollWidth > this.$refs.text.offsetWidth;
60
+ }
61
+ },
33
62
  },
34
63
  };
35
64
  </script>
36
65
 
37
66
  <template>
38
67
  <!-- START -->
39
- <span v-if="position === POSITION.START" class="gl-truncate" :title="text">
40
- <span class="gl-truncate-start gl-text-overflow-ellipsis!">&lrm;{{ text }}&lrm;</span>
68
+ <span
69
+ v-if="position === $options.POSITION.START"
70
+ v-gl-tooltip="{ disabled: isTooltipDisabled }"
71
+ v-gl-resize-observer:[withTooltip]="checkTruncationState"
72
+ class="gl-truncate"
73
+ :title="text"
74
+ >
75
+ <span ref="text" class="gl-truncate-start gl-text-overflow-ellipsis!"
76
+ >&lrm;{{ text }}&lrm;</span
77
+ >
41
78
  </span>
42
79
 
43
80
  <!-- MIDDLE -->
44
- <span v-else-if="position === POSITION.MIDDLE" class="gl-truncate" :title="text">
45
- <span class="gl-truncate-end">{{ first }}</span
81
+ <span
82
+ v-else-if="position === $options.POSITION.MIDDLE"
83
+ v-gl-tooltip="{ disabled: isTooltipDisabled }"
84
+ v-gl-resize-observer:[withTooltip]="checkTruncationState"
85
+ class="gl-truncate"
86
+ :title="text"
87
+ >
88
+ <span ref="text" class="gl-truncate-end">{{ first }}</span
46
89
  ><span class="gl-truncate-start">&lrm;{{ last }}&lrm;</span>
47
90
  </span>
48
91
 
49
92
  <!-- END -->
50
- <span v-else class="gl-truncate" :title="text">
51
- <span class="gl-truncate-end">{{ text }}</span>
93
+ <span
94
+ v-else
95
+ v-gl-tooltip="{ disabled: isTooltipDisabled }"
96
+ v-gl-resize-observer:[withTooltip]="checkTruncationState"
97
+ class="gl-truncate"
98
+ data-testid="truncate-end-container"
99
+ :title="text"
100
+ >
101
+ <span ref="text" class="gl-truncate-end">{{ text }}</span>
52
102
  </span>
53
103
  </template>
@@ -2,29 +2,44 @@ import isFunction from 'lodash/isFunction';
2
2
 
3
3
  let observer = null;
4
4
 
5
- export const GlResizeObserverDirective = {
6
- bind(el, { value: resizeHandler }) {
7
- if (!isFunction(resizeHandler)) {
8
- throw TypeError('directive value must be a function');
9
- }
5
+ const attachObserver = (el, resizeHandler) => {
6
+ if (!isFunction(resizeHandler)) {
7
+ throw TypeError('directive value must be a function');
8
+ }
10
9
 
11
- if (!observer) {
12
- // the observer instance is shared for performance reasons
13
- // more information: https://github.com/WICG/ResizeObserver/issues/59
14
- observer = new ResizeObserver((entries) => {
15
- entries.forEach((event) => {
16
- event.target.glResizeHandler(event);
17
- });
10
+ if (!observer) {
11
+ // the observer instance is shared for performance reasons
12
+ // more information: https://github.com/WICG/ResizeObserver/issues/59
13
+ observer = new ResizeObserver((entries) => {
14
+ entries.forEach((event) => {
15
+ event.target.glResizeHandler(event);
18
16
  });
19
- }
17
+ });
18
+ }
19
+
20
+ el.glResizeHandler = resizeHandler;
21
+ observer.observe(el);
22
+ };
20
23
 
21
- el.glResizeHandler = resizeHandler;
22
- observer.observe(el);
24
+ const detachObserver = (el) => {
25
+ if (el.glResizeHandler) {
26
+ delete el.glResizeHandler;
27
+ observer?.unobserve(el);
28
+ }
29
+ };
30
+
31
+ export const GlResizeObserverDirective = {
32
+ bind(el, { value: resizeHandler, arg: enabled = true }) {
33
+ if (enabled) {
34
+ attachObserver(el, resizeHandler);
35
+ }
23
36
  },
24
- unbind(el) {
25
- if (el.glResizeHandler) {
26
- delete el.glResizeHandler;
27
- observer?.unobserve(el);
37
+ update(el, { value: resizeHandler, arg: enabled = true }) {
38
+ if (enabled) {
39
+ attachObserver(el, resizeHandler);
40
+ } else {
41
+ detachObserver(el);
28
42
  }
29
43
  },
44
+ unbind: detachObserver,
30
45
  };
@@ -38,3 +38,23 @@ export default {
38
38
  </div>
39
39
  </template>
40
40
  ```
41
+
42
+ The observer can be toggled on or off by passing a boolean argument to the directive:
43
+
44
+ ```html
45
+ <script>
46
+ export default {
47
+ data() {
48
+ return {
49
+ shouldObserve: true,
50
+ };
51
+ },
52
+ methods: {
53
+ handleResize() {},
54
+ },
55
+ };
56
+ </script>
57
+ <template>
58
+ <div v-gl-resize-observer-directive[shouldObserve]="handleResize"></div>
59
+ </template>
60
+ ```
@@ -10,7 +10,7 @@ describe('resize observer directive', () => {
10
10
  const localVue = createLocalVue();
11
11
  let wrapper;
12
12
 
13
- const createComponent = ({ template } = {}) => {
13
+ const createComponent = ({ template, data = {} } = {}) => {
14
14
  const defaultTemplate = `<div v-resize-observer="handleResize"></div>`;
15
15
 
16
16
  const component = {
@@ -21,6 +21,9 @@ describe('resize observer directive', () => {
21
21
  handleResize: mockHandleResize,
22
22
  },
23
23
  template: template || defaultTemplate,
24
+ data() {
25
+ return data;
26
+ },
24
27
  };
25
28
 
26
29
  wrapper = shallowMount(component, { localVue });
@@ -52,6 +55,13 @@ describe('resize observer directive', () => {
52
55
  expect(observesElement(wrapper.element)).toBe(true);
53
56
  });
54
57
 
58
+ it('does not subscribe if the argument is false', () => {
59
+ createComponent({ template: '<div v-resize-observer:[false]="handleResize"></div>' });
60
+
61
+ expect(observersCount()).toBe(0);
62
+ expect(observesElement(wrapper.element)).toBe(false);
63
+ });
64
+
55
65
  it('passes the first entries "contentRect" and "target" to the given handler', () => {
56
66
  createComponent();
57
67
 
@@ -61,6 +71,28 @@ describe('resize observer directive', () => {
61
71
  expect(mockHandleResize).toHaveBeenCalledWith({ contentRect: {}, target: wrapper.element });
62
72
  });
63
73
 
74
+ it("detaches then reattaches observer based on argument's value", async () => {
75
+ expect(observersCount()).toBe(0);
76
+
77
+ createComponent({
78
+ template: '<div v-resize-observer:[isEnabled]="handleResize"></div>',
79
+ data: {
80
+ isEnabled: true,
81
+ },
82
+ });
83
+
84
+ expect(observersCount()).toBe(1);
85
+ expect(wrapper.element.glResizeHandler).not.toBeFalsy();
86
+
87
+ await wrapper.setData({ isEnabled: false });
88
+
89
+ expect(wrapper.element.glResizeHandler).toBeFalsy();
90
+
91
+ await wrapper.setData({ isEnabled: true });
92
+
93
+ expect(wrapper.element.glResizeHandler).not.toBeFalsy();
94
+ });
95
+
64
96
  it('does a clean up when the component is destroyed', () => {
65
97
  createComponent();
66
98