diff --git a/CHANGELOG.md b/CHANGELOG.md index b2eabb14..d93296b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ 7.2.17-SNAPSHOT (WIP) ================== +* [OLMIS-8191](https://openlmis.atlassian.net/browse/OLMIS-8191) / [OLMIS-8192](https://openlmis.atlassian.net/browse/OLMIS-8192): Add openlmis-long-text class and directive so long free-text in table cells grows with content then wraps — for both the editable textarea and its read-only display. 7.2.16 / 2026-06-09 ================== diff --git a/src/common/_variables.scss b/src/common/_variables.scss index a7948a82..02987684 100644 --- a/src/common/_variables.scss +++ b/src/common/_variables.scss @@ -65,6 +65,10 @@ $padding-small: 0.5em; $padding: (2 * $padding-small); $padding-large: (2 * $padding); +// openlmis-long-text widen-then-wrap: cap = where wrapping starts, floor = editable typing room. +$openlmis-long-text-max-width: 35em !default; +$openlmis-long-text-min-width: 14em !default; + $margin: 1em; $padding2px:2px; diff --git a/src/openlmis-table-form/_table-forms.scss b/src/openlmis-table-form/_table-forms.scss index 38193352..ea3865e4 100644 --- a/src/openlmis-table-form/_table-forms.scss +++ b/src/openlmis-table-form/_table-forms.scss @@ -142,11 +142,24 @@ td > label.checkbox { width: 100%; } -.openlmis-table td > div.input-control > textarea { - min-width: 14em; - max-width: 20em; +// Long free-text in a table column: widens with content, then wraps. Opt-in class on the +// editable textarea (sized by the openlmisLongText directive) and the read-only span. +.openlmis-long-text { + display: inline-block; + max-width: $openlmis-long-text-max-width; white-space: pre-wrap; + overflow-wrap: break-word; overflow: hidden; resize: none; vertical-align: middle; } + +// Editable: minimum typing width (the directive sets the actual width). +textarea.openlmis-long-text { + min-width: $openlmis-long-text-min-width; +} + +// Read-only: size to content up to the cap, so short cells stay narrow and long text wraps. +span.openlmis-long-text { + width: max-content; +} diff --git a/src/openlmis-table-form/textarea-auto-resize.directive.js b/src/openlmis-table-form/openlmis-long-text.directive.js similarity index 53% rename from src/openlmis-table-form/textarea-auto-resize.directive.js rename to src/openlmis-table-form/openlmis-long-text.directive.js index ab28a548..18374bd6 100644 Binary files a/src/openlmis-table-form/textarea-auto-resize.directive.js and b/src/openlmis-table-form/openlmis-long-text.directive.js differ diff --git a/src/openlmis-table-form/openlmis-long-text.directive.spec.js b/src/openlmis-table-form/openlmis-long-text.directive.spec.js new file mode 100644 index 00000000..43b2c073 --- /dev/null +++ b/src/openlmis-table-form/openlmis-long-text.directive.spec.js @@ -0,0 +1,102 @@ +/* + * This program is part of the OpenLMIS logistics management information system platform software. + * Copyright © 2017 VillageReach + * + * This program is free software: you can redistribute it and/or modify it under the terms + * of the GNU Affero General Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Affero General Public License for more details. You should have received a copy of + * the GNU Affero General Public License along with this program. If not, see + * http://www.gnu.org/licenses. For additional information contact info@OpenLMIS.org. + */ + +describe('openlmisLongText directive', function() { + + beforeEach(function() { + module('openlmis-table-form'); + + inject(function($injector) { + this.$compile = $injector.get('$compile'); + this.$rootScope = $injector.get('$rootScope'); + }); + + this.$scope = this.$rootScope.$new(); + + this.compileMarkup = function(markup) { + const element = this.$compile(markup)(this.$scope); + angular.element('body').append(element); + this.$scope.$apply(); + return element; + }; + }); + + it('should set an explicit width and height on a textarea with the class', function() { + const textarea = this.compileMarkup(''); + + expect(Number.parseInt(textarea[0].style.width, 10)).toBeGreaterThan(0); + expect(Number.parseInt(textarea[0].style.height, 10)).toBeGreaterThan(0); + }); + + it('should grow width when the value becomes longer', function() { + const textarea = this.compileMarkup(''); + const previousWidth = Number.parseInt(textarea[0].style.width, 10); + + textarea[0].value = 'a value that is clearly longer than the initial one'; + this.$scope.$apply(); + + expect(Number.parseInt(textarea[0].style.width, 10)).toBeGreaterThan(previousWidth); + }); + + it('should grow height when the value spans multiple lines', function() { + const textarea = this.compileMarkup(''); + const previousHeight = Number.parseInt(textarea[0].style.height, 10); + + textarea[0].value = 'first line\nsecond line\nthird line\nfourth line'; + this.$scope.$apply(); + + expect(Number.parseInt(textarea[0].style.height, 10)).toBeGreaterThan(previousHeight); + }); + + it('should not resize a textarea that does not have the class', function() { + const textarea = this.compileMarkup(''); + + expect(textarea[0].style.width).toEqual(''); + expect(textarea[0].style.height).toEqual(''); + }); + + it('should not set an inline size on a read-only span with the class', function() { + const span = this.compileMarkup('read only'); + + expect(span[0].style.width).toEqual(''); + expect(span[0].style.height).toEqual(''); + }); + + it('should not collapse a textarea that is hidden when linked', function() { + const textarea = this.compileMarkup( + '' + ); + + // A hidden textarea reports scrollHeight 0; sizing it then would bake height:0 and + // leave it collapsed once shown (e.g. an ng-show free-text comment). It must be skipped. + expect(textarea[0].style.height).not.toEqual('0px'); + }); + + it('should size a pre-filled textarea once it becomes visible after being hidden', function() { + const textarea = this.compileMarkup( + '' + ); + + // Hidden at link time -> skipped, no inline height yet. + expect(textarea[0].style.height).toEqual(''); + + // Shown later without a value change -> the visibility watch must size it. + textarea[0].style.display = ''; + this.$scope.$apply(); + + expect(Number.parseInt(textarea[0].style.height, 10)).toBeGreaterThan(0); + expect(Number.parseInt(textarea[0].style.width, 10)).toBeGreaterThan(0); + }); +}); diff --git a/src/openlmis-table-form/textarea-auto-resize.directive.spec.js b/src/openlmis-table-form/textarea-auto-resize.directive.spec.js deleted file mode 100644 index cd5879c6..00000000 --- a/src/openlmis-table-form/textarea-auto-resize.directive.spec.js +++ /dev/null @@ -1,79 +0,0 @@ -/* - * This program is part of the OpenLMIS logistics management information system platform software. - * Copyright © 2017 VillageReach - * - * This program is free software: you can redistribute it and/or modify it under the terms - * of the GNU Affero General Public License as published by the Free Software Foundation, either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU Affero General Public License for more details. You should have received a copy of - * the GNU Affero General Public License along with this program. If not, see - * http://www.gnu.org/licenses. For additional information contact info@OpenLMIS.org. - */ - -describe('Textarea automatic resize directive', function() { - - beforeEach(function() { - module('openlmis-table-form'); - - inject(function($injector) { - this.$compile = $injector.get('$compile'); - this.$rootScope = $injector.get('$rootScope'); - }); - - this.compileMarkup = function(markup) { - const element = this.$compile(markup)(this.$scope); - angular.element('body').append(element); - this.$scope.$apply(); - return element; - }; - - this.$scope = this.$rootScope.$new(); - - const markup = - '
| ' + - '' + - ' |