diff --git a/controls/barcodegenerator/CHANGELOG.md b/controls/barcodegenerator/CHANGELOG.md index d31b78724e..cdd688ac43 100644 --- a/controls/barcodegenerator/CHANGELOG.md +++ b/controls/barcodegenerator/CHANGELOG.md @@ -2,7 +2,7 @@ ## [Unreleased] -## 25.1.35 (2024-03-15) +## 25.1.37 (2024-03-26) ### Barcode diff --git a/controls/base/CHANGELOG.md b/controls/base/CHANGELOG.md index b81cbea6e4..f204e654cb 100644 --- a/controls/base/CHANGELOG.md +++ b/controls/base/CHANGELOG.md @@ -2,44 +2,6 @@ ## [Unreleased] -## 24.2.7 (2024-02-20) - -### Common - -#### Bug Fixes - -- `#I542405` - The issue with "component events remain after being destroyed through the template in the heap memory" has been resolved. - -## 24.2.5 (2024-02-13) - -### Common - -#### Bug Fixes - -- `#I495294` - The issue with content sanitization in the tooltip component has been resolved. - -## 24.2.3 (2024-01-31) - -### Common - -#### Bug Fixes - -- `#I547507` - The issue with "rendering the new line in the html template string" has been resolved. - -### Common - -#### Bug Fixes - -- `#I541838` - Resolved issue with time picker designator "pm" is automatically flips to "am". - -## 24.1.46 (2024-01-17) - -### Common - -#### Bug Fixes - -- `#I531468` - The issue with "rendering the html template string" has been resolved. - ## 23.2.6 (2023-11-28) ### Common diff --git a/controls/base/package.json b/controls/base/package.json index 9816e9815e..175c8d4099 100644 --- a/controls/base/package.json +++ b/controls/base/package.json @@ -1,6 +1,6 @@ { "name": "@syncfusion/ej2-base", - "version": "24.2.7", + "version": "25.1.35", "description": "A common package of Essential JS 2 base libraries, methods and class definitions", "author": "Syncfusion Inc.", "license": "SEE LICENSE IN license", @@ -45,6 +45,7 @@ "@syncfusion/ej2-icons": "*" }, "devDependencies": { + "@syncfusion/ej2-staging": "^1.0.1", "@types/chai": "^3.4.28", "@types/jasmine": "2.8.22", "@types/jasmine-ajax": "^3.1.27", diff --git a/controls/base/spec/template.spec.ts b/controls/base/spec/template.spec.ts index b1b1361131..b15e3394be 100644 --- a/controls/base/spec/template.spec.ts +++ b/controls/base/spec/template.spec.ts @@ -178,14 +178,6 @@ describe('Template', () => { expect(templateResult).toEqual(result); }); - it('new line in the template string', () => { - var data = { name: `Aston Martin\n Car Models` }; - let templateStr: string = "
"+data.name+"
"; - let getString: any = template.compile(templateStr); - let output: any = getString(templateStr); - expect(output).toEqual("
Aston Martin Car Models
"); - }); - it('JSON array input with href value with apostrophe', () => { let templateStr: string = `
FranceVINET
`; let getString: any = template.compile(templateStr); @@ -496,7 +488,7 @@ describe('Template', () => { let outputDOM: Function = template.compile(templateFunc); expect(outputDOM(data)).toEqual(output); }); - + it('Template string with encrypted value: ', () => { // Below is an base64 encrypted value `jaVasCript:/*-/*`/*\`/*'/*/**/"(/* */oNcliCk=alert('XSS') )//%0D%0A%0D%0A//` const unformattedValue: string = window.atob( diff --git a/controls/base/src/ajax.ts b/controls/base/src/ajax.ts index 8239e687a2..8775f4459b 100644 --- a/controls/base/src/ajax.ts +++ b/controls/base/src/ajax.ts @@ -122,7 +122,6 @@ export class Ajax { if (!isNullOrUndefined(this.onUploadProgress)) { this.httpRequest.upload.onprogress = this.onUploadProgress; } - // eslint-disable-next-line this.httpRequest.open(this.type, this.url, this.mode); // Set default headers if (!isNullOrUndefined(this.data) && this.contentType !== null) { diff --git a/controls/base/src/animation.ts b/controls/base/src/animation.ts index 0425829392..2f5165d0a6 100644 --- a/controls/base/src/animation.ts +++ b/controls/base/src/animation.ts @@ -525,20 +525,20 @@ export function setGlobalAnimation(value: string | GlobalAnimationMode): void { animationMode = value; } -/** - * Defines the global animation modes for all components. +/** + * Defines the global animation modes for all components. */ export enum GlobalAnimationMode { - /** - * Defines the global animation mode as Default. Animation is enabled or disabled based on the component's animation settings. - */ + /** + * Defines the global animation mode as Default. Animation is enabled or disabled based on the component's animation settings. + */ Default = 'Default', - /** - * Defines the global animation mode as Enable. Enables the animation for all components, regardless of the individual component's animation settings. - */ + /** + * Defines the global animation mode as Enable. Enables the animation for all components, regardless of the individual component's animation settings. + */ Enable = 'Enable', - /** - * Defines the global animation mode as Disable. Disables the animation for all components, regardless of the individual component's animation settings. - */ + /** + * Defines the global animation mode as Disable. Disables the animation for all components, regardless of the individual component's animation settings. + */ Disable = 'Disable' } diff --git a/controls/base/src/base.ts b/controls/base/src/base.ts index faafc210f1..70123823ba 100644 --- a/controls/base/src/base.ts +++ b/controls/base/src/base.ts @@ -1,7 +1,6 @@ import { isUndefined, isNullOrUndefined, merge, setImmediate, setValue, isBlazor, getValue, extend } from './util'; import { addClass, removeClass } from './dom'; import { Observer } from './observer'; -import {validateLicense} from './validate-lic'; export interface DomElements extends HTMLElement { // eslint-disable-next-line ej2_instances: Object[]; @@ -15,6 +14,7 @@ export interface AngularEventEmitter { subscribe?: (generatorOrNext?: any, error?: any, complete?: any) => any; } +// eslint-disable-next-line export declare type EmitType = AngularEventEmitter & ((arg?: any, ...rest: any[]) => void); export interface BlazorDotnetObject { @@ -284,13 +284,14 @@ export abstract class Base { protected destroy(): void { // eslint-disable-next-line ((this.element as HTMLElement)).ej2_instances = - ((this.element as HTMLElement)).ej2_instances ? ((this.element as HTMLElement)).ej2_instances.filter((i: Object) => { - if (proxyToRaw) { - return proxyToRaw(i) !== proxyToRaw(this); - } - return i !== this; - }) - : []; + ((this.element as HTMLElement)).ej2_instances ? + ((this.element as HTMLElement)).ej2_instances.filter((i: Object) => { + if (proxyToRaw) { + return proxyToRaw(i) !== proxyToRaw(this); + } + return i !== this; + }) + : []; removeClass([this.element], ['e-' + this.getModuleName()]); if (((this.element as HTMLElement)).ej2_instances.length === 0) { // Remove module class from the root element @@ -352,4 +353,5 @@ export function removeChildInstance(element: HTMLElement): void { } } -export let proxyToRaw: Function, setProxyToRaw = (toRaw: Function): void => { proxyToRaw = toRaw }; \ No newline at end of file +export let proxyToRaw: Function; +export const setProxyToRaw: Function = (toRaw: Function): void => { proxyToRaw = toRaw; }; diff --git a/controls/base/src/component.ts b/controls/base/src/component.ts index 69fa76f3fe..69546f05b9 100644 --- a/controls/base/src/component.ts +++ b/controls/base/src/component.ts @@ -152,22 +152,28 @@ export abstract class Component extends Base extends Base extends Base 5) { diff --git a/controls/base/src/draggable.ts b/controls/base/src/draggable.ts index 099b7b33fe..9c61267b74 100644 --- a/controls/base/src/draggable.ts +++ b/controls/base/src/draggable.ts @@ -8,9 +8,6 @@ import { PositionModel, DraggableModel } from './draggable-model'; import { select, closest, setStyleAttribute, addClass, createElement } from './dom'; import { extend, isUndefined, isNullOrUndefined, compareElementParent, isBlazor } from './util'; const defaultPosition: PositionCoordinates = { left: 0, top: 0, bottom: 0, right: 0 }; -const positionProp: string[] = ['offsetLeft', 'offsetTop']; -const axisMapper: string[] = ['x', 'y']; -const axisValueMapper: string[] = ['left', 'top']; const isDraggedObject: DragObject = { isDragged: false }; /** @@ -466,6 +463,7 @@ export class Draggable extends Base implements INotifyPropertyChang return this.getScrollableParent(element.parentNode as HTMLElement, axis); } } + /* eslint-disable */ private getScrollableValues(): void { this.parentScrollX = 0; this.parentScrollY = 0; @@ -473,6 +471,7 @@ export class Draggable extends Base implements INotifyPropertyChang const verticalScrollParent: HTMLElement = this.getScrollableParent(this.element.parentNode as HTMLElement, 'vertical'); const horizontalScrollParent: HTMLElement = this.getScrollableParent(this.element.parentNode as HTMLElement, 'horizontal'); } + /* eslint-enable */ private initialize(evt: MouseEvent & TouchEvent, curTarget?: EventTarget): void { this.currentStateTarget = evt.target; if (this.isDragStarted()) { @@ -485,6 +484,7 @@ export class Draggable extends Base implements INotifyPropertyChang this.dragProcessStarted = false; if (this.abort) { /* tslint:disable no-any */ + // eslint-disable-next-line let abortSelectors: any = this.abort; if (typeof abortSelectors === 'string') { abortSelectors = [abortSelectors]; @@ -550,6 +550,7 @@ export class Draggable extends Base implements INotifyPropertyChang } /* istanbul ignore next */ if (this.isReplaceDragEle) { + // eslint-disable-next-line element = this.currentStateCheck(evt.target as any, element); } this.offset = this.calculateParentPosition(element); @@ -707,6 +708,7 @@ export class Draggable extends Base implements INotifyPropertyChang } } if (flag) { + // eslint-disable-next-line (eleObj).instance.dragData[this.scope] = this.droppables[this.scope]; eleObj.instance.intOver(evt, eleObj.target); this.hoverObject = eleObj; @@ -777,10 +779,10 @@ export class Draggable extends Base implements INotifyPropertyChang } } else { if (this.dragArea) { - let isDialogEle: boolean = this.helperElement.classList.contains('e-dialog'); + const isDialogEle: boolean = this.helperElement.classList.contains('e-dialog'); this.dragLimit.top = this.clone ? this.dragLimit.top : 0; draEleTop = (top - iTop) < 0 ? this.dragLimit.top : (top - iTop); - draEleLeft = (left - iLeft) < 0 ? isDialogEle ? (left - (iLeft- this.borderWidth.left)) : + draEleLeft = (left - iLeft) < 0 ? isDialogEle ? (left - (iLeft - this.borderWidth.left)) : this.dragElePosition.left : (left - iLeft); } else { draEleTop = top - iTop; @@ -846,6 +848,7 @@ export class Draggable extends Base implements INotifyPropertyChang elements = this.getPathElements(evt); } /* tslint:disable no-any */ + // eslint-disable-next-line let scrollParent: any = this.getScrollParent(elements, false); if (this.elementInViewport(this.helperElement)) { this.getScrollPosition(scrollParent, draEleTop); @@ -869,8 +872,10 @@ export class Draggable extends Base implements INotifyPropertyChang this.pageY = pagey; } /* tslint:disable no-any */ + // eslint-disable-next-line private getScrollParent(node: any, reverse: boolean): any { /* tslint:disable no-any */ + // eslint-disable-next-line const nodeEl: any = reverse ? node.reverse() : node; let hasScroll: string; for (let i: number = nodeEl.length - 1; i >= 0; i--) { @@ -895,9 +900,11 @@ export class Draggable extends Base implements INotifyPropertyChang nodeEle.scrollTop -= this.helperElement.clientHeight; } }else if (nodeEle && nodeEle !== document.scrollingElement) { - if ((nodeEle.clientHeight + nodeEle.getBoundingClientRect().top - this.helperElement.clientHeight + document.scrollingElement.scrollTop) < draEleTop) { + const docScrollTop: number = document.scrollingElement.scrollTop; + const helperClientHeight: number = this.helperElement.clientHeight; + if ((nodeEle.clientHeight + nodeEle.getBoundingClientRect().top - helperClientHeight + docScrollTop ) < draEleTop) { nodeEle.scrollTop += this.helperElement.clientHeight; - }else if (nodeEle.getBoundingClientRect().top > (draEleTop - this.helperElement.clientHeight - document.scrollingElement.scrollTop)) { + } else if (nodeEle.getBoundingClientRect().top > (draEleTop - helperClientHeight - docScrollTop )) { nodeEle.scrollTop -= this.helperElement.clientHeight; } } @@ -930,8 +937,8 @@ export class Draggable extends Base implements INotifyPropertyChang return temp; } private getDocumentWidthHeight(str: string): number { - const docBody: any = document.body; - const docEle: any = document.documentElement; + const docBody: HTMLElement | null = document.body; + const docEle: HTMLElement | null = document.documentElement; const returnValue: number = Math.max( docBody['scroll' + str], docEle['scroll' + str], docBody['offset' + str], docEle['offset' + str], docEle['client' + str]); @@ -955,6 +962,7 @@ export class Draggable extends Base implements INotifyPropertyChang const eleObj: DropObject = this.checkTargetElement(evt); if (eleObj.target && eleObj.instance) { eleObj.instance.dragStopCalled = true; + // eslint-disable-next-line (eleObj).instance.dragData[this.scope] = this.droppables[this.scope]; eleObj.instance.intDrop(evt, eleObj.target); } @@ -962,8 +970,11 @@ export class Draggable extends Base implements INotifyPropertyChang document.body.classList.remove('e-prevent-select'); } /** + * @param {MouseEvent | TouchEvent} evt ? + * @returns {void} * @private */ + // eslint-disable-next-line public intDestroy(evt: MouseEvent & TouchEvent): void { this.dragProcessStarted = false; this.toggleEvents(); @@ -978,6 +989,7 @@ export class Draggable extends Base implements INotifyPropertyChang } } // triggers when property changed + // eslint-disable-next-line public onPropertyChanged(newProp: DraggableModel, oldProp: DraggableModel): void { //No Code to handle } @@ -1007,6 +1019,7 @@ export class Draggable extends Base implements INotifyPropertyChang eleWidthBound = ele.scrollWidth ? ele.scrollWidth : elementArea.right - elementArea.left; eleHeightBound = ele.scrollHeight ? (this.dragArea && !isNullOrUndefined(this.helperElement) && this.helperElement.classList.contains('e-treeview')) ? ele.clientHeight : ele.scrollHeight : elementArea.bottom - elementArea.top; const keys: string[] = ['Top', 'Left', 'Bottom', 'Right']; + /* eslint-disable */ const styles: any = getComputedStyle(ele); for (let i: number = 0; i < keys.length; i++) { const key: string = keys[parseInt(i.toString(), 10)]; @@ -1016,6 +1029,7 @@ export class Draggable extends Base implements INotifyPropertyChang (this.borderWidth)[`${lowerKey}`] = isNaN(parseFloat(tborder)) ? 0 : parseFloat(tborder); (this.padding)[`${lowerKey}`] = isNaN(parseFloat(tpadding)) ? 0 : parseFloat(tpadding); } + /* eslint-enable */ if (this.dragArea && !isNullOrUndefined(this.helperElement) && this.helperElement.classList.contains('e-treeview')) { top = elementArea.top + document.scrollingElement.scrollTop; } else { @@ -1032,7 +1046,7 @@ export class Draggable extends Base implements INotifyPropertyChang const intCoord: Coordinates = this.getCoordinates(evt); let ele: HTMLElement; const prevStyle: string = this.helperElement.style.pointerEvents || ''; - let isPointer: boolean = evt.type.indexOf('pointer') !== -1 && Browser.info.name === 'safari' && parseInt(Browser.info.version) > 12 ; + const isPointer: boolean = evt.type.indexOf('pointer') !== -1 && Browser.info.name === 'safari' && parseInt(Browser.info.version, 10) > 12 ; if (compareElementParent(evt.target as Element, this.helperElement) || evt.type.indexOf('touch') !== -1 || isPointer) { this.helperElement.style.pointerEvents = 'none'; ele = document.elementFromPoint(intCoord.clientX, intCoord.clientY); @@ -1054,6 +1068,7 @@ export class Draggable extends Base implements INotifyPropertyChang } private getMousePosition(evt: MouseEvent & TouchEvent, isdragscroll?: boolean): PositionModel { /* tslint:disable no-any */ + // eslint-disable-next-line const dragEle: any = evt.srcElement !== undefined ? evt.srcElement : evt.target; const intCoord: Coordinates = this.getCoordinates(evt); let pageX: number; @@ -1070,10 +1085,11 @@ export class Draggable extends Base implements INotifyPropertyChang pageY = this.clone ? intCoord.pageY : (intCoord.pageY + window.pageYOffset) - this.relativeYPosition; } if (document.scrollingElement && (!isdragscroll && !this.clone)) { - let isVerticalScroll: boolean = document.scrollingElement.scrollHeight > 0 && document.scrollingElement.scrollHeight > document.scrollingElement.clientHeight && document.scrollingElement.scrollTop > 0; - let isHorrizontalScroll: boolean = document.scrollingElement.scrollWidth > 0 && document.scrollingElement.scrollWidth > document.scrollingElement.clientWidth && document.scrollingElement.scrollLeft > 0; - pageX = isHorrizontalScroll ? pageX - document.scrollingElement.scrollLeft : pageX; - pageY = isVerticalScroll ? pageY - document.scrollingElement.scrollTop : pageY; + const ele: Element = document.scrollingElement; + const isVerticalScroll: boolean = ele.scrollHeight > 0 && ele.scrollHeight > ele.clientHeight && ele.scrollTop > 0; + const isHorrizontalScroll: boolean = ele.scrollWidth > 0 && ele.scrollWidth > ele.clientWidth && ele.scrollLeft > 0; + pageX = isHorrizontalScroll ? pageX - ele.scrollLeft : pageX; + pageY = isVerticalScroll ? pageY - ele.scrollTop : pageY; } return { left: pageX - (this.margin.left + this.cursorAt.left), diff --git a/controls/base/src/event-handler.ts b/controls/base/src/event-handler.ts index 91727bf7a5..9a325165bd 100644 --- a/controls/base/src/event-handler.ts +++ b/controls/base/src/event-handler.ts @@ -117,7 +117,8 @@ export class EventHandler { // eslint-disable-next-line copyData = extend([], copyData, eventData) as EventOptions[]; for (let i: number = 0; i < copyData.length; i++) { - element.removeEventListener(copyData[parseInt(i.toString(), 10)].name, copyData[parseInt(i.toString(), 10)].debounce); + const parseValue: EventOptions = copyData[parseInt(i.toString(), 10)]; + element.removeEventListener(parseValue.name, parseValue.debounce); eventData.shift(); } } diff --git a/controls/base/src/intl/date-parser.ts b/controls/base/src/intl/date-parser.ts index c4e93f09c2..2a0ae6f72b 100644 --- a/controls/base/src/intl/date-parser.ts +++ b/controls/base/src/intl/date-parser.ts @@ -370,7 +370,8 @@ export class DateParser { // eslint-disable-next-line matchString = ((prop === 'month') && (!(parseOptions).isIslamic) && ((parseOptions).culture === 'en' || (parseOptions).culture === 'en-GB' || (parseOptions).culture === 'en-US')) ? matchString[0].toUpperCase() + matchString.substring(1).toLowerCase() : matchString; - matchString = ((prop !== 'month') && (prop === 'designator') && parseOptions.culture && (parseOptions).culture.indexOf('en-') !== -1 && cultureOptions.indexOf(parseOptions.culture) === -1) + // eslint-disable-next-line + matchString = ((prop !== 'month') && (prop === 'designator') && parseOptions.culture && (parseOptions).culture.indexOf('en-') !== -1 && cultureOptions.indexOf(parseOptions.culture) === -1) ? matchString.toLowerCase() : matchString; // eslint-disable-next-line (retOptions)[prop] = (parseOptions)[prop][matchString]; diff --git a/controls/base/src/intl/intl-base.ts b/controls/base/src/intl/intl-base.ts index 87373323d0..6f32b8521c 100644 --- a/controls/base/src/intl/intl-base.ts +++ b/controls/base/src/intl/intl-base.ts @@ -814,7 +814,6 @@ export namespace IntlBase { const ret: NumericSkeleton = {}; const pattern: string = matches[1].toUpperCase(); ret.isAccount = (pattern === 'A'); - // eslint-disable-next-line (ret).type = patternMatcher[pattern]; if (skeleton.length > 1) { ret.fractionDigits = parseInt(matches[2], 10); @@ -898,7 +897,6 @@ export namespace IntlBase { const formatSplit: string[] = format.split(';'); const data: string[] = ['pData', 'nData', 'zeroData']; for (let i: number = 0; i < formatSplit.length; i++) { - // eslint-disable-next-line (options)[data[i]] = customNumberFormat(formatSplit[i], dOptions, obj); } if (isNullOrUndefined(options.nData)) { @@ -958,7 +956,6 @@ export namespace IntlBase { const symbolPattern: string = getSymbolPattern( cOptions.type, dOptions.numberMapper.numberSystem, numObject, false); if (cOptions.useGrouping) { - // eslint-disable-next-line cOptions.groupSeparator = spaceGrouping? ' ' : (dOptions).numberMapper.numberSymbols[mapper[2]]; cOptions.groupData = NumberFormat.getGroupingDetails(symbolPattern.split(';')[0]); } @@ -982,9 +979,7 @@ export namespace IntlBase { const part: string = parts[parseInt(i.toString(), 10)]; const loc: number = part.indexOf(actual); if ((loc !== -1) && ((loc < part.indexOf('\'')) || (loc > part.lastIndexOf('\'')))) { - // eslint-disable-next-line (options)[(typeMapper)[i]] = part.substr(0, loc) + symbol + part.substr(loc + 1); - // eslint-disable-next-line (options)[(typeMapper)[actual]] = true; options.type = options.isCurrency ? 'currency' : 'percent'; break; @@ -1022,7 +1017,6 @@ export namespace IntlBase { let actualPattern: string = options.format || getResultantPattern(options.skeleton, dependable.dateObject, options.type); if (isExcelFormat) { actualPattern = actualPattern.replace(patternRegex, (pattern: string): string => { - // eslint-disable-next-line return (patternMatch)[pattern]; }); if (actualPattern.indexOf('z') !== -1) { @@ -1054,10 +1048,8 @@ export namespace IntlBase { * @param {any} option ? * @returns {any} ? */ - // eslint-disable-next-line function processSymbol(actual: string, option: any): any { if (actual.indexOf(',') !== -1) { - // eslint-disable-next-line let split: any = actual.split(','); actual = (split[0] + getValue('numberMapper.numberSymbols.group', option) + split[1].replace('.', getValue('numberMapper.numberSymbols.decimal', option))); diff --git a/controls/base/src/intl/number-formatter.ts b/controls/base/src/intl/number-formatter.ts index 948c7ba533..c176fedfb4 100644 --- a/controls/base/src/intl/number-formatter.ts +++ b/controls/base/src/intl/number-formatter.ts @@ -25,6 +25,7 @@ export interface CommonOptions { minusSymbol?: string; isCustomFormat?: boolean; } +/* eslint-disable */ /** * Interface for currency processing */ @@ -33,6 +34,7 @@ interface CurrencyOptions { symbol?: string; currencySpace?: boolean; } +/* eslint-enable */ /** * Interface for grouping process */ @@ -46,13 +48,9 @@ const errorText: Object = { 'mf': 'minimumFractionDigits', 'lf': 'maximumFractionDigits' }; -const integerError: string = 'minimumIntegerDigits'; const percentSign: string = 'percentSign'; const minusSign: string = 'minusSign'; -const spaceRegex: RegExp = /\s/; const mapper: string[] = ['infinity', 'nan', 'group', 'decimal', 'exponential']; -const infinity: string = 'infinity'; -const nan: string = 'nan'; /** * Module for number formatting. * @@ -224,9 +222,11 @@ export class NumberFormat { * @param {number} value ? * @param {base.GenericFormatOptions} fOptions ? * @param {CommonOptions} dOptions ? + * @param {NumberFormatOptions} [option] ? * @returns {string} ? */ - private static intNumberFormatter(value: number, fOptions: base.GenericFormatOptions, dOptions: CommonOptions, option ?: NumberFormatOptions): string { + private static intNumberFormatter(value: number, fOptions: base.GenericFormatOptions, dOptions: CommonOptions, + option ?: NumberFormatOptions): string { let curData: base.NegativeData; if (isUndefined(fOptions.nData.type)) { return undefined; @@ -270,7 +270,7 @@ export class NumberFormat { fValue = fValue.replace('e', dOptions.numberMapper.numberSymbols[mapper[4]]); } fValue = fValue.replace('.', (dOptions).numberMapper.numberSymbols[mapper[3]]); - fValue = curData.format === "#,###,,;(#,###,,)" ? this.customPivotFormat(parseInt(fValue)) : fValue; + fValue = curData.format === '#,###,,;(#,###,,)' ? this.customPivotFormat(parseInt(fValue, 10)) : fValue; if (curData.useGrouping) { /* eslint-disable @typescript-eslint/no-explicit-any */ fValue = this.groupNumbers( @@ -281,7 +281,7 @@ export class NumberFormat { if (curData.nlead === 'N/A') { return curData.nlead; } else { - if (fValue === '0' && option && option.format=== '0') { + if (fValue === '0' && option && option.format === '0') { return fValue + curData.nend; } return curData.nlead + fValue + curData.nend; @@ -342,6 +342,7 @@ export class NumberFormat { * @param {number} value ? * @param {number} min ? * @param {number} max ? + * @param {NumberFormatOptions} [option] ? * @returns {string} ? */ private static processFraction(value: number, min: number, max: number, option ?: NumberFormatOptions): string { @@ -362,10 +363,10 @@ export class NumberFormat { } else if (!isNullOrUndefined(max) && (length > max || max === 0)) { return value.toFixed(max); } - let str=value+''; - if(str[0]==='0' && option && option.format==='###.00') + let str: string = value + ''; + if (str[0] === '0' && option && option.format === '###.00') { - str=str.slice(1); + str = str.slice(1); } return str; } @@ -393,16 +394,18 @@ export class NumberFormat { * Returns custom format for pivot table * * @param {number} value ? + * @returns {string} ? */ private static customPivotFormat(value: number): string { if (value >= 500000) { value /= 1000000; - const [integer, decimal] = value.toString().split("."); + // eslint-disable-next-line + const [integer, decimal] = value.toString().split('.'); return decimal && +decimal.substring(0, 1) >= 5 - ? Math.ceil(value).toString() - : Math.floor(value).toString(); - } - return ""; + ? Math.ceil(value).toString() + : Math.floor(value).toString(); + } + return ''; } } diff --git a/controls/base/src/intl/number-parser.ts b/controls/base/src/intl/number-parser.ts index 81d407dff2..56438ea200 100644 --- a/controls/base/src/intl/number-parser.ts +++ b/controls/base/src/intl/number-parser.ts @@ -3,6 +3,7 @@ import { extend, isNullOrUndefined, isBlazor, getValue } from '../util'; import { ParserBase as parser, NumericOptions } from './parser-base'; import { IntlBase as base } from './intl-base'; const regExp: RegExpConstructor = RegExp; +// eslint-disable-next-line const parseRegex: RegExp = new regExp('^([^0-9]*)' + '(([0-9,]*[0-9]+)(\.[0-9]+)?)' + '([Ee][+-]?[0-9]+)?([^0-9]*)$'); const groupRegex: RegExp = /,/g; @@ -24,6 +25,7 @@ export interface NumericParts { /** * interface for numeric parse options */ +// eslint-disable-next-line interface NumberParseOptions { parseRegex: string; numbericMatcher: Object; diff --git a/controls/base/src/module-loader.ts b/controls/base/src/module-loader.ts index 8e56988e43..d8e8b5510e 100644 --- a/controls/base/src/module-loader.ts +++ b/controls/base/src/module-loader.ts @@ -16,6 +16,10 @@ export interface ModuleDeclaration { * Specifies the member for module declaration. */ member: string; + /** + * Specifies the name for module declaration. + */ + name?: string; /** * Specifies whether it is a property or not. */ @@ -84,6 +88,17 @@ export class ModuleLoader { this.loadedModules = []; } + /** + * Returns the array of modules that are not loaded in the component library. + * + * @param {ModuleDeclaration[]} requiredModules - Array of modules to be required + * @returns {ModuleDeclaration[]} ? + * @private + */ + public getNonInjectedModules(requiredModules: ModuleDeclaration[]): ModuleDeclaration[] { + return requiredModules.filter((module: ModuleDeclaration) => !this.isModuleLoaded(module.member)); + } + /** * Removes all unused modules * diff --git a/controls/base/src/notify-property-change.ts b/controls/base/src/notify-property-change.ts index 9ba9ffe816..43f8fdd383 100644 --- a/controls/base/src/notify-property-change.ts +++ b/controls/base/src/notify-property-change.ts @@ -350,8 +350,9 @@ function complexArrayDefinedCallback( switch (dFunc) { case 'push': for (let i: number = 0; i < newValue.length; i++) { - Array.prototype[`${dFunc}`].apply(prop, [newValue[parseInt(i.toString(), 10)]]); - const model: Object = getArrayModel(keyString + (prop.length - 1), newValue[parseInt(i.toString(), 10)], !this.controlParent, dFunc); + const newValueParse: Object = newValue[parseInt(i.toString(), 10)]; + Array.prototype[`${dFunc}`].apply(prop, [newValueParse]); + const model: Object = getArrayModel(keyString + (prop.length - 1), newValueParse, !this.controlParent, dFunc); this.serverDataBind(model, newValue[parseInt(i.toString(), 10)], false, dFunc); } break; diff --git a/controls/base/src/observer.ts b/controls/base/src/observer.ts index b877ff7028..fcf00d750f 100644 --- a/controls/base/src/observer.ts +++ b/controls/base/src/observer.ts @@ -192,10 +192,12 @@ export class Observer { * @returns {void} ? */ public offIntlEvents(): void { - let eventsArr: any = this.boundedEvents['notifyExternalChange']; + // eslint-disable-next-line + const eventsArr: any = this.boundedEvents['notifyExternalChange']; if (eventsArr) { for (let i: number = 0; i < eventsArr.length; i++) { - let curContext: any = eventsArr[`${i}`].context; + // eslint-disable-next-line + const curContext: any = eventsArr[`${i}`].context; if (curContext && curContext.detectFunction && curContext.randomId && !curContext.isRendered) { this.off('notifyExternalChange', curContext.detectFunction, curContext.randomId); i--; diff --git a/controls/base/src/sanitize-helper.ts b/controls/base/src/sanitize-helper.ts index 8f9c658e1a..bca310a553 100644 --- a/controls/base/src/sanitize-helper.ts +++ b/controls/base/src/sanitize-helper.ts @@ -2,6 +2,7 @@ * SanitizeHtmlHelper for sanitize the value. */ import { detach } from './dom'; +import { isNullOrUndefined } from './util'; interface BeforeSanitizeHtml { /** Illustrates whether the current action needs to be prevented or not. */ @@ -152,6 +153,7 @@ export class SanitizeHtmlHelper { }; } public static sanitize(value: string): string { + if (isNullOrUndefined(value)) { return value; } const item: BeforeSanitizeHtml = this.beforeSanitize(); const output: string = this.serializeValue(item, value); return output; @@ -167,6 +169,7 @@ export class SanitizeHtmlHelper { this.removeXssAttrs(); const tempEleValue: string = this.wrapElement.innerHTML; this.removeElement(); + this.wrapElement = null; return tempEleValue.replace(/&/g, '&'); } diff --git a/controls/base/src/template-engine.ts b/controls/base/src/template-engine.ts index 27d1e2aba8..725f615205 100644 --- a/controls/base/src/template-engine.ts +++ b/controls/base/src/template-engine.ts @@ -167,6 +167,7 @@ export function getTemplateEngine(): (template: string, helper?: Object) => (dat * @returns {Function} ? * @private */ +// eslint-disable-next-line export function initializeCSPTemplate (template : Function, helper?: any): Function { let boundFunc : Function; template.prototype.CSPTemplate = true; diff --git a/controls/base/src/template.ts b/controls/base/src/template.ts index e4a9f68854..55a00bd76d 100644 --- a/controls/base/src/template.ts +++ b/controls/base/src/template.ts @@ -67,7 +67,7 @@ export function compile(template: string | Function, helper?: Object, ignorePref } else { const argName: string = 'data'; const evalExpResult: string = evalExp(template, argName, helper, ignorePrefix); - // eslint-disable-next-line + /* eslint-disable */ const condtion = `var valueRegEx = (/value=\\'([A-Za-z0-9 _]*)((.)([\\w)(!-;?-■\\s]+)['])/g); var hrefRegex = (/(?:href)([\\s='"./]+)([\\w-./?=&\\\\#"]+)((.)([\\w)(!-;/?-■\\s]+)['])/g); if(str.match(valueRegEx)){ @@ -98,6 +98,7 @@ export function compile(template: string | Function, helper?: Object, ignorePref } `; const fnCode: string = 'var str=\"' + evalExpResult + '\";' + condtion + ' return str;'; + /* eslint-enable */ const fn: Function = new Function(argName, fnCode); return fn.bind(helper); } @@ -254,7 +255,7 @@ function evalExp(str: string, nameSpace: string, helper?: Object, ignorePrefix?: * @returns {string} ? */ function addNameSpace(str: string, addNS: boolean, nameSpace: string, ignoreList: string[], ignorePrefix: boolean): string { - return ((addNS && !(NOT_NUMBER.test(str)) && ignoreList.indexOf(str.split('.')[0]) === -1 && !ignorePrefix && str !== "true" && str !== "false") ? nameSpace + '.' + str : str); + return ((addNS && !(NOT_NUMBER.test(str)) && ignoreList.indexOf(str.split('.')[0]) === -1 && !ignorePrefix && str !== 'true' && str !== 'false') ? nameSpace + '.' + str : str); } /** diff --git a/controls/base/src/validate-lic.ts b/controls/base/src/validate-lic.ts index 6b9dec1ee5..5533ad88ab 100644 --- a/controls/base/src/validate-lic.ts +++ b/controls/base/src/validate-lic.ts @@ -1,11 +1,11 @@ import { createElement } from './dom'; import { getValue, containerObject, setValue, isNullOrUndefined } from './util'; -export let componentList: string[] = ['grid', 'pivotview', 'treegrid', 'spreadsheet', 'rangeNavigator', 'DocumentEditor', 'listbox', 'inplaceeditor', 'PdfViewer', 'richtexteditor', 'DashboardLayout', 'chart', 'stockChart', 'circulargauge', 'diagram', 'heatmap', 'lineargauge', 'maps', 'slider', 'smithchart', 'barcode', 'sparkline', 'treemap', 'bulletChart', 'kanban', 'daterangepicker', 'schedule', 'gantt', 'signature', 'query-builder', 'drop-down-tree', 'carousel', 'filemanager', 'uploader', 'accordion', 'tab', 'treeview']; +export const componentList: string[] = ['grid', 'pivotview', 'treegrid', 'spreadsheet', 'rangeNavigator', 'DocumentEditor', 'listbox', 'inplaceeditor', 'PdfViewer', 'richtexteditor', 'DashboardLayout', 'chart', 'stockChart', 'circulargauge', 'diagram', 'heatmap', 'lineargauge', 'maps', 'slider', 'smithchart', 'barcode', 'sparkline', 'treemap', 'bulletChart', 'kanban', 'daterangepicker', 'schedule', 'gantt', 'signature', 'query-builder', 'drop-down-tree', 'carousel', 'filemanager', 'uploader', 'accordion', 'tab', 'treeview']; const bypassKey: number[] = [115, 121, 110, 99, 102, 117, 115, 105, 111, 110, 46, 105, 115, 76, 105, 99, 86, 97, 108, 105, 100, 97, 116, 101, 100]; -let accountURL: string; +let accountURL: string; /** * License validation module * @@ -14,7 +14,7 @@ let accountURL: string; class LicenseValidator { private isValidated: boolean = false; public isLicensed: boolean = true; - public version: string = '24'; + public version: string = '25'; public platform: RegExp = /JavaScript|ASPNET|ASPNETCORE|ASPNETMVC|FileFormats|essentialstudio/i; private errors: IErrorType = { noLicense: 'This application was built using a trial version of Syncfusion Essential Studio.' + @@ -72,11 +72,13 @@ class LicenseValidator { /** * To validate the provided license key. - */ + * + * @returns {boolean} ? + */ public validate(): boolean { - let contentKey: number[] = [115, 121, 110, 99, 102, 117, 115, 105, 111, 110, 46, + const contentKey: number[] = [115, 121, 110, 99, 102, 117, 115, 105, 111, 110, 46, 108, 105, 99, 101, 110, 115, 101, 67, 111, 110, 116, 101, 110, 116]; - let URLKey: number[] = [115, 121, 110, 99, 102, 117, 115, 105, 111, 110, 46, + const URLKey: number[] = [115, 121, 110, 99, 102, 117, 115, 105, 111, 110, 46, 99, 108, 97, 105, 109, 65, 99, 99, 111, 117, 110, 116, 85, 82, 76]; if (!this.isValidated && (containerObject && !getValue(convertToChar(bypassKey), containerObject) && !getValue('Blazor', containerObject))) { let validateMsg: string; @@ -105,7 +107,7 @@ class LicenseValidator { validateMsg = this.errors.invalidKey; } } else { - let licenseContent: string = getValue(convertToChar(contentKey), containerObject); + const licenseContent: string = getValue(convertToChar(contentKey), containerObject); validateURL = getValue(convertToChar(URLKey), containerObject); if (licenseContent && licenseContent !== '') { validateMsg = licenseContent; @@ -114,13 +116,13 @@ class LicenseValidator { } } if (validateMsg && typeof document !== 'undefined' && !isNullOrUndefined(document)) { - accountURL = (validateURL && validateURL !== '')? validateURL : "https://www.syncfusion.com/account/claim-license-key?pl=SmF2YVNjcmlwdA==&vs=MjQ=&utm_source=es_license_validation_banner&utm_medium=listing&utm_campaign=license-information"; + accountURL = (validateURL && validateURL !== '') ? validateURL : 'https://www.syncfusion.com/account/claim-license-key?pl=SmF2YVNjcmlwdA==&vs=MjU=&utm_source=es_license_validation_banner&utm_medium=listing&utm_campaign=license-information'; const errorDiv: HTMLElement = createElement('div', { innerHTML: ``+validateMsg + ' '+'Claim your free account' + height: 24px;"/>` + validateMsg + ' ' + 'Claim your free account' }); errorDiv.setAttribute('style', `position: fixed; top: 10px; @@ -191,7 +193,7 @@ class LicenseValidator { const charKey: string = decodeStr[decodeStr.length - 1]; const decryptedKey: number[] = []; for (let i: number = 0; i < decodeStr.length; i++) { - decryptedKey[parseInt(i.toString(), 10)] = decodeStr[parseInt(i.toString(), 10)].charCodeAt(0) - charKey.charCodeAt(0); + decryptedKey[`${i}`] = decodeStr[`${i}`].charCodeAt(0) - charKey.charCodeAt(0); } for (let i: number = 0; i < decryptedKey.length; i++) { buffr += String.fromCharCode(decryptedKey[parseInt(i.toString(), 10)]); @@ -226,7 +228,7 @@ let licenseValidator: LicenseValidator = new LicenseValidator(); * Converts the given number to characters. * * @param {number} cArr - Specifies the license key as number. - * @returns {string} + * @returns {string} ? */ function convertToChar(cArr: number[]): string { let ret: string = ''; @@ -259,7 +261,7 @@ export const getVersion: Function = (): string => { // Method for create overlay over the sample export const createLicenseOverlay: Function = (): void => { - let bannerTemplate: string = ` + const bannerTemplate: string = `
{ let stroke: string = seriesElements.getAttribute('stroke-width'); expect(stroke == '0').toBe(true); let labelElement: HTMLElement = document.getElementById('container0_AxisLabel_3'); - expect(labelElement.textContent == '2003').toBe(true); done(); + expect(labelElement.textContent == '2002').toBe(true); done(); }; chartObj.loaded = loaded; chartObj.series[0].dataSource = [{ x: new Date(2000, 6, 11), y: 10 }, { x: new Date(2002, 3, 7), y: 30 }, diff --git a/controls/charts/spec/chart/series/range-column-series.spec.ts b/controls/charts/spec/chart/series/range-column-series.spec.ts index ee6f217205..fcc8d6019d 100644 --- a/controls/charts/spec/chart/series/range-column-series.spec.ts +++ b/controls/charts/spec/chart/series/range-column-series.spec.ts @@ -181,7 +181,7 @@ describe('Chart', () => { let stroke: string = seriesElements.getAttribute('stroke-width'); expect(stroke == '0').toBe(true); let labelElement: HTMLElement = document.getElementById('container0_AxisLabel_3'); - expect(labelElement.textContent == 'Jun 26').toBe(true); + expect(labelElement.textContent == 'Jun 25').toBe(true); done(); }; chartObj.loaded = loaded; diff --git a/controls/charts/src/accumulation-chart/renderer/legend.ts b/controls/charts/src/accumulation-chart/renderer/legend.ts index 7e8105aab9..266281bcc9 100644 --- a/controls/charts/src/accumulation-chart/renderer/legend.ts +++ b/controls/charts/src/accumulation-chart/renderer/legend.ts @@ -262,7 +262,7 @@ export class AccumulationLegend extends BaseLegend { option.textCollection = textWrap( option.text, (legend.maximumLabelWidth ? Math.min(legend.maximumLabelWidth, (bounds.width - textPadding)) : - (bounds.width - textPadding)), legend.textStyle, this.chart.enableRtl, null, null, this.chart.themeStyle.legendLabelFont + (bounds.width - textPadding)), legend.textStyle, this.chart.enableRtl, legend.textWrap === 'AnyWhere', null, this.chart.themeStyle.legendLabelFont ); } else { option.textCollection.push(option.text); diff --git a/controls/charts/src/chart/axis/date-time-axis.ts b/controls/charts/src/chart/axis/date-time-axis.ts index 2328c95566..fa3ce8f111 100644 --- a/controls/charts/src/chart/axis/date-time-axis.ts +++ b/controls/charts/src/chart/axis/date-time-axis.ts @@ -393,16 +393,16 @@ export class DateTime extends NiceInterval { switch (axis.actualIntervalType) { case 'Years': const year: number = Math.floor(Math.floor(sResult.getFullYear() / intervalSize) * intervalSize); - sResult = new Date(year, sResult.getMonth(), sResult.getDate(), 0, 0, 0); + sResult = new Date(year, sResult.getMonth(), sResult.getDate(), sResult.getHours(), sResult.getMinutes(), sResult.getSeconds()); return sResult; case 'Months': const month: number = Math.floor(Math.floor((sResult.getMonth()) / intervalSize) * intervalSize); - sResult = new Date(sResult.getFullYear(), month, sResult.getDate(), 0, 0, 0); + sResult = new Date(sResult.getFullYear(), month, sResult.getDate(), sResult.getHours(), sResult.getMinutes(), sResult.getSeconds()); return sResult; case 'Days': const day: number = Math.floor(Math.floor((sResult.getDate()) / intervalSize) * intervalSize); - sResult = new Date(sResult.getFullYear(), sResult.getMonth(), day, 0, 0, 0); + sResult = new Date(sResult.getFullYear(), sResult.getMonth(), day, sResult.getHours(), sResult.getMinutes(), sResult.getSeconds()); return sResult; case 'Hours': diff --git a/controls/charts/src/chart/model/chart-interface.d.ts b/controls/charts/src/chart/model/chart-interface.d.ts index 6b47859512..765767d4a9 100644 --- a/controls/charts/src/chart/model/chart-interface.d.ts +++ b/controls/charts/src/chart/model/chart-interface.d.ts @@ -81,7 +81,7 @@ export interface ISharedTooltipRenderEventArgs extends IChartEventArgs { /** point informations. */ data?: IPointInformation[]; /** Defines the tooltip template. */ - template?: string; + template?: string[]; } /** * Defines the scroll events diff --git a/controls/charts/src/chart/model/chart-interface.ts b/controls/charts/src/chart/model/chart-interface.ts index ce1407aeac..3b9b804cb1 100644 --- a/controls/charts/src/chart/model/chart-interface.ts +++ b/controls/charts/src/chart/model/chart-interface.ts @@ -84,7 +84,7 @@ export interface ISharedTooltipRenderEventArgs extends IChartEventArgs { /** point informations. */ data ?: IPointInformation[]; /** Defines the tooltip template. */ - template ?: string; + template ?: string[]; } /** diff --git a/controls/charts/src/chart/series/data-label.ts b/controls/charts/src/chart/series/data-label.ts index 5b5358ed5e..f0402d0d2f 100644 --- a/controls/charts/src/chart/series/data-label.ts +++ b/controls/charts/src/chart/series/data-label.ts @@ -182,7 +182,7 @@ export class DataLabel { (point.symbolLocations.length && point.symbolLocations[0]) || (series.type === 'BoxAndWhisker' && point.regions.length) ) { - labelText = getLabelText(point, series, chart); + labelText = point.text !== null ? getLabelText(point, series, chart) : []; labelLength = labelText.length; for (let i: number = 0; i < labelLength; i++) { argsData = { diff --git a/controls/charts/src/chart/user-interaction/tooltip.ts b/controls/charts/src/chart/user-interaction/tooltip.ts index 147efc677f..d1a78bf0c7 100644 --- a/controls/charts/src/chart/user-interaction/tooltip.ts +++ b/controls/charts/src/chart/user-interaction/tooltip.ts @@ -377,7 +377,7 @@ export class Tooltip extends BaseTooltip { } this.removeText(); const argument: ISharedTooltipRenderEventArgs = { - text: [], cancel: false, name: sharedTooltipRender, data: [], point: [], series: [], headerText: '', textStyle: this.textStyle, template: '' + text: [], cancel: false, name: sharedTooltipRender, data: [], point: [], series: [], headerText: '', textStyle: this.textStyle, template: [] }; let i: number = 0; for (const series of chart.visibleSeries) { @@ -402,6 +402,9 @@ export class Tooltip extends BaseTooltip { argument.point[i as number] = data.point; argument.headerText = this.findHeader(data); (this.currentPoints).push(data); + if (this.template != null) { + argument.template.push(this.template.toString()) + }; argument.text.push(this.getTooltipText(data)); pointXValue = (!chart.requireInvertedAxis) ? chart.mouseX - data.series.clipRect.x : chart.mouseY - data.series.clipRect.y; pointYValue = chart.mouseY - data.series.clipRect.y; @@ -453,7 +456,7 @@ export class Tooltip extends BaseTooltip { private triggerSharedTooltip( argument: ISharedTooltipRenderEventArgs, point: PointData, extraPoints: PointData[], chart: Chart, isFirst: boolean, dataCollection: PointData[] ): void { - let tooltipTemplate: string; + let tooltipTemplate: string[] = argument.template; const argsData: ISharedTooltipRenderEventArgs = { cancel: false, name: sharedTooltipRender, text: argument.text, headerText: argument.headerText, textStyle: argument.textStyle, template: tooltipTemplate, @@ -482,6 +485,17 @@ export class Tooltip extends BaseTooltip { this.formattedText = this.formattedText.concat(argsData.text); this.text = argsData.text; this.headerText = argsData.headerText; + if (typeof(argsData.template) != 'object') { + argsData.template = (argsData.template as string).split(','); + if (argsData.template.length > currentPoints.length ) { + argsData.template = argsData.template.splice(argsData.template.length - 1); + } + } + else { + if (argsData.template.length > currentPoints.length ) { + argsData.template.splice(argsData.template.length - 1); + } + } const tooltip: TooltipSettingsModel = this.chart.tooltip this.findMouseValue(point, this.chart); let location: ChartLocation = this.findSharedLocation(); @@ -496,7 +510,7 @@ export class Tooltip extends BaseTooltip { new Rect(borderWidth, (chart.stockChart ? (toolbarHeight + titleHeight + borderWidth) : borderWidth), this.chart.availableSize.width - padding - borderWidth * 2, this.chart.availableSize.height - padding - borderWidth * 2), this.chart.crosshair.enable, extraPoints, this.template ? this.getTemplateText(dataCollection) : null, - this.template ? argsData.template : '' + this.template ? argsData.template.join('') : '' ); point = null; } else { diff --git a/controls/charts/src/common/annotation/annotation.ts b/controls/charts/src/common/annotation/annotation.ts index f417e3e6e9..79d61e590d 100644 --- a/controls/charts/src/common/annotation/annotation.ts +++ b/controls/charts/src/common/annotation/annotation.ts @@ -93,11 +93,13 @@ export class AnnotationBase { const stockChart: StockChart = (this.control as Chart).stockChart; let xAxis: Axis; let yAxis: Axis; let xValue: number; - for (const axis of chart.axisCollections) { if (xAxisName === axis.name || (xAxisName == null && axis.name === 'primaryXAxis')) { xAxis = axis; - if (xAxis.valueType.indexOf('Category') > -1) { + if (xAxis.isIndexed) { + xValue = Number(annotation.x); + } + else if (xAxis.valueType.indexOf('Category') > -1) { const xAnnotation: string = xAxis.valueType === 'DateTimeCategory' ? ((annotation.x as Date).getTime()).toString() : annotation.x; if (xAxis.labels.indexOf(xAnnotation) < 0) { diff --git a/controls/circulargauge/CHANGELOG.md b/controls/circulargauge/CHANGELOG.md index 39242c38dc..12fe0aef71 100644 --- a/controls/circulargauge/CHANGELOG.md +++ b/controls/circulargauge/CHANGELOG.md @@ -4,7 +4,7 @@ ## [Unreleased] -## 25.1.35 (2024-03-15) +## 25.1.37 (2024-03-26) ### Circular Gauge diff --git a/controls/compression/ReadMe.md b/controls/compression/ReadMe.md new file mode 100644 index 0000000000..929f319414 --- /dev/null +++ b/controls/compression/ReadMe.md @@ -0,0 +1,3 @@ +[![coverage](http://ej2.syncfusion.com/badges/ej2-compression/coverage.svg)](http://ej2.syncfusion.com/badges/ej2-compression) + +A common package of Essential JS 2 compression libraries, methods and class definitions. \ No newline at end of file diff --git a/controls/compression/gulpfile.js b/controls/compression/gulpfile.js new file mode 100644 index 0000000000..0bca415042 --- /dev/null +++ b/controls/compression/gulpfile.js @@ -0,0 +1,81 @@ +'use strict'; + +var gulp = require('gulp'); + +/** + * Build ts and scss files + */ +gulp.task('build', ['scripts', 'styles']); + +/** + * Compile ts files + */ +gulp.task('scripts', function(done) { + var ts = require('gulp-typescript'); + var tsProject = ts.createProject('tsconfig.json', { typescript: require('typescript') }); + + var tsResult = gulp.src(['./**/*.ts','./**/*.tsx', '!./node_modules/**/*.ts','!./node_modules/**/*.tsx'], { base: '.' }) + .pipe(tsProject()); + tsResult.js.pipe(gulp.dest('./')) + .on('end', function() { + done(); + }); +}); + +/** + * Compile styles + */ +gulp.task('styles', function() { + var sass = require('gulp-sass'); + return gulp.src(['./**/*.scss', '!./node_modules/**/*.scss'], { base: './' }) + .pipe(sass({ + outputStyle: 'expanded', + includePaths: './node_modules/@syncfusion/' + })) + .pipe(gulp.dest('.')); +}); + +/* jshint strict: false */ +/* jshint undef: false */ + +var service, proxyPort; + +/** + * Run test scripts + */ +gulp.task('test', function(done) { + var path = require('path'); + var packageJson = require('./package.json'); + if (packageJson.dependencies['@syncfusion/ej2-data'] || packageJson.name === '@syncfusion/ej2-data') { + console.log('Service Started'); + var spawn = require('child_process').spawn; + service = spawn('node', [path.join(__dirname, '/spec/services/V4service.js')]); + + service.stdout.on('data', (data) => { + proxyPort = data.toString().trim(); + console.log('Proxy port: ' + proxyPort); + startKarma(done); + }); + } else { + startKarma(done); + } +}); + +function startKarma(done) { + var karma = require('karma'); + return new karma.Server({ + configFile: __dirname + '/karma.conf.js', + singleRun: true, + browsers: ['ChromeHeadless'] + }, function(e) { + if (service) { + service.kill(); + } + if (e === 1) { + console.log('Karma has exited with ' + e); + process.exit(e); + } else { + done(); + } + }).start(); +} \ No newline at end of file diff --git a/controls/compression/karma.conf.js b/controls/compression/karma.conf.js new file mode 100644 index 0000000000..6199e7eb5d --- /dev/null +++ b/controls/compression/karma.conf.js @@ -0,0 +1,93 @@ +// Karma configuration +// Generated on Tue Apr 26 2016 09:56:05 GMT+0530 (India Standard Time) + +module.exports = function (config) { + config.set({ + + // base path that will be used to resolve all patterns (eg. files, exclude) + basePath: '', + + + // frameworks to use + // available frameworks: https://npmjs.org/browse/keyword/karma-adapter + frameworks: ['jasmine-ajax', 'jasmine', 'requirejs'], + + + // list of files / patterns to load in the browser + files: [ + "test-main.js", + { pattern: "src/**/*.js", included: false }, + { pattern: "spec/**/*.spec.js", included: false }, + { pattern: "spec/**/*.txt", included: false }, + { pattern: 'node_modules/es6-promise/dist/es6-promise.js', included: false }, + { pattern: "node_modules/@syncfusion/ej2-file-utils/**/*.js", included: false } + // Add dependent package's script files here + ], + + + // list of files to exclude + exclude: [ + ], + + + // preprocess matching files before serving them to the browser + // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor + preprocessors: {}, + + + // test results reporter to use + // possible values: 'dots', 'progress' + // available reporters: https://npmjs.org/browse/keyword/karma-reporter + reporters: ['dots', 'html'], + + // the default html configuration + htmlReporter: { + outputFile: "test-report/units.html", + pageTitle: "Unit Tests", + subPageTitle: "Asampleprojectdescription" + }, + + // web server port + port: 9876, + + + // enable / disable colors in the output (reporters and logs) + colors: true, + + + // level of logging + // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG + logLevel: config.LOG_INFO, + + + // enable / disable watching file and executing tests whenever any file changes + autoWatch: true, + + + // start these browsers + // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher + browsers: ['ChromeHeadless', 'Chrome', 'Firefox'], + + + // Continuous Integration mode + // if true, Karma captures browsers, runs the tests and exits + singleRun: false, + + // Concurrency level + // how many browser should be started simultaneous + concurrency: Infinity, + + + coverageReporter: { + type: "html", + check: { + each: { + statements: 90, + branches: 90, + functions: 100, + lines: 90 + } + } + } + }) +} diff --git a/controls/compression/license b/controls/compression/license new file mode 100644 index 0000000000..111c12a632 --- /dev/null +++ b/controls/compression/license @@ -0,0 +1,10 @@ +Essential JS 2 library is available under the Syncfusion Essential Studio program, and can be licensed either under the Syncfusion Community License Program or the Syncfusion commercial license. + +To be qualified for the Syncfusion Community License Program you must have a gross revenue of less than one (1) million U.S. dollars ($1,000,000.00 USD) per year and have less than five (5) developers in your organization, and agree to be bound by Syncfusion’s terms and conditions. + +Customers who do not qualify for the community license can contact sales@syncfusion.com for commercial licensing options. + +Under no circumstances can you use this product without (1) either a Community License or a commercial license and (2) without agreeing and abiding by Syncfusion’s license containing all terms and conditions. + +The Syncfusion license that contains the terms and conditions can be found at +https://www.syncfusion.com/content/downloads/syncfusion_license.pdf diff --git a/controls/compression/package.json b/controls/compression/package.json new file mode 100644 index 0000000000..ac9c721aeb --- /dev/null +++ b/controls/compression/package.json @@ -0,0 +1,44 @@ +{ + "name": "@syncfusion/ej2-compression", + "version": "0.17.0", + "description": "Essential JS 2 Compression library", + "author": "Syncfusion Inc.", + "license": "SEE LICENSE IN license", + "dependencies": { + "@syncfusion/ej2-file-utils": "*" + }, + "devDependencies": { + "@syncfusion/ej2-staging": "^1.0.1", + "@types/chai": "^3.4.28", + "@types/es6-promise": "0.0.28", + "@types/jasmine": "2.8.9", + "@types/jasmine-ajax": "^3.1.27", + "@types/requirejs": "^2.1.26", + "es6-promise": "^3.2.1", + "gulp": "^3.9.1", + "gulp-sass": "^3.1.0", + "gulp-typescript": "^3.1.6", + "requirejs": "^2.3.3", + "typescript": "2.3.4", + "canteen": "^1.0.5", + "jasmine-ajax": "^3.3.1", + "jasmine-core": "^2.6.1", + "karma": "^1.7.0", + "karma-chrome-launcher": "^2.2.0", + "karma-generic-preprocessor": "^1.1.0", + "karma-htmlfile-reporter": "^0.3.5", + "karma-jasmine": "^1.1.0", + "karma-jasmine-ajax": "^0.1.13", + "karma-requirejs": "^1.1.0", + "nedb": "^1.8.0", + "simple-odata-server": "^0.3.1" + }, + "main": "./dist/ej2-compression.umd.min.js", + "module": "./index.js", + "es2015": "./dist/es6/ej2-compression.es5.js", + "scripts": { + "build": "gulp build", + "test": "gulp test" + }, + "typings": "index.d.ts" +} \ No newline at end of file diff --git a/controls/compression/spec/compression-writer.spec.ts b/controls/compression/spec/compression-writer.spec.ts new file mode 100644 index 0000000000..524c181177 --- /dev/null +++ b/controls/compression/spec/compression-writer.spec.ts @@ -0,0 +1,116 @@ +import { CompressedStreamWriter, ChecksumCalculator } from '../src/compression-writer'; + + +describe('CompressedStreamWriter instance creation and destroy method testing', () => { + let compressedWriter: CompressedStreamWriter; + it('CompressedStreamWriter instance intialization', () => { + compressedWriter = new CompressedStreamWriter(); + expect('').toBe(''); + }); + it('destroy method testing', () => { + compressedWriter = new CompressedStreamWriter(); + compressedWriter.destroy(); + expect(compressedWriter.compressedData).toBe(undefined); + }); +}); +describe('CompressedStreamWriter with write method testing', () => { + let compressedWriter: CompressedStreamWriter; + beforeEach((): void => { + compressedWriter = new CompressedStreamWriter(); + }); + afterEach((): void => { + compressedWriter.destroy(); + compressedWriter = undefined; + }); + it('write with data as null', () => { + expect(() => { compressedWriter.write(null, 0, 12) }).toThrowError(); + }); + it('write with data as undefined', () => { + expect(() => { compressedWriter.write(undefined, 0, 12) }).toThrowError(); + }); + it('write with negative offset value', () => { + expect(() => { compressedWriter.write(new Uint8Array(2), -1, 0) }).toThrow(new Error('ArgumentOutOfRangeException: Offset or length is incorrect')); + }); + it('write with invalid offset', () => { + expect(() => { compressedWriter.write(new Uint8Array(2), 10, 5) }).toThrow(new Error('ArgumentOutOfRangeException: Offset or length is incorrect')); + }); + it('write with invalid offset and length', () => { + expect(() => { compressedWriter.write(new Uint8Array(2), 2, 5) }).toThrow(new Error('ArgumentOutOfRangeException: Offset or length is incorrect')); + }); + it('write with valid offset and negative length', () => { + expect(() => { compressedWriter.write(new Uint8Array(2), 2, -1) }).toThrowError(); + }); + it('write method with valid string testing', () => { + let s: string = ''; + for (let i: number = 0; i < 70000; i++) { + s += 'c'; + } + let buf: ArrayBuffer = new ArrayBuffer(s.length); + let bufView = new Uint8Array(buf); + for (let i: number = 0; i < s.length; i++) { + bufView[i] = s.charCodeAt(i); + } + compressedWriter.write(bufView, 0, s.length); + compressedWriter.destroy(); + expect(compressedWriter.compressedData).toBe(undefined); + }); + it('Test large file', () => { + let s = '' + let buf: ArrayBuffer = new ArrayBuffer(s.length); + let bufView = new Uint8Array(buf); + for (let i: number = 0; i < s.length; i++) { + bufView[i] = s.charCodeAt(i); + } + compressedWriter.write(bufView, 0, s.length); + compressedWriter.destroy(); + expect(compressedWriter.compressedData).toBe(undefined); + }); +}); +// describe('CompressedStreamWriter with write method testing', () => { +// let compressedWriter: CompressedStreamWriter; +// beforeEach((): void => { +// compressedWriter = new CompressedStreamWriter(); +// }); +// afterEach((): void => { +// compressedWriter.destroy(); +// compressedWriter = undefined; +// }); +// it('large File Testing', () => { +// let s: string = 'ஹஃப்மேன் கோடிங் இழப்பில்லாத தரவு சுருக்க வழிமுறையாகும். , ஒதுக்கப்படும் குறியீடுகளின் நீளம் தொடர்புடைய எழுத்துக்கள் அதிர்வெண்கள் அடிப்படையாக கொண்டவை யோசனை உள்ளீடு எழுத்துக்கள் மாறி legth குறியீடுகள் ஒதுக்க வேண்டும். மிகவும் அடிக்கடி பாத்திரம் சிறிய குறியீடு கிடைத்தால் குறைந்தது அடிக்கடி பாத்திரம் பெரிய குறியீடு பெறுகிறார்.所有浏览器和IE IE IE之后都支持Canvas标签 使用画布,我需要指定每个部分。 例如,我需要指定光位置和上下移,向前,向后和选择 位置在画布中起主要作用。 要在画布中插入html元素,我需要将其为图像。هوفمان الترميز هو خوارزمية ضغط البيانات ضياع. والفكرة هي تعيين رموز متغيرة - ليغث لأحرف الإدخال، وتستند أطوال الرموز المخصصة إلى ترددات الأحرف المقابلة. الطابع الأكثر شيوعا يحصل على أصغر رمز وحرف أقل تواترا يحصل على أكبر رمز.Huffman կոդավորում է lossless տվյալների սեղմման ալգորիթմը: Այն գաղափարը, այն է, որ հանձնարարել փոփոխական legth կանոնագրքերը մուտքային կերպարների, երկարության հանձնարարված կոդերը հիմնված են հաճախականությունների համապատասխան կերպարներ: Առավել հաճախակի բնույթ է ստանում ամենափոքր կոդը եւ ամենաքիչ հաճախակի բնույթ է ստանում ամենամեծ կոդը:Кодирование Хаффмана является алгоритмом сжатия данных без потерь. Идея заключается в назначении кодов с переменной длиной слова для ввода символов, длины назначенных кодов основаны на частотах соответствующих символов. Самый частый символ получает наименьший код, а наименее часто встречающийся символ получает самый большой код.హఫ్ఫ్మన్ కోడింగ్ ఒక లాస్లెస్ డేటా కంప్రెషన్ అల్గోరిథం. ఆలోచన అక్షరాలను ఇన్పుట్ వేరియబుల్-legth సంకేతాలు పెట్టేందుకు ఉంది కేటాయించబడిన సంకేతాలు పొడవులు సంబంధిత పాత్రలు పౌనఃపున్యాల ఆధారపడి ఉంటాయి. చాలా తరచుగా పాత్ర అతిచిన్న కోడ్ గెట్స్ మరియు కనీసం తరచుగా పాత్ర అతిపెద్ద కోడ్ గెట్స్.ಹಫ್ಮನ್ ಕೋಡಿಂಗ್ ಒಂದು ನಷ್ಟವಿಲ್ಲದ ದತ್ತಾಂಶ ಒತ್ತಡಕ ಕ್ರಮಾವಳಿ. ಕಲ್ಪನೆಯನ್ನು ಅಕ್ಷರಗಳನ್ನು ಇನ್ಪುಟ್ ವೇರಿಯಬಲ್-legth ಸಂಕೇತಗಳು ನಿಯೋಜಿಸಲು ಆಗಿದೆ, ಅದಕ್ಕೆ ಸಂಕೇತಗಳು ಉದ್ದಗಳು ಅನುಗುಣವಾದ ಪಾತ್ರಗಳ ಆವರ್ತನಗಳಲ್ಲಿ ಆಧರಿಸಿವೆ. ಅತ್ಯಂತ ಸಾಮಾನ್ಯ ಪಾತ್ರ ಚಿಕ್ಕ ಕೋಡ್ ಪಡೆಯುತ್ತದೆ ಮತ್ತು ಕನಿಷ್ಠ ಆಗಾಗ್ಗೆ ಪಾತ್ರ ದೊಡ್ಡ ಕೋಡ್ ಪಡೆಯುತ್ತದೆ.हफ़मान कोडिंग एक दोषरहित डेटा संपीड़न एल्गोरिथ्म है। यह विचार इनपुट वर्णों के लिए वेरिएबल-पायथ कोड प्रदान करना है, असाइन किए गए कोड की लंबाई संबंधित वर्णों की आवृत्तियों पर आधारित है। सबसे अक्सर चरित्र को सबसे छोटा कोड मिलता है और कम से कम अक्सर चरित्र सबसे बड़ा कोड मिलता है।霍夫曼编码是一种无数据压缩算法。 个想法是将可变码分配给输入字符,分配代度取决于相字符的率。 繁的字符得最小的代,最不繁的字符得最大的代Huffman کی کوڈنگ میں ایک ثابت ڈیٹا سمپیڑن الگورتھم ہے. خیال ان پٹ حروف تک متغیر legth کوڈ تفویض کرنے کے لئے ہے، تفویض کوڈ حد اسی حروف کے تعدد پر مبنی ہیں. سب سے زیادہ بار بار اس کردار سے چھوٹی کوڈ ہو جاتا ہے اور کم از کم بار بار کردار بڑا کوڈ ہو جاتا ہے.ഹുഫ്ഫ്മന് വെല്ലാൻ ഒരു നഷ്ടമാകാത്ത ഡേറ്റാ കമ്പ്രഷൻ അൽഗോരിതം ആണ്. ആശയം നിയോഗിച്ചിട്ടുള്ള കോഡുകൾ ദൈർഘ്യം ഇതേ അക്ഷരങ്ങൾ ആവൃത്തിയുള്ള അടിസ്ഥാനമാക്കിയാണ്, അക്ഷരങ്ങൾ വരെ വേരിയബിൾ-ലെഗ്ഥ് കോഡുകൾ നിയോഗിക്കുകയോ എന്നതാണ്. തുടർച്ചയായി അക്ഷരം ചെറിയ കോഡ് ലഭിക്കുന്നു കുറഞ്ഞത് കൂടെക്കൂടെയുള്ള അക്ഷരം വലിയ കോഡ് ലഭിക്കുന്നു.ハフマン符号化は、無損失のデータ圧縮アルゴリズムです。 アイデアは、入力レターに可変レッグスコードを割り当てることです。割り当てられたコードの長さは、対応する文字の頻度に基づいています。 最も頻繁な文字は最小のコードを取得し、最も頻度の低い文字は最大のコードを取得します。Huffman ਕੋਡਿੰਗ ਇੱਕ ਹਲਕੇ ਡਾਟਾ ਕੰਪਰੈਸ਼ਨ ਐਲਗੋਰਿਥਮ ਹੈ. ਇਹ ਵਿਚਾਰ ਇੰਪੁੱਟ ਅੱਖਰ ਨੂੰ ਵੇਰੀਏਬਲ-legth ਕੋਡ ਨਿਰਧਾਰਤ ਕਰਨਾ ਹੈ, ਵਿਚ ਭੇਜਿਆ ਕੋਡ ਦੀ ਲੰਬਾਈ ਅਨੁਸਾਰੀ ਅੱਖਰ ਦੀ ਫਰੀਕੁਇੰਸੀ \'ਤੇ ਆਧਾਰਿਤ ਹਨ. ਸਭ ਅਕਸਰ ਅੱਖਰ ਛੋਟੀ ਕੋਡ ਪ੍ਰਾਪਤ ਕਰਦਾ ਹੈ ਅਤੇ ਘੱਟੋ-ਘੱਟ ਅਕਸਰ ਅੱਖਰ ਵੱਡੇ ਕੋਡ ਨੂੰ ਪ੍ਰਾਪਤ ਕਰਦਾ ਹੈ.Huffman कोडिंग एक हानिरहित डाटा सङ्कुचन अल्गोरिदम छ। विचार इनपुट वर्ण गर्न चल-legth कोड नियुक्त गर्न , तोकिएको कोड को लम्बाईहरू अनुरूप वर्ण को आवृत्तियों आधारित छन्। सबैभन्दा बारम्बार वर्ण सानो कोड हुन्छ कम से कम बारम्बार वर्ण सबै भन्दा ठूलो कोड हुन्छ।Huffman ايښودنه یو lossless مالومات کېښکلو الګوریتم ده. د نظر دا ده چې آخذه خویونه متحول-legth کوډونو وټاکي، د موظف کوډونو د طول د سمون خویونه د څپو پر بنسټ دي. تر ټولو مکرر کرکټر د کوچنيو کوډ تر لاسه او د لږ تر لږه مکرر کرکټر د لوی کوډ تر لاسه.Huffman coding është një algoritëm lossless compression të dhënave. Ideja është që të caktojë kodet e ndryshueshme-legth të karaktereve të dhëna, gjatesite e kodeve të caktuara janë të bazuara në frekuencat e karaktereve përkatëse. Personazhi më i shpeshtë merr kodin e vogël dhe karakteri më pak të shpeshta merr kodin më të madh.Huffman ኮድ አንድ የተደገፈው የውሂብ መጭመቂያ ስልተቀመር ነው. ሐሳብ ግቤት ቁምፊዎች ተለዋዋጭ-legth ኮዶች ለመመደብ ነው, የተመደበውን ኮዶች ርዝመት ተጓዳኝ ቁምፊዎች frequencies ላይ የተመሠረቱ ናቸው. በጣም በተደጋጋሚ ቁምፊ ትንሹ ኮድ ያገኛል እና ቢያንስ በተደጋጋሚ ቁምፊ ትልቁ ኮድ ያገኛልHuffman կոդավորում է lossless տվյալների սեղմման ալգորիթմը: Այն գաղափարը, այն է, որ հանձնարարել փոփոխական legth կանոնագրքերը մուտքային կերպարների, երկարության հանձնարարված կոդերը հիմնված են հաճախականությունների համապատասխան կերպարներ: Առավել հաճախակի բնույթ է ստանում ամենափոքր կոդը եւ ամենաքիչ հաճախակի բնույթ է ստանում ամենամեծ կոդը:Huffman kodlaşdırma itkisiz data compression alqoritmi edir. fikir daxil simvol dəyişən legth kodları təyin edir, təyin kodları uzunluğu müvafiq simvol tezliklərdə əsaslanır. Ən tez-tez xarakter kiçik kodu alır və ən azı tez-tez xarakter böyük kodu olur.Кадаваньне Хафман ўяўляе сабой алгарытм сціску дадзеных без страт. Ідэя складаецца ў тым, каб прысвоіць зменныя legth кодаў для ўводу знакаў, даўжыні прызначаных кодаў заснаваныя на частотах, адпаведныя знакаў. Самы часты персанаж атрымлівае найменшы код і найменш часты персанаж атрымлівае найбольшы код.Huffman কোডিং একটি অবচয়হীন ডাটা কম্প্রেশন অ্যালগরিদম হয়। ধারণা ইনপুট অক্ষর পরিবর্তনশীল-legth কোড দায়িত্ব অর্পণ করা হয়, নির্ধারিত কোডের লেন্থ সংশ্লিষ্ট অক্ষরের ফ্রিকোয়েন্সি উপর ভিত্তি করে। অধিকাংশ ঘন চরিত্র ক্ষুদ্রতম কোড পায় এবং অন্তত ঘন চরিত্র বৃহত্তম কোড পায়।Хъфман кодиране е алгоритъм за компресия без загуба на данни. Идеята е да се възложи с променлива legth кодове, за да въведете символи, дължини на възложените кодовете са базирани на честотите на съответните знаци. Най-често срещаната характер получава най-малката код и малко честа характер стане най-големият код.சாகச சைக்கிள்ஸ், எந்த சாகச, வேலை மாதிரி தரவுத்தளங்கள் அடிப்படையாக கொண்டவை ஒரு பெரிய, பன்னாட்டு உற்பத்தி செய்யும் நிறுவனமாகும் கற்பனையான நிறுவனம் இயங்குகிறது. நிறுவனம் தயாரிக்கிறது மற்றும் வட அமெரிக்க, ஐரோப்பிய மற்றும் ஆசிய வணிக சந்தைகளுக்கு உலோக மற்றும் கலப்பு மிதிவண்டிகள் விற்கிறது. அதன் அடிப்படை செயல்படும் 290 ஊழியர்கள் Bothell, வாஷிங்டன் இருக்கும் போது, பல பிராந்திய விற்பனை குழுக்கள் தமது சந்தை அடிப்படை முழுவதும் இருக்கின்றன.సాహస సైకిల్స్, ఇది సాహస వర్క్స్ నమూనా డేటాబేస్ ఆధారపడి ఉంటాయి, ఒక పెద్ద, బహుళజాతి తయారీ సంస్థ కల్పిత కంపెనీ పనిచేస్తుంది. కంపెనీ తయారు మరియు ఉత్తర అమెరికా, ఐరోపా మరియు ఆసియా వాణిజ్య మార్కెట్లకు మెటల్ మరియు మిశ్రమ సైకిళ్ళు విక్రయిస్తుంది. దాని బేస్ ఆపరేషన్ 290 ఉద్యోగులతో లో బోథెల్, వాషింగ్టన్ కాగా, పలు ప్రాంతీయ అమ్మకాలు జట్లు తమ మార్కెట్ బేస్ విస్తరించి ఉన్నాయి.സാഹസികത പ്രവർത്തിക്കുന്നു സൈക്കിളുകൾ, ഏത് സാഹസിക വർക്ക്സ് സാമ്പിൾ ഡാറ്റാബേസുകൾ അടിസ്ഥാനമാക്കിയുള്ളതാണ് സാങ്കല്പിക കമ്പനി, ഒരു വലിയ ബഹുരാഷ്ട്ര നിർമ്മാണ കമ്പനിയാണ്. കമ്പനി ഉദ്പാദിപ്പിക്കുന്ന വടക്കൻ അമേരിക്കൻ, യൂറോപ്യൻ, ഏഷ്യൻ വാണിജ്യ വിപണി മെറ്റൽ, സംയോജിത സൈക്കിൾ വിൽക്കുന്നത്. അതിന്റെ അടിസ്ഥാന പ്രവർത്തനം 290 ജീവനക്കാർ ബൊഥെല്ല്, വാഷിങ്ടൺ തന്നെ, പല പ്രാദേശിക വിൽപ്പന ടീമുകൾ വിപണി അടിസ്ഥാന മുഴുവൻ സ്ഥിതിചെയ്യുന്നു.দু: সাহসিক কাজ কাজ করে আবর্তক, কল্পিত কোম্পানী যার উপর সাহসিক কাজ নমুনা ডাটাবেস ভিত্তি করে, একটি বৃহৎ, বহুজাতিক উত্পাদন কোম্পানী। কোম্পানী উত্পাদন এবং উত্তর আমেরিকার ইউরোপীয় এশীয় বাণিজ্যিক বাজারের ধাতু এবং যৌগিক বাইসাইকেল বিক্রি করে। যদিও সেটির বেস অপারেশন 290 কর্মীদের সঙ্গে Bothell, ওয়াশিংটন হয়, বিভিন্ন আঞ্চলিক বিক্রয় দল তাদের বাজার বেস সর্বত্র অবস্থিত হয়।ಸಾಹಸ ವರ್ಕ್ಸ್ ಸೈಕಲ್ಸ್, ಮೇಲೆ ಸಾಹಸ, ವರ್ಕ್ಸ್ ಮಾದರಿ ಡೇಟಾಬೇಸ್ ಆಧರಿಸಿವೆ ದೊಡ್ಡ, ಬಹುರಾಷ್ಟ್ರೀಯ ತಯಾರಿಸುವ ಸಂಸ್ಥೆ ಕಾಲ್ಪನಿಕ ಕಂಪನಿ. ಕಂಪನಿಯು ತಯಾರಿಸುತ್ತದೆ ಮತ್ತು ಉತ್ತರ ಅಮೇರಿಕ, ಯುರೋಪ್ ಮತ್ತು ಏಷ್ಯಾ ವಾಣಿಜ್ಯ ಮಾರುಕಟ್ಟೆಗಳಿಗೆ ಲೋಹದ ಮತ್ತು ಸಂಯುಕ್ತ ಸೈಕಲ್ ಮಾರುತ್ತದೆ. ಅದರ ಬೇಸ್ ಕಾರ್ಯಾಚರಣೆಯನ್ನು 290 ನೌಕರರು ಬೋಥೆಲ್, ವಾಷಿಂಗ್ಟನ್ ನಡೆಸುತ್ತಿರುವಾಗ, ಹಲವಾರು ಪ್ರಾದೇಶಿಕ ಮಾರಾಟ ತಂಡಗಳು ತಮ್ಮ ಮಾರುಕಟ್ಟೆ ಆಧಾರವನ್ನು ಇವೆ.एडवेंचर वर्क्स साइकिल, फर्जी कंपनी जिस पर एडवेंचर वर्क्स नमूना डेटाबेस आधारित हैं, एक बड़ी, बहुराष्ट्रीय कंपनी है। कंपनी उत्तर अमेरिकी, यूरोपीय और एशियाई वाणिज्यिक बाजारों में धातु और समग्र साइकिल का निर्माण और बिक्री करती है। जबकि इसके बेस ऑपरेशन 290 कर्मचारियों के साथ वाशिंगटन के बोथेल में है, कई क्षेत्रीय बिक्री टीम अपने बाजार आधार पर स्थित हैं।साहसी सायकल्स, साहसी कार्य करते जे नमुना डाटाबेस आधारित आहेत, एका मोठ्या, बहुराष्ट्रीय उत्पादन कंपनी आहे बोगस कंपनी काम करते. कंपनी उत्पादन उत्तर अमेरिकन, युरोपियन आणि आशियाई व्यावसायिक मार्केट धातू आणि संमिश्र सायकलींसाठी विकतो. त्याच्या बेस ऑपरेशन 290 कर्मचारी बोथेल्ल, वॉशिंग्टन आहे तर, अनेक प्रादेशिक विक्री संघ बाजार बेस संपूर्ण स्थित आहेत.ساہسک سائیکل، فرضی کمپنی کے ساہسک کام کرتا ہے جس پر نمونہ ڈیٹا بیس کی بنیاد پر کیا جاتا ہے، ایک بڑے، کثیر القومی مینوفیکچرنگ کمپنی ہے کام کرتا ہے. کمپنی کے تیار اور شمالی امریکی، یورپی اور ایشیائی تجارتی منڈیوں تک دھات اور جامع سائیکلوں فروخت کرتا ہے. اس کی بنیاد آپریشن 290 ملازمین کے ساتھ Bothell، واشنگٹن میں ہے، کئی علاقائی سیلز ٹیموں کو ان کے مارکیٹ بیس بھر میں واقع ہیں.સાહસિક કામ ચક્રો, કાલ્પનિક કંપની કે જેના પર સાહસ કામ નમૂના ડેટાબેઝ આધારિત છે, એક વિશાળ બહુરાષ્ટ્રીય ઉત્પાદન કંપની છે. કંપની ઉત્પાદન અને નોર્થ અમેરિકન, યુરોપિયન અને એશિયન વ્યાપારી બજારોમાં મેટલ અને સંયુક્ત સાયકલ વેચે છે. જ્યારે તેના બેઝ કામગીરી 290 કર્મચારીઓ સાથે બોથેલ, વોશિંગ્ટન છે, અનેક પ્રાદેશિક વેચાણ ટીમો તેમના બજાર આધાર સમગ્ર સ્થિત છે.அத்தியாவசிய DocIO சொந்த Word வடிவத்தில் முழுமையாக செயல்பாட்டு மைக்ரோசாப்ட் வேர்ட் ஆவணங்கள் உருவாக்குகிறது என்று ஒரு 100% சொந்த .NET நூலகமாகும். அத்தியாவசிய DocIO Microsoft Word கோப்புகளைத் படித்து எழுதப் பயன்படுத்தப்படுகிறது. அது மைக்ரோசாப்ட் ஆபிஸ் COM நூலகங்கள் ஒத்த ஒரு முழு நீள பொருள் மாதிரி கொண்டுள்ளது. அது COM இன்டராப் பயன்படுத்த முடியாது மற்றும் சி # புதிதாக கட்டப்பட்டுள்ளது. DocIO நூலகம் உட்பட சி #, VB.NET மற்றும் நிர்வகிக்கப்படும் சி ++ எந்த டாட்நெட் சூழலானது பயன்படுத்த முடியும். அது விண்டோஸ் படிவங்கள், விலத்துக்கொண்டதோடு, சில்வர்லைட், ASP.NET, ASP.NET MVC ஆனது மற்றும் விண்டோஸ் ஸ்டோர் பயன்பாடுகளில் பயன்படுத்தப்படுகிறது என்று ஒரு அல்லாத பயனர் இடைமுகம் அங்கமாகும்.അവശ്യ ദൊചിഒ നേറ്റീവ് വാക്ക് ഫോർമാറ്റിൽ പൂർണ്ണമായും പ്രവർത്തനക്ഷമമായിട്ടില്ല മൈക്രോസോഫ്റ്റ് പ്രമാണങ്ങൾ സൃഷ്ടിക്കുന്നത് 100% നേറ്റീവ് .NET ലൈബ്രറി. അവശ്യ ദൊചിഒ മൈക്രോസോഫ്റ്റ് വേഡ് ഫയലുകൾ എഴുതാനും വായിക്കാനും ഉപയോഗിക്കുന്നു. ഇത് മൈക്രോസോഫ്റ്റ് ഓഫീസ് കോം ലൈബ്രറികൾ സമാനമായ ഒരു സമ്പൂർണ വസ്തു മോഡൽ സവിശേഷതകൾ. ഇത് കോം Interop ഉപയോഗിക്കുന്നില്ല എന്ന്, C # ആദ്യം മുതൽ നിർമ്മിച്ചിരിക്കുന്നത്. ദൊചിഒ ലൈബ്രറി, ഉൾപ്പെടെ സി # ഏതെങ്കിലും .NET പരിസ്ഥിതി ഉപയോഗിക്കാൻ കഴിയും VB.NET ആൻഡ് സി ++ കൈകാര്യം. അതു, വ്പ്ഫ്, സിൽവർ, ASP.NET, ASP.NET മ്വ്ച്, Windows സ്റ്റോർ അപ്ലിക്കേഷനുകൾ വിൻഡോസ് ഫോമിൽ ഉപയോഗിക്കുന്ന ഒരു നോൺ-യുഐ ഘടകമാണ്.ఎసెన్షియల్ DocIO స్థానిక Word ఆకృతిలో పూర్తిగా ఫంక్షనల్ Microsoft Word డాక్యుమెంట్లు సృష్టించే ఒక 100% స్థానిక .NET గ్రంథాలయం. ఎసెన్షియల్ DocIO మైక్రోసాఫ్ట్ వర్డ్ ఫైళ్లు చదవడానికి మరియు వ్రాయడానికి ఉపయోగిస్తారు. ఇది మైక్రోసాఫ్ట్ ఆఫీస్ COM గ్రంధాలయాలు పోలి ఒక పూర్తి స్థాయి ఆబ్జెక్ట్ మోడల్ కలిగి ఉంది. ఇది COM ఇంటరాప్ ఉపయోగించడానికి లేదు మరియు C # మొదటి నుండి నిర్మించబడింది. DocIO లైబ్రరీ సహా సి #, VB.NET మరియు C నిర్వహించేది ++ .NET వాతావరణంలో ఉపయోగించవచ్చు. ఇది Windows పత్రాలు, WPF, Silverlight, ASP.NET, ASP.NET MVC, మరియు Windows స్టోర్ అప్లికేషన్లు ఉపయోగిస్తారు ఒక లాభాపేక్ష UI భాగం.ಎಸೆನ್ಷಿಯಲ್ DocIO ಸ್ಥಳೀಯ ಪದಗಳ ರೂಪದಲ್ಲಿ ಸಂಪೂರ್ಣವಾಗಿ ಕ್ರಿಯಾತ್ಮಕ Microsoft Word ಡಾಕ್ಯುಮೆಂಟ್ಗಳು ನಿರ್ಮಿಸುವ 100% ಸ್ಥಳೀಯ .NET ಗ್ರಂಥಾಲಯವಾಗಿದೆ. ಎಸೆನ್ಷಿಯಲ್ DocIO Microsoft Word ಫೈಲ್ಗಳನ್ನು ಓದಲು ಮತ್ತು ಬರೆಯಲು ಬಳಸಲಾಗುತ್ತದೆ. ಇದು ಮೈಕ್ರೋಸಾಫ್ಟ್ ಆಫೀಸ್ ವಾ ಗ್ರಂಥಾಲಯಗಳು ಹೋಲುವ ಪೂರ್ಣ ಪ್ರಮಾಣದ ವಸ್ತು ಮಾದರಿಯ ಒಳಗೊಂಡಿದೆ. ಇದು ವಾ Interop ಬಳಸುವುದಿಲ್ಲ ಮತ್ತು C # ಆರಂಭದಿಂದ ನಿರ್ಮಿಸಲಾಗಿದೆ. DocIO ಗ್ರಂಥಾಲಯದ ಸೇರಿದಂತೆ C # VB.NET ಮತ್ತು ವ್ಯವಸ್ಥಿತ C ++ ಯಾವುದೇ .NET ಪರಿಸರದಲ್ಲಿ ಬಳಸಬಹುದು. ಇದು ಇದೆ ವಿಂಡೋಸ್ ರೀತಿಗಳನ್ನು WPF, ಸಿಲ್ವರ್ಲೈಟ್ ASP.NET, ಆಗಿ ASP.NET MVC, ಮತ್ತು ವಿಂಡೋಸ್ ಅಂಗಡಿ ಅನ್ವಯಿಕೆಗಳಲ್ಲಿ ಬಳಸಲಾಗುತ್ತದೆ ಒಂದು ಅಲ್ಲದ ಯುಐ ಅಂಶವಾಗಿದೆ.आवश्यक DocIO एक 100% देशी .NET लाइब्रेरी है जो देशी वर्ड प्रारूप में पूरी तरह कार्यात्मक माइक्रोसॉफ्ट वर्ड दस्तावेज़ों को उत्पन्न करती है। आवश्यक दस्तावेज को माइक्रोसॉफ्ट वर्ड फाइल्स को पढ़ने और लिखने के लिए उपयोग किया जाता है। इसमें माइक्रोसॉफ्ट ऑफिस कॉम लाइब्रेरीज़ के समान एक पूर्ण ऑब्जेक्ट मॉडल है। यह COM इंटरॉप का उपयोग नहीं करता है और सी # में खरोंच से बनाया गया है। DocIO लाइब्रेरी को किसी भी। NET वातावरण में सी #, VB.NET और प्रबंधित C ++ सहित उपयोग किया जा सकता है। यह एक गैर-यूआई घटक है जिसे विंडोज फॉर्म, डब्लूपीएफ, सिल्वरलाइट, एएसपी.नेट, एएसपी.NET एमवीसी, और विंडोज स्टोर एप्लीकेशन में इस्तेमाल किया जाता है।ضروری DocIO اسے ورڈ فارمیٹ میں مکمل طور پر باضابطہ مائیکروسافٹ Word دستاویزات کو پیدا کرتا ہے کہ ایک 100٪ اسے .NET لائبریری ہے. ضروری DocIO مائیکروسافٹ ورڈ فائلوں کو پڑھنے اور لکھنے کے لئے استعمال کیا جاتا ہے. یہ مائیکروسافٹ آفس COM لائبریریوں کے لئے اسی طرح ایک مکمل آبجیکٹ ماڈل کی خصوصیات. اس COM انٹرآپ استعمال نہیں کرتا اور C # میں شروع سے بنایا گیا ہے. DocIO لائبریری سمیت C #، VB.NET اور منظم C + + کسی بھی نیٹ ماحول میں استعمال کیا جا سکتا ہے. یہ ایک غیر UI جزو ونڈوز فارم، WPF، سلور لائٹ، ASP.NET، ASP.NET MVC، اور ونڈوز سٹور ایپلی کیشنز میں استعمال کیا جاتا ہے ہے.模板在MS Word 2007中修改并保存PC的“Word 97-2003文档”,作者提供了准备电子版文所需的大部分格式化范。 经规定了所有准的纸张部件有三个原因:(1)格式化纸张时的易用性,(2)自符合促进电品同或以后生子要求,以及(3)整个格的一致性 议记录 内置距,列,行距和类型; 在本文档中提供了类型式的示例,并以示例中的括号内的斜体字标识 尽管提供了各种表格文本式,但是没有定一些件,例如多次方程,形和表格。 格式化程序将需要件,并合以下适用的准。கேன்வாஸ் டேக் முதல் IE9 இருந்து, அனைத்து உலாவிகளில் மற்றும் IE ஆதரவு. கேன்வாஸ் பயன்படுத்தி, நாம் ஒவ்வொரு பகுதியாக குறிப்பிட வேண்டும். உதாரணமாக, நாம் சுட்டியின் நிலையில் குறிப்பிட மற்றும் நகரும் காட்டிகள் அப்களை முத்தமிட்டு, அதற்கும் சற்று மற்றும் முன்னோக்கி, பின்தங்கிய திசைகளில் மற்றும் தேர்ந்தெடுத்தல் மீது வேண்டும். நிலை கேன்வாஸ் ஒரு முக்கிய பங்கு வகிக்கிறது. கேன்வாஸ் உள்ளே ஒரு HTML உறுப்பு நுழைக்க, நாம் படத்தை அது அமைக்க வேண்டும்.ஹஃப்மேன் கோடிங் இழப்பில்லாத தரவு சுருக்க வழிமுறையாகும். , ஒதுக்கப்படும் குறியீடுகளின் நீளம் தொடர்புடைய எழுத்துக்கள் அதிர்வெண்கள் அடிப்படையாக கொண்டவை யோசனை உள்ளீடு எழுத்துக்கள் மாறி legth குறியீடுகள் ஒதுக்க வேண்டும். மிகவும் அடிக்கடி பாத்திரம் சிறிய குறியீடு கிடைத்தால் குறைந்தது அடிக்கடி பாத்திரம் பெரிய குறியீடு பெறுகிறார்.所有浏览器和IE IE IE之后都支持Canvas标签 使用画布,我需要指定每个部分。 例如,我需要指定光位置和上下移,向前,向后和选择 位置在画布中起主要作用。 要在画布中插入html元素,我需要将其为图像。هوفمان الترميز هو خوارزمية ضغط البيانات ضياع. والفكرة هي تعيين رموز متغيرة - ليغث لأحرف الإدخال، وتستند أطوال الرموز المخصصة إلى ترددات الأحرف المقابلة. الطابع الأكثر شيوعا يحصل على أصغر رمز وحرف أقل تواترا يحصل على أكبر رمز.Huffman կոդավորում է lossless տվյալների սեղմման ալգորիթմը: Այն գաղափարը, այն է, որ հանձնարարել փոփոխական legth կանոնագրքերը մուտքային կերպարների, երկարության հանձնարարված կոդերը հիմնված են հաճախականությունների համապատասխան կերպարներ: Առավել հաճախակի բնույթ է ստանում ամենափոքր կոդը եւ ամենաքիչ հաճախակի բնույթ է ստանում ամենամեծ կոդը:Кодирование Хаффмана является алгоритмом сжатия данных без потерь. Идея заключается в назначении кодов с переменной длиной слова для ввода символов, длины назначенных кодов основаны на частотах соответствующих символов. Самый частый символ получает наименьший код, а наименее часто встречающийся символ получает самый большой код.హఫ్ఫ్మన్ కోడింగ్ ఒక లాస్లెస్ డేటా కంప్రెషన్ అల్గోరిథం. ఆలోచన అక్షరాలను ఇన్పుట్ వేరియబుల్-legth సంకేతాలు పెట్టేందుకు ఉంది కేటాయించబడిన సంకేతాలు పొడవులు సంబంధిత పాత్రలు పౌనఃపున్యాల ఆధారపడి ఉంటాయి. చాలా తరచుగా పాత్ర అతిచిన్న కోడ్ గెట్స్ మరియు కనీసం తరచుగా పాత్ర అతిపెద్ద కోడ్ గెట్స్.ಹಫ್ಮನ್ ಕೋಡಿಂಗ್ ಒಂದು ನಷ್ಟವಿಲ್ಲದ ದತ್ತಾಂಶ ಒತ್ತಡಕ ಕ್ರಮಾವಳಿ. ಕಲ್ಪನೆಯನ್ನು ಅಕ್ಷರಗಳನ್ನು ಇನ್ಪುಟ್ ವೇರಿಯಬಲ್-legth ಸಂಕೇತಗಳು ನಿಯೋಜಿಸಲು ಆಗಿದೆ, ಅದಕ್ಕೆ ಸಂಕೇತಗಳು ಉದ್ದಗಳು ಅನುಗುಣವಾದ ಪಾತ್ರಗಳ ಆವರ್ತನಗಳಲ್ಲಿ ಆಧರಿಸಿವೆ. ಅತ್ಯಂತ ಸಾಮಾನ್ಯ ಪಾತ್ರ ಚಿಕ್ಕ ಕೋಡ್ ಪಡೆಯುತ್ತದೆ ಮತ್ತು ಕನಿಷ್ಠ ಆಗಾಗ್ಗೆ ಪಾತ್ರ ದೊಡ್ಡ ಕೋಡ್ ಪಡೆಯುತ್ತದೆ.हफ़मान कोडिंग एक दोषरहित डेटा संपीड़न एल्गोरिथ्म है। यह विचार इनपुट वर्णों के लिए वेरिएबल-पायथ कोड प्रदान करना है, असाइन किए गए कोड की लंबाई संबंधित वर्णों की आवृत्तियों पर आधारित है। सबसे अक्सर चरित्र को सबसे छोटा कोड मिलता है और कम से कम अक्सर चरित्र सबसे बड़ा कोड मिलता है।霍夫曼编码是一种无数据压缩算法。 个想法是将可变码分配给输入字符,分配代度取决于相字符的率。 繁的字符得最小的代,最不繁的字符得最大的代Huffman کی کوڈنگ میں ایک ثابت ڈیٹا سمپیڑن الگورتھم ہے. خیال ان پٹ حروف تک متغیر legth کوڈ تفویض کرنے کے لئے ہے، تفویض کوڈ حد اسی حروف کے تعدد پر مبنی ہیں. سب سے زیادہ بار بار اس کردار سے چھوٹی کوڈ ہو جاتا ہے اور کم از کم بار بار کردار بڑا کوڈ ہو جاتا ہے.ഹുഫ്ഫ്മന് വെല്ലാൻ ഒരു നഷ്ടമാകാത്ത ഡേറ്റാ കമ്പ്രഷൻ അൽഗോരിതം ആണ്. ആശയം നിയോഗിച്ചിട്ടുള്ള കോഡുകൾ ദൈർഘ്യം ഇതേ അക്ഷരങ്ങൾ ആവൃത്തിയുള്ള അടിസ്ഥാനമാക്കിയാണ്, അക്ഷരങ്ങൾ വരെ വേരിയബിൾ-ലെഗ്ഥ് കോഡുകൾ നിയോഗിക്കുകയോ എന്നതാണ്. തുടർച്ചയായി അക്ഷരം ചെറിയ കോഡ് ലഭിക്കുന്നു കുറഞ്ഞത് കൂടെക്കൂടെയുള്ള അക്ഷരം വലിയ കോഡ് ലഭിക്കുന്നു.ハフマン符号化は、無損失のデータ圧縮アルゴリズムです。 アイデアは、入力レターに可変レッグスコードを割り当てることです。割り当てられたコードの長さは、対応する文字の頻度に基づいています。 最も頻繁な文字は最小のコードを取得し、最も頻度の低い文字は最大のコードを取得します。Huffman ਕੋਡਿੰਗ ਇੱਕ ਹਲਕੇ ਡਾਟਾ ਕੰਪਰੈਸ਼ਨ ਐਲਗੋਰਿਥਮ ਹੈ. ਇਹ ਵਿਚਾਰ ਇੰਪੁੱਟ ਅੱਖਰ ਨੂੰ ਵੇਰੀਏਬਲ-legth ਕੋਡ ਨਿਰਧਾਰਤ ਕਰਨਾ ਹੈ, ਵਿਚ ਭੇਜਿਆ ਕੋਡ ਦੀ ਲੰਬਾਈ ਅਨੁਸਾਰੀ ਅੱਖਰ ਦੀ ਫਰੀਕੁਇੰਸੀ \'ਤੇ ਆਧਾਰਿਤ ਹਨ. ਸਭ ਅਕਸਰ ਅੱਖਰ ਛੋਟੀ ਕੋਡ ਪ੍ਰਾਪਤ ਕਰਦਾ ਹੈ ਅਤੇ ਘੱਟੋ-ਘੱਟ ਅਕਸਰ ਅੱਖਰ ਵੱਡੇ ਕੋਡ ਨੂੰ ਪ੍ਰਾਪਤ ਕਰਦਾ ਹੈ.Huffman कोडिंग एक हानिरहित डाटा सङ्कुचन अल्गोरिदम छ। विचार इनपुट वर्ण गर्न चल-legth कोड नियुक्त गर्न , तोकिएको कोड को लम्बाईहरू अनुरूप वर्ण को आवृत्तियों आधारित छन्। सबैभन्दा बारम्बार वर्ण सानो कोड हुन्छ कम से कम बारम्बार वर्ण सबै भन्दा ठूलो कोड हुन्छ।Huffman ايښودنه یو lossless مالومات کېښکلو الګوریتم ده. د نظر دا ده چې آخذه خویونه متحول-legth کوډونو وټاکي، د موظف کوډونو د طول د سمون خویونه د څپو پر بنسټ دي. تر ټولو مکرر کرکټر د کوچنيو کوډ تر لاسه او د لږ تر لږه مکرر کرکټر د لوی کوډ تر لاسه.Huffman coding është një algoritëm lossless compression të dhënave. Ideja është që të caktojë kodet e ndryshueshme-legth të karaktereve të dhëna, gjatesite e kodeve të caktuara janë të bazuara në frekuencat e karaktereve përkatëse. Personazhi më i shpeshtë merr kodin e vogël dhe karakteri më pak të shpeshta merr kodin më të madh.Huffman ኮድ አንድ የተደገፈው የውሂብ መጭመቂያ ስልተቀመር ነው. ሐሳብ ግቤት ቁምፊዎች ተለዋዋጭ-legth ኮዶች ለመመደብ ነው, የተመደበውን ኮዶች ርዝመት ተጓዳኝ ቁምፊዎች frequencies ላይ የተመሠረቱ ናቸው. በጣም በተደጋጋሚ ቁምፊ ትንሹ ኮድ ያገኛል እና ቢያንስ በተደጋጋሚ ቁምፊ ትልቁ ኮድ ያገኛልHuffman կոդավորում է lossless տվյալների սեղմման ալգորիթմը: Այն գաղափարը, այն է, որ հանձնարարել փոփոխական legth կանոնագրքերը մուտքային կերպարների, երկարության հանձնարարված կոդերը հիմնված են հաճախականությունների համապատասխան կերպարներ: Առավել հաճախակի բնույթ է ստանում ամենափոքր կոդը եւ ամենաքիչ հաճախակի բնույթ է ստանում ամենամեծ կոդը:Huffman kodlaşdırma itkisiz data compression alqoritmi edir. fikir daxil simvol dəyişən legth kodları təyin edir, təyin kodları uzunluğu müvafiq simvol tezliklərdə əsaslanır. Ən tez-tez xarakter kiçik kodu alır və ən azı tez-tez xarakter böyük kodu olur.Кадаваньне Хафман ўяўляе сабой алгарытм сціску дадзеных без страт. Ідэя складаецца ў тым, каб прысвоіць зменныя legth кодаў для ўводу знакаў, даўжыні прызначаных кодаў заснаваныя на частотах, адпаведныя знакаў. Самы часты персанаж атрымлівае найменшы код і найменш часты персанаж атрымлівае найбольшы код.Huffman কোডিং একটি অবচয়হীন ডাটা কম্প্রেশন অ্যালগরিদম হয়। ধারণা ইনপুট অক্ষর পরিবর্তনশীল-legth কোড দায়িত্ব অর্পণ করা হয়, নির্ধারিত কোডের লেন্থ সংশ্লিষ্ট অক্ষরের ফ্রিকোয়েন্সি উপর ভিত্তি করে। অধিকাংশ ঘন চরিত্র ক্ষুদ্রতম কোড পায় এবং অন্তত ঘন চরিত্র বৃহত্তম কোড পায়।Хъфман кодиране е алгоритъм за компресия без загуба на данни. Идеята е да се възложи с променлива legth кодове, за да въведете символи, дължини на възложените кодовете са базирани на честотите на съответните знаци. Най-често срещаната характер получава най-малката код и малко честа характер стане най-големият код.சாகச சைக்கிள்ஸ், எந்த சாகச, வேலை மாதிரி தரவுத்தளங்கள் அடிப்படையாக கொண்டவை ஒரு பெரிய, பன்னாட்டு உற்பத்தி செய்யும் நிறுவனமாகும் கற்பனையான நிறுவனம் இயங்குகிறது. நிறுவனம் தயாரிக்கிறது மற்றும் வட அமெரிக்க, ஐரோப்பிய மற்றும் ஆசிய வணிக சந்தைகளுக்கு உலோக மற்றும் கலப்பு மிதிவண்டிகள் விற்கிறது. அதன் அடிப்படை செயல்படும் 290 ஊழியர்கள் Bothell, வாஷிங்டன் இருக்கும் போது, பல பிராந்திய விற்பனை குழுக்கள் தமது சந்தை அடிப்படை முழுவதும் இருக்கின்றன.సాహస సైకిల్స్, ఇది సాహస వర్క్స్ నమూనా డేటాబేస్ ఆధారపడి ఉంటాయి, ఒక పెద్ద, బహుళజాతి తయారీ సంస్థ కల్పిత కంపెనీ పనిచేస్తుంది. కంపెనీ తయారు మరియు ఉత్తర అమెరికా, ఐరోపా మరియు ఆసియా వాణిజ్య మార్కెట్లకు మెటల్ మరియు మిశ్రమ సైకిళ్ళు విక్రయిస్తుంది. దాని బేస్ ఆపరేషన్ 290 ఉద్యోగులతో లో బోథెల్, వాషింగ్టన్ కాగా, పలు ప్రాంతీయ అమ్మకాలు జట్లు తమ మార్కెట్ బేస్ విస్తరించి ఉన్నాయి.സാഹസികത പ്രവർത്തിക്കുന്നു സൈക്കിളുകൾ, ഏത് സാഹസിക വർക്ക്സ് സാമ്പിൾ ഡാറ്റാബേസുകൾ അടിസ്ഥാനമാക്കിയുള്ളതാണ് സാങ്കല്പിക കമ്പനി, ഒരു വലിയ ബഹുരാഷ്ട്ര നിർമ്മാണ കമ്പനിയാണ്. കമ്പനി ഉദ്പാദിപ്പിക്കുന്ന വടക്കൻ അമേരിക്കൻ, യൂറോപ്യൻ, ഏഷ്യൻ വാണിജ്യ വിപണി മെറ്റൽ, സംയോജിത സൈക്കിൾ വിൽക്കുന്നത്. അതിന്റെ അടിസ്ഥാന പ്രവർത്തനം 290 ജീവനക്കാർ ബൊഥെല്ല്, വാഷിങ്ടൺ തന്നെ, പല പ്രാദേശിക വിൽപ്പന ടീമുകൾ വിപണി അടിസ്ഥാന മുഴുവൻ സ്ഥിതിചെയ്യുന്നു.দু: সাহসিক কাজ কাজ করে আবর্তক, কল্পিত কোম্পানী যার উপর সাহসিক কাজ নমুনা ডাটাবেস ভিত্তি করে, একটি বৃহৎ, বহুজাতিক উত্পাদন কোম্পানী। কোম্পানী উত্পাদন এবং উত্তর আমেরিকার ইউরোপীয় এশীয় বাণিজ্যিক বাজারের ধাতু এবং যৌগিক বাইসাইকেল বিক্রি করে। যদিও সেটির বেস অপারেশন 290 কর্মীদের সঙ্গে Bothell, ওয়াশিংটন হয়, বিভিন্ন আঞ্চলিক বিক্রয় দল তাদের বাজার বেস সর্বত্র অবস্থিত হয়।ಸಾಹಸ ವರ್ಕ್ಸ್ ಸೈಕಲ್ಸ್, ಮೇಲೆ ಸಾಹಸ, ವರ್ಕ್ಸ್ ಮಾದರಿ ಡೇಟಾಬೇಸ್ ಆಧರಿಸಿವೆ ದೊಡ್ಡ, ಬಹುರಾಷ್ಟ್ರೀಯ ತಯಾರಿಸುವ ಸಂಸ್ಥೆ ಕಾಲ್ಪನಿಕ ಕಂಪನಿ. ಕಂಪನಿಯು ತಯಾರಿಸುತ್ತದೆ ಮತ್ತು ಉತ್ತರ ಅಮೇರಿಕ, ಯುರೋಪ್ ಮತ್ತು ಏಷ್ಯಾ ವಾಣಿಜ್ಯ ಮಾರುಕಟ್ಟೆಗಳಿಗೆ ಲೋಹದ ಮತ್ತು ಸಂಯುಕ್ತ ಸೈಕಲ್ ಮಾರುತ್ತದೆ. ಅದರ ಬೇಸ್ ಕಾರ್ಯಾಚರಣೆಯನ್ನು 290 ನೌಕರರು ಬೋಥೆಲ್, ವಾಷಿಂಗ್ಟನ್ ನಡೆಸುತ್ತಿರುವಾಗ, ಹಲವಾರು ಪ್ರಾದೇಶಿಕ ಮಾರಾಟ ತಂಡಗಳು ತಮ್ಮ ಮಾರುಕಟ್ಟೆ ಆಧಾರವನ್ನು ಇವೆ.एडवेंचर वर्क्स साइकिल, फर्जी कंपनी जिस पर एडवेंचर वर्क्स नमूना डेटाबेस आधारित हैं, एक बड़ी, बहुराष्ट्रीय कंपनी है। कंपनी उत्तर अमेरिकी, यूरोपीय और एशियाई वाणिज्यिक बाजारों में धातु और समग्र साइकिल का निर्माण और बिक्री करती है। जबकि इसके बेस ऑपरेशन 290 कर्मचारियों के साथ वाशिंगटन के बोथेल में है, कई क्षेत्रीय बिक्री टीम अपने बाजार आधार पर स्थित हैं।साहसी सायकल्स, साहसी कार्य करते जे नमुना डाटाबेस आधारित आहेत, एका मोठ्या, बहुराष्ट्रीय उत्पादन कंपनी आहे बोगस कंपनी काम करते. कंपनी उत्पादन उत्तर अमेरिकन, युरोपियन आणि आशियाई व्यावसायिक मार्केट धातू आणि संमिश्र सायकलींसाठी विकतो. त्याच्या बेस ऑपरेशन 290 कर्मचारी बोथेल्ल, वॉशिंग्टन आहे तर, अनेक प्रादेशिक विक्री संघ बाजार बेस संपूर्ण स्थित आहेत.ساہسک سائیکل، فرضی کمپنی کے ساہسک کام کرتا ہے جس پر نمونہ ڈیٹا بیس کی بنیاد پر کیا جاتا ہے، ایک بڑے، کثیر القومی مینوفیکچرنگ کمپنی ہے کام کرتا ہے. کمپنی کے تیار اور شمالی امریکی، یورپی اور ایشیائی تجارتی منڈیوں تک دھات اور جامع سائیکلوں فروخت کرتا ہے. اس کی بنیاد آپریشن 290 ملازمین کے ساتھ Bothell، واشنگٹن میں ہے، کئی علاقائی سیلز ٹیموں کو ان کے مارکیٹ بیس بھر میں واقع ہیں.સાહસિક કામ ચક્રો, કાલ્પનિક કંપની કે જેના પર સાહસ કામ નમૂના ડેટાબેઝ આધારિત છે, એક વિશાળ બહુરાષ્ટ્રીય ઉત્પાદન કંપની છે. કંપની ઉત્પાદન અને નોર્થ અમેરિકન, યુરોપિયન અને એશિયન વ્યાપારી બજારોમાં મેટલ અને સંયુક્ત સાયકલ વેચે છે. જ્યારે તેના બેઝ કામગીરી 290 કર્મચારીઓ સાથે બોથેલ, વોશિંગ્ટન છે, અનેક પ્રાદેશિક વેચાણ ટીમો તેમના બજાર આધાર સમગ્ર સ્થિત છે.அத்தியாவசிய DocIO சொந்த Word வடிவத்தில் முழுமையாக செயல்பாட்டு மைக்ரோசாப்ட் வேர்ட் ஆவணங்கள் உருவாக்குகிறது என்று ஒரு 100% சொந்த .NET நூலகமாகும். அத்தியாவசிய DocIO Microsoft Word கோப்புகளைத் படித்து எழுதப் பயன்படுத்தப்படுகிறது. அது மைக்ரோசாப்ட் ஆபிஸ் COM நூலகங்கள் ஒத்த ஒரு முழு நீள பொருள் மாதிரி கொண்டுள்ளது. அது COM இன்டராப் பயன்படுத்த முடியாது மற்றும் சி # புதிதாக கட்டப்பட்டுள்ளது. DocIO நூலகம் உட்பட சி #, VB.NET மற்றும் நிர்வகிக்கப்படும் சி ++ எந்த டாட்நெட் சூழலானது பயன்படுத்த முடியும். அது விண்டோஸ் படிவங்கள், விலத்துக்கொண்டதோடு, சில்வர்லைட், ASP.NET, ASP.NET MVC ஆனது மற்றும் விண்டோஸ் ஸ்டோர் பயன்பாடுகளில் பயன்படுத்தப்படுகிறது என்று ஒரு அல்லாத பயனர் இடைமுகம் அங்கமாகும்.അവശ്യ ദൊചിഒ നേറ്റീവ് വാക്ക് ഫോർമാറ്റിൽ പൂർണ്ണമായും പ്രവർത്തനക്ഷമമായിട്ടില്ല മൈക്രോസോഫ്റ്റ് പ്രമാണങ്ങൾ സൃഷ്ടിക്കുന്നത് 100% നേറ്റീവ് .NET ലൈബ്രറി. അവശ്യ ദൊചിഒ മൈക്രോസോഫ്റ്റ് വേഡ് ഫയലുകൾ എഴുതാനും വായിക്കാനും ഉപയോഗിക്കുന്നു. ഇത് മൈക്രോസോഫ്റ്റ് ഓഫീസ് കോം ലൈബ്രറികൾ സമാനമായ ഒരു സമ്പൂർണ വസ്തു മോഡൽ സവിശേഷതകൾ. ഇത് കോം Interop ഉപയോഗിക്കുന്നില്ല എന്ന്, C # ആദ്യം മുതൽ നിർമ്മിച്ചിരിക്കുന്നത്. ദൊചിഒ ലൈബ്രറി, ഉൾപ്പെടെ സി # ഏതെങ്കിലും .NET പരിസ്ഥിതി ഉപയോഗിക്കാൻ കഴിയും VB.NET ആൻഡ് സി ++ കൈകാര്യം. അതു, വ്പ്ഫ്, സിൽവർ, ASP.NET, ASP.NET മ്വ്ച്, Windows സ്റ്റോർ അപ്ലിക്കേഷനുകൾ വിൻഡോസ് ഫോമിൽ ഉപയോഗിക്കുന്ന ഒരു നോൺ-യുഐ ഘടകമാണ്.ఎసెన్షియల్ DocIO స్థానిక Word ఆకృతిలో పూర్తిగా ఫంక్షనల్ Microsoft Word డాక్యుమెంట్లు సృష్టించే ఒక 100% స్థానిక .NET గ్రంథాలయం. ఎసెన్షియల్ DocIO మైక్రోసాఫ్ట్ వర్డ్ ఫైళ్లు చదవడానికి మరియు వ్రాయడానికి ఉపయోగిస్తారు. ఇది మైక్రోసాఫ్ట్ ఆఫీస్ COM గ్రంధాలయాలు పోలి ఒక పూర్తి స్థాయి ఆబ్జెక్ట్ మోడల్ కలిగి ఉంది. ఇది COM ఇంటరాప్ ఉపయోగించడానికి లేదు మరియు C # మొదటి నుండి నిర్మించబడింది. DocIO లైబ్రరీ సహా సి #, VB.NET మరియు C నిర్వహించేది ++ .NET వాతావరణంలో ఉపయోగించవచ్చు. ఇది Windows పత్రాలు, WPF, Silverlight, ASP.NET, ASP.NET MVC, మరియు Windows స్టోర్ అప్లికేషన్లు ఉపయోగిస్తారు ఒక లాభాపేక్ష UI భాగం.ಎಸೆನ್ಷಿಯಲ್ DocIO ಸ್ಥಳೀಯ ಪದಗಳ ರೂಪದಲ್ಲಿ ಸಂಪೂರ್ಣವಾಗಿ ಕ್ರಿಯಾತ್ಮಕ Microsoft Word ಡಾಕ್ಯುಮೆಂಟ್ಗಳು ನಿರ್ಮಿಸುವ 100% ಸ್ಥಳೀಯ .NET ಗ್ರಂಥಾಲಯವಾಗಿದೆ. ಎಸೆನ್ಷಿಯಲ್ DocIO Microsoft Word ಫೈಲ್ಗಳನ್ನು ಓದಲು ಮತ್ತು ಬರೆಯಲು ಬಳಸಲಾಗುತ್ತದೆ. ಇದು ಮೈಕ್ರೋಸಾಫ್ಟ್ ಆಫೀಸ್ ವಾ ಗ್ರಂಥಾಲಯಗಳು ಹೋಲುವ ಪೂರ್ಣ ಪ್ರಮಾಣದ ವಸ್ತು ಮಾದರಿಯ ಒಳಗೊಂಡಿದೆ. ಇದು ವಾ Interop ಬಳಸುವುದಿಲ್ಲ ಮತ್ತು C # ಆರಂಭದಿಂದ ನಿರ್ಮಿಸಲಾಗಿದೆ. DocIO ಗ್ರಂಥಾಲಯದ ಸೇರಿದಂತೆ C # VB.NET ಮತ್ತು ವ್ಯವಸ್ಥಿತ C ++ ಯಾವುದೇ .NET ಪರಿಸರದಲ್ಲಿ ಬಳಸಬಹುದು. ಇದು ಇದೆ ವಿಂಡೋಸ್ ರೀತಿಗಳನ್ನು WPF, ಸಿಲ್ವರ್ಲೈಟ್ ASP.NET, ಆಗಿ ASP.NET MVC, ಮತ್ತು ವಿಂಡೋಸ್ ಅಂಗಡಿ ಅನ್ವಯಿಕೆಗಳಲ್ಲಿ ಬಳಸಲಾಗುತ್ತದೆ ಒಂದು ಅಲ್ಲದ ಯುಐ ಅಂಶವಾಗಿದೆ.आवश्यक DocIO एक 100% देशी .NET लाइब्रेरी है जो देशी वर्ड प्रारूप में पूरी तरह कार्यात्मक माइक्रोसॉफ्ट वर्ड दस्तावेज़ों को उत्पन्न करती है। आवश्यक दस्तावेज को माइक्रोसॉफ्ट वर्ड फाइल्स को पढ़ने और लिखने के लिए उपयोग किया जाता है। इसमें माइक्रोसॉफ्ट ऑफिस कॉम लाइब्रेरीज़ के समान एक पूर्ण ऑब्जेक्ट मॉडल है। यह COM इंटरॉप का उपयोग नहीं करता है और सी # में खरोंच से बनाया गया है। DocIO लाइब्रेरी को किसी भी। NET वातावरण में सी #, VB.NET और प्रबंधित C ++ सहित उपयोग किया जा सकता है। यह एक गैर-यूआई घटक है जिसे विंडोज फॉर्म, डब्लूपीएफ, सिल्वरलाइट, एएसपी.नेट, एएसपी.NET एमवीसी, और विंडोज स्टोर एप्लीकेशन में इस्तेमाल किया जाता है।ضروری DocIO اسے ورڈ فارمیٹ میں مکمل طور پر باضابطہ مائیکروسافٹ Word دستاویزات کو پیدا کرتا ہے کہ ایک 100٪ اسے .NET لائبریری ہے. ضروری DocIO مائیکروسافٹ ورڈ فائلوں کو پڑھنے اور لکھنے کے لئے استعمال کیا جاتا ہے. یہ مائیکروسافٹ آفس COM لائبریریوں کے لئے اسی طرح ایک مکمل آبجیکٹ ماڈل کی خصوصیات. اس COM انٹرآپ استعمال نہیں کرتا اور C # میں شروع سے بنایا گیا ہے. DocIO لائبریری سمیت C #، VB.NET اور منظم C + + کسی بھی نیٹ ماحول میں استعمال کیا جا سکتا ہے. یہ ایک غیر UI جزو ونڈوز فارم، WPF، سلور لائٹ، ASP.NET، ASP.NET MVC، اور ونڈوز سٹور ایپلی کیشنز میں استعمال کیا جاتا ہے ہے.模板在MS Word 2007中修改并保存PC的“Word 97-2003文档”,作者提供了准备电子版文所需的大部分格式化范。 经规定了所有准的纸张部件有三个原因:(1)格式化纸张时的易用性,(2)自符合促进电品同或以后生子要求,以及(3)整个格的一致性 议记录 内置距,列,行距和类型; 在本文档中提供了类型式的示例,并以示例中的括号内的斜体字标识 尽管提供了各种表格文本式,但是没有定一些件,例如多次方程,形和表格。 格式化程序将需要件,并合以下适用的准。கேன்வாஸ் டேக் முதல் IE9 இருந்து, அனைத்து உலாவிகளில் மற்றும் IE ஆதரவு. கேன்வாஸ் பயன்படுத்தி, நாம் ஒவ்வொரு பகுதியாக குறிப்பிட வேண்டும். உதாரணமாக, நாம் சுட்டியின் நிலையில் குறிப்பிட மற்றும் நகரும் காட்டிகள் அப்களை முத்தமிட்டு, அதற்கும் சற்று மற்றும் முன்னோக்கி, பின்தங்கிய திசைகளில் மற்றும் தேர்ந்தெடுத்தல் மீது வேண்டும். நிலை கேன்வாஸ் ஒரு முக்கிய பங்கு வகிக்கிறது. கேன்வாஸ் உள்ளே ஒரு HTML உறுப்பு நுழைக்க, நாம் படத்தை அது அமைக்க வேண்டும்.'; +// let buf: ArrayBuffer = new ArrayBuffer(s.length); +// let bufView = new Uint8Array(buf); +// for (let i: number = 0; i < s.length; i++) { +// bufView[i] = s.charCodeAt(i); +// } +// s = ''; +// let currentIndex: number = 0; +// let nextIndex: number = 0; +// do { +// if (currentIndex >= bufView.length) { +// compressedWriter.close(); +// break; +// } +// nextIndex = Math.min(bufView.length, currentIndex + 16384); +// let subArray: Uint8Array = bufView.subarray(currentIndex, nextIndex); +// compressedWriter.write(subArray, 0, nextIndex - currentIndex); +// currentIndex = nextIndex; +// } while (currentIndex <= bufView.length); +// compressedWriter.destroy(); +// expect(compressedWriter.compressedData).toBe(undefined); +// }); +// }); +// describe('Compress String', () => { +// it('Pass input as String', () => { +// let compress = new CompressedStreamWriter(); +// let text: string = 'Hello world!!!'; +// compress.write(text, 0, text.length); +// compress.close(); +// let compressedString = compress.getCompressedString; +// compress.destroy(); +// expect(compressedString).not.toBe(''); +// expect(compress.getCompressedString).toBe(''); +// }) +// it('Check Sum Calculator Constructor Testing', () => { +// expect(() => { let checkSum = new ChecksumCalculator(); }).not.toThrowError(); +// }); +// }); \ No newline at end of file diff --git a/controls/compression/spec/test.txt b/controls/compression/spec/test.txt new file mode 100644 index 0000000000..3f9e75c593 Binary files /dev/null and b/controls/compression/spec/test.txt differ diff --git a/controls/compression/spec/zip-archive.spec.ts b/controls/compression/spec/zip-archive.spec.ts new file mode 100644 index 0000000000..5ecee92c7e --- /dev/null +++ b/controls/compression/spec/zip-archive.spec.ts @@ -0,0 +1,385 @@ +import { ZipArchive, ZipArchiveItem, Utils } from '../src/index'; +import '../node_modules/es6-promise/dist/es6-promise'; +/** + * Zip Archive Spec + */ +describe('Create ZipArchive instance', (): void => { + beforeEach((): void => { + jasmine.DEFAULT_TIMEOUT_INTERVAL = 500; + }); + afterEach((): void => { + }); + it('Validate constructor', (done): void => { + /** + * instantiate ZipArchive class + */ + let archive: ZipArchive = new ZipArchive(); + setTimeout((): void => { + expect('').toBe(''); + done(); + }, 50); + }); + it('Validate constructor', (done): void => { + /** + * instantiate ZipArchive class + */ + let archive: ZipArchive = new ZipArchive(); + archive.destroy(); + setTimeout((): void => { + expect(archive.length).toBe(0); + done(); + }, 50); + }); +}); +describe('add directory method testing', () => { + let archive: ZipArchive; + beforeEach(() => { + archive = new ZipArchive(); + jasmine.DEFAULT_TIMEOUT_INTERVAL = 500; + }); + afterEach(() => { + archive.destroy(); + archive = undefined; + }); + it('directory name as null', () => { + expect(() => { archive.addDirectory(null) }).toThrowError(); + }); + it('directory name as undefined', () => { + expect(() => { archive.addDirectory(undefined) }).toThrowError(); + }); + it('directory name as empty', () => { + expect(() => { archive.addDirectory(''); }).toThrowError(); + }); + it('directory with valid string', () => { + expect(() => { archive.addDirectory('Sample/'); }).not.toThrowError(); + }); + it('directory with valid string', () => { + archive.addDirectory('Sample/'); + expect(() => { archive.addDirectory('Sample/'); }).toThrowError(); + }); + +}); +describe('create ZipArchiveItem instance', (): void => { + let archiveItem: ZipArchiveItem; + beforeEach((): void => { + jasmine.DEFAULT_TIMEOUT_INTERVAL = 500; + }); + afterEach((): void => { + if (archiveItem != undefined) { + archiveItem.destroy(); + } + archiveItem = undefined; + }); + it('zipArchiveItem constructor creation with itemName and data as null', () => { + expect(() => { archiveItem = new ZipArchiveItem(null, null); }).toThrowError(); + }); + it('zipArchiveItem constructor creation with itemName and data as undefined', () => { + expect(() => { archiveItem = new ZipArchiveItem(undefined, undefined); }).toThrowError(); + }); + it('zipArchiveItem constructor creation with itemName as null and data as valid', () => { + expect(() => { archiveItem = new ZipArchiveItem(new Blob(['sample']), null); }).toThrowError(); + }); + it('zipArchiveItem constructor creation with itemName as empty and data as valid', () => { + expect(() => { archiveItem = new ZipArchiveItem(new Blob(['sample']), ''); }).toThrowError(); + }); + it('zipArchiveItem constructor creation with itemName as empty and data as valid', () => { + expect(() => { archiveItem = new ZipArchiveItem(null, 'sample.txt'); }).toThrowError(); + }); + it('zipArchiveItem constructor creation with itemName as empty and data as valid', () => { + expect(() => { archiveItem = new ZipArchiveItem(undefined, 'sample.txt'); }).toThrowError(); + }); + it('zipArchiveItem constructor creation with itemName as valid and data as valid', () => { + archiveItem = new ZipArchiveItem(new Blob(['sample']), 'sampleFile'); + let archive: ZipArchive = new ZipArchive(); + archive.addItem(archiveItem); + archive.saveAsBlob().then((obj) => { + expect(obj).toBeDefined() + archive.destroy(); + }); + expect(archiveItem.name).toBe('sampleFile'); + }, 100); + it('zipArchiveItem constructor validation', () => { + archiveItem = new ZipArchiveItem(new Blob(['compression']), 'sample.txt'); + expect(archiveItem.name).toBe('sample.txt'); + }); + it('zipArchiveItem constructor creation with data as arrayBuffer', () => { + archiveItem = new ZipArchiveItem(new Uint8Array(2), 'sample.txt'); + expect(archiveItem.data instanceof Uint8Array).toBe(true); + }); +}); +describe('add item method testing', () => { + let archiveItem: ZipArchiveItem; + let archive: ZipArchive; + beforeEach((): void => { + archive = new ZipArchive(); + archiveItem = new ZipArchiveItem(new Blob(['sample compression']), 'sample.txt'); + jasmine.DEFAULT_TIMEOUT_INTERVAL = 500; + }); + afterEach((): void => { + archiveItem.destroy(); + archiveItem = undefined; + }); + it('item as null', () => { + expect(() => { archive.addItem(null); }).toThrowError(); + }); + it('item as undefined', () => { + expect(() => { archive.addItem(undefined); }).toThrowError(); + }); + it('item as valid value', () => { + archive.addItem(archiveItem); + expect(archiveItem.name).toBe('sample.txt'); + }); + it('gets file length', () => { + archive = new ZipArchive(); + archive.addDirectory('word'); + archiveItem = new ZipArchiveItem(new Blob(['Xml'], { type: "text/plain" }), 'document.xml'); + archive.addItem(archiveItem); + expect(archive.length).toBe(2); + expect(archive.contains(archiveItem)).toBe(true); + let item: ZipArchiveItem = archive.getItem(1); + expect(item == archiveItem).toBe(true); + }); + it('gets file length not in the collection', () => { + archive = new ZipArchive(); + archive.addDirectory('word'); + archiveItem = new ZipArchiveItem(new Blob(['Xml'], { type: "text/plain" }), 'document.xml'); + let item: ZipArchiveItem = archive.getItem(2); + let item1 = archive.getItem(-1); + expect(item1).toBe(undefined); + expect(item).toBe(undefined); + expect(archive.contains(archiveItem)).toBe(false); + }); +}); +describe('save method and small size document testing', () => { + let archive: ZipArchive; + let archiveItem: ZipArchiveItem; + beforeEach((): void => { + archive = new ZipArchive(); + jasmine.DEFAULT_TIMEOUT_INTERVAL = 500; + }); + afterEach((): void => { + archive.destroy(); + archive = undefined; + if (archiveItem !== undefined) { + archiveItem.destroy(); + } + archiveItem = undefined; + }); + it('public method testing', (done) => { + archive.addDirectory('sample'); + archive.compressionLevel = 'NoCompression'; + archiveItem = new ZipArchiveItem(new Blob(['compression']), 'sample/textFile.txt'); + archive.addItem(archiveItem); + archive.saveAsBlob(); + setTimeout(() => { + expect('').toBe(''); + done(); + }, 50); + }); + it('add directory method testing', () => { + archive.addDirectory('sample/'); + archiveItem = new ZipArchiveItem(new Blob(['compression']), 'sample//textFile.txt'); + archive.addItem(archiveItem); + expect(archiveItem.name).toBe('sample//textFile.txt'); + }); + it('text file input as empty string ', () => { + archiveItem = new ZipArchiveItem(new Blob(['']), 'textFile.txt'); + archive.compressionLevel = 'NoCompression'; + archive.addItem(archiveItem); + archive.saveAsBlob(); + expect(archive.compressionLevel).toBe('NoCompression'); + }, 100); + it('save method with null parameter', () => { + expect(() => { archive.save(null) }).toThrowError(); + }); + it('Duplicate Item', () => { + archiveItem = new ZipArchiveItem(new Blob(['']), 'textFile.txt'); + archive.compressionLevel = 'NoCompression'; + archive.addItem(archiveItem); + expect(() => { archive.addItem(archiveItem); }).toThrowError(); + }, 100); + it('save method with undefined parameter', (done) => { + expect(() => { archive.save(undefined) }).toThrowError(); + done(); + }, 100); + it('save method with undefined parameter', (done) => { + expect(() => { archive.save('sample.zip') }).toThrowError(); + done(); + }, 100); + it('Blob with empty string', () => { + archive.addDirectory('sample/'); + archive.compressionLevel = 'NoCompression'; + archiveItem = new ZipArchiveItem(new Blob(['']), 'sample//textFile.txt'); + archive.addItem(archiveItem); + expect(archiveItem.name).toBe('sample//textFile.txt'); + expect(archive.compressionLevel).toBe('NoCompression'); + }); + it('Blob with valid string', (done) => { + archive.addDirectory('sample/'); + archive.compressionLevel = 'NoCompression'; + archiveItem = new ZipArchiveItem(new Blob(['document']), 'sample//textFile.txt'); + let archiveItem1 = new ZipArchiveItem(new Blob(['document']), 'sample//textFile1.txt'); + archive.addItem(archiveItem); + archive.addItem(archiveItem1); + archive.saveAsBlob().then(() => { + expect('').toBe(''); + done(); + }); + }, 100); +}); +describe('file and folder creation with No compression', () => { + let archive: ZipArchive; + beforeEach(() => { + archive = new ZipArchive(); + archive.compressionLevel = 'NoCompression'; + jasmine.DEFAULT_TIMEOUT_INTERVAL = 500; + }); + afterEach(() => { + archive.destroy(); + archive = undefined; + }); + it('Single file creation with No compression', () => { + let archiveItem: ZipArchiveItem = new ZipArchiveItem(new Blob(['Sample text file']), 'sample.txt'); + archive.addItem(archiveItem); + archive.saveAsBlob(); + expect(archive.compressionLevel).toBe('NoCompression'); + expect(archiveItem.name).toBe('sample.txt'); + }, 200); + it('Single folder and single file creation with No compression', () => { + archive.addDirectory('folder'); + let archiveItem: ZipArchiveItem = new ZipArchiveItem(new Blob(['Sample text file']), 'folder/sample.txt'); + archive.addItem(archiveItem); + archive.saveAsBlob(); + expect(archive.compressionLevel).toBe('NoCompression'); + expect(archiveItem.name).toBe('folder/sample.txt'); + }, 200); + it('Multiple file Creations with no compression', () => { + let archiveItem1: ZipArchiveItem = new ZipArchiveItem(new Blob(['Sample text file']), 'sample1.txt'); + let archiveItem2: ZipArchiveItem = new ZipArchiveItem(new Blob(['Sample text file']), 'sample2.txt'); + let archiveItem3: ZipArchiveItem = new ZipArchiveItem(new Blob(['Sample text file']), 'sample3.txt'); + archive.addItem(archiveItem1); + archive.addItem(archiveItem2); + archive.addItem(archiveItem3); + archive.saveAsBlob(); + expect(archive.compressionLevel).toBe('NoCompression'); + }, 200); + it('subFolder creation and subFolder contains files', () => { + archive.addDirectory('folder'); + archive.addDirectory('folder/subFolder'); + archive.addDirectory('folder/subFolder/subFolder2'); + let archiveItem1: ZipArchiveItem = new ZipArchiveItem(new Blob(['Sample text file']), 'folder/subFolder/sample1.txt'); + let archiveItem2: ZipArchiveItem = new ZipArchiveItem(new Blob(['Sample text file']), 'folder/subFolder/sample2.txt'); + let archiveItem3: ZipArchiveItem = new ZipArchiveItem(new Blob(['Sample text file']), 'folder/subFolder/subFolder2/sample3.txt'); + archive.addItem(archiveItem1); + archive.addItem(archiveItem2); + archive.addItem(archiveItem3); + archive.saveAsBlob(); + expect(archive.compressionLevel).toBe('NoCompression'); + expect(archiveItem3.name).toBe('folder/subFolder/subFolder2/sample3.txt'); + }, 200); + it('Compression for small size document testing with No Compression', (done) => { + let zip: ZipArchive = new ZipArchive(); + zip.compressionLevel = 'NoCompression'; + let blob: Blob = new Blob([''], { type: 'text/plain' }); + let rels: Blob = new Blob([''], { type: 'text/plain' }) + let core: Blob = new Blob(['Syncfusion Software Pvt Ltd AnbalaganSyncfusion Anbalagan52017-03-29T16:00:00Z2017-03-29T16:00:00Z'], { type: 'text/plain' }); + let app: Blob = new Blob(['0119Microsoft Office Word011falsefalse9falsefalse16.0000'], { type: 'text/plain' }) + let documrels = new Blob([''], { type: 'text/plain' }) + let theme = new Blob([''], { type: 'text/plain' }) + let document = new Blob(['Syncfusion Software Pvt Ltd'], { type: 'test/plain' }); + let fontTable = new Blob([''], { type: 'text\plain' }); + let settings = new Blob([''], { type: 'text\plain' }); + let s: string = ''; + s += ''; + s += ''; + s += ''; + let style = new Blob([s], { type: 'text/plain' }); + let webSettings = new Blob([''], { type: 'text\plain' }); + let zipItem = new ZipArchiveItem(blob, '[Content_Types].xml'); + let relsItem = new ZipArchiveItem(rels, '_rels/.rels'); + let coreItem = new ZipArchiveItem(core, 'docProps/core.xml'); + let appItem = new ZipArchiveItem(app, 'docProps/app.xml'); + let docurelsIt = new ZipArchiveItem(documrels, 'word/_rels/document.xml.rels') + let themeItem = new ZipArchiveItem(theme, 'word/theme/theme1.xml'); + let documentItem = new ZipArchiveItem(document, 'word/document.xml'); + let fontTableItem = new ZipArchiveItem(fontTable, 'word/fontTable.xml'); + let settingsItem = new ZipArchiveItem(settings, 'word/settings.xml'); + let styleItem = new ZipArchiveItem(style, 'word/styles.xml'); + let wecse = new ZipArchiveItem(webSettings, 'word/webSettings.xml'); + zip.addDirectory('_rels'); + zip.addDirectory('docProps'); + zip.addDirectory('word'); + zip.addDirectory('word/_rels'); + zip.addDirectory('word/theme'); + zip.addItem(appItem); + zip.addItem(coreItem); + zip.addItem(relsItem); + zip.addItem(zipItem); + zip.addItem(docurelsIt); + zip.addItem(themeItem); + zip.addItem(documentItem); + zip.addItem(fontTableItem); + zip.addItem(settingsItem); + zip.addItem(styleItem); + zip.addItem(wecse); + zip.saveAsBlob(); + setTimeout((): void => { + expect('').toBe(''); + done(); + }, 50); + }, 200); +}); +describe('file and folder creation with Normal compression', () => { + let archive: ZipArchive; + beforeEach(() => { + archive = new ZipArchive(); + archive.compressionLevel = 'Normal'; + jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000; + }); + afterEach(() => { + archive.destroy(); + archive = undefined + }); + it('Single file creation with No compression', () => { + let archiveItem: ZipArchiveItem = new ZipArchiveItem(new Blob(['Sample text file']), 'sample.txt'); + archive.compressionLevel = 'Normal'; + archive.addItem(archiveItem); + archive.saveAsBlob(); + expect(archive.compressionLevel).toBe('Normal'); + }, 200); + it('Single folder and single file creation with Normal', () => { + archive.addDirectory('folder'); + let archiveItem: ZipArchiveItem = new ZipArchiveItem(new Blob(['Sample text file']), 'folder/sample.txt'); + archive.compressionLevel = 'Normal'; + archive.addItem(archiveItem); + archive.saveAsBlob(); + expect(archive.compressionLevel).toBe('Normal'); + expect(archiveItem.name).toBe('folder/sample.txt'); + }, 200); + it('Multiple file Creations with no compression', () => { + let archiveItem1: ZipArchiveItem = new ZipArchiveItem(new Blob(['Sample text file']), 'sample1.txt'); + let archiveItem2: ZipArchiveItem = new ZipArchiveItem(new Blob(['Sample text file']), 'sample2.txt'); + let archiveItem3: ZipArchiveItem = new ZipArchiveItem(new Blob(['Sample text file']), 'sample3.txt'); + archive.addItem(archiveItem1); + archive.addItem(archiveItem2); + archive.addItem(archiveItem3); + archive.saveAsBlob(); + expect(archive.compressionLevel).toBe('Normal'); + }, 200); + it('subfolder creation and subfolder contains files', () => { + archive.addDirectory('folder'); + archive.addDirectory('folder/subfolder'); + archive.addDirectory('folder/subfolder/subfolder2'); + let archiveItem1: ZipArchiveItem = new ZipArchiveItem(new Blob(['Sample text file']), 'folder/subfolder/sample1.txt'); + let archiveItem2: ZipArchiveItem = new ZipArchiveItem(new Blob(['Sample text file']), 'folder/subfolder/sample2'); + let archiveItem3: ZipArchiveItem = new ZipArchiveItem(new Blob(['Sample text file']), 'folder/subfolder/subfolder2/sample3.txt'); + archive.addItem(archiveItem1); + archive.addItem(archiveItem2); + archive.addItem(archiveItem3); + archive.saveAsBlob(); + expect(archive.compressionLevel).toBe('Normal'); + expect(archiveItem1.name).toBe('folder/subfolder/sample1.txt'); + }, 200); + +}); + diff --git a/controls/compression/src/checksum-calculator.ts b/controls/compression/src/checksum-calculator.ts new file mode 100644 index 0000000000..38660d84e3 --- /dev/null +++ b/controls/compression/src/checksum-calculator.ts @@ -0,0 +1,61 @@ +/* eslint-disable */ +/// +/// Checksum calculator, based on Adler32 algorithm. +/// +export class ChecksumCalculator { + /// + /// Bits offset, used in adler checksum calculation. + /// + private static DEF_CHECKSUM_BIT_OFFSET: number = 16; + /// + /// Lagrest prime, less than 65535 + /// + private static DEF_CHECKSUM_BASE: number = 65521; + /// + /// Count of iteration used in calculated of the adler checksumm. + /// + private static DEF_CHECKSUM_ITERATIONSCOUNT: number = 3800; + + /// + /// Updates checksum by calculating checksum of the + /// given buffer and adding it to current value. + /// + /// Current checksum. + /// Data byte array. + /// Offset in the buffer. + /// Length of data to be used from the stream. + public static ChecksumUpdate(checksum: number, buffer: Uint8Array, offset: number, length: number): void { + let checkSumUInt: number = checksum; + let s1: number = checkSumUInt & 65535; + let s2: number = checkSumUInt >> this.DEF_CHECKSUM_BIT_OFFSET; + + while (length > 0) { + let steps: number = Math.min(length, this.DEF_CHECKSUM_ITERATIONSCOUNT); + length -= steps; + + while (--steps >= 0) { + s1 = s1 + (buffer[offset++] & 255); + s2 = s2 + s1; + } + + s1 %= this.DEF_CHECKSUM_BASE; + s2 %= this.DEF_CHECKSUM_BASE; + } + + checkSumUInt = (s2 << this.DEF_CHECKSUM_BIT_OFFSET) | s1; + checksum = checkSumUInt; + } + /// + /// Generates checksum by calculating checksum of the + /// given buffer. + /// + /// Data byte array. + /// Offset in the buffer. + /// Length of data to be used from the stream. + public static ChecksumGenerate(buffer: Uint8Array, offset: number, length: number): number { + const result: number = 1; + ChecksumCalculator.ChecksumUpdate(result, buffer, offset, length); + return result; + } +} +/* eslint-enable */ \ No newline at end of file diff --git a/controls/compression/src/compression-reader.ts b/controls/compression/src/compression-reader.ts new file mode 100644 index 0000000000..f67195f2f1 --- /dev/null +++ b/controls/compression/src/compression-reader.ts @@ -0,0 +1,891 @@ +/* eslint-disable */ +import { DecompressorHuffmanTree } from './decompressor-huffman-tree'; +import { Utils } from './utils'; +import { ChecksumCalculator } from './checksum-calculator'; +export class CompressedStreamReader { + private static readonly DEF_REVERSE_BITS: Uint8Array = new Uint8Array([0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15]); + + /// + /// Code lengths for the code length alphabet. + /// + public defaultHuffmanDynamicTree: number[] = [16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15]; + /// + /// Mask for compression method to be decoded from 16-bit header. + /// + private DEF_HEADER_METHOD_MASK: number = 15 << 8; + /// + /// Mask for compression info to be decoded from 16-bit header. + /// + private DEF_HEADER_INFO_MASK: number = 240 << 8; + /// + /// Mask for check bits to be decoded from 16-bit header. + /// + private DEF_HEADER_FLAGS_FCHECK: number = 31; + /// + /// Mask for dictionary presence to be decoded from 16-bit header. + /// + private DEF_HEADER_FLAGS_FDICT: number = 32; + /// + /// Mask for compression level to be decoded from 16-bit header. + /// + private DEF_HEADER_FLAGS_FLEVEL: number = 192; + + /// + /// Minimum count of repetions. + /// + private static readonly DEF_HUFFMAN_DYNTREE_REPEAT_MINIMUMS: number[] = [3, 3, 11]; + + /// + /// Bits, that responds for different repetion modes. + /// + private static readonly DEF_HUFFMAN_DYNTREE_REPEAT_BITS: number[] = [2, 3, 7]; + + /// + /// Maximum size of the data window. + /// + private DEF_MAX_WINDOW_SIZE: number = 65535; + + /// + /// Length bases. + /// + private static readonly DEF_HUFFMAN_REPEAT_LENGTH_BASE: number[] = + [ + 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, + 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258 + ]; + + /// + /// Length extended bits count. + /// + private static readonly DEF_HUFFMAN_REPEAT_LENGTH_EXTENSION: number[] = + [ + 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, + 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0 + ]; + + /// + /// Distance bases. + /// + private static readonly DEF_HUFFMAN_REPEAT_DISTANCE_BASE: number[] = + [ + 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, + 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, + 8193, 12289, 16385, 24577 + ]; + + /// + /// Distance extanded bits count. + /// + private static readonly DEF_HUFFMAN_REPEAT_DISTANCE_EXTENSION: number[] = + [ + 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, + 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, + 12, 12, 13, 13 + ]; + + /// + /// Maximum length of the repeatable block. + /// + private DEF_HUFFMAN_REPEATE_MAX: number = 258; + /// + /// End of the block sign. + /// + private DEF_HUFFMAN_END_BLOCK: number = 256; + /// + /// Minimal length code. + /// + private DEF_HUFFMAN_LENGTH_MINIMUMCODE: number = 257; + /// + /// Maximal length code. + /// + private DEF_HUFFMAN_LENGTH_MAXIMUMCODE: number = 285; + /// + /// Maximal distance code. + /// + private DEF_HUFFMAN_DISTANCE_MAXIMUMCODE: number = 29; + + /// + /// Input stream. + /// + private mInputStream: Stream; + + /// + /// Currently calculated checksum, + /// based on Adler32 algorithm. + /// + private mCheckSum: number = 1; + + /// + /// Currently read 4 bytes. + /// + private tBuffer: number = 0; + public get mBuffer(): number { + return this.tBuffer; + } + public set mBuffer(value: number) { + this.tBuffer = value; + } + + /// + /// Count of bits that are in buffer. + /// + private mBufferedBits: number = 0; + + /// + /// Temporary buffer. + /// + private mTempBuffer: Uint8Array = new Uint8Array(4); + + /// + /// 32k buffer for unpacked data. + /// + private mBlockBuffer: Uint8Array = new Uint8Array(this.DEF_MAX_WINDOW_SIZE); + + /// + /// No wrap mode. + /// + private mbNoWrap: boolean = false; + + /// + /// Window size, can not be larger than 32k. + /// + private mWindowSize: number = 0; + + /// + /// Current position in output stream. + /// Current in-block position can be extracted by applying Int16.MaxValue mask. + /// + private mCurrentPosition: number = 0; + + /// + /// Data length. + /// Current in-block position can be extracted by applying Int16.MaxValue mask. + /// + private mDataLength: number = 0; + + /// + /// Sign of uncompressed data reading. + /// + private mbReadingUncompressed: boolean; + + /// + /// Size of the block with uncompressed data. + /// + private mUncompressedDataLength: number; + + /// + /// Specifies wheather next block can to be read. + /// Reading can be denied because the header of the last block have been read. + /// + private mbCanReadNextBlock: boolean = true; + + /// + /// Specifies wheather user can read more data from stream. + /// + private mbCanReadMoreData: boolean = true; + + /// + /// Current lengths huffman tree. + /// + private mCurrentLengthTree: DecompressorHuffmanTree; + + /// + /// Current distances huffman tree. + /// + private mCurrentDistanceTree: DecompressorHuffmanTree; + + /// + /// Specifies wheather checksum has been read. + /// + private mbCheckSumRead: boolean = false; + /** + * Initializes compressor and writes ZLib header if needed. + * @param {boolean} noWrap - optional if true, ZLib header and checksum will not be written. + */ + + + + /// + /// Reads specified count of bits without adjusting position. + /// + /// Count of bits to be read. + /// Read value. + public peekBits(count: number): number { + if (count < 0) { + throw new DOMException('count', 'Bits count can not be less than zero.'); + } + + if (count > 32) { + throw new DOMException('count', 'Count of bits is too large.'); + } + + // If buffered data is not enough to give result, + // fill buffer. + if (this.mBufferedBits < count) { + this.fillBuffer(); + } + + // If you want to read 4 bytes and there is partial data in + // buffer, than you will fail. + if (this.mBufferedBits < count) { + return -1; + } + + // Create bitmask for reading of count bits + const bitMask: number = ~(4294967295 << count); + + const result: number = Utils.bitConverterUintToInt32(this.mBuffer & bitMask); + + //Debug.WriteLine( /*new string( ' ', 32 - mBufferedBits + (int)( ( 32 - mBufferedBits ) / 8 ) ) + BitsToString( (int)mBuffer, mBufferedBits ) + " " + BitsToString( result, count ) +*/ " " + result.ToString() ); + + return result; + } + protected fillBuffer(): void { + const length: number = 4 - (this.mBufferedBits >> 3) - + (((this.mBufferedBits & 7) !== 0) ? 1 : 0); + + if (length === 0) { + return; + } + //TODO: fix this + const bytesRead: number = this.mInputStream.read(this.mTempBuffer, 0, length); + for (let i: number = 0; i < bytesRead; i++) { + this.mBuffer = Utils.bitConverterInt32ToUint(this.mBuffer | + (Utils.bitConverterInt32ToUint(this.mTempBuffer[i] << this.mBufferedBits))); + this.mBufferedBits += 8; + } + //TODO: fix this + } + /// + /// Skips specified count of bits. + /// + /// Count of bits to be skipped. + public skipBits(count: number): void { + if (count < 0) { + throw new DOMException('count', 'Bits count can not be less than zero.'); + } + + if (count === 0) { + return; + } + + if (count >= this.mBufferedBits) { + count -= this.mBufferedBits; + this.mBufferedBits = 0; + this.mBuffer = 0; + + // if something left, skip it. + if (count > 0) { + // Skip entire bytes. + this.mInputStream.position += (count >> 3); //TODO: fix this + count &= 7; + + // Skip bits. + if (count > 0) { + this.fillBuffer(); + this.mBufferedBits -= count; + this.mBuffer = Utils.bitConverterInt32ToUint(this.mBuffer >>> count); + } + } + } + else { + this.mBufferedBits -= count; + this.mBuffer = Utils.bitConverterInt32ToUint(this.mBuffer >>> count); + } + } + public get availableBits(): number { + return this.mBufferedBits; + + } + + constructor(stream: Uint8Array, bNoWrap: boolean) { + + if (stream == null) { + throw new DOMException('stream'); + } + + if (stream.length === 0) { + throw new DOMException('stream - string can not be empty'); + } + + DecompressorHuffmanTree.init(); + + this.mInputStream = new Stream(stream); + this.mbNoWrap = bNoWrap; + + if (!this.mbNoWrap) { + this.readZLibHeader(); + } + + this.decodeBlockHeader(); + } + + /// + /// Reads ZLib header with compression method and flags. + /// + protected readZLibHeader(): void { + // first 8 bits - compression Method and flags + // 8 other - flags + const header: number = this.readInt16(); + + //Debug.WriteLine( BitsToString( header ) ); + + if (header === -1) { + throw new DOMException('Header of the stream can not be read.'); + } + + if (header % 31 !== 0) { + throw new DOMException('Header checksum illegal'); + } + + if ((header & this.DEF_HEADER_METHOD_MASK) !== (8 << 8)) { + throw new DOMException('Unsupported compression method.'); + } + + this.mWindowSize = Math.pow(2, ((header & this.DEF_HEADER_INFO_MASK) >> 12) + 8); + + if (this.mWindowSize > 65535) { + throw new DOMException('Unsupported window size for deflate compression method.'); + } + + if ((header & this.DEF_HEADER_FLAGS_FDICT) >> 5 === 1) { + // Get dictionary. + throw new DOMException('Custom dictionary is not supported at the moment.'); + } + + } + /// + /// TODO: place correct comment here + /// + /// + /// TODO: place correct comment here + /// + protected readInt16(): number { + let result: number = (this.readBits(8) << 8); + result |= this.readBits(8); + return result; + } + /// + /// Reads specified count of bits from stream. + /// + /// Count of bits to be read. + /// + /// TODO: place correct comment here + /// + protected readBits(count: number): number { + const result: number = this.peekBits(count); + + if (result === -1) { + return -1; + } + + this.mBufferedBits -= count; + this.mBuffer = Utils.bitConverterInt32ToUint(this.mBuffer >>> count); + return result; + } + /// + /// Reads and decodes block of data. + /// + /// True if buffer was empty and new data was read, otherwise - False. + protected decodeBlockHeader(): boolean { + if (!this.mbCanReadNextBlock) { + return false; + } + + const bFinalBlock: number = this.readBits(1); + if (bFinalBlock === -1) { + return false; + } + + const blockType: number = this.readBits(2); + if (blockType === -1) { + return false; + } + + this.mbCanReadNextBlock = (bFinalBlock === 0); + // ChecksumReset(); + + switch (blockType) { + case 0: + // Uncompressed data + this.mbReadingUncompressed = true; + + this.skipToBoundary(); + const length: number = this.readInt16Inverted(); + const lengthComplement: number = this.readInt16Inverted(); + + if (length !== (lengthComplement ^ 0xffff)) { + throw new DOMException('Wrong block length.'); + } + + if (length > 65535) { + throw new DOMException('Uncompressed block length can not be more than 65535.'); + } + + this.mUncompressedDataLength = length; + this.mCurrentLengthTree = null; + this.mCurrentDistanceTree = null; + break; + + case 1: + // Compressed data with fixed huffman codes. + this.mbReadingUncompressed = false; + this.mUncompressedDataLength = -1; + this.mCurrentLengthTree = DecompressorHuffmanTree.lengthTree; + this.mCurrentDistanceTree = DecompressorHuffmanTree.distanceTree; + break; + + case 2: + // Compressed data with dynamic huffman codes. + this.mbReadingUncompressed = false; + this.mUncompressedDataLength = -1; + const trees: any = this.decodeDynamicHeader(this.mCurrentLengthTree, this.mCurrentDistanceTree); + this.mCurrentLengthTree = trees.lengthTree; + this.mCurrentDistanceTree = trees.distanceTree; + break; + + default: + throw new DOMException('Wrong block type.'); + } + + return true; + } + /// + /// Discards left-most partially used byte. + /// + protected skipToBoundary(): void { + this.mBuffer = Utils.bitConverterInt32ToUint(this.mBuffer >>> (this.mBufferedBits & 7)); + this.mBufferedBits &= ~7; + } + /// + /// TODO: place correct comment here + /// + /// + /// TODO: place correct comment here + /// + protected readInt16Inverted(): number { + let result: number = (this.readBits(8)); + result |= this.readBits(8) << 8; + return result; + } + /// + /// Reades dynamic huffman codes from block header. + /// + /// Literals/Lengths tree. + /// Distances tree. + protected decodeDynamicHeader(lengthTree: DecompressorHuffmanTree, distanceTree: DecompressorHuffmanTree): any { + let bLastSymbol: number = 0; + let iLengthsCount: number = this.readBits(5); + let iDistancesCount: number = this.readBits(5); + let iCodeLengthsCount: number = this.readBits(4); + + if (iLengthsCount < 0 || iDistancesCount < 0 || iCodeLengthsCount < 0) { + throw new DOMException('Wrong dynamic huffman codes.'); + } + + iLengthsCount += 257; + iDistancesCount += 1; + + const iResultingCodeLengthsCount: number = iLengthsCount + iDistancesCount; + const arrResultingCodeLengths: Uint8Array = new Uint8Array(iResultingCodeLengthsCount); + const arrDecoderCodeLengths: Uint8Array = new Uint8Array(19); + iCodeLengthsCount += 4; + let iCurrentCode: number = 0; + + while (iCurrentCode < iCodeLengthsCount) { + const len: number = this.readBits(3); + + if (len < 0) { + throw new DOMException('Wrong dynamic huffman codes.'); + } + + arrDecoderCodeLengths[this.defaultHuffmanDynamicTree[iCurrentCode++]] = len; + } + + const treeInternalDecoder: DecompressorHuffmanTree = new DecompressorHuffmanTree(arrDecoderCodeLengths); + + iCurrentCode = 0; + + for (; ;) { + let symbol: number; + let bNeedBreak: boolean = false; + symbol = treeInternalDecoder.unpackSymbol(this); + while ((symbol & ~15) === 0) { + arrResultingCodeLengths[iCurrentCode++] = bLastSymbol = symbol; + + if (iCurrentCode === iResultingCodeLengthsCount) { + bNeedBreak = true; + break; + } + symbol = treeInternalDecoder.unpackSymbol(this); + } + + if (bNeedBreak) { + break; + } + + if (symbol < 0) { + throw new DOMException('Wrong dynamic huffman codes.'); + } + + if (symbol >= 17) { + bLastSymbol = 0; + } + else if (iCurrentCode === 0) { + throw new DOMException('Wrong dynamic huffman codes.'); + } + + const miRepSymbol: number = symbol - 16; + const bits: number = CompressedStreamReader.DEF_HUFFMAN_DYNTREE_REPEAT_BITS[miRepSymbol]; + + let count: number = this.readBits(bits); + + if (count < 0) { + throw new DOMException('Wrong dynamic huffman codes.'); + } + + count += CompressedStreamReader.DEF_HUFFMAN_DYNTREE_REPEAT_MINIMUMS[miRepSymbol]; + + if (iCurrentCode + count > iResultingCodeLengthsCount) { + throw new DOMException('Wrong dynamic huffman codes.'); + } + + while (count-- > 0) { + arrResultingCodeLengths[iCurrentCode++] = bLastSymbol; + } + + if (iCurrentCode === iResultingCodeLengthsCount) { + break; + } + } + + let tempArray: Uint8Array = new Uint8Array(iLengthsCount); + + tempArray.set(arrResultingCodeLengths.subarray(0, iLengthsCount), 0); + //sourceArray, sourceIndex, destinationArray, destinationIndex, length + //Array.copy( arrResultingCodeLengths, 0, tempArray, 0, iLengthsCount ); + lengthTree = new DecompressorHuffmanTree(tempArray); + + tempArray = arrResultingCodeLengths.slice(iLengthsCount, iLengthsCount + iDistancesCount); + + //Array.copy( arrResultingCodeLengths, iLengthsCount, tempArray, 0, iDistancesCount ); + distanceTree = new DecompressorHuffmanTree(tempArray); + return { 'lengthTree': lengthTree, 'distanceTree': distanceTree }; + } + /// + /// Decodes huffman codes. + /// + /// True if some data was read. + private readHuffman(): boolean { + let free: number = this.DEF_MAX_WINDOW_SIZE - (this.mDataLength - this.mCurrentPosition); + let dataRead: boolean = false; + //long maxdistance = DEF_MAX_WINDOW_SIZE >> 1; + const readdata: any = {}; + // DEF_HUFFMAN_REPEATE_MAX - longest repeatable block, we should always reserve space for it because + // if we should not, we will have buffer overrun. + while (free >= this.DEF_HUFFMAN_REPEATE_MAX) { + let symbol: number; + symbol = this.mCurrentLengthTree.unpackSymbol(this); + // Only codes 0..255 are valid independent symbols. + while (((symbol) & ~0xff) === 0) { + readdata[(this.mDataLength + 1) % this.DEF_MAX_WINDOW_SIZE] = symbol; + this.mBlockBuffer[this.mDataLength++ % this.DEF_MAX_WINDOW_SIZE] = symbol; + dataRead = true; + + if (--free < this.DEF_HUFFMAN_REPEATE_MAX) { + return true; + } + + //if( (mDataLength - mCurrentPosition ) < maxdistance ) return true; + symbol = this.mCurrentLengthTree.unpackSymbol(this); + } + + if (symbol < this.DEF_HUFFMAN_LENGTH_MINIMUMCODE) { + if (symbol < this.DEF_HUFFMAN_END_BLOCK) { + throw new DOMException('Illegal code.'); + } + const numDataRead: number = dataRead ? 1 : 0; + this.mbCanReadMoreData = this.decodeBlockHeader(); + const numReadMore: number = (this.mbCanReadMoreData) ? 1 : 0; + return (numDataRead | numReadMore) ? true : false; + } + + if (symbol > this.DEF_HUFFMAN_LENGTH_MAXIMUMCODE) { + throw new DOMException('Illegal repeat code length.'); + } + + let iRepeatLength: number = CompressedStreamReader.DEF_HUFFMAN_REPEAT_LENGTH_BASE[symbol - + this.DEF_HUFFMAN_LENGTH_MINIMUMCODE]; + + let iRepeatExtraBits: number = CompressedStreamReader.DEF_HUFFMAN_REPEAT_LENGTH_EXTENSION[symbol - + this.DEF_HUFFMAN_LENGTH_MINIMUMCODE]; + + if (iRepeatExtraBits > 0) { + const extra: number = this.readBits(iRepeatExtraBits); + + if (extra < 0) { + throw new DOMException('Wrong data.'); + } + + iRepeatLength += extra; + } + + // Unpack repeat distance. + symbol = this.mCurrentDistanceTree.unpackSymbol(this); + + if (symbol < 0 || symbol > CompressedStreamReader.DEF_HUFFMAN_REPEAT_DISTANCE_BASE.length) { + throw new DOMException('Wrong distance code.'); + } + + let iRepeatDistance: number = CompressedStreamReader.DEF_HUFFMAN_REPEAT_DISTANCE_BASE[symbol]; + iRepeatExtraBits = CompressedStreamReader.DEF_HUFFMAN_REPEAT_DISTANCE_EXTENSION[symbol]; + + if (iRepeatExtraBits > 0) { + const extra: number = this.readBits(iRepeatExtraBits); + + if (extra < 0) { + throw new DOMException('Wrong data.'); + } + + iRepeatDistance += extra; + } + + // Copy data in slow repeat mode + for (let i: number = 0; i < iRepeatLength; i++) { + this.mBlockBuffer[this.mDataLength % this.DEF_MAX_WINDOW_SIZE] = + this.mBlockBuffer[(this.mDataLength - iRepeatDistance) % this.DEF_MAX_WINDOW_SIZE]; + this.mDataLength++; + free--; + } + + dataRead = true; + + } + + return dataRead; + } + /// + /// Reads data to buffer. + /// + /// Output buffer for data. + /// Offset in output data. + /// Length of the data to be read. + /// Count of bytes actually read. + public read(buffer: Uint8Array, offset: number, length: number): number { + if (buffer == null) { + throw new DOMException('buffer'); + } + + if (offset < 0 || offset > buffer.length - 1) { + throw new DOMException('offset', 'Offset does not belong to specified buffer.'); + } + + if (length < 0 || length > buffer.length - offset) { + throw new DOMException('length', 'Length is illegal.'); + } + + const initialLength: number = length; + + while (length > 0) { + // Read from internal buffer. + if (this.mCurrentPosition < this.mDataLength) { + // Position in buffer array. + const inBlockPosition: number = (this.mCurrentPosition % this.DEF_MAX_WINDOW_SIZE); + // We can not read more than we have in buffer at once, + // and we not read more than till the array end. + let dataToCopy: number = Math.min(this.DEF_MAX_WINDOW_SIZE - inBlockPosition, (this.mDataLength - this.mCurrentPosition)); + // Reading not more, than the rest of the buffer. + dataToCopy = Math.min(dataToCopy, length); + + + + + + //sourceArray, sourceIndex, destinationArray, destinationIndex, length + // Copy data. + //Array.Copy( mBlockBuffer, inBlockPosition, buffer, offset, dataToCopy ); + //buffer.set(this.mBlockBuffer.slice(inBlockPosition, dataToCopy), offset); +Utils.arrayCopy(this.mBlockBuffer, inBlockPosition, buffer, offset, dataToCopy); + // Correct position, length, + this.mCurrentPosition += dataToCopy; + offset += dataToCopy; + length -= dataToCopy; + } + else { + + + if (!this.mbCanReadMoreData) { + break; + } + + const oldDataLength: number = this.mDataLength; + + if (!this.mbReadingUncompressed) { + if (!this.readHuffman()) { + break; + } + } + else { + if (this.mUncompressedDataLength === 0) { + // If there is no more data in stream, just exit. + this.mbCanReadMoreData = this.decodeBlockHeader(); + if (!(this.mbCanReadMoreData)) { + break; + } + } + else { + // Position of the data end in block buffer. + const inBlockPosition: number = (this.mDataLength % this.DEF_MAX_WINDOW_SIZE); + const dataToRead: number = Math.min(this.mUncompressedDataLength, this.DEF_MAX_WINDOW_SIZE - inBlockPosition); + const dataRead: number = this.readPackedBytes(this.mBlockBuffer, inBlockPosition, dataToRead); + + if (dataToRead !== dataRead) { + throw new DOMException('Not enough data in stream.'); + } + + this.mUncompressedDataLength -= dataRead; + + this.mDataLength += dataRead; + } + } + + + if (oldDataLength < this.mDataLength) { + const start: number = (oldDataLength % this.DEF_MAX_WINDOW_SIZE); + const end: number = (this.mDataLength % this.DEF_MAX_WINDOW_SIZE); + + if (start < end) { + this.checksumUpdate(this.mBlockBuffer, start, end - start); + } + else { + this.checksumUpdate(this.mBlockBuffer, start, this.DEF_MAX_WINDOW_SIZE - start); + + if (end > 0) { + this.checksumUpdate(this.mBlockBuffer, 0, end); + } + } + } + } + } + + if (!this.mbCanReadMoreData && !this.mbCheckSumRead && !this.mbNoWrap) { + this.skipToBoundary(); + const checkSum: number = this.readInt32(); + + //Debug.Assert( checkSum == mCheckSum, "" ); + if (checkSum !== this.mCheckSum) { + throw new DOMException('Checksum check failed.'); + } + + this.mbCheckSumRead = true; + } + + return initialLength - length; + } + /// + /// Reads array of bytes. + /// + /// Output buffer. + /// Offset in output buffer. + /// Length of the data to be read. + /// Count of bytes actually read to the buffer. + protected readPackedBytes(buffer: Uint8Array, offset: number, length: number): number { + if (buffer == null) { + throw new DOMException('buffer'); + } + + if (offset < 0 || offset > buffer.length - 1) { + throw new DOMException('offset", "Offset can not be less than zero or greater than buffer length - 1.'); + } + + if (length < 0) { + throw new DOMException('length", "Length can not be less than zero.'); + } + + if (length > buffer.length - offset) { + throw new DOMException('length", "Length is too large.'); + } + + if ((this.mBufferedBits & 7) !== 0) { + throw new DOMException('Reading of unalligned data is not supported.'); + } + + if (length === 0) { + return 0; + } + + let result: number = 0; + + while (this.mBufferedBits > 0 && length > 0) { + buffer[offset++] = (this.mBuffer); + this.mBufferedBits -= 8; + this.mBuffer = Utils.bitConverterInt32ToUint(this.mBuffer >>> 8); + length--; + result++; + } + + if (length > 0) { + //TODO: Fix this. + result += this.mInputStream.read(buffer, offset, length); + } + + return result; + } + /// + /// TODO: place correct comment here + /// + /// + /// TODO: place correct comment here + /// + protected readInt32(): number { + let result: number = this.readBits(8) << 24; + result |= this.readBits(8) << 16; + result |= this.readBits(8) << 8; + result |= this.readBits(8); + return result; + } + /// + /// Updates checksum by calculating checksum of the + /// given buffer and adding it to current value. + /// + /// Data byte array. + /// Offset in the buffer. + /// Length of data to be used from the stream. + protected checksumUpdate(buffer: Uint8Array, offset: number, length: number): void { + ChecksumCalculator.ChecksumUpdate(this.mCheckSum, buffer, offset, length); + } +} + + +export class Stream { + public inputStream: Uint8Array; + public get length(): number { + return this.inputStream.buffer.byteLength; + } + public position: number = 0; + + constructor(input: Uint8Array) { + this.inputStream = new Uint8Array(input.buffer); + } + + public read(buffer: Uint8Array, start: number, length: number): number { + let temp: Uint8Array = new Uint8Array(this.inputStream.buffer, this.position + start); + let data: Uint8Array = temp.subarray(0, length); + buffer.set(data, 0); + this.position += data.byteLength; + return data.byteLength; + } + public readByte(): number { + return this.inputStream[this.position++]; + } + public write(inputBuffer: Uint8Array, offset: number, count: number): void { + Utils.arrayCopy(inputBuffer, 0, this.inputStream, this.position + offset, count) + // this.inputStream = new Uint8Array(this.inputStream.buffer, this.position + offset); + // this.inputStream.set(inputBuffer, offset); + this.position += count; + } + public toByteArray(): Uint8Array { + return new Uint8Array(this.inputStream.buffer); + } +} +/* eslint-enable */ \ No newline at end of file diff --git a/controls/compression/src/compression-writer.ts b/controls/compression/src/compression-writer.ts new file mode 100644 index 0000000000..c4db36f594 --- /dev/null +++ b/controls/compression/src/compression-writer.ts @@ -0,0 +1,984 @@ +/* eslint-disable */ +import { Encoding } from '@syncfusion/ej2-file-utils'; +/** + * array literal codes + */ +const ARR_LITERAL_CODES: Int16Array = new Int16Array(286); +const ARR_LITERAL_LENGTHS: Uint8Array = new Uint8Array(286); +const ARR_DISTANCE_CODES: Int16Array = new Int16Array(30); +const ARR_DISTANCE_LENGTHS: Uint8Array = new Uint8Array(30); + +/** + * represent compression stream writer + * ```typescript + * let compressedWriter = new CompressedStreamWriter(); + * let text: string = 'Hello world!!!'; + * compressedWriter.write(text, 0, text.length); + * compressedWriter.close(); + * ``` + */ +export class CompressedStreamWriter { + private static isHuffmanTreeInitiated: boolean = false; + private stream: Uint8Array[]; + private pendingBuffer: Uint8Array = new Uint8Array(1 << 16); + private pendingBufLength: number = 0; + private pendingBufCache: number = 0; + private pendingBufBitsInCache: number = 0; + private treeLiteral: CompressorHuffmanTree; + private treeDistances: CompressorHuffmanTree; + private treeCodeLengths: CompressorHuffmanTree; + private bufferPosition: number = 0; + private arrLiterals: Uint8Array; + private arrDistances: Uint16Array; + private extraBits: number = 0; + private currentHash: number = 0; + private hashHead: Int16Array; + private hashPrevious: Int16Array; + private matchStart: number = 0; + private matchLength: number = 0; + private matchPrevAvail: boolean = false; + private blockStart: number = 0; + private stringStart: number = 0; + private lookAhead: number = 0; + private dataWindow: Uint8Array; + private inputBuffer: Uint8Array; + private totalBytesIn: number = 0; + private inputOffset: number = 0; + private inputEnd: number = 0; + private windowSize: number = 1 << 15; + private windowMask: number = this.windowSize - 1; + private hashSize: number = 1 << 15; + private hashMask: number = this.hashSize - 1; + private hashShift: number = Math.floor((15 + 3 - 1) / 3); + private maxDist: number = this.windowSize - 262; + private checkSum: number = 1; + private noWrap: Boolean = false; + /** + * get compressed data + */ + get compressedData(): Uint8Array[] { + return this.stream; + } + + get getCompressedString(): string { + let compressedString: string = ''; + if (this.stream !== undefined) { + for (let i: number = 0; i < this.stream.length; i++) { + compressedString += String.fromCharCode.apply(null, this.stream[i]); + } + } + return compressedString; + } + /** + * Initializes compressor and writes ZLib header if needed. + * @param {boolean} noWrap - optional if true, ZLib header and checksum will not be written. + */ + constructor(noWrap?: boolean) { + if (!CompressedStreamWriter.isHuffmanTreeInitiated) { + CompressedStreamWriter.initHuffmanTree(); + CompressedStreamWriter.isHuffmanTreeInitiated = true; + } + this.treeLiteral = new CompressorHuffmanTree(this, 286, 257, 15); + this.treeDistances = new CompressorHuffmanTree(this, 30, 1, 15); + this.treeCodeLengths = new CompressorHuffmanTree(this, 19, 4, 7); + this.arrDistances = new Uint16Array((1 << 14)); + this.arrLiterals = new Uint8Array((1 << 14)); + this.stream = []; + this.dataWindow = new Uint8Array(2 * this.windowSize); + this.hashHead = new Int16Array(this.hashSize); + this.hashPrevious = new Int16Array(this.windowSize); + this.blockStart = this.stringStart = 1; + this.noWrap = noWrap; + if (!noWrap) { + this.writeZLibHeader(); + } + } + /** + * Compresses data and writes it to the stream. + * @param {Uint8Array} data - data to compress + * @param {number} offset - offset in data + * @param {number} length - length of the data + * @returns {void} + */ + public write(data: Uint8Array | string, offset: number, length: number): void { + if (data === undefined || data === null) { + throw new Error('ArgumentException: data cannot null or undefined'); + } + let end: number = offset + length; + if (0 > offset || offset > end || end > data.length) { + throw new Error('ArgumentOutOfRangeException: Offset or length is incorrect'); + } + if (typeof data === 'string') { + let encode: Encoding = new Encoding(false); + encode.type = 'Utf8'; + data = new Uint8Array(encode.getBytes(data, 0, data.length)); + end = offset + data.length; + } + this.inputBuffer = data as Uint8Array; + this.inputOffset = offset; + this.inputEnd = end; + if (!this.noWrap) { + this.checkSum = ChecksumCalculator.checksumUpdate(this.checkSum, this.inputBuffer, this.inputOffset, end); + } + while (!(this.inputEnd === this.inputOffset) || !(this.pendingBufLength === 0)) { + this.pendingBufferFlush(); + this.compressData(false); + } + } + /** + * write ZLib header to the compressed data + * @return {void} + */ + public writeZLibHeader(): void { + /* Initialize header.*/ + let headerDate: number = (8 + (7 << 4)) << 8; + /* Save compression level.*/ + headerDate |= ((5 >> 2) & 3) << 6; + /* Align header.*/ + headerDate += 31 - (headerDate % 31); + /* Write header to stream.*/ + this.pendingBufferWriteShortBytes(headerDate); + } + /** + * Write Most Significant Bytes in to stream + * @param {number} s - check sum value + */ + public pendingBufferWriteShortBytes(s: number): void { + this.pendingBuffer[this.pendingBufLength++] = s >> 8; + this.pendingBuffer[this.pendingBufLength++] = s; + } + private compressData(finish: boolean): boolean { + let success: boolean; + do { + this.fillWindow(); + let canFlush: boolean = (finish && this.inputEnd === this.inputOffset); + success = this.compressSlow(canFlush, finish); + } + while (this.pendingBufLength === 0 && success); + return success; + } + private compressSlow(flush: boolean, finish: boolean): boolean { + if (this.lookAhead < 262 && !flush) { + return false; + } + while (this.lookAhead >= 262 || flush) { + if (this.lookAhead === 0) { + return this.lookAheadCompleted(finish); + } + if (this.stringStart >= 2 * this.windowSize - 262) { + this.slideWindow(); + } + let prevMatch: number = this.matchStart; + let prevLen: number = this.matchLength; + if (this.lookAhead >= 3) { + this.discardMatch(); + } + if (prevLen >= 3 && this.matchLength <= prevLen) { + prevLen = this.matchPreviousBest(prevMatch, prevLen); + } else { + this.matchPreviousAvailable(); + } + if (this.bufferPosition >= (1 << 14)) { + return this.huffmanIsFull(finish); + } + } + return true; + } + private discardMatch(): void { + let hashHead: number = this.insertString(); + if (hashHead !== 0 && this.stringStart - hashHead <= this.maxDist && this.findLongestMatch(hashHead)) { + if (this.matchLength <= 5 && (this.matchLength === 3 && this.stringStart - this.matchStart > 4096)) { + this.matchLength = 3 - 1; + } + } + } + private matchPreviousAvailable(): void { + if (this.matchPrevAvail) { + this.huffmanTallyLit(this.dataWindow[this.stringStart - 1] & 0xff); + } + this.matchPrevAvail = true; + this.stringStart++; + this.lookAhead--; + } + private matchPreviousBest(prevMatch: number, prevLen: number): number { + this.huffmanTallyDist(this.stringStart - 1 - prevMatch, prevLen); + prevLen -= 2; + do { + this.stringStart++; + this.lookAhead--; + if (this.lookAhead >= 3) { + this.insertString(); + } + } while (--prevLen > 0); + this.stringStart++; + this.lookAhead--; + this.matchPrevAvail = false; + this.matchLength = 3 - 1; + return prevLen; + } + private lookAheadCompleted(finish: boolean): boolean { + if (this.matchPrevAvail) { + this.huffmanTallyLit(this.dataWindow[this.stringStart - 1] & 0xff); + } + this.matchPrevAvail = false; + this.huffmanFlushBlock(this.dataWindow, this.blockStart, this.stringStart - this.blockStart, finish); + this.blockStart = this.stringStart; + return false; + } + private huffmanIsFull(finish: boolean): boolean { + let len: number = this.stringStart - this.blockStart; + if (this.matchPrevAvail) { + len--; + } + let lastBlock: boolean = (finish && this.lookAhead === 0 && !this.matchPrevAvail); + this.huffmanFlushBlock(this.dataWindow, this.blockStart, len, lastBlock); + this.blockStart += len; + return !lastBlock; + } + private fillWindow(): void { + if (this.stringStart >= this.windowSize + this.maxDist) { + this.slideWindow(); + } + while (this.lookAhead < 262 && this.inputOffset < this.inputEnd) { + let more: number = 2 * this.windowSize - this.lookAhead - this.stringStart; + if (more > this.inputEnd - this.inputOffset) { + more = this.inputEnd - this.inputOffset; + } + this.dataWindow.set(this.inputBuffer.subarray(this.inputOffset, this.inputOffset + more), this.stringStart + this.lookAhead); + this.inputOffset += more; + this.totalBytesIn += more; + this.lookAhead += more; + } + if (this.lookAhead >= 3) { + this.updateHash(); + } + } + private slideWindow(): void { + this.dataWindow.set(this.dataWindow.subarray(this.windowSize, this.windowSize + this.windowSize), 0); + this.matchStart -= this.windowSize; + this.stringStart -= this.windowSize; + this.blockStart -= this.windowSize; + for (let i: number = 0; i < this.hashSize; ++i) { + let m: number = this.hashHead[i] & 0xffff; + this.hashHead[i] = (((m >= this.windowSize) ? (m - this.windowSize) : 0)); + } + for (let i: number = 0; i < this.windowSize; i++) { + let m: number = this.hashPrevious[i] & 0xffff; + this.hashPrevious[i] = ((m >= this.windowSize) ? (m - this.windowSize) : 0); + } + } + private insertString(): number { + let match: number; + let hash: number = ((this.currentHash << this.hashShift) ^ this.dataWindow[this.stringStart + (3 - 1)]) & this.hashMask; + this.hashPrevious[this.stringStart & this.windowMask] = match = this.hashHead[hash]; + this.hashHead[hash] = this.stringStart; + this.currentHash = hash; + return match & 0xffff; + } + private findLongestMatch(curMatch: number): boolean { + let chainLen: number = 4096; + let niceLen: number = 258; + let scan: number = this.stringStart; + let match: number; + let bestEnd: number = this.stringStart + this.matchLength; + let bestLength: number = Math.max(this.matchLength, 3 - 1); + let limit: number = Math.max(this.stringStart - this.maxDist, 0); + let stringEnd: number = this.stringStart + 258 - 1; + let scanEnd1: number = this.dataWindow[bestEnd - 1]; + let scanEnd: number = this.dataWindow[bestEnd]; + let data: Uint8Array = this.dataWindow; + if (bestLength >= 32) { + chainLen >>= 2; + } + if (niceLen > this.lookAhead) { + niceLen = this.lookAhead; + } + do { + if (data[curMatch + bestLength] !== scanEnd || + data[curMatch + bestLength - 1] !== scanEnd1 || + data[curMatch] !== data[scan] || + data[curMatch + 1] !== data[scan + 1]) { + continue; + } + match = curMatch + 2; + scan += 2; + /* tslint:disable */ + while (data[++scan] === data[++match] && data[++scan] === data[++match] && + data[++scan] === data[++match] && data[++scan] === data[++match] && + data[++scan] === data[++match] && data[++scan] === data[++match] && + data[++scan] === data[++match] && data[++scan] === data[++match] && scan < stringEnd) { + /* tslint:disable */ + } + if (scan > bestEnd) { + this.matchStart = curMatch; + bestEnd = scan; + bestLength = scan - this.stringStart; + if (bestLength >= niceLen) { + break; + } + scanEnd1 = data[bestEnd - 1]; + scanEnd = data[bestEnd]; + } + scan = this.stringStart; + } + while ((curMatch = (this.hashPrevious[curMatch & this.windowMask] & 0xffff)) > limit && --chainLen !== 0); + this.matchLength = Math.min(bestLength, this.lookAhead); + return this.matchLength >= 3; + } + private updateHash(): void { + this.currentHash = (this.dataWindow[this.stringStart] << this.hashShift) ^ this.dataWindow[this.stringStart + 1]; + } + private huffmanTallyLit(literal: number): boolean { + this.arrDistances[this.bufferPosition] = 0; + this.arrLiterals[this.bufferPosition++] = literal; + this.treeLiteral.codeFrequencies[literal]++; + return this.bufferPosition >= (1 << 14); + } + private huffmanTallyDist(dist: number, len: number): boolean { + this.arrDistances[this.bufferPosition] = dist; + this.arrLiterals[this.bufferPosition++] = (len - 3); + let lc: number = this.huffmanLengthCode(len - 3); + this.treeLiteral.codeFrequencies[lc]++; + if (lc >= 265 && lc < 285) { + this.extraBits += Math.floor((lc - 261) / 4); + } + let dc: number = this.huffmanDistanceCode(dist - 1); + this.treeDistances.codeFrequencies[dc]++; + if (dc >= 4) { + this.extraBits += Math.floor((dc / 2 - 1)); + } + return this.bufferPosition >= (1 << 14); + } + private huffmanFlushBlock(stored: Uint8Array, storedOffset: number, storedLength: number, lastBlock: boolean): void { + this.treeLiteral.codeFrequencies[256]++; + this.treeLiteral.buildTree(); + this.treeDistances.buildTree(); + this.treeLiteral.calculateBLFreq(this.treeCodeLengths); + this.treeDistances.calculateBLFreq(this.treeCodeLengths); + this.treeCodeLengths.buildTree(); + let blTreeCodes: number = 4; + for (let i: number = 18; i > blTreeCodes; i--) { + if (this.treeCodeLengths.codeLengths[CompressorHuffmanTree.huffCodeLengthOrders[i]] > 0) { + blTreeCodes = i + 1; + } + } + let opt_len: number = 14 + blTreeCodes * 3 + this.treeCodeLengths.getEncodedLength() + + this.treeLiteral.getEncodedLength() + this.treeDistances.getEncodedLength() + this.extraBits; + let static_len: number = this.extraBits; + for (let i: number = 0; i < 286; i++) { + static_len += this.treeLiteral.codeFrequencies[i] * ARR_LITERAL_LENGTHS[i]; + } + for (let i = 0; i < 30; i++) { + static_len += this.treeDistances.codeFrequencies[i] * ARR_DISTANCE_LENGTHS[i]; + } + if (opt_len >= static_len) { + // Force static trees. + opt_len = static_len; + } + if (storedOffset >= 0 && storedLength + 4 < opt_len >> 3) { + this.huffmanFlushStoredBlock(stored, storedOffset, storedLength, lastBlock); + } else if (opt_len == static_len) { + // Encode with static tree. + this.pendingBufferWriteBits((1 << 1) + (lastBlock ? 1 : 0), 3); + this.treeLiteral.setStaticCodes(ARR_LITERAL_CODES, ARR_LITERAL_LENGTHS); + this.treeDistances.setStaticCodes(ARR_DISTANCE_CODES, ARR_DISTANCE_LENGTHS); + this.huffmanCompressBlock(); + this.huffmanReset(); + } else { + this.pendingBufferWriteBits((2 << 1) + (lastBlock ? 1 : 0), 3); + this.huffmanSendAllTrees(blTreeCodes); + this.huffmanCompressBlock(); + this.huffmanReset(); + } + } + private huffmanFlushStoredBlock(stored: Uint8Array, storedOffset: number, storedLength: number, lastBlock: boolean): void { + this.pendingBufferWriteBits((0 << 1) + (lastBlock ? 1 : 0), 3); + this.pendingBufferAlignToByte(); + this.pendingBufferWriteShort(storedLength); + this.pendingBufferWriteShort(~storedLength); + this.pendingBufferWriteByteBlock(stored, storedOffset, storedLength); + this.huffmanReset(); + } + private huffmanLengthCode(len: number): number { + if (len === 255) { + return 285; + } + let code: number = 257; + while (len >= 8) { + code += 4; + len >>= 1; + } + return code + len; + } + private huffmanDistanceCode(distance: number): number { + let code: number = 0; + while (distance >= 4) { + code += 2; + distance >>= 1; + } + return code + distance; + } + private huffmanSendAllTrees(blTreeCodes: number): void { + this.treeCodeLengths.buildCodes(); + this.treeLiteral.buildCodes(); + this.treeDistances.buildCodes(); + this.pendingBufferWriteBits(this.treeLiteral.treeLength - 257, 5); + this.pendingBufferWriteBits(this.treeDistances.treeLength - 1, 5); + this.pendingBufferWriteBits(blTreeCodes - 4, 4); + for (let rank: number = 0; rank < blTreeCodes; rank++) { + this.pendingBufferWriteBits( + this.treeCodeLengths.codeLengths[CompressorHuffmanTree.huffCodeLengthOrders[rank]] + , 3); + } + this.treeLiteral.writeTree(this.treeCodeLengths); + this.treeDistances.writeTree(this.treeCodeLengths); + } + private huffmanReset(): void { + this.bufferPosition = 0; + this.extraBits = 0; + this.treeLiteral.reset(); + this.treeDistances.reset(); + this.treeCodeLengths.reset(); + } + private huffmanCompressBlock(): void { + for (let i: number = 0; i < this.bufferPosition; i++) { + let literalLen: number = this.arrLiterals[i] & 255; + let dist: number = this.arrDistances[i]; + if (dist-- !== 0) { + let lc: number = this.huffmanLengthCode(literalLen); + this.treeLiteral.writeCodeToStream(lc); + let bits: number = Math.floor((lc - 261) / 4); + if (bits > 0 && bits <= 5) { + this.pendingBufferWriteBits(literalLen & ((1 << bits) - 1), bits); + } + let dc: number = this.huffmanDistanceCode(dist); + this.treeDistances.writeCodeToStream(dc); + bits = Math.floor(dc / 2 - 1); + if (bits > 0) { + this.pendingBufferWriteBits(dist & ((1 << bits) - 1), bits); + } + } else { + this.treeLiteral.writeCodeToStream(literalLen); + } + } + this.treeLiteral.writeCodeToStream(256); + } + /** + * write bits in to internal buffer + * @param {number} b - source of bits + * @param {number} count - count of bits to write + */ + public pendingBufferWriteBits(b: number, count: number): void { + let uint: Uint32Array = new Uint32Array(1); + uint[0] = this.pendingBufCache | (b << this.pendingBufBitsInCache); + this.pendingBufCache = uint[0]; + this.pendingBufBitsInCache += count; + this.pendingBufferFlushBits(); + } + private pendingBufferFlush(isClose?: boolean): void { + this.pendingBufferFlushBits(); + if (this.pendingBufLength > 0) { + let array: Uint8Array = new Uint8Array(this.pendingBufLength); + array.set(this.pendingBuffer.subarray(0, this.pendingBufLength), 0); + this.stream.push(array); + } + this.pendingBufLength = 0; + } + private pendingBufferFlushBits(): number { + let result: number = 0; + while (this.pendingBufBitsInCache >= 8 && this.pendingBufLength < (1 << 16)) { + this.pendingBuffer[this.pendingBufLength++] = this.pendingBufCache; + this.pendingBufCache >>= 8; + this.pendingBufBitsInCache -= 8; + result++; + } + return result; + } + private pendingBufferWriteByteBlock(data: Uint8Array, offset: number, length: number): void { + let array: Uint8Array = data.subarray(offset, offset + length); + this.pendingBuffer.set(array, this.pendingBufLength); + this.pendingBufLength += length; + } + private pendingBufferWriteShort(s: number): void { + this.pendingBuffer[this.pendingBufLength++] = s; + this.pendingBuffer[this.pendingBufLength++] = (s >> 8); + } + private pendingBufferAlignToByte(): void { + if (this.pendingBufBitsInCache > 0) { + this.pendingBuffer[this.pendingBufLength++] = this.pendingBufCache; + } + this.pendingBufCache = 0; + this.pendingBufBitsInCache = 0; + } + /** + * Huffman Tree literal calculation + * @private + */ + public static initHuffmanTree(): void { + let i: number = 0; + while (i < 144) { + ARR_LITERAL_CODES[i] = CompressorHuffmanTree.bitReverse((0x030 + i) << 8); + ARR_LITERAL_LENGTHS[i++] = 8; + } + while (i < 256) { + ARR_LITERAL_CODES[i] = CompressorHuffmanTree.bitReverse((0x190 - 144 + i) << 7); + ARR_LITERAL_LENGTHS[i++] = 9; + } + while (i < 280) { + ARR_LITERAL_CODES[i] = CompressorHuffmanTree.bitReverse((0x000 - 256 + i) << 9); + ARR_LITERAL_LENGTHS[i++] = 7; + } + while (i < 286) { + ARR_LITERAL_CODES[i] = CompressorHuffmanTree.bitReverse((0x0c0 - 280 + i) << 8); + ARR_LITERAL_LENGTHS[i++] = 8; + } + for (i = 0; i < 30; i++) { + ARR_DISTANCE_CODES[i] = CompressorHuffmanTree.bitReverse(i << 11); + ARR_DISTANCE_LENGTHS[i] = 5; + } + } + /** + * close the stream and write all pending buffer in to stream + * @returns {void} + */ + public close(): void { + do { + this.pendingBufferFlush(true); + if (!this.compressData(true)) { + this.pendingBufferFlush(true); + this.pendingBufferAlignToByte(); + if (!this.noWrap) { + this.pendingBufferWriteShortBytes(this.checkSum >> 16); + this.pendingBufferWriteShortBytes(this.checkSum & 0xffff); + } + this.pendingBufferFlush(true); + } + } + while (!(this.inputEnd === this.inputOffset) || + !(this.pendingBufLength === 0)); + } + /** + * release allocated un-managed resource + * @returns {void} + */ + public destroy(): void { + this.stream = []; + this.stream = undefined; + this.pendingBuffer = undefined; + this.treeLiteral = undefined; + this.treeDistances = undefined; + this.treeCodeLengths = undefined; + this.arrLiterals = undefined; + this.arrDistances = undefined; + this.hashHead = undefined; + this.hashPrevious = undefined; + this.dataWindow = undefined; + this.inputBuffer = undefined; + this.pendingBufLength = undefined; + this.pendingBufCache = undefined; + this.pendingBufBitsInCache = undefined; + this.bufferPosition = undefined; + this.extraBits = undefined; + this.currentHash = undefined; + this.matchStart = undefined; + this.matchLength = undefined; + this.matchPrevAvail = undefined; + this.blockStart = undefined; + this.stringStart = undefined; + this.lookAhead = undefined; + this.totalBytesIn = undefined; + this.inputOffset = undefined; + this.inputEnd = undefined; + this.windowSize = undefined; + this.windowMask = undefined; + this.hashSize = undefined; + this.hashMask = undefined; + this.hashShift = undefined; + this.maxDist = undefined; + this.checkSum = undefined; + this.noWrap = undefined; + } +} +/** + * represent the Huffman Tree + */ +export class CompressorHuffmanTree { + private codeFrequency: Uint16Array; + private codes: Int16Array; + private codeLength: Uint8Array; + private lengthCount: Int32Array; + private codeMinCount: number; + private codeCount: number; + private maxLength: number; + private writer: CompressedStreamWriter; + private static reverseBits: number[] = [0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15]; + public static huffCodeLengthOrders: number[] = [16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15]; + public get treeLength(): number { + return this.codeCount; + } + public get codeLengths(): Uint8Array { + return this.codeLength; + } + public get codeFrequencies(): Uint16Array { + return this.codeFrequency; + } + /** + * Create new Huffman Tree + * @param {CompressedStreamWriter} writer instance + * @param {number} elementCount - element count + * @param {number} minCodes - minimum count + * @param {number} maxLength - maximum count + */ + constructor(writer: CompressedStreamWriter, elementCount: number, minCodes: number, maxLength: number) { + this.writer = writer; + this.codeMinCount = minCodes; + this.maxLength = maxLength; + this.codeFrequency = new Uint16Array(elementCount); + this.lengthCount = new Int32Array(maxLength); + } + public setStaticCodes(codes: Int16Array, lengths: Uint8Array): void { + let temp: Int16Array = new Int16Array(codes.length); + temp.set(codes, 0); + this.codes = temp; + let lengthTemp: Uint8Array = new Uint8Array(lengths.length); + lengthTemp.set(lengths, 0); + this.codeLength = lengthTemp; + } + /** + * reset all code data in tree + * @returns {void} + */ + public reset(): void { + for (let i: number = 0; i < this.codeFrequency.length; i++) { + this.codeFrequency[i] = 0; + } + this.codes = undefined; + this.codeLength = undefined; + } + /** + * write code to the compressor output stream + * @param {number} code - code to be written + * @returns {void} + */ + public writeCodeToStream(code: number): void { + this.writer.pendingBufferWriteBits(this.codes[code] & 0xffff, this.codeLength[code]); + } + /** + * calculate code from their frequencies + * @returns {void} + */ + public buildCodes(): void { + let nextCode: Int32Array = new Int32Array(this.maxLength); + this.codes = new Int16Array(this.codeCount); + let code: number = 0; + for (let bitsCount: number = 0; bitsCount < this.maxLength; bitsCount++) { + nextCode[bitsCount] = code; + code += this.lengthCount[bitsCount] << (15 - bitsCount); + } + for (let i: number = 0; i < this.codeCount; i++) { + let bits = this.codeLength[i]; + if (bits > 0) { + this.codes[i] = CompressorHuffmanTree.bitReverse(nextCode[bits - 1]); + nextCode[bits - 1] += 1 << (16 - bits); + } + } + } + public static bitReverse(value: number): number { + return (CompressorHuffmanTree.reverseBits[value & 15] << 12 + | CompressorHuffmanTree.reverseBits[(value >> 4) & 15] << 8 + | CompressorHuffmanTree.reverseBits[(value >> 8) & 15] << 4 + | CompressorHuffmanTree.reverseBits[value >> 12]); + } + /** + * calculate length of compressed data + * @returns {number} + */ + public getEncodedLength(): number { + let len: number = 0; + for (let i: number = 0; i < this.codeFrequency.length; i++) { + len += this.codeFrequency[i] * this.codeLength[i]; + } + return len; + } + /** + * calculate code frequencies + * @param {CompressorHuffmanTree} blTree + * @returns {void} + */ + public calculateBLFreq(blTree: CompressorHuffmanTree): void { + let maxCount: number; + let minCount: number; + let count: number; + let curLen: number = -1; + let i: number = 0; + while (i < this.codeCount) { + count = 1; + let nextLen: number = this.codeLength[i]; + if (nextLen === 0) { + maxCount = 138; + minCount = 3; + } else { + maxCount = 6; + minCount = 3; + if (curLen !== nextLen) { + blTree.codeFrequency[nextLen]++; + count = 0; + } + } + curLen = nextLen; + i++; + while (i < this.codeCount && curLen === this.codeLength[i]) { + i++; + if (++count >= maxCount) { + break; + } + } + if (count < minCount) { + blTree.codeFrequency[curLen] += count; + } else if (curLen !== 0) { + blTree.codeFrequency[16]++; + } else if (count <= 10) { + blTree.codeFrequency[17]++; + } else { + blTree.codeFrequency[18]++; + } + } + } + /** + * @param {CompressorHuffmanTree} blTree - write tree to output stream + * @returns {void} + */ + public writeTree(blTree: CompressorHuffmanTree): void { + let maxRepeatCount: number; + let minRepeatCount: number; + let currentRepeatCount: number; + let currentCodeLength: number = -1; + let i: number = 0; + while (i < this.codeCount) { + currentRepeatCount = 1; + let nextLen: number = this.codeLength[i]; + if (nextLen === 0) { + maxRepeatCount = 138; + minRepeatCount = 3; + } else { + maxRepeatCount = 6; + minRepeatCount = 3; + if (currentCodeLength !== nextLen) { + blTree.writeCodeToStream(nextLen); + currentRepeatCount = 0; + } + } + currentCodeLength = nextLen; + i++; + while (i < this.codeCount && currentCodeLength === this.codeLength[i]) { + i++; + if (++currentRepeatCount >= maxRepeatCount) { + break; + } + } + if (currentRepeatCount < minRepeatCount) { + while (currentRepeatCount-- > 0) { + blTree.writeCodeToStream(currentCodeLength); + } + } else if (currentCodeLength !== 0) { + blTree.writeCodeToStream(16); + this.writer.pendingBufferWriteBits(currentRepeatCount - 3, 2); + } else if (currentRepeatCount <= 10) { + blTree.writeCodeToStream(17); + this.writer.pendingBufferWriteBits(currentRepeatCount - 3, 3); + } else { + blTree.writeCodeToStream(18); + this.writer.pendingBufferWriteBits(currentRepeatCount - 11, 7); + } + } + } + /** + * Build huffman tree + * @returns {void} + */ + public buildTree(): void { + let codesCount: number = this.codeFrequency.length; + let arrTree: Int32Array = new Int32Array(codesCount); + let treeLength: number = 0; + let maxCount: number = 0; + for (let n = 0; n < codesCount; n++) { + let freq: number = this.codeFrequency[n]; + if (freq !== 0) { + let pos: number = treeLength++; + let pPos: number = 0; + while (pos > 0 && this.codeFrequency[arrTree[pPos = Math.floor((pos - 1) / 2)]] > freq) { + arrTree[pos] = arrTree[pPos]; + pos = pPos; + } + arrTree[pos] = n; + maxCount = n; + } + } + while (treeLength < 2) { + arrTree[treeLength++] = + (maxCount < 2) ? ++maxCount : 0; + } + this.codeCount = Math.max(maxCount + 1, this.codeMinCount); + let leafsCount: number = treeLength; + let nodesCount: number = leafsCount; + let child: Int32Array = new Int32Array(4 * treeLength - 2); + let values: Int32Array = new Int32Array(2 * treeLength - 1); + for (let i: number = 0; i < treeLength; i++) { + let node: number = arrTree[i]; + let iIndex: number = 2 * i; + child[iIndex] = node; + child[iIndex + 1] = -1; + values[i] = (this.codeFrequency[node] << 8); + arrTree[i] = i; + } + this.constructHuffmanTree(arrTree, treeLength, values, nodesCount, child); + this.buildLength(child); + } + private constructHuffmanTree(arrTree: Int32Array, treeLength: number, values: Int32Array, nodesCount: number, child: Int32Array): void { + do { + let first: number = arrTree[0]; + let last: number = arrTree[--treeLength]; + let lastVal: number = values[last]; + let pPos: number = 0; + let path: number = 1; + while (path < treeLength) { + if (path + 1 < treeLength && values[arrTree[path]] > values[arrTree[path + 1]]) { + path++; + } + arrTree[pPos] = arrTree[path]; + pPos = path; path = pPos * 2 + 1; + } + while ((path = pPos) > 0 && values[arrTree[pPos = Math.floor((path - 1) / 2)]] > lastVal) { + arrTree[path] = arrTree[pPos]; + } + arrTree[path] = last; + let second: number = arrTree[0]; + last = nodesCount++; + child[2 * last] = first; + child[2 * last + 1] = second; + let minDepth: number = Math.min(values[first] & 0xff, values[second] & 0xff); + values[last] = lastVal = values[first] + values[second] - minDepth + 1; + pPos = 0; + path = 1; + /* tslint:disable */ + while (path < treeLength) { + if (path + 1 < treeLength && values[arrTree[path]] > values[arrTree[path + 1]]) { + path++; + } + arrTree[pPos] = arrTree[path]; + pPos = path; + path = pPos * 2 + 1; + } /* tslint:disable */ + while ((path = pPos) > 0 && values[arrTree[pPos = Math.floor((path - 1) / 2)]] > lastVal) { + arrTree[path] = arrTree[pPos]; + } + arrTree[path] = last; + } + while (treeLength > 1); + } + private buildLength(child: Int32Array): void { + this.codeLength = new Uint8Array(this.codeFrequency.length); + let numNodes: number = Math.floor(child.length / 2); + let numLeafs: number = Math.floor((numNodes + 1) / 2); + let overflow: number = 0; + for (let i = 0; i < this.maxLength; i++) { + this.lengthCount[i] = 0; + } + overflow = this.calculateOptimalCodeLength(child, overflow, numNodes); + if (overflow === 0) { + return; + } + let iIncreasableLength: number = this.maxLength - 1; + do { + while (this.lengthCount[--iIncreasableLength] === 0) { + /* tslint:disable */ + } + do { + this.lengthCount[iIncreasableLength]--; + this.lengthCount[++iIncreasableLength]++; + overflow -= (1 << (this.maxLength - 1 - iIncreasableLength)); + } + while (overflow > 0 && iIncreasableLength < this.maxLength - 1); + } + while (overflow > 0); + this.recreateTree(child, overflow, numLeafs); + } + private recreateTree(child: Int32Array, overflow: number, numLeafs: number): void { + this.lengthCount[this.maxLength - 1] += overflow; + this.lengthCount[this.maxLength - 2] -= overflow; + let nodePtr: number = 2 * numLeafs; + for (let bits: number = this.maxLength; bits !== 0; bits--) { + let n = this.lengthCount[bits - 1]; + while (n > 0) { + let childPtr: number = 2 * child[nodePtr++]; + if (child[childPtr + 1] === -1) { + this.codeLength[child[childPtr]] = bits; + n--; + } + } + } + } + private calculateOptimalCodeLength(child: Int32Array, overflow: number, numNodes: number): number { + let lengths: Int32Array = new Int32Array(numNodes); + lengths[numNodes - 1] = 0; + for (let i: number = numNodes - 1; i >= 0; i--) { + let childIndex: number = 2 * i + 1; + if (child[childIndex] !== -1) { + let bitLength: number = lengths[i] + 1; + if (bitLength > this.maxLength) { + bitLength = this.maxLength; + overflow++; + } + lengths[child[childIndex - 1]] = lengths[child[childIndex]] = bitLength; + } else { + let bitLength = lengths[i]; + this.lengthCount[bitLength - 1]++; + this.codeLength[child[childIndex - 1]] = lengths[i]; + } + } + return overflow + } +} + +/** + * Checksum calculator, based on Adler32 algorithm. + */ +export class ChecksumCalculator { + private static checkSumBitOffset: number = 16; + private static checksumBase: number = 65521; + private static checksumIterationCount: number = 3800; + + /** + * Updates checksum by calculating checksum of the + * given buffer and adding it to current value. + * @param {number} checksum - current checksum. + * @param {Uint8Array} buffer - data byte array. + * @param {number} offset - offset in the buffer. + * @param {number} length - length of data to be used from the stream. + * @returns {number} + */ + public static checksumUpdate(checksum: number, buffer: Uint8Array, offset: number, length: number): number { + let uint = new Uint32Array(1); + uint[0] = checksum; + let checksum_uint: number = uint[0]; + let s1 = uint[0] = checksum_uint & 65535; + let s2 = uint[0] = checksum_uint >> ChecksumCalculator.checkSumBitOffset; + + while (length > 0) { + let steps = Math.min(length, ChecksumCalculator.checksumIterationCount); + length -= steps; + + while (--steps >= 0) { + s1 = s1 + (uint[0] = (buffer[offset++] & 255)); + s2 = s2 + s1; + } + + s1 %= ChecksumCalculator.checksumBase; + s2 %= ChecksumCalculator.checksumBase; + } + + checksum_uint = (s2 << ChecksumCalculator.checkSumBitOffset) | s1; + return checksum_uint; + } +} +/* eslint-enable */ \ No newline at end of file diff --git a/controls/compression/src/decompressor-huffman-tree.ts b/controls/compression/src/decompressor-huffman-tree.ts new file mode 100644 index 0000000000..52ff18de2c --- /dev/null +++ b/controls/compression/src/decompressor-huffman-tree.ts @@ -0,0 +1,276 @@ +/* eslint-disable */ +import { Utils, CompressedStreamReader } from './index'; + +export class DecompressorHuffmanTree{ + /// + /// Maximum count of bits. + /// + private static MAX_BITLEN: number = 15; + + /// + /// Build huffman tree. + /// + private m_Tree: Int16Array; + + /// + /// Huffman tree for encoding and decoding lengths. + /// + public static m_LengthTree: DecompressorHuffmanTree; + + /// + /// huffman tree for encoding and decoding distances. + /// + public static m_DistanceTree: DecompressorHuffmanTree; + + + public constructor(lengths: Uint8Array) { + + this.buildTree( lengths ); + } + public static init(): void { + let lengths: Uint8Array; + let index: number ; + + // Generate huffman tree for lengths. + lengths = new Uint8Array(288); + index = 0; + + while( index < 144 ) + { + lengths[ index++ ] = 8; + } + + while( index < 256 ) + { + lengths[ index++ ] = 9; + } + + while( index < 280 ) + { + lengths[ index++ ] = 7; + } + + while( index < 288 ) + { + lengths[ index++ ] = 8; + } + + DecompressorHuffmanTree.m_LengthTree = new DecompressorHuffmanTree( lengths ); + + // Generate huffman tree for distances. + lengths = new Uint8Array(32); + index = 0; + + while( index < 32 ) + { + lengths[ index++ ] = 5; + } + + DecompressorHuffmanTree.m_DistanceTree = new DecompressorHuffmanTree( lengths ); + + } + + + /// + /// Prepares data for generating huffman tree. + /// + /// Array of counts of each code length. + /// Numerical values of the smallest code for each code length. + /// Array of code lengths. + /// Calculated tree size. + /// Code. + private prepareData(blCount: number[], nextCode: number[], lengths:Uint8Array) : any + { + let code: number = 0; + let treeSize: number = 512; + + // Count number of codes for each code length. + for( let i = 0; i < lengths.length; i++ ) + { + let length = lengths[ i ]; + + if( length > 0 ) + { + blCount[ length ]++; + } + } + + for( let bits = 1; bits <= DecompressorHuffmanTree.MAX_BITLEN; bits++ ) + { + nextCode[ bits ] = code; + code += blCount[ bits ] << ( 16 - bits ); + + if( bits >= 10 ) + { + let start = nextCode[ bits ] & 0x1ff80; + let end = code & 0x1ff80; + treeSize += ( end - start ) >> ( 16 - bits ); + } + } + + /* if( code != 65536 ) + throw new ZipException( "Code lengths don't add up properly." );*/ + + return { 'code': code, 'treeSize': treeSize }; + } + /// + /// Generates huffman tree. + /// + /// Array of counts of each code length. + /// Numerical values of the smallest code for each code length. + /// Precalculated code. + /// Array of code lengths. + /// Calculated size of the tree. + /// Generated tree. + private treeFromData(blCount: number[], nextCode: number[], lengths:Uint8Array, code: number, treeSize: number): Int16Array + { + let tree: Int16Array = new Int16Array(treeSize); + let pointer: number = 512; + let increment: number = 1 << 7; + + for( let bits = DecompressorHuffmanTree.MAX_BITLEN; bits >= 10; bits-- ) + { + let end = code & 0x1ff80; + code -= blCount[ bits ] << ( 16 - bits ); + let start = code & 0x1ff80; + + for( let i = start; i < end; i += increment ) + { + tree[ Utils.bitReverse( i ) ] = Utils.bitConverterInt32ToInt16( ( -pointer << 4 ) | bits ); + pointer += 1 << ( bits - 9 ); + } + } + + for( let i = 0; i < lengths.length; i++ ) + { + let bits = lengths[ i ]; + + if( bits == 0 ) + { + continue; + } + + code = nextCode[ bits ]; + let revcode = Utils.bitReverse( code ); + + if( bits <= 9 ) + { + do + { + tree[ revcode ] = Utils.bitConverterInt32ToInt16( ( i << 4 ) | bits ); + revcode += 1 << bits; + } + while( revcode < 512 ); + } + else + { + let subTree = tree[ revcode & 511 ]; + let treeLen = 1 << ( subTree & 15 ); + subTree = -( subTree >> 4 ); + + do + { + tree[ subTree | ( revcode >> 9 ) ] = Utils.bitConverterInt32ToInt16( ( i << 4 ) | bits ); + revcode += 1 << bits; + } + while( revcode < treeLen ); + } + + nextCode[ bits ] = code + ( 1 << ( 16 - bits ) ); + } + + return tree; + } + /// + /// Builds huffman tree from array of code lengths. + /// + /// Array of code lengths. + private buildTree( lengths: Uint8Array ): void + { + // Count of codes for each code length. + let blCount: number[] = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]; + // Numerical value of the smallest code for each code length. + let nextCode: number[] = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]; + let prepareData: any = this.prepareData( blCount, nextCode, lengths); + this.m_Tree = this.treeFromData( blCount, nextCode, lengths, prepareData.code, prepareData.treeSize ); + } + /// + /// Reads and decompresses one symbol. + /// + /// + /// + public unpackSymbol( input: CompressedStreamReader ): number + { + let lookahead: number; + let symbol: number; + + if( ( lookahead = input.peekBits( 9 ) ) >= 0 ) + { + if( ( symbol = this.m_Tree[ lookahead ] ) >= 0 ) + { + input.skipBits( (symbol & 15) ); + return symbol >> 4; + } + + let subtree: number = -( symbol >> 4 ); + let bitlen: number = symbol & 15; + + if( ( lookahead = input.peekBits( bitlen ) ) >= 0 ) + { + symbol = this.m_Tree[ subtree | ( lookahead >> 9 ) ]; + input.skipBits( (symbol & 15) ); + return symbol >> 4; + } + else + { + let bits = input.availableBits; + lookahead = input.peekBits( bits ); + symbol = this.m_Tree[ subtree | ( lookahead >> 9 ) ]; + + if( ( symbol & 15 ) <= bits ) + { + input.skipBits( (symbol & 15) ); + return symbol >> 4; + } + else + { + return -1; + } + } + } + else + { + let bits = input.availableBits; + lookahead = input.peekBits( bits ); + symbol = this.m_Tree[ lookahead ]; + + if( symbol >= 0 && ( symbol & 15 ) <= bits ) + { + input.skipBits( (symbol & 15) ); + return symbol >> 4; + } + else + { + return -1; + } + } + } + + /// + /// GET huffman tree for encoding and decoding lengths. + /// + public static get lengthTree(): DecompressorHuffmanTree{ + return this.m_LengthTree; + + } + + /// + /// GET huffman tree for encoding and decoding distances. + /// + public static get distanceTree(): DecompressorHuffmanTree { + return this.m_DistanceTree; + + } + +} +/* eslint-enable */ \ No newline at end of file diff --git a/controls/compression/src/index.ts b/controls/compression/src/index.ts new file mode 100644 index 0000000000..8b0b7fe603 --- /dev/null +++ b/controls/compression/src/index.ts @@ -0,0 +1,9 @@ +/** + * export ZipArchive class + */ + +export * from './zip-archive'; +export * from './compression-writer'; +export * from './utils'; +export * from './decompressor-huffman-tree'; +export * from './compression-reader'; diff --git a/controls/compression/src/utils.ts b/controls/compression/src/utils.ts new file mode 100644 index 0000000000..71a0d4d124 --- /dev/null +++ b/controls/compression/src/utils.ts @@ -0,0 +1,129 @@ +/* eslint-disable */ +export class Utils { + private static reverseBits: number[] = [0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15]; + public static huffCodeLengthOrders: number[] = [16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15]; + public static bitReverse(value: number): number { + return (Utils.reverseBits[value & 15] << 12 + | Utils.reverseBits[(value >> 4) & 15] << 8 + | Utils.reverseBits[(value >> 8) & 15] << 4 + | Utils.reverseBits[value >> 12]); + } + public static bitConverterToInt32(value: Uint8Array, index: number): number { + return value[index] | value[index+1] << 8 | value[index+2] << 16 | value[index+3] << 24; + } + public static bitConverterToInt16(value: Uint8Array, index: number): number { + return value[index] | value[index+1] << 8; + } + public static bitConverterToUInt32(value: number): number { + let uint: Uint32Array = new Uint32Array(1); + uint[0] = value; + return uint[0]; + } + public static bitConverterToUInt16(value: Uint8Array, index: number): number { + let uint: Uint16Array = new Uint16Array(1); + uint[0] = (value[index] | value[index+1] << 8); + return uint[0]; + } + public static bitConverterUintToInt32(value: number): number { + let uint: Int32Array = new Int32Array(1); + uint[0] = value; + return uint[0]; + } + public static bitConverterInt32ToUint(value: number): number { + let uint: Uint32Array = new Uint32Array(1); + uint[0] = value; + return uint[0]; + } + public static bitConverterInt32ToInt16(value: number): number { + let uint: Int16Array = new Int16Array(1); + uint[0] = value; + return uint[0]; + } + public static byteToString(value: Uint8Array): string { + let str: string = ''; + for (let i: number = 0; i < value.length; i++) { + str += String.fromCharCode(value[i]); + } + return str; + } + public static byteIntToString(value: Int8Array): string { + let str: string = ''; + for (let i: number = 0; i < value.length; i++) { + str += String.fromCharCode(value[i]); + } + return str; + } + public static arrayCopy(source: Uint8Array, sourceIndex: number, destination: Uint8Array, destinationIndex:number, dataToCopy: number): void { + let temp: Uint8Array = new Uint8Array(source.buffer, sourceIndex); + let data: Uint8Array = temp.subarray(0, dataToCopy); + destination.set(data, destinationIndex); + } + public static mergeArray(arrayOne: Uint8Array, arrayTwo: Uint8Array): Uint8Array { + let mergedArray: Uint8Array = new Uint8Array(arrayOne.length + arrayTwo.length); +mergedArray.set(arrayOne); +mergedArray.set(arrayTwo, arrayOne.length); +return mergedArray; + } + /** + * @private + */ + public static encodedString(input: string): Uint8Array { + let keyStr: string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; + let chr1: number; + let chr2: number; + let chr3: number; + let encode1: number; + let encode2: number; + let encode3: number; + let encode4: number; + let count: number = 0; + let resultIndex: number = 0; + + /*let dataUrlPrefix: string = 'data:';*/ + + input = input.replace(/[^A-Za-z0-9\+\/\=]/g, ''); + + let totalLength: number = input.length * 3 / 4; + if (input.charAt(input.length - 1) === keyStr.charAt(64)) { + totalLength--; + } + if (input.charAt(input.length - 2) === keyStr.charAt(64)) { + totalLength--; + } + if (totalLength % 1 !== 0) { + // totalLength is not an integer, the length does not match a valid + // base64 content. That can happen if: + // - the input is not a base64 content + // - the input is *almost* a base64 content, with a extra chars at the + // beginning or at the end + // - the input uses a base64 variant (base64url for example) + throw new Error('Invalid base64 input, bad content length.'); + } + + + let output: Uint8Array = new Uint8Array(totalLength | 0); + + while (count < input.length) { + + encode1 = keyStr.indexOf(input.charAt(count++)); + encode2 = keyStr.indexOf(input.charAt(count++)); + encode3 = keyStr.indexOf(input.charAt(count++)); + encode4 = keyStr.indexOf(input.charAt(count++)); + + chr1 = (encode1 << 2) | (encode2 >> 4); + chr2 = ((encode2 & 15) << 4) | (encode3 >> 2); + chr3 = ((encode3 & 3) << 6) | encode4; + + output[resultIndex++] = chr1; + + if (encode3 !== 64) { + output[resultIndex++] = chr2; + } + if (encode4 !== 64) { + output[resultIndex++] = chr3; + } + } + return output; + } +} +/* eslint-enable */ \ No newline at end of file diff --git a/controls/compression/src/zip-archive.ts b/controls/compression/src/zip-archive.ts new file mode 100644 index 0000000000..34954d47af --- /dev/null +++ b/controls/compression/src/zip-archive.ts @@ -0,0 +1,870 @@ +/* eslint-disable */ +import { CompressedStreamWriter, Stream, CompressedStreamReader } from './index'; +import { Save } from '@syncfusion/ej2-file-utils'; +import { Utils } from './utils'; + +const CRC32TABLE: number[] = []; +/// +/// Size of the int value in bytes. +/// +const INT_SIZE: number = 4; +/// +/// Size of the short value in bytes. +/// +const SHORT_SIZE = 2; +/// +/// End of central directory signature. +/// +const CentralDirectoryEndSignature: number = 0x06054b50; +/// +/// Offset to the size field in the End of central directory record. +/// +const CentralDirSizeOffset: number = 12; +/// +/// Central header signature. +/// +const CentralHeaderSignature: number = 0x02014b50; + /// + /// Buffer size. + /// + const BufferSize: number = 4096; +/** + * class provide compression library + * ```typescript + * let archive = new ZipArchive(); + * archive.compressionLevel = 'Normal'; + * let archiveItem = new ZipArchiveItem(archive, 'directoryName\fileName.txt'); + * archive.addItem(archiveItem); + * archive.save(fileName.zip); + * ``` + */ +export class ZipArchive { + private files: (ZipArchiveItem | string)[]; + private level: CompressionLevel; +public get items(): (ZipArchiveItem | string)[] { + return this.files; +} + /** + * gets compression level + */ + get compressionLevel(): CompressionLevel { + return this.level; + } + /** + * sets compression level + */ + set compressionLevel(level: CompressionLevel) { + this.level = level; + } + /** + * gets items count + */ + get length(): number { + if (this.files === undefined) { + return 0; + } + return this.files.length; + } + + /** + * constructor for creating ZipArchive instance + */ + constructor() { + if (CRC32TABLE.length === 0) { + ZipArchive.initCrc32Table(); + } + this.files = []; + this.level = 'Normal'; + Save.isMicrosoftBrowser = !(!navigator.msSaveBlob); + } + + /** + * add new item to archive + * @param {ZipArchiveItem} item - item to be added + * @returns {void} + */ + public addItem(item: ZipArchiveItem): void { + if (item === null || item === undefined) { + throw new Error('ArgumentException: item cannot be null or undefined'); + } + for (let i: number = 0; i < this.files.length; i++) { + let file: ZipArchiveItem = this.files[i] as ZipArchiveItem; + if (file instanceof ZipArchiveItem) { + if (file.name === item.name) { + throw new Error('item with same name already exist'); + } + } + } + this.files.push(item as ZipArchiveItem); + } + + /** + * add new directory to archive + * @param directoryName directoryName to be created + * @returns {void} + */ + public addDirectory(directoryName: string): void { + if (directoryName === null || directoryName === undefined) { + throw new Error('ArgumentException: string cannot be null or undefined'); + } + if (directoryName.length === 0) { + throw new Error('ArgumentException: string cannot be empty'); + } + if (directoryName.slice(-1) !== '/') { + directoryName += '/'; + } + if (this.files.indexOf(directoryName) !== -1) { + throw new Error('item with same name already exist'); + } + this.files.push(directoryName as string); + } + /** + * gets item at specified index + * @param {number} index - item index + * @returns {ZipArchiveItem} + */ + public getItem(index: number): ZipArchiveItem { + if (index >= 0 && index < this.files.length) { + return this.files[index] as ZipArchiveItem; + } + return undefined; + } + /** + * determines whether an element is in the collection + * @param {string | ZipArchiveItem} item - item to search + * @returns {boolean} + */ + public contains(item: string | ZipArchiveItem): boolean { + return this.files.indexOf(item) !== -1 ? true : false; + } + public open(base64String: string): void { + + //return promise = new Promise((resolve: Function, reject: Function) => { + let zipArchive: ZipArchive = this; + + + + let zipByteArray: Uint8Array = Utils.encodedString(base64String); + if (zipByteArray.length == 0) + throw new DOMException("stream"); + let stream: Stream = new Stream(zipByteArray); + + //let lCentralDirEndPosition = this.findValueFromEnd( arrBuffer, Constants.CentralDirectoryEndSignature, 65557 ); + let lCentralDirEndPosition: number = ZipArchive.findValueFromEnd(stream, CentralDirectoryEndSignature, 65557); + if (lCentralDirEndPosition < 0) + throw new DOMException("Can't locate end of central directory record. Possible wrong file format or archive is corrupt."); + + // Step2. Locate central directory and iterate through all items + stream.position = lCentralDirEndPosition + CentralDirSizeOffset; + let iCentralDirSize: number = ZipArchive.ReadInt32(stream); + + let lCentralDirPosition: number = lCentralDirEndPosition - iCentralDirSize; + + // verify that this is really central directory + stream.position = lCentralDirPosition; + this.readCentralDirectoryDataAndExtractItems(stream); + + //}); + + + // let zipArchive: ZipArchive = this; + //let promise: Promise; + // return promise = new Promise((resolve: Function, reject: Function) => { + // let reader: FileReader = new FileReader(); + // reader.onload = (e: Event) => { + // let data: Uint8Array = new Uint8Array((e.target as any).result); + // let zipReader: ZipReader = new ZipReader(data); + // zipReader.readEntries().then((entries: ZipEntry[]) => { + // for (let i: number = 0; i < entries.length; i++) { + // let entry: ZipEntry = entries[i]; + // let item: ZipArchiveItem = new ZipArchiveItem(zipArchive, entry.fileName); + // item.data = entry.data; + // item.compressionMethod = entry.compressionMethod; + // item.crc = entry.crc; + // item.lastModified = entry.lastModified; + // item.lastModifiedDate = entry.lastModifiedDate; + // item.size = entry.size; + // item.uncompressedSize = entry.uncompressedSize; + // zipArchive.addItem(item); + // } + // resolve(zipArchive); + // }); + // }; + // reader.readAsArrayBuffer(fileName); + // }); + + } + /// + /// Read central directory record from the stream. + /// + /// Stream to read from. + public readCentralDirectoryDataAndExtractItems(stream: Stream): void { + if (stream == null) + throw new DOMException("stream"); + + let itemHelper: ZipArchiveItemHelper ; + while (ZipArchive.ReadInt32(stream) == CentralHeaderSignature) { + itemHelper = new ZipArchiveItemHelper(); + itemHelper.readCentralDirectoryData(stream); + itemHelper + + // let item: ZipArchiveItem = new ZipArchiveItem(this); + // item.ReadCentralDirectoryData(stream); + // m_arrItems.Add(item); + } + itemHelper.readData(stream, itemHelper.checkCrc); + itemHelper.decompressData(); + this.files.push(new ZipArchiveItem(itemHelper.unCompressedStream.buffer, itemHelper.name)); + } + /** + * save archive with specified file name + * @param {string} fileName save archive with specified file name + * @returns {Promise} + */ + public save(fileName: string): Promise { + if (fileName === null || fileName === undefined || fileName.length === 0) { + throw new Error('ArgumentException: fileName cannot be null or undefined'); + } + if (this.files.length === 0) { + throw new Error('InvalidOperation'); + } + let zipArchive: ZipArchive = this; + let promise: Promise; + return promise = new Promise((resolve: Function, reject: Function) => { + zipArchive.saveInternal(fileName, false).then(() => { + resolve(zipArchive); + }); + }); + } + + /** + * Save archive as blob + * @return {Promise} + */ + public saveAsBlob(): Promise { + let zipArchive: ZipArchive = this; + let promise: Promise; + return promise = new Promise((resolve: Function, reject: Function) => { + zipArchive.saveInternal('', true).then((blob: Blob) => { + resolve(blob); + }); + }); + } + private saveInternal(fileName: string, skipFileSave: boolean): Promise { + let zipArchive: ZipArchive = this; + let promise: Promise; + return promise = new Promise((resolve: Function, reject: Function) => { + let zipData: ZippedObject[] = []; + let dirLength: number = 0; + for (let i: number = 0; i < zipArchive.files.length; i++) { + let compressedObject: Promise = this.getCompressedData(this.files[i]); + compressedObject.then((data: CompressedData) => { + dirLength = zipArchive.constructZippedObject(zipData, data, dirLength, data.isDirectory); + if (zipData.length === zipArchive.files.length) { + let blob: Blob = zipArchive.writeZippedContent(fileName, zipData, dirLength, skipFileSave); + resolve(blob); + } + }); + } + }); + } + /** + * release allocated un-managed resource + * @returns {void} + */ + public destroy(): void { + if (this.files !== undefined && this.files.length > 0) { + for (let i: number = 0; i < this.files.length; i++) { + let file: ZipArchiveItem | string = this.files[i]; + if (file instanceof ZipArchiveItem) { + (file as ZipArchiveItem).destroy(); + } + file = undefined; + } + this.files = []; + } + this.files = undefined; + this.level = undefined; + } + private getCompressedData(item: ZipArchiveItem | string): Promise { + let zipArchive: ZipArchive = this; + let promise: Promise = new Promise((resolve: Function, reject: Function) => { + if (item instanceof ZipArchiveItem) { + let reader: FileReader = new FileReader(); + reader.onload = () => { + let input: Uint8Array = new Uint8Array(reader.result as ArrayBuffer); + let data: CompressedData = { + fileName: item.name, crc32Value: 0, compressedData: [], + compressedSize: undefined, uncompressedDataSize: input.length, compressionType: undefined, + isDirectory: false + }; + if (zipArchive.level === 'Normal') { + zipArchive.compressData(input, data, CRC32TABLE); + let length: number = 0; + for (let i: number = 0; i < data.compressedData.length; i++) { + length += data.compressedData[i].length; + } + data.compressedSize = length; + data.compressionType = '\x08\x00'; //Deflated = 8 + } else { + data.compressedSize = input.length; + data.crc32Value = zipArchive.calculateCrc32Value(0, input, CRC32TABLE); + data.compressionType = '\x00\x00'; // Stored = 0 + (data.compressedData as Uint8Array[]).push(input); + } + resolve(data); + }; + reader.readAsArrayBuffer(item.data as Blob); + } else { + let data: CompressedData = { + fileName: item as string, crc32Value: 0, compressedData: '', compressedSize: 0, uncompressedDataSize: 0, + compressionType: '\x00\x00', isDirectory: true + }; + resolve(data); + } + }); + return promise; + } + private compressData(input: Uint8Array, data: CompressedData, crc32Table: number[]): void { + let compressor: CompressedStreamWriter = new CompressedStreamWriter(true); + let currentIndex: number = 0; + let nextIndex: number = 0; + do { + if (currentIndex >= input.length) { + compressor.close(); + break; + } + nextIndex = Math.min(input.length, currentIndex + 16384); + let subArray: Uint8Array = input.subarray(currentIndex, nextIndex); + data.crc32Value = this.calculateCrc32Value(data.crc32Value, subArray, crc32Table); + compressor.write(subArray, 0, nextIndex - currentIndex); + currentIndex = nextIndex; + } while (currentIndex <= input.length); + data.compressedData = compressor.compressedData; + compressor.destroy(); + } + private constructZippedObject(zipParts: ZippedObject[], data: CompressedData, dirLength: number, isDirectory: boolean): number { + let extFileAttr: number = 0; + let date: Date = new Date(); + if (isDirectory) { + extFileAttr = extFileAttr | 0x00010; // directory flag + } + extFileAttr = extFileAttr | (0 & 0x3F); + let header: string = this.writeHeader(data, date); + let localHeader: string = 'PK\x03\x04' + header + data.fileName; + let centralDir: string = this.writeCentralDirectory(data, header, dirLength, extFileAttr); + zipParts.push({ localHeader: localHeader, centralDir: centralDir, compressedData: data }); + return dirLength + localHeader.length + data.compressedSize; + } + private writeHeader(data: CompressedData, date: Date): string { + let zipHeader: string = ''; + zipHeader += '\x0A\x00' + '\x00\x00'; // version needed to extract & general purpose bit flag + zipHeader += data.compressionType; // compression method Deflate=8,Stored=0 + zipHeader += this.getBytes(this.getModifiedTime(date), 2); // last modified Time + zipHeader += this.getBytes(this.getModifiedDate(date), 2); // last modified date + zipHeader += this.getBytes(data.crc32Value, 4); // crc-32 value + zipHeader += this.getBytes(data.compressedSize, 4); // compressed file size + zipHeader += this.getBytes(data.uncompressedDataSize, 4); // uncompressed file size + zipHeader += this.getBytes(data.fileName.length, 2); // file name length + zipHeader += this.getBytes(0, 2); // extra field length + return zipHeader; + } + private writeZippedContent(fileName: string, zipData: ZippedObject[], localDirLen: number, skipFileSave: boolean): Blob { + let cenDirLen: number = 0; + let buffer: ArrayBuffer[] = []; + for (let i: number = 0; i < zipData.length; i++) { + let item: ZippedObject = zipData[i]; + cenDirLen += item.centralDir.length; + buffer.push(this.getArrayBuffer(item.localHeader)); + while (item.compressedData.compressedData.length) { + buffer.push((item.compressedData.compressedData as Uint8Array[]).shift().buffer); + } + } + for (let i: number = 0; i < zipData.length; i++) { + buffer.push(this.getArrayBuffer(zipData[i].centralDir)); + } + buffer.push(this.getArrayBuffer(this.writeFooter(zipData, cenDirLen, localDirLen))); + let blob: Blob = new Blob(buffer, { type: 'application/zip' }); + if (!skipFileSave) { + Save.save(fileName, blob); + } + return blob; + } + private writeCentralDirectory(data: CompressedData, localHeader: string, offset: number, externalFileAttribute: number): string { + let directoryHeader: string = 'PK\x01\x02' + + this.getBytes(0x0014, 2) + localHeader + // inherit from file header + this.getBytes(0, 2) + // comment length + '\x00\x00' + '\x00\x00' + // internal file attributes + this.getBytes(externalFileAttribute, 4) + // external file attributes + this.getBytes(offset, 4) + // local fileHeader relative offset + data.fileName; + return directoryHeader; + } + private writeFooter(zipData: ZippedObject[], centralLength: number, localLength: number): string { + let dirEnd: string = 'PK\x05\x06' + '\x00\x00' + '\x00\x00' + + this.getBytes(zipData.length, 2) + this.getBytes(zipData.length, 2) + + this.getBytes(centralLength, 4) + this.getBytes(localLength, 4) + + this.getBytes(0, 2); + return dirEnd; + } + private getArrayBuffer(input: string): ArrayBuffer { + let a: Uint8Array = new Uint8Array(input.length); + for (let j: number = 0; j < input.length; ++j) { + a[j] = input.charCodeAt(j) & 0xFF; + } + return a.buffer; + } + private getBytes(value: number, offset: number): string { + let bytes: string = ''; + for (let i: number = 0; i < offset; i++) { + bytes += String.fromCharCode(value & 0xff); + value = value >>> 8; + } + return bytes; + } + private getModifiedTime(date: Date): number { + let modTime: number = date.getHours(); + modTime = modTime << 6; + modTime = modTime | date.getMinutes(); + modTime = modTime << 5; + return modTime = modTime | date.getSeconds() / 2; + } + private getModifiedDate(date: Date): number { + let modiDate: number = date.getFullYear() - 1980; + modiDate = modiDate << 4; + modiDate = modiDate | (date.getMonth() + 1); + modiDate = modiDate << 5; + return modiDate = modiDate | date.getDate(); + } + private calculateCrc32Value(crc32Value: number, input: Uint8Array, crc32Table: number[]): number { + crc32Value ^= -1; + for (let i: number = 0; i < input.length; i++) { + crc32Value = (crc32Value >>> 8) ^ crc32Table[(crc32Value ^ input[i]) & 0xFF]; + } + return (crc32Value ^ (-1)); + } + /** + * construct cyclic redundancy code table + * @private + */ + public static initCrc32Table(): void { + let i: number; + for (let j: number = 0; j < 256; j++) { + i = j; + for (let k: number = 0; k < 8; k++) { + i = ((i & 1) ? (0xEDB88320 ^ (i >>> 1)) : (i >>> 1)); + } + CRC32TABLE[j] = i; + } + } + public static findValueFromEnd(stream: Stream, value: number, maxCount: number) { + if (stream == null) + throw new DOMException("stream"); + + // if( !stream.CanSeek || !stream.CanRead ) + // throw new ArgumentOutOfRangeException( "We need to have seekable and readable stream." ); + + // read last 4 bytes and compare with required value + let lStreamSize: number = stream.inputStream.buffer.byteLength; + + if (lStreamSize < 4) + return -1; + + let arrBuffer: Uint8Array = new Uint8Array(4); + let lLastPos: number = Math.max(0, lStreamSize - maxCount); + let lCurrentPosition: number = lStreamSize - 1 - INT_SIZE; + + stream.position = lCurrentPosition; + stream.read(arrBuffer, 0, INT_SIZE); + let uiCurValue: number = arrBuffer[0]; + let bFound: boolean = (uiCurValue == value); + + if (!bFound) { + while (lCurrentPosition > lLastPos) { + // remove unnecessary byte and replace it with new value. + uiCurValue <<= 8; + lCurrentPosition--; + stream.position = lCurrentPosition; + uiCurValue += stream.readByte(); + + if (uiCurValue == value) { + bFound = true; + break; + } + } + } + + return bFound ? lCurrentPosition : -1; + } + /// + /// Extracts Int32 value from the stream. + /// + /// Stream to read data from. + /// Extracted value. + public static ReadInt32(stream: Stream): number { + + + + let buffer: Uint8Array = new Uint8Array(INT_SIZE); + + + + if (stream.read(buffer, 0, INT_SIZE) != INT_SIZE) { + throw new DOMException("Unable to read value at the specified position - end of stream was reached."); + } + + return Utils.bitConverterToInt32(buffer, 0); + } + /// + /// Extracts Int16 value from the stream. + /// + /// Stream to read data from. + /// Extracted value. + public static ReadInt16(stream: Stream): number { + let buffer: Uint8Array = new Uint8Array(SHORT_SIZE) + if (stream.read(buffer, 0, SHORT_SIZE) != SHORT_SIZE) { + throw new DOMException("Unable to read value at the specified position - end of stream was reached."); + } + + return Utils.bitConverterToInt16(buffer, 0); + } + /// + /// Extracts unsigned Int16 value from the stream. + /// + /// Stream to read data from. + /// Extracted value. + public static ReadUInt16(stream: Stream): number { + { + let buffer: Uint8Array = new Uint8Array(SHORT_SIZE) + if (stream.read(buffer, 0, SHORT_SIZE) != SHORT_SIZE) { + throw new DOMException("Unable to read value at the specified position - end of stream was reached."); + } + + return Utils.bitConverterToInt16(buffer, 0); + } + } + +} +export class ZipArchiveItemHelper { + public compressedStream : Uint8Array; + public name: string; + public unCompressedStream : Uint8Array; + /// + /// Zip header signature. + /// + public headerSignature: number = 0x04034b50; + /// + /// General purpose bit flag. + /// + private options: number; + /// + /// Compression method. + /// + private compressionMethod: number; + /// + /// Indicates whether we should check Crc value when reading item's data. Check + /// is performed when user gets access to decompressed data for the first time. + /// + public checkCrc: boolean = true; + /// + /// Crc. + /// + private crc32: number = 0; + /// + /// Compressed data size. + /// + private compressedSize: number; + /// + /// Original (not compressed) data size. + /// + private originalSize: number; + /// + /// Offset to the local header. + /// + private localHeaderOffset: number; + /// + /// Item's external attributes. + /// + private externalAttributes: number; + /// + /// Read data from the stream based on the central directory. + /// + /// Stream to read data from, stream.Position must point at just after correct file header. + public readCentralDirectoryData(stream: Stream): void { + // on the current moment we ignore "version made by" and "version needed to extract" fields. + stream.position += 4; + + this.options = ZipArchive.ReadInt16(stream); + this.compressionMethod = ZipArchive.ReadInt16(stream); + this.checkCrc = (this.compressionMethod != 99); //COmpression.Defalte != SecurityConstants.AES + + + + + + //m_bCompressed = true; + + // on the current moment we ignore "last mod file time" and "last mod file date" fields. + let lastModified: number = ZipArchive.ReadInt32(stream); + + //LastModified = ConvertToDateTime(lastModified); + + this.crc32 = Utils.bitConverterToUInt32(ZipArchive.ReadInt32(stream)); + this.compressedSize = ZipArchive.ReadInt32(stream); + this.originalSize = ZipArchive.ReadInt32(stream); + + let iFileNameLength = ZipArchive.ReadInt16(stream); + let iExtraFieldLenth = ZipArchive.ReadInt16(stream); + let iCommentLength = ZipArchive.ReadInt16(stream); + + // on the current moment we ignore and "disk number start" (2 bytes), + // "internal file attributes" (2 bytes). + stream.position += 4; + + this.externalAttributes = ZipArchive.ReadInt32(stream); + this.localHeaderOffset = ZipArchive.ReadInt32(stream); + + let arrBuffer: Uint8Array = new Uint8Array(iFileNameLength); + + stream.read(arrBuffer, 0, iFileNameLength); + + + let m_strItemName = Utils.byteToString(arrBuffer); + m_strItemName = m_strItemName.replace("\\", "/"); +this.name = m_strItemName; + stream.position += iExtraFieldLenth + iCommentLength; + if (this.options != 0) this.options = 0; + } + /// + /// Reads zipped data from the stream. + /// + /// Stream to read data from. + /// Indicates whether we should check crc value after data decompression. + public readData(stream: Stream, checkCrc: boolean): void { + if (stream.length == 0) + throw new DOMException("stream"); + + stream.position = this.localHeaderOffset; + this.checkCrc = checkCrc; + + this.readLocalHeader(stream); + this.readCompressedData( stream ); + } + public decompressData(): void { + if(this.compressionMethod == 8){ + if( this.originalSize > 0 ) + { +this.decompressDataOld(); + } + } + } + private decompressDataOld(): void + { + let reader: CompressedStreamReader = new CompressedStreamReader( this.compressedStream, true ); + + let decompressedData: Stream; + if(this.originalSize>0) + decompressedData = new Stream(new Uint8Array(this.originalSize)); + let arrBuffer:Uint8Array = new Uint8Array(BufferSize ); + let iReadBytes: number; + let past:Uint8Array = new Uint8Array(0); + while ((iReadBytes = reader.read(arrBuffer, 0, BufferSize)) > 0) { +// past = new Uint8Array(decompressedData.length); +// let currentBlock: Uint8Array = arrBuffer.subarray(0, iReadBytes); + decompressedData.write(arrBuffer.subarray(0, iReadBytes), 0, iReadBytes); + } + this.unCompressedStream = decompressedData.toByteArray(); + // this.originalSize = decompressedData.Length; + // m_bControlStream = true; + // m_streamData = decompressedData; + // decompressedData.SetLength( m_lOriginalSize ); + // decompressedData.Capacity = ( int )m_lOriginalSize; + + if (this.checkCrc) { + //TODO: fix this + //CheckCrc(decompressedData.ToArray()); + + } + + //m_streamData.Position = 0; + } + /// + /// Extracts local header from the stream. + /// + /// Stream to read data from. + private readLocalHeader(stream: Stream) { + if (stream.length == 0) + throw new DOMException("stream"); + + if (ZipArchive.ReadInt32(stream) != this.headerSignature) + throw new DOMException("Can't find local header signature - wrong file format or file is corrupt."); + + // TODO: it is good to verify data read from the central directory record, + // but on the current moment we simply skip it. + stream.position += 22; + + let iNameLength = ZipArchive.ReadInt16(stream); + let iExtraLength = ZipArchive.ReadUInt16(stream); + + if (this.compressionMethod == 99) //SecurityConstants.AES + { + // stream.Position += iNameLength + 8; + // m_archive.EncryptionAlgorithm = (EncryptionAlgorithm)stream.ReadByte(); + // m_actualCompression = new byte[2]; + // stream.Read(m_actualCompression, 0, 2); + } + else if (iExtraLength > 2) { + stream.position += iNameLength; + let headerVal = ZipArchive.ReadInt16(stream); + if (headerVal == 0x0017) //PKZipEncryptionHeader + throw new DOMException("UnSupported"); + else + stream.position += iExtraLength - 2; + } + else + stream.position += iNameLength + iExtraLength; + } + /// + /// Extracts compressed data from the stream. + /// + /// Stream to read data from. + private readCompressedData(stream: Stream ): void + { + let dataStream: Stream; + if( this.compressedSize > 0 ) + { + + let iBytesLeft: number = this.compressedSize; + dataStream = new Stream(new Uint8Array(iBytesLeft)); + + + let arrBuffer: Uint8Array = new Uint8Array(BufferSize); + + while( iBytesLeft > 0 ) + { + let iBytesToRead: number = Math.min( iBytesLeft, BufferSize ); + + if( stream.read( arrBuffer, 0, iBytesToRead ) != iBytesToRead ) + throw new DOMException( "End of file reached - wrong file format or file is corrupt." ); + dataStream.write(arrBuffer.subarray(0, iBytesToRead), 0, iBytesToRead); + + iBytesLeft -= iBytesToRead; + } + + // if(m_archive.Password != null) + // { + // byte[] dataBuffer = new byte[dataStream.Length]; + // dataBuffer = dataStream.ToArray(); + // dataStream=new MemoryStream( Decrypt(dataBuffer)); + // } + this.compressedStream = new Uint8Array(dataStream.inputStream); + // m_bControlStream = true; + } + else if(this.compressedSize<0) //If compression size is negative, then read until the next header signature reached. + { + // MemoryStream dataStream = new MemoryStream(); + // int bt = 0; + // bool proceed=true; + // while (proceed) + // { + // if ((bt = stream.ReadByte()) == Constants.HeaderSignatureStartByteValue) + // { + // stream.Position -= 1; + // int headerSignature = ZipArchive.ReadInt32(stream); + // if (headerSignature==Constants.CentralHeaderSignature || headerSignature==Constants.CentralHeaderSignature) + // { + // proceed = false; + + // } + // stream.Position -= 3; + // } + // if (proceed) + // dataStream.WriteByte((byte)bt); + // } + // m_streamData = dataStream; + // m_lCompressedSize = m_streamData.Length; + // m_bControlStream = true; + } + else if(this.compressedSize == 0) + { + // m_streamData = new MemoryStream(); + } + } +} +/** + * Class represent unique ZipArchive item + * ```typescript + * let archiveItem = new ZipArchiveItem(archive, 'directoryName\fileName.txt'); + * ``` + */ +export class ZipArchiveItem { + public data: Blob | ArrayBuffer; + private decompressedStream: Blob | ArrayBuffer; + private fileName: string; + public get dataStream(): Blob | ArrayBuffer { + return this.decompressedStream; + } + /** + * Get the name of archive item + * @returns string + */ + get name(): string { + return this.fileName; + } + /** + * Set the name of archive item + * @param {string} value + */ + set name(value: string) { + this.fileName = value; + } + /** + * constructor for creating {ZipArchiveItem} instance + * @param {Blob|ArrayBuffer} data file data + * @param {itemName} itemName absolute file path + */ + constructor(data: Blob | ArrayBuffer, itemName: string) { + if (data === null || data === undefined) { + throw new Error('ArgumentException: data cannot be null or undefined'); + } + if (itemName === null || itemName === undefined) { + throw new Error('ArgumentException: string cannot be null or undefined'); + } + if (itemName.length === 0) { + throw new Error('string cannot be empty'); + } + this.data = data; + this.name = itemName; + } + /** + * release allocated un-managed resource + * @returns {void} + */ + public destroy(): void { + this.fileName = undefined; + this.data = undefined; + } +} +export interface CompressedData { + fileName: string; + compressedData: Uint8Array[] | string; + uncompressedDataSize: number; + compressedSize: number; + crc32Value: number; + compressionType: string; + isDirectory: boolean; +} +export interface ZippedObject { + localHeader: string; + centralDir: string; + compressedData: CompressedData; +} +/** + * Compression level. + */ +export type CompressionLevel = + /* Pack without compression */ + 'NoCompression' | + /* Use normal compression, middle between speed and size*/ + 'Normal'; +/* eslint-enable */ \ No newline at end of file diff --git a/controls/compression/test-main.js b/controls/compression/test-main.js new file mode 100644 index 0000000000..6b610e1953 --- /dev/null +++ b/controls/compression/test-main.js @@ -0,0 +1,36 @@ +var allTestFiles = []; +var TEST_REGEXP = /(spec|test)\.js$/i; + +// Get a list of all the test files to include +Object.keys(window.__karma__.files).forEach(function(file) { + if (TEST_REGEXP.test(file)) { + // Normalize paths to RequireJS module names. + // If you require sub-dependencies of test files to be loaded as-is (requiring file extension) + // then do not normalize the paths + var normalizedTestModule = file.replace(/^\/base\/|\.js$/g, ''); + allTestFiles.push(normalizedTestModule); + } +}); + +require.config({ + // Karma serves files under /base, which is the basePath from your config file + baseUrl: '/base', + + packages: [ + { + name: '@syncfusion/ej2-file-utils', + location: 'node_modules/@syncfusion/ej2-file-utils/dist', + main: 'ej2-file-utils.umd.min.js' + } + // Include dependent packages + ], + + // dynamically load all test files + deps: allTestFiles, + + // we have to kickoff jasmine, as it is asynchronous + callback: window.__karma__.start, + + // number of seconds to wait before giving up on loading a script + waitSeconds: 30, +}); diff --git a/controls/compression/tsconfig.json b/controls/compression/tsconfig.json new file mode 100644 index 0000000000..82da9231fa --- /dev/null +++ b/controls/compression/tsconfig.json @@ -0,0 +1,31 @@ +{ + "compilerOptions": { + "target": "es5", + "module": "amd", + "declaration": true, + "removeComments": true, + "noLib": false, + "experimentalDecorators": true, + "sourceMap": true, + "pretty": true, + "allowUnreachableCode": false, + "allowUnusedLabels": false, + "noImplicitAny": true, + "noImplicitReturns": true, + "noImplicitUseStrict": false, + "noFallthroughCasesInSwitch": true, + "allowJs": false, + "forceConsistentCasingInFileNames": true, + "moduleResolution": "node", + "lib": ["es5", "es2015.promise", "dom"], + "types": ["jasmine","jasmine-ajax","requirejs","chai"] + }, + "exclude": [ + "node_modules", + "dist", + "public", + "coverage", + "test-report" + ], + "compileOnSave": false +} diff --git a/controls/diagrams/CHANGELOG.md b/controls/diagrams/CHANGELOG.md index 10000140c9..26c63eb15a 100644 --- a/controls/diagrams/CHANGELOG.md +++ b/controls/diagrams/CHANGELOG.md @@ -2,6 +2,15 @@ ## [Unreleased] +## 25.1.37 (2024-03-26) + +### Diagram + +#### Bug Fixes + +- `#I555965` - Now, the overview updated properly without any shadows while dragging html nodes. +- `#I562959` - Now, drawing connector from group node port works properly. + ## 25.1.35 (2024-03-15) ### Diagram diff --git a/controls/diagrams/package.json b/controls/diagrams/package.json index deac82c69a..4c776d08d8 100644 --- a/controls/diagrams/package.json +++ b/controls/diagrams/package.json @@ -1,6 +1,6 @@ { "name": "@syncfusion/ej2-diagrams", - "version": "19.18.0", + "version": "25.1.35", "description": "Feature-rich diagram control to create diagrams like flow charts, organizational charts, mind maps, and BPMN diagrams. Its rich feature set includes built-in shapes, editing, serializing, exporting, printing, overview, data binding, and automatic layouts.", "author": "Syncfusion Inc.", "license": "SEE LICENSE IN license", diff --git a/controls/diagrams/spec/overview/overview.spec.ts b/controls/diagrams/spec/overview/overview.spec.ts index 7d970ac668..8865bb30ef 100644 --- a/controls/diagrams/spec/overview/overview.spec.ts +++ b/controls/diagrams/spec/overview/overview.spec.ts @@ -2035,5 +2035,78 @@ describe('Overview', () => { }); }); + + describe('872140-Dragging HTML nodes in a diagram leaves shadows on the overview', () => { + let diagram: Diagram; + let overview: Overview; + let ele: HTMLElement; + let ove: HTMLElement; + let mouseEvents: MouseEvents = new MouseEvents(); + beforeAll((): void => { + ele = createElement('div', { id: 'diagramShadow', styles: "width:74%;height: 500px; float:left" }); + document.body.appendChild(ele); + ove = createElement('div', { id: 'overviewShadow', styles: "width:25%;height:200px;float:left; border-color:lightgray;border-style:solid;" }); + document.body.appendChild(ove); + + let nodes:NodeModel[] = [ + { id: "Node1",offsetX: 400,offsetY: 250,width: 100,height: 100,shape: { + type: "HTML", + content: '
' + } + }, + { id: "Node2",offsetX: 400,offsetY: 250,width: 100,height: 100 + }, + + ]; + diagram = new Diagram({ + width: '100%', + height: '700px', + rulerSettings:{showRulers:true}, + nodes:nodes, + getNodeDefaults: (obj: NodeModel, diagram: Diagram) => { + obj.height = 100; + return obj; + } + }); + diagram.appendTo('#diagramShadow'); + + let overview: Overview = new Overview({ + sourceID: 'diagramShadow', + }); + overview.appendTo('#overviewShadow'); + + + }); + + afterAll((): void => { + overview.destroy(); + diagram.destroy(); + ele.remove(); + ove.remove(); + }); + + it('Drag the html node in diagram', (done: Function) => { + let html = diagram.nameTable['Node1']; + let overview = (document.getElementById('overviewShadow') as any).ej2_instances[0]; + let overviewRect = document.getElementById(overview.canvas.id + 'overviewrect'); + let x = Number(overviewRect.getAttribute('x')); + let y = Number(overviewRect.getAttribute('y')); + let width = Number(overviewRect.getAttribute('width')); + let height = Number(overviewRect.getAttribute('height')); + diagram.select([html]); + let diagramCanvas: HTMLElement = document.getElementById(diagram.element.id + 'content'); + mouseEvents.mouseMoveEvent(diagramCanvas,html.offsetX,html.offsetY); + mouseEvents.mouseDownEvent(diagramCanvas,html.offsetX,html.offsetY); + mouseEvents.mouseMoveEvent(diagramCanvas,html.offsetX + 10,html.offsetY + 10); + mouseEvents.mouseUpEvent(diagramCanvas,html.offsetX + 10,html.offsetY + 10); + let curX = Number(overviewRect.getAttribute('x')); + let curY = Number(overviewRect.getAttribute('y')); + let curWidth = Number(overviewRect.getAttribute('width')); + let curHeight = Number(overviewRect.getAttribute('height')); + expect(x === curX && y === curY && width === curWidth && height === curHeight).toBe(true); + done(); + }); + + }); }); \ No newline at end of file diff --git a/controls/diagrams/src/diagram/interaction/command-manager.ts b/controls/diagrams/src/diagram/interaction/command-manager.ts index e353203603..e1a51065eb 100644 --- a/controls/diagrams/src/diagram/interaction/command-manager.ts +++ b/controls/diagrams/src/diagram/interaction/command-manager.ts @@ -42,7 +42,7 @@ import { Snapping } from '../objects/snapping'; import { LayoutAnimation } from '../objects/layout-animation'; import { Container } from '../core/containers/container'; import { Canvas } from '../core/containers/canvas'; -import { getDiagramElement, getAdornerLayerSvg, getHTMLLayer, getAdornerLayer, getSelectorElement } from '../utility/dom-util'; +import { getDiagramElement, getAdornerLayerSvg, getHTMLLayer, getAdornerLayer, getSelectorElement, setAttributeHtml } from '../utility/dom-util'; import { Point } from '../primitives/point'; import { Size } from '../primitives/size'; import { getObjectType, getPoint, intersect2, getOffsetOfConnector, canShowCorner } from './../utility/diagram-util'; @@ -6000,10 +6000,41 @@ Remove terinal segment in initial } this.diagram.diagramActions = this.diagram.diagramActions & ~(DiagramAction.PreventZIndexOnDragging | DiagramAction.DragUsingMouse); this.diagram.refreshCanvasLayers(); + //Bug 872140: Dragging HTML nodes in a diagram leaves shadows on the overview + this.checkHtmlObjectDrag(obj); return true; } return false; - } + }; + // Checks if any HTML object is being dragged and reset the canvas to clear the shadow of the HTML node border. + private checkHtmlObjectDrag(obj: SelectorModel | NodeModel | ConnectorModel){ + let isHtmlObjDragged = false; + if(this.diagram.views && this.diagram.views.length > 1){ + if(obj instanceof Selector){ + isHtmlObjDragged = obj.nodes.some(node => node.shape && node.shape.type === 'HTML'); + } else if((obj as NodeModel).shape && (obj as NodeModel).shape.type === 'HTML') { + isHtmlObjDragged = true; + } + if(isHtmlObjDragged){ + this.resetOverviewCanvas(); + } + } + }; + //Resetting Overview canvas + private resetOverviewCanvas(){ + for (const temp of this.diagram.views) { + const view: View = this.diagram.views[temp]; + if (!(view instanceof Diagram)) { + const rect: HTMLElement = document.getElementById((view as any).canvas.id + 'overviewrect'); + const x = Number(rect.getAttribute('x')); + const y = Number(rect.getAttribute('y')); + const width = Number(rect.getAttribute('width')); + const height = Number(rect.getAttribute('height')); + const attr: Object = { x: x, y: y, width: Math.max(1, width), height: Math.max(1, height) }; + setAttributeHtml(rect, attr); + } + } + }; /** @private */ public scaleSelectedItems(sx: number, sy: number, pivot: PointModel): boolean { let obj: SelectorModel | NodeModel | ConnectorModel = this.diagram.selectedItems; diff --git a/controls/documenteditor/CHANGELOG.md b/controls/documenteditor/CHANGELOG.md index 54df18862d..45e132388b 100644 --- a/controls/documenteditor/CHANGELOG.md +++ b/controls/documenteditor/CHANGELOG.md @@ -2,6 +2,29 @@ ## [Unreleased] +## 25.1.37 (2024-03-26) + +### DocumentEditor + +#### Bug Fixes + +- `#I545513` - Added the preservation support for table style property in Document editor. +- `#I548396` - Resolved the page number not refreshed issue while delete page. +- `#I549835` - Resolved the document lagging issue. +- `#I553758` - Resolved the editing issue in the attached document, which contains a chart. +- `#I556874` - Resolved the script error issue when performing undo action on table. +- `#I558460` - Resolved the tab rendering issue in the attached document. +- `#I558529` - Resolved the form field editing issue in read only mode. +- `#I558289` - Resolved the list numbering issue. +- `#I558259` - Resolved the content formatting issue when removing hyperlink. +- `#I559197` - Resolved the drag and drop issue. +- `#I559912` - Resolved the image removed issue when selecting an image and perform enter action. +- `#I561716` - Resolved the duplicate image string added to sfdt issue while drag and drop. +- `#I561052` - Resolved the cursor position issue in mobile mode. +- `#I563837` - Resolved the table overlapping issue in the attached document. +- `#F186648` - Resolved the script error issue while opening a attached document. +- `#F186745` - Resolved the table splitting issue in the merge cell. + ## 25.1.35 (2024-03-15) ### DocumentEditor diff --git a/controls/documenteditor/package.json b/controls/documenteditor/package.json index 280ffb3ff6..0502a08220 100644 --- a/controls/documenteditor/package.json +++ b/controls/documenteditor/package.json @@ -1,6 +1,6 @@ { "name": "@syncfusion/ej2-documenteditor", - "version": "19.92.0", + "version": "25.1.35", "description": "Feature-rich document editor control with built-in support for context menu, options pane and dialogs.", "keywords": [ "ej2", diff --git a/controls/documenteditor/spec/implementation/collaboration/footnote_endnote.spec.ts b/controls/documenteditor/spec/implementation/collaboration/footnote_endnote.spec.ts index df45f8fa91..868ed6ccc6 100644 --- a/controls/documenteditor/spec/implementation/collaboration/footnote_endnote.spec.ts +++ b/controls/documenteditor/spec/implementation/collaboration/footnote_endnote.spec.ts @@ -1,115 +1,115 @@ -import { DocumentEditorContainer, ContainerContentChangeEventArgs, CONTROL_CHARACTERS } from '../../../src/index'; -import { createElement } from '@syncfusion/ej2-base'; -import { Toolbar } from '../../../src/document-editor-container/tool-bar/tool-bar'; -/** - * Footnote endnote collaborative editing spec - */ -describe('Enable track changes in collaborative editing', () => { - let container: DocumentEditorContainer; - let element: HTMLElement; - let args: ContainerContentChangeEventArgs; - beforeAll(() => { - element = createElement('div'); - document.body.appendChild(element); - DocumentEditorContainer.Inject(Toolbar); - container = new DocumentEditorContainer(); - container.appendTo(element); - container.documentEditor.enableCollaborativeEditing = true; - }); - afterAll(() => { - expect(() => { container.destroy(); }).not.toThrowError(); - expect(element.childNodes.length).toBe(0); - document.body.removeChild(element); - document.body.innerHTML = ''; - element = undefined; - container = undefined; - }); +// import { DocumentEditorContainer, ContainerContentChangeEventArgs, CONTROL_CHARACTERS } from '../../../src/index'; +// import { createElement } from '@syncfusion/ej2-base'; +// import { Toolbar } from '../../../src/document-editor-container/tool-bar/tool-bar'; +// /** +// * Footnote endnote collaborative editing spec +// */ +// describe('Enable track changes in collaborative editing', () => { +// let container: DocumentEditorContainer; +// let element: HTMLElement; +// let args: ContainerContentChangeEventArgs; +// beforeAll(() => { +// element = createElement('div'); +// document.body.appendChild(element); +// DocumentEditorContainer.Inject(Toolbar); +// container = new DocumentEditorContainer(); +// container.appendTo(element); +// container.documentEditor.enableCollaborativeEditing = true; +// }); +// afterAll(() => { +// expect(() => { container.destroy(); }).not.toThrowError(); +// expect(element.childNodes.length).toBe(0); +// document.body.removeChild(element); +// document.body.innerHTML = ''; +// element = undefined; +// container = undefined; +// }); - it('insert footnote', () => { - console.log('insert footnote'); - let argsEle: ContainerContentChangeEventArgs; - container.contentChange = function (args: ContainerContentChangeEventArgs) { - argsEle = args; - } - container.documentEditor.editorModule.insertText('Syncfusion Software'); - container.documentEditor.selection.select('0;0;4', '0;0;4'); - container.documentEditor.editor.insertFootnote(); - expect(argsEle.operations[0].markerData.type).toBe("Footnote"); - expect(argsEle.operations[0].length).toBe(4); - expect(argsEle.operations[0].text).toBe(CONTROL_CHARACTERS.Marker_Start); - }); +// it('insert footnote', () => { +// console.log('insert footnote'); +// let argsEle: ContainerContentChangeEventArgs; +// container.contentChange = function (args: ContainerContentChangeEventArgs) { +// argsEle = args; +// } +// container.documentEditor.editorModule.insertText('Syncfusion Software'); +// container.documentEditor.selection.select('0;0;4', '0;0;4'); +// container.documentEditor.editor.insertFootnote(); +// expect(argsEle.operations[0].markerData.type).toBe("Footnote"); +// expect(argsEle.operations[0].length).toBe(4); +// expect(argsEle.operations[0].text).toBe(CONTROL_CHARACTERS.Marker_Start); +// }); - it('footnote undo/redo', () => { - console.log('footnote undo/redo'); - let argsEle: ContainerContentChangeEventArgs; - container.contentChange = function (args: ContainerContentChangeEventArgs) { - argsEle = args; - } - container.documentEditor.openBlank(); - container.documentEditor.editorModule.insertText('Syncfusion Software'); - container.documentEditor.selection.select('0;0;4', '0;0;4'); - container.documentEditor.editor.insertFootnote(); - container.documentEditor.editorHistory.undo(); - expect(argsEle.operations[0].length).toBe(4); - expect(argsEle.operations[0].action).toBe('Delete'); - container.documentEditor.editorHistory.redo(); - expect(argsEle.operations[0].type).toBe('Paste'); - expect(argsEle.operations[0].pasteContent).toBeDefined(); - //Undo redo delete footnote - container.documentEditor.selection.select('0;0;5', '0;0;5'); - container.documentEditor.editor.onBackSpace(); - expect(argsEle.operations[0].length).toBe(4); - expect(argsEle.operations[0].action).toBe('Delete'); - container.documentEditor.editorHistory.undo(); - expect(argsEle.operations[0].type).toBe('Paste'); - expect(argsEle.operations[0].pasteContent).toBeDefined(); - container.documentEditor.editorHistory.redo(); - expect(argsEle.operations[0].length).toBe(4); - expect(argsEle.operations[0].action).toBe('Delete'); - }); +// it('footnote undo/redo', () => { +// console.log('footnote undo/redo'); +// let argsEle: ContainerContentChangeEventArgs; +// container.contentChange = function (args: ContainerContentChangeEventArgs) { +// argsEle = args; +// } +// container.documentEditor.openBlank(); +// container.documentEditor.editorModule.insertText('Syncfusion Software'); +// container.documentEditor.selection.select('0;0;4', '0;0;4'); +// container.documentEditor.editor.insertFootnote(); +// container.documentEditor.editorHistory.undo(); +// expect(argsEle.operations[0].length).toBe(4); +// expect(argsEle.operations[0].action).toBe('Delete'); +// container.documentEditor.editorHistory.redo(); +// expect(argsEle.operations[0].type).toBe('Paste'); +// expect(argsEle.operations[0].pasteContent).toBeDefined(); +// //Undo redo delete footnote +// container.documentEditor.selection.select('0;0;5', '0;0;5'); +// container.documentEditor.editor.onBackSpace(); +// expect(argsEle.operations[0].length).toBe(4); +// expect(argsEle.operations[0].action).toBe('Delete'); +// container.documentEditor.editorHistory.undo(); +// expect(argsEle.operations[0].type).toBe('Paste'); +// expect(argsEle.operations[0].pasteContent).toBeDefined(); +// container.documentEditor.editorHistory.redo(); +// expect(argsEle.operations[0].length).toBe(4); +// expect(argsEle.operations[0].action).toBe('Delete'); +// }); - it('insert endnote', () => { - console.log('insert endnote'); - let argsEle: ContainerContentChangeEventArgs; - container.contentChange = function (args: ContainerContentChangeEventArgs) { - argsEle = args; - } - container.documentEditor.openBlank(); - container.documentEditor.editorModule.insertText('Syncfusion Software'); - container.documentEditor.selection.select('0;0;4', '0;0;4'); - container.documentEditor.editor.insertEndnote(); - expect(argsEle.operations[0].markerData.type).toBe("Endnote"); - expect(argsEle.operations[0].length).toBe(4); - expect(argsEle.operations[0].text).toBe(CONTROL_CHARACTERS.Marker_Start); - }); +// it('insert endnote', () => { +// console.log('insert endnote'); +// let argsEle: ContainerContentChangeEventArgs; +// container.contentChange = function (args: ContainerContentChangeEventArgs) { +// argsEle = args; +// } +// container.documentEditor.openBlank(); +// container.documentEditor.editorModule.insertText('Syncfusion Software'); +// container.documentEditor.selection.select('0;0;4', '0;0;4'); +// container.documentEditor.editor.insertEndnote(); +// expect(argsEle.operations[0].markerData.type).toBe("Endnote"); +// expect(argsEle.operations[0].length).toBe(4); +// expect(argsEle.operations[0].text).toBe(CONTROL_CHARACTERS.Marker_Start); +// }); - it('endnote undo/redo', () => { - console.log('endnote undo/redo'); - let argsEle: ContainerContentChangeEventArgs; - container.contentChange = function (args: ContainerContentChangeEventArgs) { - argsEle = args; - } - container.documentEditor.openBlank(); - container.documentEditor.editorModule.insertText('Syncfusion Software'); - container.documentEditor.selection.select('0;0;4', '0;0;4'); - container.documentEditor.editor.insertEndnote(); - container.documentEditor.editorHistory.undo(); - expect(argsEle.operations[0].length).toBe(4); - expect(argsEle.operations[0].action).toBe('Delete'); - container.documentEditor.editorHistory.redo(); - expect(argsEle.operations[0].type).toBe('Paste'); - expect(argsEle.operations[0].pasteContent).toBeDefined(); - //Undo redo delete endtnote - // Undo is not working correctly after backspace. - // container.documentEditor.selection.select('0;0;5', '0;0;5'); - // container.documentEditor.editor.onBackSpace(); - // expect(argsEle.operations[0].length).toBe(4); - // expect(argsEle.operations[0].action).toBe('Delete'); - // container.documentEditor.editorHistory.undo(); - // expect(argsEle.operations[0].type).toBe('Paste'); - // expect(argsEle.operations[0].pasteContent).toBeDefined(); - // container.documentEditor.editorHistory.redo(); - // expect(argsEle.operations[0].length).toBe(4); - // expect(argsEle.operations[0].action).toBe('Delete'); - }); -}); \ No newline at end of file +// it('endnote undo/redo', () => { +// console.log('endnote undo/redo'); +// let argsEle: ContainerContentChangeEventArgs; +// container.contentChange = function (args: ContainerContentChangeEventArgs) { +// argsEle = args; +// } +// container.documentEditor.openBlank(); +// container.documentEditor.editorModule.insertText('Syncfusion Software'); +// container.documentEditor.selection.select('0;0;4', '0;0;4'); +// container.documentEditor.editor.insertEndnote(); +// container.documentEditor.editorHistory.undo(); +// expect(argsEle.operations[0].length).toBe(4); +// expect(argsEle.operations[0].action).toBe('Delete'); +// container.documentEditor.editorHistory.redo(); +// expect(argsEle.operations[0].type).toBe('Paste'); +// expect(argsEle.operations[0].pasteContent).toBeDefined(); +// //Undo redo delete endtnote +// // Undo is not working correctly after backspace. +// // container.documentEditor.selection.select('0;0;5', '0;0;5'); +// // container.documentEditor.editor.onBackSpace(); +// // expect(argsEle.operations[0].length).toBe(4); +// // expect(argsEle.operations[0].action).toBe('Delete'); +// // container.documentEditor.editorHistory.undo(); +// // expect(argsEle.operations[0].type).toBe('Paste'); +// // expect(argsEle.operations[0].pasteContent).toBeDefined(); +// // container.documentEditor.editorHistory.redo(); +// // expect(argsEle.operations[0].length).toBe(4); +// // expect(argsEle.operations[0].action).toBe('Delete'); +// }); +// }); \ No newline at end of file diff --git a/controls/documenteditor/spec/implementation/collaboration/formatting.spec.ts b/controls/documenteditor/spec/implementation/collaboration/formatting.spec.ts index 93fa955362..e4428c2ffd 100644 --- a/controls/documenteditor/spec/implementation/collaboration/formatting.spec.ts +++ b/controls/documenteditor/spec/implementation/collaboration/formatting.spec.ts @@ -1,164 +1,164 @@ -import { DocumentEditorContainer, ContainerContentChangeEventArgs, WCharacterFormat, WParagraphFormat, WSectionFormat, WTableFormat, WRowFormat, WCellFormat } from '../../../src/index'; -import { createElement } from '@syncfusion/ej2-base'; -import { Toolbar } from '../../../src/document-editor-container/tool-bar/tool-bar'; -/** - * Formatting Collaborative editing spec - */ -describe('Formatting in collaborative editing', () => { - let container: DocumentEditorContainer; - let element: HTMLElement; - let args: ContainerContentChangeEventArgs; - beforeAll(() => { - element = createElement('div'); - document.body.appendChild(element); - DocumentEditorContainer.Inject(Toolbar); - container = new DocumentEditorContainer(); - container.appendTo(element); - container.documentEditor.enableCollaborativeEditing = true; - }); - afterAll(() => { - expect(() => { container.destroy(); }).not.toThrowError(); - expect(element.childNodes.length).toBe(0); - document.body.removeChild(element); - document.body.innerHTML = ''; - element = undefined; - container = undefined; - }); +// import { DocumentEditorContainer, ContainerContentChangeEventArgs, WCharacterFormat, WParagraphFormat, WSectionFormat, WTableFormat, WRowFormat, WCellFormat } from '../../../src/index'; +// import { createElement } from '@syncfusion/ej2-base'; +// import { Toolbar } from '../../../src/document-editor-container/tool-bar/tool-bar'; +// /** +// * Formatting Collaborative editing spec +// */ +// describe('Formatting in collaborative editing', () => { +// let container: DocumentEditorContainer; +// let element: HTMLElement; +// let args: ContainerContentChangeEventArgs; +// beforeAll(() => { +// element = createElement('div'); +// document.body.appendChild(element); +// DocumentEditorContainer.Inject(Toolbar); +// container = new DocumentEditorContainer(); +// container.appendTo(element); +// container.documentEditor.enableCollaborativeEditing = true; +// }); +// afterAll(() => { +// expect(() => { container.destroy(); }).not.toThrowError(); +// expect(element.childNodes.length).toBe(0); +// document.body.removeChild(element); +// document.body.innerHTML = ''; +// element = undefined; +// container = undefined; +// }); - it('Undo/Redo character formatting', () => { - console.log('Undo/Redo character formatting'); - let argsEle: ContainerContentChangeEventArgs; - container.contentChange = function (args: ContainerContentChangeEventArgs) { - if (args.operations.length > 0) { - argsEle = args; - } - } - container.documentEditor.editorModule.insertText('Syncfusion Software'); - container.documentEditor.selection.select('0;0;4', '0;0;10'); - container.documentEditor.selection.characterFormat.bold = true; - expect(argsEle.operations[0].type).toBe("CharacterFormat"); - expect(argsEle.operations[0].format).toBe("{\"bold\":true}"); - container.documentEditor.editorHistory.undo(); - let format: WCharacterFormat = new WCharacterFormat(undefined); - let characterFormat: any = JSON.parse(argsEle.operations[0].format); - container.documentEditor.documentHelper.owner.parser.parseCharacterFormat(0, characterFormat, format); - expect(format.bold).toBe(false); - container.documentEditor.editorHistory.redo(); - characterFormat = JSON.parse(argsEle.operations[0].format); - container.documentEditor.documentHelper.owner.parser.parseCharacterFormat(0, characterFormat, format); - expect(format.bold).toBe(true); - }); +// it('Undo/Redo character formatting', () => { +// console.log('Undo/Redo character formatting'); +// let argsEle: ContainerContentChangeEventArgs; +// container.contentChange = function (args: ContainerContentChangeEventArgs) { +// if (args.operations.length > 0) { +// argsEle = args; +// } +// } +// container.documentEditor.editorModule.insertText('Syncfusion Software'); +// container.documentEditor.selection.select('0;0;4', '0;0;10'); +// container.documentEditor.selection.characterFormat.bold = true; +// expect(argsEle.operations[0].type).toBe("CharacterFormat"); +// expect(argsEle.operations[0].format).toBe("{\"bold\":true}"); +// container.documentEditor.editorHistory.undo(); +// let format: WCharacterFormat = new WCharacterFormat(undefined); +// let characterFormat: any = JSON.parse(argsEle.operations[0].format); +// container.documentEditor.documentHelper.owner.parser.parseCharacterFormat(0, characterFormat, format); +// expect(format.bold).toBe(false); +// container.documentEditor.editorHistory.redo(); +// characterFormat = JSON.parse(argsEle.operations[0].format); +// container.documentEditor.documentHelper.owner.parser.parseCharacterFormat(0, characterFormat, format); +// expect(format.bold).toBe(true); +// }); - it('Undo/Redo paragraph formatting', () => { - console.log('Undo/Redo paragraph formatting'); - let argsEle: ContainerContentChangeEventArgs; - container.contentChange = function (args: ContainerContentChangeEventArgs) { - if (args.operations.length > 0) { - argsEle = args; - } - } - container.documentEditor.openBlank(); - container.documentEditor.editorModule.insertText('Syncfusion Software'); - container.documentEditor.selection.select('0;0;4', '0;0;4'); - container.documentEditor.selection.paragraphFormat.textAlignment = "Center"; - expect(argsEle.operations[0].type).toBe("ParagraphFormat"); - expect(argsEle.operations[0].format).toBe("{\"textAlignment\":\"Center\"}"); - container.documentEditor.editorHistory.undo(); - let format: WParagraphFormat = new WParagraphFormat(undefined); - container.documentEditor.documentHelper.owner.parser.parseParagraphFormat(0, JSON.parse(argsEle.operations[0].format), format); - expect(format.textAlignment).toBe("Left"); - container.documentEditor.editorHistory.redo(); - container.documentEditor.documentHelper.owner.parser.parseParagraphFormat(0, JSON.parse(argsEle.operations[0].format), format); - expect(format.textAlignment).toBe("Center"); - }); +// it('Undo/Redo paragraph formatting', () => { +// console.log('Undo/Redo paragraph formatting'); +// let argsEle: ContainerContentChangeEventArgs; +// container.contentChange = function (args: ContainerContentChangeEventArgs) { +// if (args.operations.length > 0) { +// argsEle = args; +// } +// } +// container.documentEditor.openBlank(); +// container.documentEditor.editorModule.insertText('Syncfusion Software'); +// container.documentEditor.selection.select('0;0;4', '0;0;4'); +// container.documentEditor.selection.paragraphFormat.textAlignment = "Center"; +// expect(argsEle.operations[0].type).toBe("ParagraphFormat"); +// expect(argsEle.operations[0].format).toBe("{\"textAlignment\":\"Center\"}"); +// container.documentEditor.editorHistory.undo(); +// let format: WParagraphFormat = new WParagraphFormat(undefined); +// container.documentEditor.documentHelper.owner.parser.parseParagraphFormat(0, JSON.parse(argsEle.operations[0].format), format); +// expect(format.textAlignment).toBe("Left"); +// container.documentEditor.editorHistory.redo(); +// container.documentEditor.documentHelper.owner.parser.parseParagraphFormat(0, JSON.parse(argsEle.operations[0].format), format); +// expect(format.textAlignment).toBe("Center"); +// }); - it('Undo/Redo Section formatting', () => { - console.log('Undo/Redo Section formatting'); - let argsEle: ContainerContentChangeEventArgs; - container.contentChange = function (args: ContainerContentChangeEventArgs) { - if (args.operations.length > 0) { - argsEle = args; - } - } - container.documentEditor.openBlank(); - container.documentEditor.editorModule.insertText('Syncfusion Software'); - container.documentEditor.selection.sectionFormat.topMargin = 108; - expect(argsEle.operations[0].type).toBe("SectionFormat"); - expect(argsEle.operations[0].format).toBe("{\"topMargin\":108}"); - container.documentEditor.editorHistory.undo(); - let sectionFormat: WSectionFormat = new WSectionFormat(); - container.documentEditor.documentHelper.owner.parser.parseSectionFormat(0, JSON.parse(argsEle.operations[0].format), sectionFormat); - expect(sectionFormat.topMargin).toBe(72); - container.documentEditor.editorHistory.redo(); - sectionFormat = new WSectionFormat(); - container.documentEditor.documentHelper.owner.parser.parseSectionFormat(0, JSON.parse(argsEle.operations[0].format), sectionFormat); - expect(sectionFormat.topMargin).toBe(108); - }); +// it('Undo/Redo Section formatting', () => { +// console.log('Undo/Redo Section formatting'); +// let argsEle: ContainerContentChangeEventArgs; +// container.contentChange = function (args: ContainerContentChangeEventArgs) { +// if (args.operations.length > 0) { +// argsEle = args; +// } +// } +// container.documentEditor.openBlank(); +// container.documentEditor.editorModule.insertText('Syncfusion Software'); +// container.documentEditor.selection.sectionFormat.topMargin = 108; +// expect(argsEle.operations[0].type).toBe("SectionFormat"); +// expect(argsEle.operations[0].format).toBe("{\"topMargin\":108}"); +// container.documentEditor.editorHistory.undo(); +// let sectionFormat: WSectionFormat = new WSectionFormat(); +// container.documentEditor.documentHelper.owner.parser.parseSectionFormat(0, JSON.parse(argsEle.operations[0].format), sectionFormat); +// expect(sectionFormat.topMargin).toBe(72); +// container.documentEditor.editorHistory.redo(); +// sectionFormat = new WSectionFormat(); +// container.documentEditor.documentHelper.owner.parser.parseSectionFormat(0, JSON.parse(argsEle.operations[0].format), sectionFormat); +// expect(sectionFormat.topMargin).toBe(108); +// }); - it('Undo/Redo Table formatting', () => { - console.log('Undo/Redo Table formatting'); - let argsEle: ContainerContentChangeEventArgs; - container.contentChange = function (args: ContainerContentChangeEventArgs) { - if (args.operations.length > 0) { - argsEle = args; - } - } - container.documentEditor.openBlank(); - container.documentEditor.editorModule.insertTable(2, 2); - container.documentEditor.selection.tableFormat.leftIndent = 30; - expect(argsEle.operations[0].offset).toBe(1); - expect(argsEle.operations[0].type).toBe("TableFormat"); - expect(argsEle.operations[0].format).toBe("{\"leftIndent\":30}"); - container.documentEditor.editorHistory.undo(); - let tableFormat: WTableFormat = new WTableFormat(); - container.documentEditor.documentHelper.owner.parser.parseTableFormat(JSON.parse(argsEle.operations[0].format), tableFormat, 0); - expect(tableFormat.leftIndent).toBe(0); - container.documentEditor.editorHistory.redo(); - container.documentEditor.documentHelper.owner.parser.parseTableFormat(JSON.parse(argsEle.operations[0].format), tableFormat, 0); - expect(tableFormat.leftIndent).toBe(30); - }); +// it('Undo/Redo Table formatting', () => { +// console.log('Undo/Redo Table formatting'); +// let argsEle: ContainerContentChangeEventArgs; +// container.contentChange = function (args: ContainerContentChangeEventArgs) { +// if (args.operations.length > 0) { +// argsEle = args; +// } +// } +// container.documentEditor.openBlank(); +// container.documentEditor.editorModule.insertTable(2, 2); +// container.documentEditor.selection.tableFormat.leftIndent = 30; +// expect(argsEle.operations[0].offset).toBe(1); +// expect(argsEle.operations[0].type).toBe("TableFormat"); +// expect(argsEle.operations[0].format).toBe("{\"leftIndent\":30}"); +// container.documentEditor.editorHistory.undo(); +// let tableFormat: WTableFormat = new WTableFormat(); +// container.documentEditor.documentHelper.owner.parser.parseTableFormat(JSON.parse(argsEle.operations[0].format), tableFormat, 0); +// expect(tableFormat.leftIndent).toBe(0); +// container.documentEditor.editorHistory.redo(); +// container.documentEditor.documentHelper.owner.parser.parseTableFormat(JSON.parse(argsEle.operations[0].format), tableFormat, 0); +// expect(tableFormat.leftIndent).toBe(30); +// }); - it('Undo/Redo Row formatting', () => { - console.log('Undo/Redo Row formatting'); - let argsEle: ContainerContentChangeEventArgs; - container.contentChange = function (args: ContainerContentChangeEventArgs) { - if (args.operations.length > 0) { - argsEle = args; - } - } - container.documentEditor.openBlank(); - container.documentEditor.editorModule.insertTable(2, 2); - container.documentEditor.selection.rowFormat.heightType = 'AtLeast'; - container.documentEditor.selection.rowFormat.height = 30; - expect(argsEle.operations[0].offset).toBe(4); - expect(argsEle.operations[0].type).toBe("RowFormat"); - expect(argsEle.operations[0].format).toBe("{\"height\":30}"); - container.documentEditor.editorHistory.undo(); - let rowFormat: WRowFormat = new WRowFormat(); - container.documentEditor.documentHelper.owner.parser.parseRowFormat(JSON.parse(argsEle.operations[0].format), rowFormat, 0); - expect(rowFormat.height).toBe(0); - container.documentEditor.editorHistory.redo(); - container.documentEditor.documentHelper.owner.parser.parseRowFormat(JSON.parse(argsEle.operations[0].format), rowFormat, 0); - expect(rowFormat.height).toBe(30); - }); +// it('Undo/Redo Row formatting', () => { +// console.log('Undo/Redo Row formatting'); +// let argsEle: ContainerContentChangeEventArgs; +// container.contentChange = function (args: ContainerContentChangeEventArgs) { +// if (args.operations.length > 0) { +// argsEle = args; +// } +// } +// container.documentEditor.openBlank(); +// container.documentEditor.editorModule.insertTable(2, 2); +// container.documentEditor.selection.rowFormat.heightType = 'AtLeast'; +// container.documentEditor.selection.rowFormat.height = 30; +// expect(argsEle.operations[0].offset).toBe(4); +// expect(argsEle.operations[0].type).toBe("RowFormat"); +// expect(argsEle.operations[0].format).toBe("{\"height\":30}"); +// container.documentEditor.editorHistory.undo(); +// let rowFormat: WRowFormat = new WRowFormat(); +// container.documentEditor.documentHelper.owner.parser.parseRowFormat(JSON.parse(argsEle.operations[0].format), rowFormat, 0); +// expect(rowFormat.height).toBe(0); +// container.documentEditor.editorHistory.redo(); +// container.documentEditor.documentHelper.owner.parser.parseRowFormat(JSON.parse(argsEle.operations[0].format), rowFormat, 0); +// expect(rowFormat.height).toBe(30); +// }); - it('Undo/Redo Cell formatting', () => { - console.log('Undo/Redo Cell formatting'); - let argsEle: ContainerContentChangeEventArgs; - container.contentChange = function (args: ContainerContentChangeEventArgs) { - if (args.operations.length > 0) { - argsEle = args; - } - } - container.documentEditor.openBlank(); - container.documentEditor.editorModule.insertTable(2, 2); - container.documentEditor.selection.cellFormat.preferredWidth = 300; - expect(argsEle.operations[0].offset).toBe(3); - expect(argsEle.operations[0].type).toBe("CellFormat"); - expect(argsEle.operations[0].format).toBe("{\"preferredWidth\":300}"); - container.documentEditor.editorHistory.undo(); - expect(argsEle.operations[0].format).toBe("{\"preferredWidth\":234}"); - container.documentEditor.editorHistory.redo(); - expect(argsEle.operations[0].format).toBe("{\"preferredWidth\":300}"); - }); -}); \ No newline at end of file +// it('Undo/Redo Cell formatting', () => { +// console.log('Undo/Redo Cell formatting'); +// let argsEle: ContainerContentChangeEventArgs; +// container.contentChange = function (args: ContainerContentChangeEventArgs) { +// if (args.operations.length > 0) { +// argsEle = args; +// } +// } +// container.documentEditor.openBlank(); +// container.documentEditor.editorModule.insertTable(2, 2); +// container.documentEditor.selection.cellFormat.preferredWidth = 300; +// expect(argsEle.operations[0].offset).toBe(3); +// expect(argsEle.operations[0].type).toBe("CellFormat"); +// expect(argsEle.operations[0].format).toBe("{\"preferredWidth\":300}"); +// container.documentEditor.editorHistory.undo(); +// expect(argsEle.operations[0].format).toBe("{\"preferredWidth\":234}"); +// container.documentEditor.editorHistory.redo(); +// expect(argsEle.operations[0].format).toBe("{\"preferredWidth\":300}"); +// }); +// }); \ No newline at end of file diff --git a/controls/documenteditor/spec/implementation/collaboration/remote_action.spec.ts b/controls/documenteditor/spec/implementation/collaboration/remote_action.spec.ts index bbf9b17e90..d05ad2f621 100644 --- a/controls/documenteditor/spec/implementation/collaboration/remote_action.spec.ts +++ b/controls/documenteditor/spec/implementation/collaboration/remote_action.spec.ts @@ -1,49 +1,49 @@ -import { DocumentEditorContainer, ContainerContentChangeEventArgs, DocumentEditor, ActionInfo, CONTROL_CHARACTERS, TableWidget, TableRowWidget, TableCellWidget, CollaborativeEditingHandler } from '../../../src/index'; -import { createElement, isNullOrUndefined } from '@syncfusion/ej2-base'; -import { Toolbar } from '../../../src/document-editor-container/tool-bar/tool-bar'; -/** - * Apply remote action Collaborative editing spec - */ -describe('Apply remote action in collaborative editing', () => { - let container: DocumentEditorContainer; - let element: HTMLElement; - let args: ContainerContentChangeEventArgs; - beforeAll(() => { - DocumentEditor.Inject(CollaborativeEditingHandler); - element = createElement('div'); - document.body.appendChild(element); - DocumentEditorContainer.Inject(Toolbar); - container = new DocumentEditorContainer(); - container.appendTo(element); - container.documentEditor.enableCollaborativeEditing = true; - }); - afterAll(() => { - container.destroy(); - expect(element.childNodes.length).toBe(0); - document.body.removeChild(element); - expect(() => { container.destroy(); }).not.toThrowError(); - document.body.innerHTML = ''; - element = undefined; - container = undefined; - }); +// import { DocumentEditorContainer, ContainerContentChangeEventArgs, DocumentEditor, ActionInfo, CONTROL_CHARACTERS, TableWidget, TableRowWidget, TableCellWidget, CollaborativeEditingHandler } from '../../../src/index'; +// import { createElement, isNullOrUndefined } from '@syncfusion/ej2-base'; +// import { Toolbar } from '../../../src/document-editor-container/tool-bar/tool-bar'; +// /** +// * Apply remote action Collaborative editing spec +// */ +// describe('Apply remote action in collaborative editing', () => { +// let container: DocumentEditorContainer; +// let element: HTMLElement; +// let args: ContainerContentChangeEventArgs; +// beforeAll(() => { +// DocumentEditor.Inject(CollaborativeEditingHandler); +// element = createElement('div'); +// document.body.appendChild(element); +// DocumentEditorContainer.Inject(Toolbar); +// container = new DocumentEditorContainer(); +// container.appendTo(element); +// container.documentEditor.enableCollaborativeEditing = true; +// }); +// afterAll(() => { +// container.destroy(); +// expect(element.childNodes.length).toBe(0); +// document.body.removeChild(element); +// expect(() => { container.destroy(); }).not.toThrowError(); +// document.body.innerHTML = ''; +// element = undefined; +// container = undefined; +// }); - it('Apply Remote action as row', () => { - console.log('Insert Row in romote action'); - let argsEle: ContainerContentChangeEventArgs; - container.contentChange = function (args: ContainerContentChangeEventArgs) { - if (args.operations.length > 0) { - argsEle = args; - } - } - const actions: ActionInfo = { "currentUser": "Mugunthan Anbalagan", "roomName": "Paragraph Formatting.docx", "connectionId": "_3ET94I_P_UjEeJDbGAuLQ", "version": 1, "operations": [{ "action": "Insert", "offset": 5, "text": "\u0013", "length": 1, "skipOperation": false, "format": "{\"height\":0,\"allowBreakAcrossPages\":true,\"heightType\":\"Auto\",\"isHeader\":false,\"borders\":{\"top\":{},\"left\":{},\"right\":{},\"bottom\":{},\"diagonalDown\":{},\"diagonalUp\":{},\"horizontal\":{},\"vertical\":{}},\"gridBefore\":0,\"gridBeforeWidth\":0,\"gridBeforeWidthType\":\"Point\",\"gridAfter\":0,\"gridAfterWidth\":0,\"gridAfterWidthType\":\"Point\",\"leftIndent\":0}" }, { "action": "Insert", "offset": 5, "text": "\u0014", "type": "CellFormat", "length": 1, "skipOperation": false, "format": "{\"borders\":{\"top\":{},\"left\":{},\"right\":{},\"bottom\":{},\"diagonalDown\":{},\"diagonalUp\":{},\"horizontal\":{},\"vertical\":{}},\"shading\":{},\"preferredWidth\":111.475,\"preferredWidthType\":\"Point\",\"cellWidth\":111.475,\"columnSpan\":1,\"rowSpan\":1,\"verticalAlignment\":\"Top\"}" }, { "action": "Insert", "offset": 5, "text": "\u0014", "type": "ParagraphFormat", "length": 1, "skipOperation": false, "format": "{\"borders\":{\"top\":{},\"left\":{},\"right\":{},\"bottom\":{},\"horizontal\":{},\"vertical\":{}},\"leftIndent\":0,\"rightIndent\":0,\"firstLineIndent\":0,\"textAlignment\":\"Left\",\"beforeSpacing\":0,\"afterSpacing\":0,\"spaceBeforeAuto\":false,\"spaceAfterAuto\":false,\"lineSpacing\":1,\"lineSpacingType\":\"Multiple\",\"styleName\":\"Normal\",\"outlineLevel\":\"BodyText\",\"bidi\":false,\"keepLinesTogether\":false,\"keepWithNext\":false,\"contextualSpacing\":false,\"widowControl\":true}" }, { "action": "Insert", "offset": 5, "text": "\u0014", "type": "CharacterFormat", "length": 0, "skipOperation": false, "format": "{\"bold\":false,\"italic\":false,\"fontSize\":12,\"fontFamily\":\"Times New Roman\",\"underline\":\"None\",\"strikethrough\":\"None\",\"baselineAlignment\":\"Normal\",\"highlightColor\":\"NoColor\",\"fontColor\":\"#00000000\",\"bidi\":false,\"bdo\":\"None\",\"boldBidi\":false,\"italicBidi\":false,\"fontSizeBidi\":12,\"fontFamilyBidi\":\"Times New Roman\",\"allCaps\":false,\"localeIdBidi\":1025,\"complexScript\":false,\"fontFamilyAscii\":\"Times New Roman\",\"fontFamilyNonFarEast\":\"Times New Roman\",\"fontFamilyFarEast\":\"Times New Roman\",\"characterSpacing\":0,\"scaling\":100}" }, { "action": "Insert", "offset": 5, "text": "\u0014", "type": "CellFormat", "length": 1, "skipOperation": false, "format": "{\"borders\":{\"top\":{},\"left\":{},\"right\":{},\"bottom\":{},\"diagonalDown\":{},\"diagonalUp\":{},\"horizontal\":{},\"vertical\":{}},\"shading\":{},\"preferredWidth\":111.475,\"preferredWidthType\":\"Point\",\"cellWidth\":111.475,\"columnSpan\":1,\"rowSpan\":1,\"verticalAlignment\":\"Top\"}" }, { "action": "Insert", "offset": 5, "text": "\u0014", "type": "ParagraphFormat", "length": 1, "skipOperation": false, "format": "{\"borders\":{\"top\":{},\"left\":{},\"right\":{},\"bottom\":{},\"horizontal\":{},\"vertical\":{}},\"leftIndent\":0,\"rightIndent\":0,\"firstLineIndent\":0,\"textAlignment\":\"Left\",\"beforeSpacing\":0,\"afterSpacing\":0,\"spaceBeforeAuto\":false,\"spaceAfterAuto\":false,\"lineSpacing\":1,\"lineSpacingType\":\"Multiple\",\"styleName\":\"Normal\",\"outlineLevel\":\"BodyText\",\"bidi\":false,\"keepLinesTogether\":false,\"keepWithNext\":false,\"contextualSpacing\":false,\"widowControl\":true}" }, { "action": "Insert", "offset": 5, "text": "\u0014", "type": "CharacterFormat", "length": 0, "skipOperation": false, "format": "{\"bold\":false,\"italic\":false,\"fontSize\":12,\"fontFamily\":\"Times New Roman\",\"underline\":\"None\",\"strikethrough\":\"None\",\"baselineAlignment\":\"Normal\",\"highlightColor\":\"NoColor\",\"fontColor\":\"#00000000\",\"bidi\":false,\"bdo\":\"None\",\"boldBidi\":false,\"italicBidi\":false,\"fontSizeBidi\":12,\"fontFamilyBidi\":\"Times New Roman\",\"allCaps\":false,\"localeIdBidi\":1025,\"complexScript\":false,\"fontFamilyAscii\":\"Times New Roman\",\"fontFamilyNonFarEast\":\"Times New Roman\",\"fontFamilyFarEast\":\"Times New Roman\",\"characterSpacing\":0,\"scaling\":100}" }] }; - container.documentEditor.editorModule.insertTable(2, 2); - container.documentEditor.editorModule.insertTable(2, 2); - //Inserting row in nested table - let connections: CollaborativeEditingHandler; - if (isNullOrUndefined(container.documentEditor.collaborativeEditingHandlerModule)) { - connections = new CollaborativeEditingHandler(container.documentEditor); - } - connections.applyRemoteAction('action', actions); - expect(((((container.documentEditor.documentHelper.pages[0].bodyWidgets[0].childWidgets[0] as TableWidget).childWidgets[0] as TableRowWidget).childWidgets[0] as TableCellWidget).childWidgets[0] as TableWidget).childWidgets.length).toBe(3); - }); -}); \ No newline at end of file +// it('Apply Remote action as row', () => { +// console.log('Insert Row in romote action'); +// let argsEle: ContainerContentChangeEventArgs; +// container.contentChange = function (args: ContainerContentChangeEventArgs) { +// if (args.operations.length > 0) { +// argsEle = args; +// } +// } +// const actions: ActionInfo = { "currentUser": "Mugunthan Anbalagan", "roomName": "Paragraph Formatting.docx", "connectionId": "_3ET94I_P_UjEeJDbGAuLQ", "version": 1, "operations": [{ "action": "Insert", "offset": 5, "text": "\u0013", "length": 1, "skipOperation": false, "format": "{\"height\":0,\"allowBreakAcrossPages\":true,\"heightType\":\"Auto\",\"isHeader\":false,\"borders\":{\"top\":{},\"left\":{},\"right\":{},\"bottom\":{},\"diagonalDown\":{},\"diagonalUp\":{},\"horizontal\":{},\"vertical\":{}},\"gridBefore\":0,\"gridBeforeWidth\":0,\"gridBeforeWidthType\":\"Point\",\"gridAfter\":0,\"gridAfterWidth\":0,\"gridAfterWidthType\":\"Point\",\"leftIndent\":0}" }, { "action": "Insert", "offset": 5, "text": "\u0014", "type": "CellFormat", "length": 1, "skipOperation": false, "format": "{\"borders\":{\"top\":{},\"left\":{},\"right\":{},\"bottom\":{},\"diagonalDown\":{},\"diagonalUp\":{},\"horizontal\":{},\"vertical\":{}},\"shading\":{},\"preferredWidth\":111.475,\"preferredWidthType\":\"Point\",\"cellWidth\":111.475,\"columnSpan\":1,\"rowSpan\":1,\"verticalAlignment\":\"Top\"}" }, { "action": "Insert", "offset": 5, "text": "\u0014", "type": "ParagraphFormat", "length": 1, "skipOperation": false, "format": "{\"borders\":{\"top\":{},\"left\":{},\"right\":{},\"bottom\":{},\"horizontal\":{},\"vertical\":{}},\"leftIndent\":0,\"rightIndent\":0,\"firstLineIndent\":0,\"textAlignment\":\"Left\",\"beforeSpacing\":0,\"afterSpacing\":0,\"spaceBeforeAuto\":false,\"spaceAfterAuto\":false,\"lineSpacing\":1,\"lineSpacingType\":\"Multiple\",\"styleName\":\"Normal\",\"outlineLevel\":\"BodyText\",\"bidi\":false,\"keepLinesTogether\":false,\"keepWithNext\":false,\"contextualSpacing\":false,\"widowControl\":true}" }, { "action": "Insert", "offset": 5, "text": "\u0014", "type": "CharacterFormat", "length": 0, "skipOperation": false, "format": "{\"bold\":false,\"italic\":false,\"fontSize\":12,\"fontFamily\":\"Times New Roman\",\"underline\":\"None\",\"strikethrough\":\"None\",\"baselineAlignment\":\"Normal\",\"highlightColor\":\"NoColor\",\"fontColor\":\"#00000000\",\"bidi\":false,\"bdo\":\"None\",\"boldBidi\":false,\"italicBidi\":false,\"fontSizeBidi\":12,\"fontFamilyBidi\":\"Times New Roman\",\"allCaps\":false,\"localeIdBidi\":1025,\"complexScript\":false,\"fontFamilyAscii\":\"Times New Roman\",\"fontFamilyNonFarEast\":\"Times New Roman\",\"fontFamilyFarEast\":\"Times New Roman\",\"characterSpacing\":0,\"scaling\":100}" }, { "action": "Insert", "offset": 5, "text": "\u0014", "type": "CellFormat", "length": 1, "skipOperation": false, "format": "{\"borders\":{\"top\":{},\"left\":{},\"right\":{},\"bottom\":{},\"diagonalDown\":{},\"diagonalUp\":{},\"horizontal\":{},\"vertical\":{}},\"shading\":{},\"preferredWidth\":111.475,\"preferredWidthType\":\"Point\",\"cellWidth\":111.475,\"columnSpan\":1,\"rowSpan\":1,\"verticalAlignment\":\"Top\"}" }, { "action": "Insert", "offset": 5, "text": "\u0014", "type": "ParagraphFormat", "length": 1, "skipOperation": false, "format": "{\"borders\":{\"top\":{},\"left\":{},\"right\":{},\"bottom\":{},\"horizontal\":{},\"vertical\":{}},\"leftIndent\":0,\"rightIndent\":0,\"firstLineIndent\":0,\"textAlignment\":\"Left\",\"beforeSpacing\":0,\"afterSpacing\":0,\"spaceBeforeAuto\":false,\"spaceAfterAuto\":false,\"lineSpacing\":1,\"lineSpacingType\":\"Multiple\",\"styleName\":\"Normal\",\"outlineLevel\":\"BodyText\",\"bidi\":false,\"keepLinesTogether\":false,\"keepWithNext\":false,\"contextualSpacing\":false,\"widowControl\":true}" }, { "action": "Insert", "offset": 5, "text": "\u0014", "type": "CharacterFormat", "length": 0, "skipOperation": false, "format": "{\"bold\":false,\"italic\":false,\"fontSize\":12,\"fontFamily\":\"Times New Roman\",\"underline\":\"None\",\"strikethrough\":\"None\",\"baselineAlignment\":\"Normal\",\"highlightColor\":\"NoColor\",\"fontColor\":\"#00000000\",\"bidi\":false,\"bdo\":\"None\",\"boldBidi\":false,\"italicBidi\":false,\"fontSizeBidi\":12,\"fontFamilyBidi\":\"Times New Roman\",\"allCaps\":false,\"localeIdBidi\":1025,\"complexScript\":false,\"fontFamilyAscii\":\"Times New Roman\",\"fontFamilyNonFarEast\":\"Times New Roman\",\"fontFamilyFarEast\":\"Times New Roman\",\"characterSpacing\":0,\"scaling\":100}" }] }; +// container.documentEditor.editorModule.insertTable(2, 2); +// container.documentEditor.editorModule.insertTable(2, 2); +// //Inserting row in nested table +// let connections: CollaborativeEditingHandler; +// if (isNullOrUndefined(container.documentEditor.collaborativeEditingHandlerModule)) { +// connections = new CollaborativeEditingHandler(container.documentEditor); +// } +// connections.applyRemoteAction('action', actions); +// expect(((((container.documentEditor.documentHelper.pages[0].bodyWidgets[0].childWidgets[0] as TableWidget).childWidgets[0] as TableRowWidget).childWidgets[0] as TableCellWidget).childWidgets[0] as TableWidget).childWidgets.length).toBe(3); +// }); +// }); \ No newline at end of file diff --git a/controls/documenteditor/spec/implementation/collaboration/single_length_ops.spec.ts b/controls/documenteditor/spec/implementation/collaboration/single_length_ops.spec.ts index 8fb4c4cd0f..dadbedd4fc 100644 --- a/controls/documenteditor/spec/implementation/collaboration/single_length_ops.spec.ts +++ b/controls/documenteditor/spec/implementation/collaboration/single_length_ops.spec.ts @@ -1,41 +1,41 @@ -import { DocumentEditorContainer, ContainerContentChangeEventArgs, CONTROL_CHARACTERS, WCharacterFormat } from '../../../src/index'; -import { createElement } from '@syncfusion/ej2-base'; -import { Toolbar } from '../../../src/document-editor-container/tool-bar/tool-bar'; -/** - * insert text - */ -describe('Insert single length text', () => { - let container: DocumentEditorContainer; - let element: HTMLElement; - let args: ContainerContentChangeEventArgs; - beforeAll(() => { - element = createElement('div'); - document.body.appendChild(element); - DocumentEditorContainer.Inject(Toolbar); - container = new DocumentEditorContainer(); - container.appendTo(element); - container.documentEditor.enableCollaborativeEditing = true; - }); - afterAll(() => { - expect(() => { container.destroy(); }).not.toThrowError(); - expect(element.childNodes.length).toBe(0); - document.body.removeChild(element); - document.body.innerHTML = ''; - element = undefined; - container = undefined; - }); +// import { DocumentEditorContainer, ContainerContentChangeEventArgs, CONTROL_CHARACTERS, WCharacterFormat } from '../../../src/index'; +// import { createElement } from '@syncfusion/ej2-base'; +// import { Toolbar } from '../../../src/document-editor-container/tool-bar/tool-bar'; +// /** +// * insert text +// */ +// describe('Insert single length text', () => { +// let container: DocumentEditorContainer; +// let element: HTMLElement; +// let args: ContainerContentChangeEventArgs; +// beforeAll(() => { +// element = createElement('div'); +// document.body.appendChild(element); +// DocumentEditorContainer.Inject(Toolbar); +// container = new DocumentEditorContainer(); +// container.appendTo(element); +// container.documentEditor.enableCollaborativeEditing = true; +// }); +// afterAll(() => { +// expect(() => { container.destroy(); }).not.toThrowError(); +// expect(element.childNodes.length).toBe(0); +// document.body.removeChild(element); +// document.body.innerHTML = ''; +// element = undefined; +// container = undefined; +// }); - it('insert enter', () => { - console.log('insert enter'); - let argsEle: ContainerContentChangeEventArgs; - container.contentChange = function (args: ContainerContentChangeEventArgs) { - argsEle = args; - } - container.documentEditor.editor.insertText('Syncfusion Software'); - container.documentEditor.selection.select('0;0;4', '0;0;4'); - container.documentEditor.editor.onEnter(); - expect(argsEle.operations[0].action).toBe('Insert'); - expect(argsEle.operations[0].length).toBe(1); - expect(argsEle.operations[0].text).toBe(CONTROL_CHARACTERS.Paragraph); - }); -}); \ No newline at end of file +// it('insert enter', () => { +// console.log('insert enter'); +// let argsEle: ContainerContentChangeEventArgs; +// container.contentChange = function (args: ContainerContentChangeEventArgs) { +// argsEle = args; +// } +// container.documentEditor.editor.insertText('Syncfusion Software'); +// container.documentEditor.selection.select('0;0;4', '0;0;4'); +// container.documentEditor.editor.onEnter(); +// expect(argsEle.operations[0].action).toBe('Insert'); +// expect(argsEle.operations[0].length).toBe(1); +// expect(argsEle.operations[0].text).toBe(CONTROL_CHARACTERS.Paragraph); +// }); +// }); \ No newline at end of file diff --git a/controls/documenteditor/spec/implementation/collaboration/table_paste_option.spec.ts b/controls/documenteditor/spec/implementation/collaboration/table_paste_option.spec.ts index 2bccfa1944..ba8c17d3c9 100644 --- a/controls/documenteditor/spec/implementation/collaboration/table_paste_option.spec.ts +++ b/controls/documenteditor/spec/implementation/collaboration/table_paste_option.spec.ts @@ -1,158 +1,158 @@ -import { DocumentEditorContainer, ContainerContentChangeEventArgs, CONTROL_CHARACTERS, HelperMethods, Operation } from '../../../src/index'; -import { createElement } from '@syncfusion/ej2-base'; -import { Toolbar } from '../../../src/document-editor-container/tool-bar/tool-bar'; -/** - * Footnote endnote collaborative editing spec - */ -describe('Table paste options in collaborative editing', () => { - let container: DocumentEditorContainer; - let element: HTMLElement; - let args: ContainerContentChangeEventArgs; - let pasteValue: string = "UEsDBBQAAAAIABZlM1jb23r+FQQAAOFOAAAEAAAAc2ZkdOxcS2/bMAz+K4F2DQo7ie0mt2FFjkWx7bb14Gds1C/IcrM16H8fKcpumqZt3KBDkTIHi5IoyZIofhIdcSOqWmVFdhf/SCIlFkq28Vg0cSgWvzYi0E+pn0rWQGyEH8gbsbDHIo0gbkEIzzMMoTgEQSQb5MshCs9GJ+ZrZHKgZuSGoO4KlcQA7Ykvlv4tl+J+LOTqqOKqPqp4cNzL3x5VGviOKB1FXXG7K249VxzZ2wHswJ8XYuGczWCCOkIVhikgApi2pSdMsAGggSPTz5bqJnFJmjto2sbyCXCKb36eBTITEN3qmmVBQkDFAxNmJoQakOrqQHq7lsTfiSfxTkK5k+KHVD8MpG1Np0ghx8RyJkhjA0SHNAUN8NsW9bzWvc2z0oyJ7Kmkp4LGEH5HNAG8JIY+hXo6MCM3o1TlOlCUfbMuKcQ1BuEaX6AfH/NWT9fhqxNsP1p3B7E/rLOD2B/W1QHsKG4Z6Z4cssQFzA1J04ES9PEF5n7cda4K2yIu1eiU+xhHmarkCfbw+v4alkJIABlWOUoyCLhcG6IGSbcd5+wcNdnUcmzP8c4xGfoOFd7Ssmbk3ELOg8ArHQZ10SBdNQAZQVdBkq4epRkRcN+M66XAqMioyKjIqMio2KOiAzrSBcx1bMcFOGRYPH1YfDTljIt8WuTTIp8W+bT4+LTIuPjZcfFabxPZ8s6Wd7a8s+WdLe9seWfLO1ve3/eb9alspdjyzt+j+Xs0f4/m79HPf49mC8Ong0W2vDMuMi4yLjIuMi7ycZEt7/yf95e3aWx5Z8s7W97Z8s6Wd7a8s+WdLe98E4xvgvFNML4JduRNMLa8s+Wd74LxXTC+C8Z3wfguGOPiJ/6j1pP/vOMWCpjKNs+hjRi3V0SrIDdOaNpEUaPkhYTGZ+5MXW82nfTOSXaTd12VQPneK4fxraEvH+69jH/QAA5wn2ENc59hDXOfYX0cSXmLmxnj5CGtaVZS4tCacqBDmZ9ZETejy3g9+l4Vfvl2xzL+/srIscS+jPKZnP/qX+b8Bf8y3tx2XdexPG9izWceu5vR7ma0/klxnFFjxeYsl8JsTV090D2FhznXnmgSJNmbaxJVkqcp1EJEoeIhCjUPUVGCQg1hFRNhxE7W5N2nbiiMS3gXkAaQJ8rPujAmvhWuGvFV+kEWojSjeIO8XfmrWIA6fUloegl5TSKMdtSehnB5ajWn19HS/BCaGvWX/PVoZmhEXFay8HN4q/LPdoydQh0z/nq/qDUpDvFFnPhtrkZXvvRX0q/T0bIqlcCJjxRIqJHawlREyqemmJYtlMFUFeRiyleS3LolCSnrBNrYCKBhXre9Mf1u0bMJNIMNoabalz/r8qESkw9xkJT4XWoNqwKYYK2AIMJeAbHkHwAAAP//AwBQSwECLQAUAAAACAAWZTNY29t6/hUEAADhTgAABAAAAAAAAAAAACAAAAAAAAAAc2ZkdFBLBQYAAAAAAQABADIAAAA3BAAAAAA="; - beforeAll(() => { - element = createElement('div'); - document.body.appendChild(element); - DocumentEditorContainer.Inject(Toolbar); - container = new DocumentEditorContainer(); - container.appendTo(element); - container.documentEditor.enableCollaborativeEditing = true; - }); - afterAll(() => { - expect(() => { container.destroy(); }).not.toThrowError(); - expect(element.childNodes.length).toBe(0); - document.body.removeChild(element); - document.body.innerHTML = ''; - element = undefined; - container = undefined; - }); +// import { DocumentEditorContainer, ContainerContentChangeEventArgs, CONTROL_CHARACTERS, HelperMethods, Operation } from '../../../src/index'; +// import { createElement } from '@syncfusion/ej2-base'; +// import { Toolbar } from '../../../src/document-editor-container/tool-bar/tool-bar'; +// /** +// * Footnote endnote collaborative editing spec +// */ +// describe('Table paste options in collaborative editing', () => { +// let container: DocumentEditorContainer; +// let element: HTMLElement; +// let args: ContainerContentChangeEventArgs; +// let pasteValue: string = "UEsDBBQAAAAIABZlM1jb23r+FQQAAOFOAAAEAAAAc2ZkdOxcS2/bMAz+K4F2DQo7ie0mt2FFjkWx7bb14Gds1C/IcrM16H8fKcpumqZt3KBDkTIHi5IoyZIofhIdcSOqWmVFdhf/SCIlFkq28Vg0cSgWvzYi0E+pn0rWQGyEH8gbsbDHIo0gbkEIzzMMoTgEQSQb5MshCs9GJ+ZrZHKgZuSGoO4KlcQA7Ykvlv4tl+J+LOTqqOKqPqp4cNzL3x5VGviOKB1FXXG7K249VxzZ2wHswJ8XYuGczWCCOkIVhikgApi2pSdMsAGggSPTz5bqJnFJmjto2sbyCXCKb36eBTITEN3qmmVBQkDFAxNmJoQakOrqQHq7lsTfiSfxTkK5k+KHVD8MpG1Np0ghx8RyJkhjA0SHNAUN8NsW9bzWvc2z0oyJ7Kmkp4LGEH5HNAG8JIY+hXo6MCM3o1TlOlCUfbMuKcQ1BuEaX6AfH/NWT9fhqxNsP1p3B7E/rLOD2B/W1QHsKG4Z6Z4cssQFzA1J04ES9PEF5n7cda4K2yIu1eiU+xhHmarkCfbw+v4alkJIABlWOUoyCLhcG6IGSbcd5+wcNdnUcmzP8c4xGfoOFd7Ssmbk3ELOg8ArHQZ10SBdNQAZQVdBkq4epRkRcN+M66XAqMioyKjIqMio2KOiAzrSBcx1bMcFOGRYPH1YfDTljIt8WuTTIp8W+bT4+LTIuPjZcfFabxPZ8s6Wd7a8s+WdLe9seWfLO1ve3/eb9alspdjyzt+j+Xs0f4/m79HPf49mC8Ong0W2vDMuMi4yLjIuMi7ycZEt7/yf95e3aWx5Z8s7W97Z8s6Wd7a8s+WdLe98E4xvgvFNML4JduRNMLa8s+Wd74LxXTC+C8Z3wfguGOPiJ/6j1pP/vOMWCpjKNs+hjRi3V0SrIDdOaNpEUaPkhYTGZ+5MXW82nfTOSXaTd12VQPneK4fxraEvH+69jH/QAA5wn2ENc59hDXOfYX0cSXmLmxnj5CGtaVZS4tCacqBDmZ9ZETejy3g9+l4Vfvl2xzL+/srIscS+jPKZnP/qX+b8Bf8y3tx2XdexPG9izWceu5vR7ma0/klxnFFjxeYsl8JsTV090D2FhznXnmgSJNmbaxJVkqcp1EJEoeIhCjUPUVGCQg1hFRNhxE7W5N2nbiiMS3gXkAaQJ8rPujAmvhWuGvFV+kEWojSjeIO8XfmrWIA6fUloegl5TSKMdtSehnB5ajWn19HS/BCaGvWX/PVoZmhEXFay8HN4q/LPdoydQh0z/nq/qDUpDvFFnPhtrkZXvvRX0q/T0bIqlcCJjxRIqJHawlREyqemmJYtlMFUFeRiyleS3LolCSnrBNrYCKBhXre9Mf1u0bMJNIMNoabalz/r8qESkw9xkJT4XWoNqwKYYK2AIMJeAbHkHwAAAP//AwBQSwECLQAUAAAACAAWZTNY29t6/hUEAADhTgAABAAAAAAAAAAAACAAAAAAAAAAc2ZkdFBLBQYAAAAAAQABADIAAAA3BAAAAAA="; +// beforeAll(() => { +// element = createElement('div'); +// document.body.appendChild(element); +// DocumentEditorContainer.Inject(Toolbar); +// container = new DocumentEditorContainer(); +// container.appendTo(element); +// container.documentEditor.enableCollaborativeEditing = true; +// }); +// afterAll(() => { +// expect(() => { container.destroy(); }).not.toThrowError(); +// expect(element.childNodes.length).toBe(0); +// document.body.removeChild(element); +// document.body.innerHTML = ''; +// element = undefined; +// container = undefined; +// }); - it('insert table paste insert column option', () => { - console.log('insert table paste insert column option'); - let operations: Operation[] = []; - let addpaste: boolean = false; - container.contentChange = function (args: ContainerContentChangeEventArgs) { - if (addpaste) { - operations.push(...args.operations); - } - } - container.documentEditor.editorModule.insertTable(2, 2); - container.documentEditor.editorModule.insertText('Syncfusion'); - container.documentEditor.selection.select("0;0;0;1;0;0", "0;0;0;1;0;0"); - container.documentEditor.editorModule.insertText('Syncfusion'); - container.documentEditor.selection.select("0;0;1;0;0;0", "0;0;1;0;0;0"); - container.documentEditor.editorModule.insertText('Syncfusion'); - container.documentEditor.selection.select("0;0;1;1;0;0", "0;0;1;1;0;0"); - container.documentEditor.editorModule.insertText('Syncfusion'); - container.documentEditor.selection.select("0;0;0;0;0;0", "0;0;0;0;0;0"); - addpaste = true; - container.documentEditor.editorModule.pasteContents(HelperMethods.getSfdtDocument({ sfdt: pasteValue })); - expect(operations[0].action).toBe('Delete'); - expect(operations[0].length).toBe(51); - expect(operations[0].offset).toBe(1); - expect(operations[1].action).toBe('Insert'); - expect(operations[1].length).toBe(209); - expect(operations[1].offset).toBe(1); - expect(operations[1].pasteContent).toBeDefined(); - operations = []; - }); +// it('insert table paste insert column option', () => { +// console.log('insert table paste insert column option'); +// let operations: Operation[] = []; +// let addpaste: boolean = false; +// container.contentChange = function (args: ContainerContentChangeEventArgs) { +// if (addpaste) { +// operations.push(...args.operations); +// } +// } +// container.documentEditor.editorModule.insertTable(2, 2); +// container.documentEditor.editorModule.insertText('Syncfusion'); +// container.documentEditor.selection.select("0;0;0;1;0;0", "0;0;0;1;0;0"); +// container.documentEditor.editorModule.insertText('Syncfusion'); +// container.documentEditor.selection.select("0;0;1;0;0;0", "0;0;1;0;0;0"); +// container.documentEditor.editorModule.insertText('Syncfusion'); +// container.documentEditor.selection.select("0;0;1;1;0;0", "0;0;1;1;0;0"); +// container.documentEditor.editorModule.insertText('Syncfusion'); +// container.documentEditor.selection.select("0;0;0;0;0;0", "0;0;0;0;0;0"); +// addpaste = true; +// container.documentEditor.editorModule.pasteContents(HelperMethods.getSfdtDocument({ sfdt: pasteValue })); +// expect(operations[0].action).toBe('Delete'); +// expect(operations[0].length).toBe(51); +// expect(operations[0].offset).toBe(1); +// expect(operations[1].action).toBe('Insert'); +// expect(operations[1].length).toBe(209); +// expect(operations[1].offset).toBe(1); +// expect(operations[1].pasteContent).toBeDefined(); +// operations = []; +// }); - it('insert table paste overwrite cell option', () => { - console.log('insert table paste overwrite cell option'); - let operations: Operation[] = []; - let addpaste: boolean = false; - container.contentChange = function (args: ContainerContentChangeEventArgs) { - if (addpaste) { - operations.push(...args.operations); - } - } - container.documentEditor.openBlank(); - container.documentEditor.editorModule.insertTable(2, 2); - container.documentEditor.editorModule.insertText('Syncfusion'); - container.documentEditor.selection.select("0;0;0;1;0;0", "0;0;0;1;0;0"); - container.documentEditor.editorModule.insertText('Syncfusion'); - container.documentEditor.selection.select("0;0;1;0;0;0", "0;0;1;0;0;0"); - container.documentEditor.editorModule.insertText('Syncfusion'); - container.documentEditor.selection.select("0;0;1;1;0;0", "0;0;1;1;0;0"); - container.documentEditor.editorModule.insertText('Syncfusion'); - container.documentEditor.selection.select("0;0;1;0;0;0", "0;0;1;0;0;0"); - addpaste = true; - container.documentEditor.editorModule.pasteContents(HelperMethods.getSfdtDocument({ sfdt: pasteValue })); - expect(operations[0].action).toBe('Delete'); - expect(operations[0].length).toBe(51); - expect(operations[0].offset).toBe(1); - expect(operations[1].action).toBe('Insert'); - expect(operations[1].length).toBe(182); - expect(operations[1].offset).toBe(1); - expect(operations[1].pasteContent).toBeDefined(); - operations = []; - }); +// it('insert table paste overwrite cell option', () => { +// console.log('insert table paste overwrite cell option'); +// let operations: Operation[] = []; +// let addpaste: boolean = false; +// container.contentChange = function (args: ContainerContentChangeEventArgs) { +// if (addpaste) { +// operations.push(...args.operations); +// } +// } +// container.documentEditor.openBlank(); +// container.documentEditor.editorModule.insertTable(2, 2); +// container.documentEditor.editorModule.insertText('Syncfusion'); +// container.documentEditor.selection.select("0;0;0;1;0;0", "0;0;0;1;0;0"); +// container.documentEditor.editorModule.insertText('Syncfusion'); +// container.documentEditor.selection.select("0;0;1;0;0;0", "0;0;1;0;0;0"); +// container.documentEditor.editorModule.insertText('Syncfusion'); +// container.documentEditor.selection.select("0;0;1;1;0;0", "0;0;1;1;0;0"); +// container.documentEditor.editorModule.insertText('Syncfusion'); +// container.documentEditor.selection.select("0;0;1;0;0;0", "0;0;1;0;0;0"); +// addpaste = true; +// container.documentEditor.editorModule.pasteContents(HelperMethods.getSfdtDocument({ sfdt: pasteValue })); +// expect(operations[0].action).toBe('Delete'); +// expect(operations[0].length).toBe(51); +// expect(operations[0].offset).toBe(1); +// expect(operations[1].action).toBe('Insert'); +// expect(operations[1].length).toBe(182); +// expect(operations[1].offset).toBe(1); +// expect(operations[1].pasteContent).toBeDefined(); +// operations = []; +// }); - it('insert table paste option', () => { - console.log('insert table paste option'); - let operations: Operation[] = []; - let addpaste: boolean = false; - container.contentChange = function (args: ContainerContentChangeEventArgs) { - if (addpaste) { - operations.push(...args.operations); - } - } - container.documentEditor.openBlank(); - container.documentEditor.editorModule.insertTable(2, 2); - container.documentEditor.editorModule.insertText('Syncfusion'); - container.documentEditor.selection.select("0;0;0;1;0;0", "0;0;0;1;0;0"); - container.documentEditor.editorModule.insertText('Syncfusion'); - container.documentEditor.selection.select("0;0;1;0;0;0", "0;0;1;0;0;0"); - container.documentEditor.editorModule.insertText('Syncfusion'); - container.documentEditor.selection.select("0;0;1;1;0;0", "0;0;1;1;0;0"); - container.documentEditor.editorModule.insertText('Syncfusion'); - container.documentEditor.selection.select("0;0;1;0;0;0", "0;0;1;0;0;0"); - container.documentEditor.editorModule.pasteContents(HelperMethods.getSfdtDocument({ sfdt: pasteValue })); - addpaste = true; - container.documentEditor.editorModule.copiedTextContent = "Document editor Document editor Document editor Document editor Document editor Document editor Document editor Document editor Document editor"; - container.documentEditor.editor.applyTablePasteOptions('InsertAsRows'); - expect(operations[0].action).toBe('Delete'); - expect(operations[0].length).toBe(182); - expect(operations[0].offset).toBe(1); - expect(operations[1].action).toBe('Insert'); - expect(operations[1].length).toBe(51); - expect(operations[1].offset).toBe(1); - expect(operations[1].pasteContent).toBeDefined(); - expect(operations[2].action).toBe('Delete'); - expect(operations[2].length).toBe(51); - expect(operations[2].offset).toBe(1); - expect(operations[3].action).toBe('Insert'); - expect(operations[3].length).toBe(207); - expect(operations[3].offset).toBe(1); - expect(operations[3].pasteContent).toBeDefined(); - operations = []; - container.documentEditor.editor.applyTablePasteOptions('OverwriteCells'); - expect(operations[0].action).toBe('Delete'); - expect(operations[0].length).toBe(207); - expect(operations[0].offset).toBe(1); - expect(operations[1].action).toBe('Insert'); - expect(operations[1].length).toBe(51); - expect(operations[1].offset).toBe(1); - expect(operations[1].pasteContent).toBeDefined(); - expect(operations[2].action).toBe('Delete'); - expect(operations[2].length).toBe(51); - expect(operations[2].offset).toBe(1); - expect(operations[3].action).toBe('Insert'); - expect(operations[3].length).toBe(182); - expect(operations[3].offset).toBe(1); - expect(operations[3].pasteContent).toBeDefined(); - operations = []; - container.documentEditor.editor.applyTablePasteOptions('NestTable'); - expect(operations[0].action).toBe('Delete'); - expect(operations[0].length).toBe(182); - expect(operations[0].offset).toBe(1); - expect(operations[1].action).toBe('Insert'); - expect(operations[1].length).toBe(51); - expect(operations[1].offset).toBe(1); - expect(operations[1].pasteContent).toBeDefined(); - expect(operations[2].action).toBe('Insert'); - expect(operations[2].length).toBe(157); - expect(operations[2].offset).toBe(29); - expect(operations[2].pasteContent).toBeDefined(); - addpaste = false; - }); -}); \ No newline at end of file +// it('insert table paste option', () => { +// console.log('insert table paste option'); +// let operations: Operation[] = []; +// let addpaste: boolean = false; +// container.contentChange = function (args: ContainerContentChangeEventArgs) { +// if (addpaste) { +// operations.push(...args.operations); +// } +// } +// container.documentEditor.openBlank(); +// container.documentEditor.editorModule.insertTable(2, 2); +// container.documentEditor.editorModule.insertText('Syncfusion'); +// container.documentEditor.selection.select("0;0;0;1;0;0", "0;0;0;1;0;0"); +// container.documentEditor.editorModule.insertText('Syncfusion'); +// container.documentEditor.selection.select("0;0;1;0;0;0", "0;0;1;0;0;0"); +// container.documentEditor.editorModule.insertText('Syncfusion'); +// container.documentEditor.selection.select("0;0;1;1;0;0", "0;0;1;1;0;0"); +// container.documentEditor.editorModule.insertText('Syncfusion'); +// container.documentEditor.selection.select("0;0;1;0;0;0", "0;0;1;0;0;0"); +// container.documentEditor.editorModule.pasteContents(HelperMethods.getSfdtDocument({ sfdt: pasteValue })); +// addpaste = true; +// container.documentEditor.editorModule.copiedTextContent = "Document editor Document editor Document editor Document editor Document editor Document editor Document editor Document editor Document editor"; +// container.documentEditor.editor.applyTablePasteOptions('InsertAsRows'); +// expect(operations[0].action).toBe('Delete'); +// expect(operations[0].length).toBe(182); +// expect(operations[0].offset).toBe(1); +// expect(operations[1].action).toBe('Insert'); +// expect(operations[1].length).toBe(51); +// expect(operations[1].offset).toBe(1); +// expect(operations[1].pasteContent).toBeDefined(); +// expect(operations[2].action).toBe('Delete'); +// expect(operations[2].length).toBe(51); +// expect(operations[2].offset).toBe(1); +// expect(operations[3].action).toBe('Insert'); +// expect(operations[3].length).toBe(207); +// expect(operations[3].offset).toBe(1); +// expect(operations[3].pasteContent).toBeDefined(); +// operations = []; +// container.documentEditor.editor.applyTablePasteOptions('OverwriteCells'); +// expect(operations[0].action).toBe('Delete'); +// expect(operations[0].length).toBe(207); +// expect(operations[0].offset).toBe(1); +// expect(operations[1].action).toBe('Insert'); +// expect(operations[1].length).toBe(51); +// expect(operations[1].offset).toBe(1); +// expect(operations[1].pasteContent).toBeDefined(); +// expect(operations[2].action).toBe('Delete'); +// expect(operations[2].length).toBe(51); +// expect(operations[2].offset).toBe(1); +// expect(operations[3].action).toBe('Insert'); +// expect(operations[3].length).toBe(182); +// expect(operations[3].offset).toBe(1); +// expect(operations[3].pasteContent).toBeDefined(); +// operations = []; +// container.documentEditor.editor.applyTablePasteOptions('NestTable'); +// expect(operations[0].action).toBe('Delete'); +// expect(operations[0].length).toBe(182); +// expect(operations[0].offset).toBe(1); +// expect(operations[1].action).toBe('Insert'); +// expect(operations[1].length).toBe(51); +// expect(operations[1].offset).toBe(1); +// expect(operations[1].pasteContent).toBeDefined(); +// expect(operations[2].action).toBe('Insert'); +// expect(operations[2].length).toBe(157); +// expect(operations[2].offset).toBe(29); +// expect(operations[2].pasteContent).toBeDefined(); +// addpaste = false; +// }); +// }); \ No newline at end of file diff --git a/controls/documenteditor/spec/implementation/collaboration/trackchange.spec.ts b/controls/documenteditor/spec/implementation/collaboration/trackchange.spec.ts index 0145ece746..a0a718cfd5 100644 --- a/controls/documenteditor/spec/implementation/collaboration/trackchange.spec.ts +++ b/controls/documenteditor/spec/implementation/collaboration/trackchange.spec.ts @@ -1,193 +1,193 @@ -import { DocumentEditorContainer, ContainerContentChangeEventArgs } from '../../../src/index'; -import { createElement } from '@syncfusion/ej2-base'; -import { Toolbar } from '../../../src/document-editor-container/tool-bar/tool-bar'; -/** - * Track changes collaborative editing spec - */ -describe('Enable track changes in collaborative editing', () => { - let container: DocumentEditorContainer; - let element: HTMLElement; - let args: ContainerContentChangeEventArgs; - beforeAll(() => { - element = createElement('div'); - document.body.appendChild(element); - DocumentEditorContainer.Inject(Toolbar); - container = new DocumentEditorContainer(); - container.appendTo(element); - container.documentEditor.enableCollaborativeEditing = true; - }); - afterAll(() => { - expect(() => { container.destroy(); }).not.toThrowError(); - expect(element.childNodes.length).toBe(0); - document.body.removeChild(element); - document.body.innerHTML = ''; - element = undefined; - container = undefined; - }); +// import { DocumentEditorContainer, ContainerContentChangeEventArgs } from '../../../src/index'; +// import { createElement } from '@syncfusion/ej2-base'; +// import { Toolbar } from '../../../src/document-editor-container/tool-bar/tool-bar'; +// /** +// * Track changes collaborative editing spec +// */ +// describe('Enable track changes in collaborative editing', () => { +// let container: DocumentEditorContainer; +// let element: HTMLElement; +// let args: ContainerContentChangeEventArgs; +// beforeAll(() => { +// element = createElement('div'); +// document.body.appendChild(element); +// DocumentEditorContainer.Inject(Toolbar); +// container = new DocumentEditorContainer(); +// container.appendTo(element); +// container.documentEditor.enableCollaborativeEditing = true; +// }); +// afterAll(() => { +// expect(() => { container.destroy(); }).not.toThrowError(); +// expect(element.childNodes.length).toBe(0); +// document.body.removeChild(element); +// document.body.innerHTML = ''; +// element = undefined; +// container = undefined; +// }); - it('Undo/Redo insert text', () => { - console.log('Undo/Redo insert text when enable track changes'); - let argsEle: ContainerContentChangeEventArgs; - container.contentChange = function (args: ContainerContentChangeEventArgs) { - argsEle = args; - } - container.documentEditor.editorModule.insertText('Syncfusion Software'); - container.documentEditor.enableTrackChanges = true; - container.documentEditor.editorModule.insertText('S'); - expect(argsEle.operations[0].markerData).toBeDefined(); - container.documentEditor.editorHistory.undo(); - expect(argsEle.operations.length).toBe(1); - container.documentEditor.editorHistory.redo(); - expect(argsEle.operations[0].type).toBe('Paste') - expect(argsEle.operations[0].pasteContent).toBeDefined(); - }); +// it('Undo/Redo insert text', () => { +// console.log('Undo/Redo insert text when enable track changes'); +// let argsEle: ContainerContentChangeEventArgs; +// container.contentChange = function (args: ContainerContentChangeEventArgs) { +// argsEle = args; +// } +// container.documentEditor.editorModule.insertText('Syncfusion Software'); +// container.documentEditor.enableTrackChanges = true; +// container.documentEditor.editorModule.insertText('S'); +// expect(argsEle.operations[0].markerData).toBeDefined(); +// container.documentEditor.editorHistory.undo(); +// expect(argsEle.operations.length).toBe(1); +// container.documentEditor.editorHistory.redo(); +// expect(argsEle.operations[0].type).toBe('Paste') +// expect(argsEle.operations[0].pasteContent).toBeDefined(); +// }); - it('Undo/Redo delete text', () => { - console.log('Undo/Redo delete text when enable track changes'); - let argsEle: ContainerContentChangeEventArgs; - container.contentChange = function (args: ContainerContentChangeEventArgs) { - argsEle = args; - } - container.documentEditor.openBlank(); - container.documentEditor.enableTrackChanges = false; - container.documentEditor.editorModule.insertText('Syncfusion Software'); - container.documentEditor.enableTrackChanges = true; - container.documentEditor.selection.select('0;0;2', '0;0;2'); - container.documentEditor.editorModule.onBackSpace(); - expect(argsEle.operations[0].markerData).toBeDefined(); - container.documentEditor.editorHistory.undo(); - expect(argsEle.operations[0].action).toBe('Delete'); - expect(argsEle.operations[0].markerData.isSkipTracking).toBe(true); - expect(argsEle.operations[1].type).toBe('Paste') - expect(argsEle.operations[1].pasteContent).toBeDefined(); - container.documentEditor.editorHistory.redo(); - expect(argsEle.operations[0].action).toBe('Format'); - expect(argsEle.operations[0].markerData).toBeDefined(); - }); +// it('Undo/Redo delete text', () => { +// console.log('Undo/Redo delete text when enable track changes'); +// let argsEle: ContainerContentChangeEventArgs; +// container.contentChange = function (args: ContainerContentChangeEventArgs) { +// argsEle = args; +// } +// container.documentEditor.openBlank(); +// container.documentEditor.enableTrackChanges = false; +// container.documentEditor.editorModule.insertText('Syncfusion Software'); +// container.documentEditor.enableTrackChanges = true; +// container.documentEditor.selection.select('0;0;2', '0;0;2'); +// container.documentEditor.editorModule.onBackSpace(); +// expect(argsEle.operations[0].markerData).toBeDefined(); +// container.documentEditor.editorHistory.undo(); +// expect(argsEle.operations[0].action).toBe('Delete'); +// expect(argsEle.operations[0].markerData.isSkipTracking).toBe(true); +// expect(argsEle.operations[1].type).toBe('Paste') +// expect(argsEle.operations[1].pasteContent).toBeDefined(); +// container.documentEditor.editorHistory.redo(); +// expect(argsEle.operations[0].action).toBe('Format'); +// expect(argsEle.operations[0].markerData).toBeDefined(); +// }); - it('Undo/Redo Split revision', () => { - console.log('Undo/Redo split revision track changes'); - let argsEle: ContainerContentChangeEventArgs; - container.contentChange = function (args: ContainerContentChangeEventArgs) { - argsEle = args; - } - container.documentEditor.openBlank(); - container.documentEditor.enableTrackChanges = false; - container.documentEditor.editorModule.insertText('Syncfusion Software'); - container.documentEditor.enableTrackChanges = true; - container.documentEditor.selection.select('0;0;2', '0;0;7'); - container.documentEditor.editorModule.onBackSpace(); - expect(argsEle.operations[0].markerData).toBeDefined(); - container.documentEditor.selection.select('0;0;4', '0;0;4'); - container.documentEditor.editorModule.insertText('a'); - let insertRevisionID: string = argsEle.operations[0].markerData.revisionId; - expect(argsEle.operations[0].markerData.splittedRevisions.length).toBe(1); - container.documentEditor.editorHistory.undo(); - expect(argsEle.operations[0].action).toBe('Delete'); - container.documentEditor.editorHistory.redo(); - expect(argsEle.operations[0].type).toBe('Paste') - expect(argsEle.operations[0].pasteContent).toBeDefined(); - }); +// it('Undo/Redo Split revision', () => { +// console.log('Undo/Redo split revision track changes'); +// let argsEle: ContainerContentChangeEventArgs; +// container.contentChange = function (args: ContainerContentChangeEventArgs) { +// argsEle = args; +// } +// container.documentEditor.openBlank(); +// container.documentEditor.enableTrackChanges = false; +// container.documentEditor.editorModule.insertText('Syncfusion Software'); +// container.documentEditor.enableTrackChanges = true; +// container.documentEditor.selection.select('0;0;2', '0;0;7'); +// container.documentEditor.editorModule.onBackSpace(); +// expect(argsEle.operations[0].markerData).toBeDefined(); +// container.documentEditor.selection.select('0;0;4', '0;0;4'); +// container.documentEditor.editorModule.insertText('a'); +// let insertRevisionID: string = argsEle.operations[0].markerData.revisionId; +// expect(argsEle.operations[0].markerData.splittedRevisions.length).toBe(1); +// container.documentEditor.editorHistory.undo(); +// expect(argsEle.operations[0].action).toBe('Delete'); +// container.documentEditor.editorHistory.redo(); +// expect(argsEle.operations[0].type).toBe('Paste') +// expect(argsEle.operations[0].pasteContent).toBeDefined(); +// }); - it('Backspace on muliple paragraph', () => { - console.log('Backspace on muliple paragraph'); - let argsEle: ContainerContentChangeEventArgs; - container.contentChange = function (args: ContainerContentChangeEventArgs) { - argsEle = args; - } - container.documentEditor.openBlank(); - container.documentEditor.enableTrackChanges = false; - container.documentEditor.editorModule.insertText('Syncfusion Software'); - container.documentEditor.editor.onEnter(); - container.documentEditor.editorModule.insertText('Syncfusion Software'); - container.documentEditor.editor.onEnter(); - container.documentEditor.editorModule.insertText('Syncfusion Software'); - container.documentEditor.enableTrackChanges = true; - container.documentEditor.selection.select('0;0;0', '0;2;10'); - container.documentEditor.editorModule.onBackSpace(); - expect(argsEle.operations[0].markerData).toBeDefined(); - expect(argsEle.operations[0].markerData.revisionType).toBe('Deletion'); - expect(argsEle.operations[0].markerData.splittedRevisions.length).toBe(4); - }); +// it('Backspace on muliple paragraph', () => { +// console.log('Backspace on muliple paragraph'); +// let argsEle: ContainerContentChangeEventArgs; +// container.contentChange = function (args: ContainerContentChangeEventArgs) { +// argsEle = args; +// } +// container.documentEditor.openBlank(); +// container.documentEditor.enableTrackChanges = false; +// container.documentEditor.editorModule.insertText('Syncfusion Software'); +// container.documentEditor.editor.onEnter(); +// container.documentEditor.editorModule.insertText('Syncfusion Software'); +// container.documentEditor.editor.onEnter(); +// container.documentEditor.editorModule.insertText('Syncfusion Software'); +// container.documentEditor.enableTrackChanges = true; +// container.documentEditor.selection.select('0;0;0', '0;2;10'); +// container.documentEditor.editorModule.onBackSpace(); +// expect(argsEle.operations[0].markerData).toBeDefined(); +// expect(argsEle.operations[0].markerData.revisionType).toBe('Deletion'); +// expect(argsEle.operations[0].markerData.splittedRevisions.length).toBe(4); +// }); - it('Selection with insert', () => { - console.log('Selection with insert'); - let argsEle: ContainerContentChangeEventArgs; - container.contentChange = function (args: ContainerContentChangeEventArgs) { - argsEle = args; - } - container.documentEditor.openBlank(); - container.documentEditor.enableTrackChanges = false; - container.documentEditor.editorModule.insertText('Syncfusion Software'); - container.documentEditor.enableTrackChanges = true; - container.documentEditor.selection.select('0;0;0', '0;0;10'); - container.documentEditor.editorModule.insertText('S'); - expect(argsEle.operations[0].action).toBe('Format'); - expect(argsEle.operations[0].length).toBe(10); - expect(argsEle.operations[0].markerData.revisionType).toBe('Deletion'); - expect(argsEle.operations[1].action).toBe('Insert'); - expect(argsEle.operations[1].offset).toBe(11); - expect(argsEle.operations[1].markerData.revisionType).toBe('Insertion'); - }); +// it('Selection with insert', () => { +// console.log('Selection with insert'); +// let argsEle: ContainerContentChangeEventArgs; +// container.contentChange = function (args: ContainerContentChangeEventArgs) { +// argsEle = args; +// } +// container.documentEditor.openBlank(); +// container.documentEditor.enableTrackChanges = false; +// container.documentEditor.editorModule.insertText('Syncfusion Software'); +// container.documentEditor.enableTrackChanges = true; +// container.documentEditor.selection.select('0;0;0', '0;0;10'); +// container.documentEditor.editorModule.insertText('S'); +// expect(argsEle.operations[0].action).toBe('Format'); +// expect(argsEle.operations[0].length).toBe(10); +// expect(argsEle.operations[0].markerData.revisionType).toBe('Deletion'); +// expect(argsEle.operations[1].action).toBe('Insert'); +// expect(argsEle.operations[1].offset).toBe(11); +// expect(argsEle.operations[1].markerData.revisionType).toBe('Insertion'); +// }); - it('Selection with insert revision and delete', () => { - console.log('Selection with insert revision and delete'); - let argsEle: ContainerContentChangeEventArgs; - container.contentChange = function (args: ContainerContentChangeEventArgs) { - argsEle = args; - } - container.documentEditor.openBlank(); - container.documentEditor.enableTrackChanges = false; - container.documentEditor.editorModule.insertText('Syncfusion Software'); - container.documentEditor.enableTrackChanges = true; - container.documentEditor.selection.select('0;0;1', '0;0;1'); - container.documentEditor.editorModule.insertText('S'); - container.documentEditor.selection.select('0;0;3', '0;0;3'); - container.documentEditor.editorModule.insertText('S'); - container.documentEditor.selection.select('0;0;5', '0;0;5'); - container.documentEditor.editorModule.insertText('S'); - container.documentEditor.selection.select('0;0;0', '0;0;13'); - container.documentEditor.editorModule.onBackSpace(); - expect(argsEle.operations.length).toBe(4); - expect(argsEle.operations[0].action).toBe('Delete'); - expect(argsEle.operations[0].offset).toBe(6); - expect(argsEle.operations[1].action).toBe('Delete'); - expect(argsEle.operations[1].offset).toBe(4); - expect(argsEle.operations[2].action).toBe('Delete'); - expect(argsEle.operations[2].offset).toBe(2); - expect(argsEle.operations[3].action).toBe('Format'); - expect(argsEle.operations[3].offset).toBe(1); - expect(argsEle.operations[3].length).toBe(10); - expect(argsEle.operations[3].markerData).toBeDefined(); - }); +// it('Selection with insert revision and delete', () => { +// console.log('Selection with insert revision and delete'); +// let argsEle: ContainerContentChangeEventArgs; +// container.contentChange = function (args: ContainerContentChangeEventArgs) { +// argsEle = args; +// } +// container.documentEditor.openBlank(); +// container.documentEditor.enableTrackChanges = false; +// container.documentEditor.editorModule.insertText('Syncfusion Software'); +// container.documentEditor.enableTrackChanges = true; +// container.documentEditor.selection.select('0;0;1', '0;0;1'); +// container.documentEditor.editorModule.insertText('S'); +// container.documentEditor.selection.select('0;0;3', '0;0;3'); +// container.documentEditor.editorModule.insertText('S'); +// container.documentEditor.selection.select('0;0;5', '0;0;5'); +// container.documentEditor.editorModule.insertText('S'); +// container.documentEditor.selection.select('0;0;0', '0;0;13'); +// container.documentEditor.editorModule.onBackSpace(); +// expect(argsEle.operations.length).toBe(4); +// expect(argsEle.operations[0].action).toBe('Delete'); +// expect(argsEle.operations[0].offset).toBe(6); +// expect(argsEle.operations[1].action).toBe('Delete'); +// expect(argsEle.operations[1].offset).toBe(4); +// expect(argsEle.operations[2].action).toBe('Delete'); +// expect(argsEle.operations[2].offset).toBe(2); +// expect(argsEle.operations[3].action).toBe('Format'); +// expect(argsEle.operations[3].offset).toBe(1); +// expect(argsEle.operations[3].length).toBe(10); +// expect(argsEle.operations[3].markerData).toBeDefined(); +// }); - it('Selection with insert revision and delete', () => { - console.log('Selection with insert revision and delete'); - let argsEle: ContainerContentChangeEventArgs; - container.contentChange = function (args: ContainerContentChangeEventArgs) { - argsEle = args; - } - container.documentEditor.openBlank(); - container.documentEditor.enableTrackChanges = false; - container.documentEditor.editorModule.insertText('Syncfusion Software'); - container.documentEditor.enableTrackChanges = true; - container.documentEditor.selection.select('0;0;1', '0;0;1'); - container.documentEditor.editorModule.insertText('S'); - container.documentEditor.selection.select('0;0;3', '0;0;3'); - container.documentEditor.editorModule.insertText('S'); - container.documentEditor.selection.select('0;0;5', '0;0;5'); - container.documentEditor.editorModule.insertText('S'); - container.documentEditor.selection.select('0;0;0', '0;0;13'); - container.documentEditor.editorModule.onBackSpace(); - container.documentEditor.editorHistory.undo(); - expect(argsEle.operations[0].length).toBe(10); - expect(argsEle.operations[0].action).toBe('Delete'); - expect(argsEle.operations[0].offset).toBe(1); - expect(argsEle.operations[1].type).toBe('Paste') - expect(argsEle.operations[1].pasteContent).toBeDefined(); - container.documentEditor.editorHistory.redo(); - expect(argsEle.operations[0].action).toBe('Format'); - expect(argsEle.operations[0].markerData).toBeDefined(); - }); -}); \ No newline at end of file +// it('Selection with insert revision and delete', () => { +// console.log('Selection with insert revision and delete'); +// let argsEle: ContainerContentChangeEventArgs; +// container.contentChange = function (args: ContainerContentChangeEventArgs) { +// argsEle = args; +// } +// container.documentEditor.openBlank(); +// container.documentEditor.enableTrackChanges = false; +// container.documentEditor.editorModule.insertText('Syncfusion Software'); +// container.documentEditor.enableTrackChanges = true; +// container.documentEditor.selection.select('0;0;1', '0;0;1'); +// container.documentEditor.editorModule.insertText('S'); +// container.documentEditor.selection.select('0;0;3', '0;0;3'); +// container.documentEditor.editorModule.insertText('S'); +// container.documentEditor.selection.select('0;0;5', '0;0;5'); +// container.documentEditor.editorModule.insertText('S'); +// container.documentEditor.selection.select('0;0;0', '0;0;13'); +// container.documentEditor.editorModule.onBackSpace(); +// container.documentEditor.editorHistory.undo(); +// expect(argsEle.operations[0].length).toBe(10); +// expect(argsEle.operations[0].action).toBe('Delete'); +// expect(argsEle.operations[0].offset).toBe(1); +// expect(argsEle.operations[1].type).toBe('Paste') +// expect(argsEle.operations[1].pasteContent).toBeDefined(); +// container.documentEditor.editorHistory.redo(); +// expect(argsEle.operations[0].action).toBe('Format'); +// expect(argsEle.operations[0].markerData).toBeDefined(); +// }); +// }); \ No newline at end of file diff --git a/controls/documenteditor/spec/implementation/editor_6.spec.ts b/controls/documenteditor/spec/implementation/editor_6.spec.ts index 70ab1b04e3..7554c38e56 100644 --- a/controls/documenteditor/spec/implementation/editor_6.spec.ts +++ b/controls/documenteditor/spec/implementation/editor_6.spec.ts @@ -313,3 +313,794 @@ console.log('Undo - Enter -after apply character format and paragraph is not emp expect(editor.selection.characterFormat.fontSize).toBe(11); }); }); + +describe('Table Style validation', () => { + let editor: DocumentEditor = undefined; + beforeAll(() => { + let ele: HTMLElement = createElement('div', { id: 'container' }); + document.body.appendChild(ele); + editor = new DocumentEditor({ enableEditor: true, isReadOnly: false }); + DocumentEditor.Inject(Editor, Selection, EditorHistory); editor.enableEditorHistory = true; + (editor.documentHelper as any).containerCanvasIn = TestHelper.containerCanvas; + (editor.documentHelper as any).selectionCanvasIn = TestHelper.selectionCanvas; + (editor.documentHelper.render as any).pageCanvasIn = TestHelper.pageCanvas; + (editor.documentHelper.render as any).selectionCanvasIn = TestHelper.pageSelectionCanvas; + editor.appendTo('#container'); + }); + afterAll((done) => { + editor.destroy(); + document.body.removeChild(document.getElementById('container')); + editor = undefined; + setTimeout(function () { + document.body.innerHTML = ''; + done(); + }, 1000); + }); + + it('Document opening with table style test', () => { + console.log('Document opening with table style test'); + expect(() => { editor.open(getJson()); }).not.toThrowError(); + }); + + function getJson(): string { + const json: any = { + "optimizeSfdt": false, + "sections": [ + { + "blocks": [ + { + "inlines": [] + }, + { + "rows": [ + { + "rowFormat": { + "allowBreakAcrossPages": true, + "isHeader": false, + "height": 0.0, + "heightType": "AtLeast", + "borders": { + "left": { + "lineStyle": "None", + "lineWidth": 0.0, + "shadow": false, + "space": 0.0, + "hasNoneStyle": true + }, + "right": { + "lineStyle": "None", + "lineWidth": 0.0, + "shadow": false, + "space": 0.0, + "hasNoneStyle": true + }, + "top": { + "lineStyle": "None", + "lineWidth": 0.0, + "shadow": false, + "space": 0.0, + "hasNoneStyle": true + }, + "bottom": { + "lineStyle": "None", + "lineWidth": 0.0, + "shadow": false, + "space": 0.0, + "hasNoneStyle": true + }, + "vertical": { + "lineStyle": "None", + "lineWidth": 0.0, + "shadow": false, + "space": 0.0, + "hasNoneStyle": true + }, + "horizontal": { + "lineStyle": "None", + "lineWidth": 0.0, + "shadow": false, + "space": 0.0, + "hasNoneStyle": true + }, + "diagonalDown": { + "lineStyle": "None", + "lineWidth": 0.0, + "shadow": false, + "space": 0.0, + "hasNoneStyle": false + }, + "diagonalUp": { + "lineStyle": "None", + "lineWidth": 0.0, + "shadow": false, + "space": 0.0, + "hasNoneStyle": false + } + }, + "leftIndent": 13.25 + }, + "cells": [ + { + "blocks": [ + { + "characterFormat": { + "fontSize": 11.0, + "fontFamily": "Arial", + "fontSizeBidi": 11.0, + "fontFamilyBidi": "Arial", + "fontFamilyAscii": "Arial", + "fontFamilyFarEast": "minorHAnsi", + "fontFamilyNonFarEast": "Arial" + }, + "paragraphFormat": { + "lineSpacing": 1.0, + "lineSpacingType": "Multiple" + }, + "inlines": [] + } + ], + "cellFormat": { + "columnSpan": 1, + "rowSpan": 1, + "preferredWidth": 31.25, + "preferredWidthType": "Point", + "verticalAlignment": "Top", + "borders": { + "left": { + "lineStyle": "None", + "lineWidth": 0.0, + "shadow": false, + "space": 0.0, + "hasNoneStyle": false + }, + "right": { + "lineStyle": "None", + "lineWidth": 0.0, + "shadow": false, + "space": 0.0, + "hasNoneStyle": false + }, + "top": { + "lineStyle": "None", + "lineWidth": 0.0, + "shadow": false, + "space": 0.0, + "hasNoneStyle": false + }, + "bottom": { + "lineStyle": "None", + "lineWidth": 0.0, + "shadow": false, + "space": 0.0, + "hasNoneStyle": false + }, + "vertical": { + "lineStyle": "None", + "lineWidth": 0.0, + "shadow": false, + "space": 0.0, + "hasNoneStyle": false + }, + "horizontal": { + "lineStyle": "None", + "lineWidth": 0.0, + "shadow": false, + "space": 0.0, + "hasNoneStyle": false + }, + "diagonalDown": { + "lineStyle": "None", + "lineWidth": 0.0, + "shadow": false, + "space": 0.0, + "hasNoneStyle": false + }, + "diagonalUp": { + "lineStyle": "None", + "lineWidth": 0.0, + "shadow": false, + "space": 0.0, + "hasNoneStyle": false + } + }, + "cellWidth": 31.25 + } + }, + { + "blocks": [ + { + "characterFormat": { + "fontSize": 11.0, + "fontFamily": "Arial", + "fontSizeBidi": 11.0, + "fontFamilyBidi": "Arial", + "fontFamilyAscii": "Arial", + "fontFamilyFarEast": "minorHAnsi", + "fontFamilyNonFarEast": "Arial" + }, + "paragraphFormat": { + "lineSpacing": 1.0, + "lineSpacingType": "Multiple" + }, + "inlines": [ + { + "text": "Video content production and editing ", + "characterFormat": { + "fontSize": 11.0, + "fontFamily": "Arial", + "fontSizeBidi": 11.0, + "fontFamilyBidi": "Arial", + "fontFamilyAscii": "Arial", + "fontFamilyFarEast": "minorHAnsi", + "fontFamilyNonFarEast": "Arial" + } + } + ] + } + ], + "cellFormat": { + "columnSpan": 1, + "rowSpan": 1, + "preferredWidth": 436.25, + "preferredWidthType": "Point", + "verticalAlignment": "Top", + "borders": { + "left": { + "lineStyle": "None", + "lineWidth": 0.0, + "shadow": false, + "space": 0.0, + "hasNoneStyle": false + }, + "right": { + "lineStyle": "None", + "lineWidth": 0.0, + "shadow": false, + "space": 0.0, + "hasNoneStyle": false + }, + "top": { + "lineStyle": "None", + "lineWidth": 0.0, + "shadow": false, + "space": 0.0, + "hasNoneStyle": false + }, + "bottom": { + "lineStyle": "None", + "lineWidth": 0.0, + "shadow": false, + "space": 0.0, + "hasNoneStyle": false + }, + "vertical": { + "lineStyle": "None", + "lineWidth": 0.0, + "shadow": false, + "space": 0.0, + "hasNoneStyle": false + }, + "horizontal": { + "lineStyle": "None", + "lineWidth": 0.0, + "shadow": false, + "space": 0.0, + "hasNoneStyle": false + }, + "diagonalDown": { + "lineStyle": "None", + "lineWidth": 0.0, + "shadow": false, + "space": 0.0, + "hasNoneStyle": false + }, + "diagonalUp": { + "lineStyle": "None", + "lineWidth": 0.0, + "shadow": false, + "space": 0.0, + "hasNoneStyle": false + } + }, + "cellWidth": 436.25 + } + } + ] + } + ], + "title": null, + "description": null, + "tableFormat": { + "allowAutoFit": true, + "leftIndent": 13.25, + "tableAlignment": "Left", + "preferredWidth": 467.5, + "preferredWidthType": "Point", + "borders": { + "left": { + "lineStyle": "None", + "lineWidth": 0.0, + "shadow": false, + "space": 0.0, + "hasNoneStyle": true + }, + "right": { + "lineStyle": "None", + "lineWidth": 0.0, + "shadow": false, + "space": 0.0, + "hasNoneStyle": true + }, + "top": { + "lineStyle": "None", + "lineWidth": 0.0, + "shadow": false, + "space": 0.0, + "hasNoneStyle": true + }, + "bottom": { + "lineStyle": "None", + "lineWidth": 0.0, + "shadow": false, + "space": 0.0, + "hasNoneStyle": true + }, + "vertical": { + "lineStyle": "None", + "lineWidth": 0.0, + "shadow": false, + "space": 0.0, + "hasNoneStyle": true + }, + "horizontal": { + "lineStyle": "None", + "lineWidth": 0.0, + "shadow": false, + "space": 0.0, + "hasNoneStyle": true + }, + "diagonalDown": { + "lineStyle": "None", + "lineWidth": 0.0, + "shadow": false, + "space": 0.0, + "hasNoneStyle": false + }, + "diagonalUp": { + "lineStyle": "None", + "lineWidth": 0.0, + "shadow": false, + "space": 0.0, + "hasNoneStyle": false + } + }, + "bidi": false, + "horizontalPositionAbs": "Left", + "horizontalPosition": 0.0, + "styleName": "Table Grid" + } + }, + { + "inlines": [] + } + ], + "headersFooters": { + "firstPageHeader": { + "blocks": [ + { + "inlines": [] + } + ] + }, + "firstPageFooter": { + "blocks": [ + { + "inlines": [] + } + ] + } + }, + "sectionFormat": { + "headerDistance": 36.0, + "footerDistance": 36.0, + "pageWidth": 612.0, + "pageHeight": 792.0, + "leftMargin": 72.0, + "rightMargin": 72.0, + "topMargin": 72.0, + "bottomMargin": 72.0, + "differentFirstPage": false, + "differentOddAndEvenPages": false, + "bidi": false, + "restartPageNumbering": true, + "pageStartingNumber": 1, + "endnoteNumberFormat": "LowerCaseRoman", + "footNoteNumberFormat": "Arabic", + "restartIndexForFootnotes": "DoNotRestart", + "restartIndexForEndnotes": "DoNotRestart", + "pageNumberStyle": "Arabic", + "breakCode": "NewPage" + } + } + ], + "characterFormat": { + "fontSize": 11.0, + "fontFamily": "Arial", + "fontSizeBidi": 11.0, + "fontFamilyBidi": "Arial", + "fontFamilyAscii": "Arial", + "fontFamilyFarEast": "Arial", + "fontFamilyNonFarEast": "Arial", + "localeId": 9, + "localeIdFarEast": 1033, + "localeIdBidi": 1025 + }, + "paragraphFormat": { + "lineSpacing": 1.149999976158142, + "lineSpacingType": "Multiple" + }, + "background": { + "color": "#FFFFFFFF" + }, + "styles": [ + { + "type": "Paragraph", + "name": "Normal", + "next": "Normal" + }, + { + "type": "Paragraph", + "name": "Heading 1", + "basedOn": "Normal", + "next": "Normal", + "characterFormat": { + "fontSize": 20.0, + "fontSizeBidi": 20.0 + }, + "paragraphFormat": { + "beforeSpacing": 20.0, + "afterSpacing": 6.0, + "outlineLevel": "Level1", + "keepWithNext": true, + "keepLinesTogether": true + } + }, + { + "type": "Paragraph", + "name": "Heading 2", + "basedOn": "Normal", + "next": "Normal", + "characterFormat": { + "fontSize": 16.0, + "fontSizeBidi": 16.0 + }, + "paragraphFormat": { + "beforeSpacing": 18.0, + "afterSpacing": 6.0, + "outlineLevel": "Level2", + "keepWithNext": true, + "keepLinesTogether": true + } + }, + { + "type": "Paragraph", + "name": "Heading 3", + "basedOn": "Normal", + "next": "Normal", + "characterFormat": { + "fontSize": 14.0, + "fontColor": "#434343FF", + "fontSizeBidi": 14.0 + }, + "paragraphFormat": { + "beforeSpacing": 16.0, + "afterSpacing": 4.0, + "outlineLevel": "Level3", + "keepWithNext": true, + "keepLinesTogether": true + } + }, + { + "type": "Paragraph", + "name": "Heading 4", + "basedOn": "Normal", + "next": "Normal", + "characterFormat": { + "fontSize": 12.0, + "fontColor": "#666666FF", + "fontSizeBidi": 12.0 + }, + "paragraphFormat": { + "beforeSpacing": 14.0, + "afterSpacing": 4.0, + "outlineLevel": "Level4", + "keepWithNext": true, + "keepLinesTogether": true + } + }, + { + "type": "Paragraph", + "name": "Heading 5", + "basedOn": "Normal", + "next": "Normal", + "characterFormat": { + "fontColor": "#666666FF" + }, + "paragraphFormat": { + "beforeSpacing": 12.0, + "afterSpacing": 4.0, + "outlineLevel": "Level5", + "keepWithNext": true, + "keepLinesTogether": true + } + }, + { + "type": "Paragraph", + "name": "Heading 6", + "basedOn": "Normal", + "next": "Normal", + "characterFormat": { + "italic": true, + "fontColor": "#666666FF" + }, + "paragraphFormat": { + "beforeSpacing": 12.0, + "afterSpacing": 4.0, + "outlineLevel": "Level6", + "keepWithNext": true, + "keepLinesTogether": true + } + }, + { + "type": "Character", + "name": "Default Paragraph Font" + }, + { + "type": "Table", + "name": "Normal Table", + "next": "Normal Table" + }, + { + "type": "Paragraph", + "name": "Title", + "basedOn": "Normal", + "next": "Normal", + "characterFormat": { + "fontSize": 26.0, + "fontSizeBidi": 26.0 + }, + "paragraphFormat": { + "afterSpacing": 3.0, + "keepWithNext": true, + "keepLinesTogether": true + } + }, + { + "type": "Paragraph", + "name": "Subtitle", + "basedOn": "Normal", + "next": "Normal", + "characterFormat": { + "fontSize": 15.0, + "fontColor": "#666666FF", + "fontSizeBidi": 15.0 + }, + "paragraphFormat": { + "afterSpacing": 16.0, + "keepWithNext": true, + "keepLinesTogether": true + } + }, + { + "type": "Table", + "name": "a", + "basedOn": "Normal Table", + "next": "a" + }, + { + "type": "Character", + "name": "Hyperlink", + "basedOn": "Default Paragraph Font", + "characterFormat": { + "underline": "Single", + "fontColor": "#0000FFFF" + } + }, + { + "type": "Paragraph", + "name": "List Paragraph", + "basedOn": "Normal", + "next": "List Paragraph", + "paragraphFormat": { + "leftIndent": 36.0, + "contextualSpacing": true + } + }, + { + "type": "Table", + "name": "Table Grid", + "basedOn": "Normal Table", + "next": "Table Grid" + } + ], + "defaultTabWidth": 36.0, + "formatting": false, + "trackChanges": false, + "protectionType": "NoProtection", + "enforcement": false, + "dontUseHTMLParagraphAutoSpacing": false, + "alignTablesRowByRow": false, + "formFieldShading": true, + "footnotes": { + "separator": [ + { + "paragraphFormat": { + "lineSpacing": 1.0, + "lineSpacingType": "Multiple" + }, + "inlines": [ + { + "text": "\u0003" + } + ] + } + ], + "continuationSeparator": [ + { + "paragraphFormat": { + "lineSpacing": 1.0, + "lineSpacingType": "Multiple" + }, + "inlines": [ + { + "text": "\u0004" + } + ] + } + ], + "continuationNotice": [ + { + "inlines": [] + } + ] + }, + "endnotes": { + "separator": [ + { + "paragraphFormat": { + "lineSpacing": 1.0, + "lineSpacingType": "Multiple" + }, + "inlines": [ + { + "text": "\u0003" + } + ] + } + ], + "continuationSeparator": [ + { + "paragraphFormat": { + "lineSpacing": 1.0, + "lineSpacingType": "Multiple" + }, + "inlines": [ + { + "text": "\u0004" + } + ] + } + ], + "continuationNotice": [ + { + "inlines": [] + } + ] + }, + "compatibilityMode": "Word2013", + "allowSpaceOfSameStyleInTable": false, + "themeFontLanguages": { + "localeId": 1033, + "localeIdBidi": 1025 + }, + "themes": { + "fontScheme": { + "fontSchemeName": "Office", + "majorFontScheme": { + "fontSchemeList": [ + { + "name": "latin", + "typeface": "Calibri" + }, + { + "name": "ea" + }, + { + "name": "cs" + } + ], + "fontTypeface": { + "Jpan": "MS ゴシック", + "Hang": "맑은 고딕", + "Hans": "宋体", + "Hant": "新細明體", + "Arab": "Times New Roman", + "Hebr": "Times New Roman", + "Thai": "Angsana New", + "Ethi": "Nyala", + "Beng": "Vrinda", + "Gujr": "Shruti", + "Khmr": "MoolBoran", + "Knda": "Tunga", + "Guru": "Raavi", + "Cans": "Euphemia", + "Cher": "Plantagenet Cherokee", + "Yiii": "Microsoft Yi Baiti", + "Tibt": "Microsoft Himalaya", + "Thaa": "MV Boli", + "Deva": "Mangal", + "Telu": "Gautami", + "Taml": "Latha", + "Syrc": "Estrangelo Edessa", + "Orya": "Kalinga", + "Mlym": "Kartika", + "Laoo": "DokChampa", + "Sinh": "Iskoola Pota", + "Mong": "Mongolian Baiti", + "Viet": "Times New Roman", + "Uigh": "Microsoft Uighur", + "Geor": "Sylfaen" + } + }, + "minorFontScheme": { + "fontSchemeList": [ + { + "name": "latin", + "typeface": "Cambria" + }, + { + "name": "ea" + }, + { + "name": "cs" + } + ], + "fontTypeface": { + "Jpan": "MS 明朝", + "Hang": "맑은 고딕", + "Hans": "宋体", + "Hant": "新細明體", + "Arab": "Arial", + "Hebr": "Arial", + "Thai": "Cordia New", + "Ethi": "Nyala", + "Beng": "Vrinda", + "Gujr": "Shruti", + "Khmr": "DaunPenh", + "Knda": "Tunga", + "Guru": "Raavi", + "Cans": "Euphemia", + "Cher": "Plantagenet Cherokee", + "Yiii": "Microsoft Yi Baiti", + "Tibt": "Microsoft Himalaya", + "Thaa": "MV Boli", + "Deva": "Mangal", + "Telu": "Gautami", + "Taml": "Latha", + "Syrc": "Estrangelo Edessa", + "Orya": "Kalinga", + "Mlym": "Kartika", + "Laoo": "DokChampa", + "Sinh": "Iskoola Pota", + "Mong": "Mongolian Baiti", + "Viet": "Arial", + "Uigh": "Microsoft Uighur", + "Geor": "Sylfaen" + } + } + } + } + }; + return JSON.stringify(json); + } +}); diff --git a/controls/documenteditor/src/document-editor/base/types.ts b/controls/documenteditor/src/document-editor/base/types.ts index fbee956e1d..1be9d71d12 100644 --- a/controls/documenteditor/src/document-editor/base/types.ts +++ b/controls/documenteditor/src/document-editor/base/types.ts @@ -894,7 +894,11 @@ export type StyleType = /** * Character style. */ - 'Character'; + 'Character' | + /** + * Table style. + */ + 'Table'; /** * Specifies table row placement. diff --git a/controls/documenteditor/src/document-editor/implementation/collaboration/collaboration.ts b/controls/documenteditor/src/document-editor/implementation/collaboration/collaboration.ts index 2d14e3af5c..94f7d3ef9f 100644 --- a/controls/documenteditor/src/document-editor/implementation/collaboration/collaboration.ts +++ b/controls/documenteditor/src/document-editor/implementation/collaboration/collaboration.ts @@ -285,7 +285,6 @@ export class CollaborativeEditingHandler { let currentEditMode: boolean = this.documentEditor.commentReviewPane.commentPane.isEditMode; let currenteditorHistory = this.documentEditor.editorHistoryModule.lastOperation; let currentTextArea: HTMLTextAreaElement; - let isFieldOperation: boolean = false; if (!isNullOrUndefined(this.documentEditor.commentReviewPane.commentPane.currentEditingComment)) { currentTextArea = this.documentEditor.commentReviewPane.commentPane.currentEditingComment.textArea as HTMLTextAreaElement; } @@ -297,7 +296,7 @@ export class CollaborativeEditingHandler { this.documentEditor.currentUser = markerData.author; } if (!isNullOrUndefined(markerData) && !isNullOrUndefined(markerData.isSkipTracking) && markerData.isSkipTracking && this.documentEditor.enableTrackChanges) { - this.documentEditor.editorModule.isSkipOperationsBuild = true; + this.documentEditor.skipSettingsOps = true; this.documentEditor.enableTrackChanges = false; } if (action.operations[i].skipOperation || (!isNullOrUndefined(action.operations[i].markerData) && action.operations[i].markerData.skipOperation)) { @@ -505,7 +504,7 @@ export class CollaborativeEditingHandler { this.documentEditor.editorModule.insertElementsInternal(this.documentEditor.selectionModule.start, [element]); this.documentEditor.editorModule.fireContentChange(); } else if (markerData.type && markerData.type === 'Field') { - isFieldOperation = true; + this.documentEditor.editor.isFieldOperation = true; let type: number = op2.text === CONTROL_CHARACTERS.Marker_Start ? 0 : op2.text === CONTROL_CHARACTERS.Marker_End ? 1 : op2.text === CONTROL_CHARACTERS.Field_Separator ? 2 : undefined; if (!isNullOrUndefined(type) && isNullOrUndefined(markerData.checkBoxValue)) { var field = new FieldElementBox(type); @@ -735,7 +734,7 @@ export class CollaborativeEditingHandler { } this.documentEditor.editorModule.revisionData = []; if(this.documentEditor.enableTrackChanges != trackingCurrentValue) { - this.documentEditor.editorModule.isSkipOperationsBuild = true; + this.documentEditor.skipSettingsOps = true; this.documentEditor.enableTrackChanges = trackingCurrentValue; } this.documentEditor.currentUser = currentUser; @@ -761,9 +760,9 @@ export class CollaborativeEditingHandler { this.documentEditor.optionsPaneModule.searchIconClickInternal(); } } - if (isFieldOperation) { + if (this.documentEditor.editor.isFieldOperation) { this.documentEditor.editorModule.layoutWholeDocument(); - isFieldOperation = false; + this.documentEditor.editor.isFieldOperation = false; } if (!isNullOrUndefined(this.rowWidget)) { let ownerTable: TableWidget = this.rowWidget.ownerTable.combineWidget(this.documentEditor.viewer) as TableWidget; @@ -1465,8 +1464,10 @@ export class CollaborativeEditingHandler { let data: ActionInfo = dataObject[i] if (data.connectionId === this.connectionId) { this.acknowledgementReceived(); + this.logMessage(this.isSyncServerChanges ? 'SignalR Server sync' + data.version : 'SignalR Same user sync:' + data.version); } else { this.handleRemoteOperation(data); + this.logMessage('Received: ' + JSON.stringify(JSON.stringify(data))); } this.updateVersion(data.version); this.logMessage('Server sync ack:' + data.version); diff --git a/controls/documenteditor/src/document-editor/implementation/editor-history/base-history-info.ts b/controls/documenteditor/src/document-editor/implementation/editor-history/base-history-info.ts index 098d1e26a7..ba6828f39c 100644 --- a/controls/documenteditor/src/document-editor/implementation/editor-history/base-history-info.ts +++ b/controls/documenteditor/src/document-editor/implementation/editor-history/base-history-info.ts @@ -204,26 +204,22 @@ export class BaseHistoryInfo { } public updateSelection(): void { - this.updateCollaborativeSelection(); + this.updateCollaborativeSelection(this.owner.selectionModule.start.clone(), this.owner.selectionModule.end.clone()); let blockInfo: ParagraphInfo = this.owner.selectionModule.getParagraphInfo(this.owner.selectionModule.start); this.selectionStart = this.owner.selectionModule.getHierarchicalIndex(blockInfo.paragraph, blockInfo.offset.toString()); blockInfo = this.owner.selectionModule.getParagraphInfo(this.owner.selectionModule.end); this.selectionEnd = this.owner.selectionModule.getHierarchicalIndex(blockInfo.paragraph, blockInfo.offset.toString()); } - private updateCollaborativeSelection(): void { + private updateCollaborativeSelection(start: TextPosition, end: TextPosition): void { if (this.owner.enableCollaborativeEditing && !this.owner.editorModule.isRemoteAction) { //TODO: Need to consider formard and backward selection - let start: TextPosition; - let end: TextPosition; if (this.action == 'RemoveEditRange') { let startEdit: EditRangeStartElementBox = this.owner.selectionModule.getEditRangeStartElement(); let position: PositionInfo = this.owner.selectionModule.getPosition(startEdit); start = position.startPosition; end = position.endPosition; } else { - start = this.owner.selectionModule.start.clone(); - end = this.owner.selectionModule.end.clone(); this.updateTableSelection(start, end); } this.startIndex = this.owner.selectionModule.getAbsolutePositionFromRelativePosition(start); @@ -247,7 +243,7 @@ export class BaseHistoryInfo { } else { this.endIndex -= this.owner.selectionModule.getTableRelativeValue(end, start); } - if (this.action === 'BackSpace' || this.action === 'Delete') { + // if (this.action === 'BackSpace' || this.action === 'Delete') { let isParagraphStart: boolean = isForward ? (start.paragraph.equals(end.paragraph) && start.isAtParagraphStart) : (start.paragraph.equals(end.paragraph) && end.isAtParagraphStart); if ((isParagraphStart || !start.paragraph.equals(end.paragraph))) { if (isForward) { @@ -256,7 +252,7 @@ export class BaseHistoryInfo { this.startIndex += this.paraInclude(start); } } - } + // } this.splitOperationForDelete(start, end); // Code for Comparing the offset calculated using old approach and optimized approach // throwCustomError(this.newStartIndex !== this.startIndex, "New StartIndex " + this.newStartIndex + " and old StartIndex " + this.startIndex + " doesnot match"); @@ -504,8 +500,11 @@ export class BaseHistoryInfo { if (!isNullOrUndefined(this.insertPosition)) { this.insertIndex = this.owner.selectionModule.getAbsolutePositionFromRelativePosition(this.insertPosition); } + this.startIndex = this.insertIndex; if (!isNullOrUndefined(this.endPosition)) { - this.updateCollaborativeSelection(); + let startPosition: TextPosition = this.owner.selection.getTextPosBasedOnLogicalIndex(this.insertPosition); + let endPosition: TextPosition = this.owner.selection.getTextPosBasedOnLogicalIndex(this.endPosition); + this.updateCollaborativeSelection(startPosition, endPosition); } this.startIndex = this.insertIndex; } @@ -525,6 +524,8 @@ export class BaseHistoryInfo { } } this.isRemovedNodes = true; + } else { + this.isRemovedNodes = false; } this.removedNodesIn = []; if (isNullOrUndefined(this.endPosition)) { @@ -613,13 +614,15 @@ export class BaseHistoryInfo { // Use this property to skip deletion if already selected content deleted case. let isRemoveContent: boolean = false; if (this.endRevisionLogicalIndex && deletedNodes.length > 0) { + let currentPosition: TextPosition = sel.getTextPosBasedOnLogicalIndex(this.endRevisionLogicalIndex); if (this.editorHistory.isUndoing || (this.editorHistory.isRedoing && insertTextPosition.isAtSamePosition(endTextPosition))) { - let currentPosition: TextPosition = sel.getTextPosBasedOnLogicalIndex(this.endRevisionLogicalIndex); sel.selectPosition(insertTextPosition, currentPosition); - if(this.owner.enableCollaborativeEditing) { - this.endIndex = this.owner.selectionModule.getAbsolutePositionFromRelativePosition(currentPosition); - } } + this.collabEnd = this.endRevisionLogicalIndex; + if (this.owner.enableCollaborativeEditing) { + this.endIndex = this.owner.selectionModule.getAbsolutePositionFromRelativePosition(currentPosition); + this.endIndex += this.paraInclude(currentPosition); + } if (this.editorHistory.isUndoing) { this.owner.editorModule.deleteSelectedContents(sel, true); isRemoveContent = true; @@ -1968,6 +1971,7 @@ export class BaseHistoryInfo { } operation.markerData.splittedRevisions.push(this.markerData[j]); this.markerData.splice(j, 1); + j--; } } } @@ -2107,10 +2111,6 @@ export class BaseHistoryInfo { if (this.editorHistory.isUndoing && this.isRemovedNodes) { operations.push(this.getUndoRedoOperation(action)); } else { - if (this.editorHistory.isRedoing && action === 'InsertTable') { - this.insertedNodes.reverse(); - this.insertedNodes.splice(0, 1); - } let tableRowOperation: Operation[] = this.buildTableRowCellOperation(action); for (let i: number = 0; i < tableRowOperation.length; i++) { operations.push(tableRowOperation[i]); @@ -2180,7 +2180,19 @@ export class BaseHistoryInfo { } else { if (this.removedNodes.length > 0) { if (this.owner.enableTrackChanges) { - operations = this.getDeleteOperationsForTrackChanges(); + if (this.editorHistory.isRedoing) { + if (this.removedNodes.length > 0) { + let deleteOperation: Operation = this.getDeleteOperation(action); + deleteOperation.markerData = { isSkipTracking: true }; + operations.push(deleteOperation); + } + if (this.isRemovedNodes) { + let operationCollection: Operation[] = this.getDeleteContent(action); + operations = [...operations, ...operationCollection]; + } + } else { + operations = this.getDeleteOperationsForTrackChanges(); + } } else { let deleteOperation: Operation = this.getDeleteOperation(action); operations.push(deleteOperation); @@ -2209,6 +2221,7 @@ export class BaseHistoryInfo { } } } + this.markerData = []; break; case 'ResolveComment': case 'EditComment': @@ -2612,7 +2625,7 @@ export class BaseHistoryInfo { if (!isNullOrUndefined(element)) { do { let insertedText; - let Data: MarkerInfo; + let data: MarkerInfo; let elementLength; let characterFormat; let type; @@ -2629,31 +2642,45 @@ export class BaseHistoryInfo { if (element.fieldType === 0 && element.formFieldData) { type = this.formFieldType; - Data = this.markerData.pop(); - if (isNullOrUndefined(Data)) { - Data = {}; + if (element.revisions.length > 0) { + data = this.owner.editorModule.getRevisionMarkerData(data, element.revisions[0]); } - Data.type = 'Field'; - Data.formFieldData = JSON.stringify(element.formFieldData); + if (isNullOrUndefined(data)) { + data = {}; + } + data.type = 'Field'; + data.formFieldData = JSON.stringify(element.formFieldData); } else { - Data = this.markerData.pop(); - if (isNullOrUndefined(Data)) { - Data = {}; + if(element.revisions.length > 0) { + data = this.owner.editorModule.getRevisionMarkerData(data, element.revisions[0]); + } + if (isNullOrUndefined(data)) { + data = {}; } - Data.type = 'Field'; + data.type = 'Field'; } elementLength = element.length; } else if (this.fieldBegin.formFieldData && element instanceof BookmarkElementBox) { + if (element.revisions.length > 0) { + data = this.owner.editorModule.getRevisionMarkerData(data, element.revisions[0]); + } insertedText = element.bookmarkType === 0 ? CONTROL_CHARACTERS.Marker_Start : CONTROL_CHARACTERS.Marker_End; - Data = { 'bookmarkName': element.name, 'type': 'Bookmark' }; + if (isNullOrUndefined(data)) { + data = {}; + } + data.bookmarkName = element.name; + data.type = 'Bookmark'; elementLength = element.length; } else if (element instanceof TextElementBox) { insertedText = element.text; elementLength = element.length; - Data = this.markerData.pop(); + if (element.revisions.length > 0) { + data = this.owner.editorModule.getRevisionMarkerData(data, element.revisions[0]); + } } if (!(element instanceof BookmarkElementBox)) { - let characterData: any = this.owner.sfdtExportModule.writeCharacterFormat(element.characterFormat, 0); + let characterData: any = {}; + HelperMethods.writeCharacterFormat(characterData, true, element.characterFormat, undefined, true); characterFormat = JSON.stringify(characterData); } let operation: Operation = { @@ -2662,12 +2689,12 @@ export class BaseHistoryInfo { type: type, text: insertedText, length: elementLength, - markerData: Data, + markerData: data, format: characterFormat } operations.push(operation); elementOffset += element.length; - Data = undefined; + data = undefined; type = undefined; characterFormat = undefined; if (element instanceof FieldElementBox && element.fieldType === 1) { @@ -2675,14 +2702,21 @@ export class BaseHistoryInfo { if (this.fieldBegin.formFieldData && element.nextNode instanceof BookmarkElementBox) { let elementBox: BookmarkElementBox = element.nextNode; insertedText = elementBox.bookmarkType === 0 ? CONTROL_CHARACTERS.Marker_Start : CONTROL_CHARACTERS.Marker_End; - Data = { 'bookmarkName': elementBox.name, 'type': 'Bookmark' }; + if (element.revisions.length > 0) { + data = this.owner.editorModule.getRevisionMarkerData(data, elementBox.revisions[0]); + } + if (isNullOrUndefined(data)) { + data = {}; + } + data.bookmarkName = elementBox.name; + data.type = 'Bookmark'; elementLength = elementBox.length; let operation: Operation = { action: 'Insert', offset: elementOffset, text: insertedText, length: elementLength, - markerData: Data + markerData: data }; operations.push(operation); } @@ -2705,7 +2739,7 @@ export class BaseHistoryInfo { paraEnd.offset = endPosition.offset - 1; let isParaSelected: boolean = startPosition.isAtParagraphStart && paraEnd.isAtParagraphEnd; if (isParaSelected && (!startPosition.currentWidget.paragraph.isInsideTable)) { - operations.push(this.getInsertOperation('Enter')); + operations.push(this.getInsertOperation('Enter', false, true)); operations.push(this.getUndoRedoOperation(action)); } else if (startPosition.paragraph == endPosition.paragraph) { if (startPosition.isAtSamePosition(endPosition)) { @@ -2975,7 +3009,7 @@ export class BaseHistoryInfo { * @private * @returns {Operation} */ - public getInsertOperation(action: Action, setEndIndex?: boolean): Operation { + public getInsertOperation(action: Action, setEndIndex?: boolean, skipMarkerData?: boolean): Operation { let insertedText: string = action === 'Enter' ? '\n' : this.insertedText; let length: number; if (action === 'InsertTable' || action === 'InsertTableBelow' || action === 'InsertRowAbove' || action === 'InsertRowBelow' @@ -2999,7 +3033,7 @@ export class BaseHistoryInfo { imageData: this.insertedData, format: this.format, } - if (!isNullOrUndefined(this.markerData)) { + if (!isNullOrUndefined(this.markerData) && !skipMarkerData) { operation.markerData = this.markerData.pop(); } if (this.insertedElement instanceof FootnoteElementBox) { @@ -3084,21 +3118,22 @@ export class BaseHistoryInfo { this.insertIndex -= 1; } } + if (this.insertedNodes.length > 1 && action === 'InsertTable') { + let enterOperation: Operation = this.getInsertOperation('Enter', false, true); + if (isNullOrUndefined(enterOperation.markerData)) { + enterOperation.markerData = {}; + } + enterOperation.markerData.isSkipTracking = true; + operations.push(enterOperation); + } for (let i: number = 0; i < this.insertedNodes.length; i++) { - if (this.insertedNodes[i] instanceof ParagraphWidget && action === 'InsertTable') { - let enterOperation: Operation = this.getInsertOperation('Enter'); - if (isNullOrUndefined(enterOperation.markerData)) { - enterOperation.markerData = {}; - } - enterOperation.markerData.isSkipTracking = true; - operations.push(enterOperation); - } else if (this.insertedNodes[i] instanceof TableWidget) { + if (this.insertedNodes[i] instanceof TableWidget) { let tableWidget: TableWidget = (this.insertedNodes[i] as TableWidget).combineWidget(this.owner.viewer) as TableWidget; this.tableRelatedLength = action === 'InsertTableBelow' ? 0 : 1; this.insertedText = CONTROL_CHARACTERS.Table; let tableFormat: any = this.owner.sfdtExportModule ? this.owner.sfdtExportModule.writeTableFormat(tableWidget.tableFormat, 0) : {}; this.format = JSON.stringify(tableFormat); - operations.push(this.getInsertOperation(action)); + operations.push(this.getInsertOperation(action, false, true)); for (let j: number = 0; j < tableWidget.childWidgets.length; j++) { let row: TableRowWidget = tableWidget.childWidgets[j] as TableRowWidget; operations.push(this.buildRowOperation(row, action)); @@ -3234,7 +3269,7 @@ export class BaseHistoryInfo { this.owner.sfdtExportModule.assignRowFormat(rowFormat, row.rowFormat, 0); } this.format = JSON.stringify(rowFormat); - if(row.rowFormat.revisions.length > 0) { + if (action === 'InsertTable' && row.rowFormat.revisions.length > 0) { let revision: Revision = row.rowFormat.revisions[row.rowFormat.revisions.length - 1]; let lastRevision: MarkerInfo = this.markerData[this.markerData.length - 1]; if (!(!isNullOrUndefined(lastRevision) && lastRevision.revisionId === revision.revisionID)) { @@ -3244,7 +3279,6 @@ export class BaseHistoryInfo { this.tableRelatedLength = 1; let operation: Operation = this.getInsertOperation(action); this.format = undefined; - this.markerData = []; return operation } /** @@ -3270,7 +3304,7 @@ export class BaseHistoryInfo { this.type = 'CellFormat'; let cellFormat: any = !isNullOrUndefined(this.owner.sfdtExportModule) ? this.owner.sfdtExportModule.writeCellFormat(cell.cellFormat, 0) : {}; this.format = JSON.stringify(cellFormat); - operations.push(this.getInsertOperation(action)); + operations.push(this.getInsertOperation(action, false, true)); if (!isCellInserted) { return operations; } @@ -3278,12 +3312,12 @@ export class BaseHistoryInfo { this.type = 'ParagraphFormat'; let paragraphFormat: any = this.owner.sfdtExportModule.writeParagraphFormat((cell.childWidgets[0] as ParagraphWidget).paragraphFormat, 0, true); this.format = JSON.stringify(paragraphFormat); - operations.push(this.getInsertOperation(action)); + operations.push(this.getInsertOperation(action, false, true)); this.tableRelatedLength = 0; this.type = 'CharacterFormat'; let characterData: any = this.owner.sfdtExportModule.writeCharacterFormat((cell.childWidgets[0] as ParagraphWidget).characterFormat, 0, true); this.format = JSON.stringify(characterData); - operations.push(this.getInsertOperation(action)); + operations.push(this.getInsertOperation(action, false, true)); this.format = undefined; this.type = undefined; return operations; @@ -3389,13 +3423,13 @@ export class BaseHistoryInfo { * @private * @returns {Operation} */ - public getFormatOperation(element?: ElementBox, action?: string): Operation { + public getFormatOperation(element?: ElementBox, action?: string, skipIncrement?: boolean): Operation { if (this.startIndex > this.endIndex) { [this.startIndex, this.endIndex] = [this.endIndex, this.startIndex]; } let length: number = 0; - if (this.endIndex === this.startIndex && this.action !== 'StyleName' && this.action !== 'DeleteBookmark' && this.action !== 'RemoveEditRange' && this.action !== 'InsertHyperlink') { - if (this.action === 'BackSpace') { + if (this.endIndex === this.startIndex && !skipIncrement && this.action !== 'DeleteBookmark' && this.action !== 'RemoveEditRange' && this.action !== 'InsertHyperlink') { + if(this.action === 'BackSpace') { this.startIndex--; } else { this.endIndex++; @@ -3698,17 +3732,19 @@ export class BaseHistoryInfo { } let formatOperation: Operation; if (action === 'ListFormat') { - formatOperation = this.getFormatOperation(undefined, action); + formatOperation = this.getFormatOperation(undefined, undefined, true); + formatOperation.type = 'ListFormat'; this.createListFormat(action, formatOperation); } else { - formatOperation = this.getFormatOperation(); + formatOperation = this.getFormatOperation(undefined, undefined, true); } operations.push(formatOperation); } } else { let operation: Operation; if (action === 'ListFormat') { - operation = this.getFormatOperation(undefined, action); + operation = this.getFormatOperation(undefined, undefined, true); + operation.type = 'ListFormat'; this.createListFormat(action, operation); } else { if (start.paragraph.isInsideTable && isCell) { @@ -3718,7 +3754,7 @@ export class BaseHistoryInfo { this.endIndex = this.startIndex + length; this.writeBorderFormat(isBorder, isShading, start.paragraph.associatedCell); } - operation = this.getFormatOperation(); + operation = this.getFormatOperation(undefined, undefined, true); } operations.push(operation); } diff --git a/controls/documenteditor/src/document-editor/implementation/editor-history/history-info.ts b/controls/documenteditor/src/document-editor/implementation/editor-history/history-info.ts index f6f87e4b17..97a121586c 100644 --- a/controls/documenteditor/src/document-editor/implementation/editor-history/history-info.ts +++ b/controls/documenteditor/src/document-editor/implementation/editor-history/history-info.ts @@ -119,8 +119,17 @@ export class HistoryInfo extends BaseHistoryInfo { } } else { if (this.editorHistory.isUndoing) { - for (let i: number = 0; i < 3; i++) { - operations.push(this.getDeleteOperation('Delete')); + for (let i: number = 0; i < this.modifiedActions.length; i++) { + let currentHistory = this.modifiedActions[parseInt(i.toString(), 10)]; + currentHistory.endIndex = currentHistory.startIndex; + //Basically for pagebreak and column break there will three paragraph difference. So for transformation we sended three backspace operation. + operations.push(currentHistory.getDeleteOperation('Delete')); + operations.push(currentHistory.getDeleteOperation('Delete')); + operations.push(currentHistory.getDeleteOperation('Delete')); + if (currentHistory.isRemovedNodes) { + let operationCollection: Operation[] = currentHistory.getDeleteContent('BackSpace'); + operations = [...operations, ...operationCollection]; + } } } else { for (let i: number = 0; i < this.modifiedActions.length; i++) { @@ -129,9 +138,12 @@ export class HistoryInfo extends BaseHistoryInfo { operations.push(currentHistory.getDeleteOperation(action)); } } - operations.push(this.getInsertOperation('Enter')); + let operation: Operation = this.getInsertOperation('Enter'); + operation.markerData = { skipOperation: true }; + //Basically for pagebreak and column break there will three paragraph difference. So for transformation we sended three insert operation. + operations.push(operation); operations.push(this.getInsertOperation(action)); - operations.push(this.getInsertOperation('Enter')); + operations.push(operation); } } break; diff --git a/controls/documenteditor/src/document-editor/implementation/editor/editor.ts b/controls/documenteditor/src/document-editor/implementation/editor/editor.ts index 48bd1250d8..ecc798ae80 100644 --- a/controls/documenteditor/src/document-editor/implementation/editor/editor.ts +++ b/controls/documenteditor/src/document-editor/implementation/editor/editor.ts @@ -238,6 +238,14 @@ export class Editor { public isXmlMapped: boolean = false; private isAutoList: boolean = false; private combineLastBlock: boolean = false; + /** + * @private + */ + public remotePasteRevision: Revision[] = []; + /** + * @private + */ + public isFieldOperation: boolean = false; /** * @private * @returns {boolean} - Returns the restrict formatting @@ -2493,7 +2501,7 @@ export class Editor { this.editorHistory.updateHistory(); this.editorHistory.updateComplexHistory(); } - if (isNullOrUndefined(revisionType) || revisionType === 'Insertion') { + if ((isNullOrUndefined(revisionType) || revisionType === 'Insertion') && !this.isFieldOperation) { this.reLayout(selection); } this.documentHelper.isTextInput = false; @@ -2700,8 +2708,8 @@ export class Editor { let currentRevision: Revision = revisions[i]; if (!this.isRevisionAlreadyIn(elementToInclude, currentRevision) || elementToInclude instanceof WCharacterFormat) { elementToInclude.revisions.splice(0, 0, currentRevision); - if (this.editorHistory && !isNullOrUndefined(this.editorHistory.currentBaseHistoryInfo)) { - // this.editorHistory.currentBaseHistoryInfo.markerData.push(this.getMarkerData(undefined, undefined, currentRevision)); + if (this.editorHistory && !isNullOrUndefined(this.editorHistory.currentBaseHistoryInfo) && this.editorHistory.currentBaseHistoryInfo.markerData.length === 0) { + this.editorHistory.currentBaseHistoryInfo.markerData.push(this.getMarkerData(undefined, undefined, currentRevision)); } if (elementToInclude instanceof FootnoteElementBox) { this.insertRevisionForFootnoteWidget(elementToInclude, currentRevision); @@ -3091,7 +3099,11 @@ export class Editor { item.revisions.push(revision); revision.range.push(item); } - this.updateRevisionCollection(revision); + if (this.isRemoteAction && this.documentHelper.owner.parser.isPaste) { + this.remotePasteRevision.push(revision); + } else { + this.updateRevisionCollection(revision); + } return revision; } @@ -3448,6 +3460,13 @@ export class Editor { return lastPara; } + private updatePasteRevision(): void { + for (let i: number = 0; i < this.remotePasteRevision.length; i++) { + this.updateRevisionCollection(this.remotePasteRevision[i]); + } + this.remotePasteRevision = []; + } + private updateRevisionCollection(revision: Revision): void { let isInserted: boolean = false; let paraIndex: TextPosition = undefined; @@ -3529,17 +3548,6 @@ export class Editor { } } this.documentHelper.updateAuthorIdentity(); - } else { - if (this.isRemoteAction) { - if (this.owner.trackChangesPane.revisions.indexOf(revision) < 0) { - let currentChangeView: ChangesSingleView = new ChangesSingleView(this.owner, this.owner.trackChangesPane); - this.owner.trackChangesPane.changesInfoDiv.appendChild(currentChangeView.createSingleChangesDiv(revision)); - this.owner.trackChangesPane.revisions.push(revision); - this.owner.trackChangesPane.changes.add(revision, currentChangeView); - this.owner.trackChangesPane.renderedChanges.add(revision, currentChangeView); - } - } - this.documentHelper.updateAuthorIdentity(); } } /** @@ -4444,7 +4452,7 @@ export class Editor { if (selection.start.paragraph.isEmpty()) { return selection.start.paragraph.characterFormat; } else { - let info: ElementInfo = selection.start.currentWidget.getInline(selection.start.offset, 0); + let info: ElementInfo = selection.start.currentWidget.getInline(selection.start.offset + 1, 0); return info.element.characterFormat; } } @@ -5329,7 +5337,11 @@ export class Editor { if (!isNullOrUndefined(parser.revisionCollection)) { parser.revisionCollection = undefined; } - parser.revisionCollection = new Dictionary(); + if (this.isRemoteAction) { + parser.revisionCollection = this.documentHelper.revisionsInternal; + } else { + parser.revisionCollection = new Dictionary(); + } let revisionCollection: Dictionary = parser.revisionCollection; if (!(this.documentHelper.owner.sfdtExportModule.copyWithTrackChange && parser.isCutPerformed)) { if (pasteContent[revisionsProperty[this.keywordIndex]].length >= 1) { @@ -5343,7 +5355,7 @@ export class Editor { } } } - if (revisionCheck) { + if (revisionCheck && !this.isRemoteAction) { let revision: Revision = revisionCollection.get(pasteContent[revisionsProperty[this.keywordIndex]][i][revisionIdProperty[this.keywordIndex]]); revisionChanges.push(revision); } @@ -5658,6 +5670,10 @@ export class Editor { this.documentHelper.owner.parser.parseImages(images); } this.pasteContentsInternal(this.getBlocks(content, true), true, currentFormat); + if (this.isRemoteAction) { + this.updatePasteRevision(); + this.owner.trackChangesPane.updateTrackChanges(); + } if (content[commentsProperty[this.keywordIndex]] && content[commentsProperty[this.keywordIndex]].length > 0) { this.documentHelper.layout.layoutComments(this.documentHelper.comments); } @@ -5728,7 +5744,7 @@ export class Editor { //if (!this.isSkipHistory) { this.initHistory('Paste'); //} - if (!selection.isEmpty || this.documentHelper.isListTextSelected) { + if ((!selection.isEmpty && (!this.owner.documentHelper.isDragging || !this.owner.selection.isImageSelected)) || this.documentHelper.isListTextSelected) { isRemoved = this.removeSelectedContentInternal(selection, selection.start, selection.end); } if (isRemoved) { @@ -6218,7 +6234,7 @@ export class Editor { this.owner.enableLocalPaste? isNullOrUndefined((widget as ParagraphWidget).isCreatedUsingHtmlSpanTag)? false: !(widget as ParagraphWidget).isCreatedUsingHtmlSpanTag : (widget as ParagraphWidget).isCreatedUsingHtmlSpanTag); - if (widget instanceof ParagraphWidget && (isPara || (j === widgets.length - 1 && (this.isInsertingTOC || isConsiderLastBlock || this.owner.documentHelper.isDragging))) + if (widget instanceof ParagraphWidget && (isPara || (j === widgets.length - 1 && (this.isInsertingTOC || isConsiderLastBlock || this.owner.documentHelper.isDragging || this.isRemoteAction))) && (!isNullOrUndefined(widget.paragraphFormat.listFormat) && isNullOrUndefined(widget.paragraphFormat.listFormat.list) && widget.paragraphFormat.listFormat.listId === -1)) { @@ -6388,17 +6404,23 @@ export class Editor { } else { owner = table.containerWidget; } - + if (table.isInsideTable) { + owner = owner.combineWidget(this.owner.viewer); + } //remove old table revisions if it is present. this.constructRevisionsForTable(table, false); if (!skipRemoving) { this.removeBlock(table, true); } this.removeRevisionFromTable(table); - //Inserts table in the current table position. + if (owner instanceof TableCellWidget) { + owner = owner.combineWidget(this.owner.viewer); + } else { + let curretBlock: BlockWidget = this.documentHelper.layout.checkAndGetBlock(owner, insertIndex); + insertIndex = owner.childWidgets.indexOf(curretBlock); + } + //Inserts table in the current table position. let blockAdvCollection: IWidget[] = owner.childWidgets; - let curretBlock: BlockWidget = this.documentHelper.layout.checkAndGetBlock(owner, insertIndex); - insertIndex = owner.childWidgets.indexOf(curretBlock); blockAdvCollection.splice(insertIndex, 0, newTable); newTable.index = table.index; table.containerWidget = undefined; @@ -6784,6 +6806,25 @@ export class Editor { } if (this.isPaste) { + if (this.isRemoteAction) { + let revision: Revision[] = (paragraphFormat.ownerBase as ParagraphWidget).characterFormat.revisions; + let isBreak: boolean = false; + for (let i: number = 0; i < revision.length; i++) { + paragraph.characterFormat.revisions.push(revision[i]); + let range: object[] = revision[i].range; + for (let j: number = 0; j < range.length; j++) { + if (range[j] instanceof WCharacterFormat && (range[j] as WCharacterFormat) == (paragraphFormat.ownerBase as ParagraphWidget).characterFormat) { + range.splice(j, 1); + range.push(paragraph.characterFormat); + isBreak = true; + break; + } + } + if (isBreak) { + break; + } + } + } this.viewer.updateClientAreaForBlock(paragraph, true); paragraph.x = this.viewer.clientActiveArea.x; } @@ -10562,7 +10603,7 @@ export class Editor { this.insertRevision(currentElement, 'Insertion'); } } - if (isNullOrUndefined(value)) { + if (isNullOrUndefined(value) && isNullOrUndefined(property)) { format.clearFormat(); if (!isNullOrUndefined(this.editorHistory) && !isNullOrUndefined(this.editorHistory.currentBaseHistoryInfo)) { this.editorHistory.currentBaseHistoryInfo.insertedFormat = format[property]; @@ -15054,7 +15095,7 @@ export class Editor { startListLevel = this.documentHelper.layout.getListLevel(tempList, levelNumber); if (levelNumber > 0) { initialListLevel = this.documentHelper.layout.getListLevel(tempList, 0); - isSameList = !isNullOrUndefined(initialListLevel) && levelNumber > 0 && selection.start.isInSameParagraph(selection.end); + isSameList = !isNullOrUndefined(initialListLevel) && levelNumber > 0 && selection.start.isInSameListParagraph(selection.end); } let abstractList: WAbstractList = tempList.abstractList; if (!abstractList) { @@ -15114,7 +15155,7 @@ export class Editor { selection.paragraphFormat.listLevelNumber = 0; } selection.paragraphFormat.setList(list); - } else if (isSameList && !isNullOrUndefined(list)) { + } else if (isSameList && !isNullOrUndefined(list) && !isUpdate) { let tempList: WList = this.documentHelper.getListById(currentParagraph.paragraphFormat.listFormat.listId); let listLevel: WListLevel = this.documentHelper.layout.getListLevel(tempList, levelNumber); if (listLevelPattern === 'Bullet') { @@ -15261,11 +15302,16 @@ export class Editor { let commentStartToInsert = this.checkAndRemoveComments(); this.initHistory('Enter'); let isRemoved: boolean = true; - if (!selection.isEmpty) { + if (!selection.isEmpty && !selection.isImageSelected) { // this.initHistoryWithSelection(selection, 'Enter'); isRemoved = this.removeSelectedContents(selection); } if (isRemoved) { + if (selection.isImageSelected && !selection.isForward) { + let start = selection.start; + selection.start = selection.end; + selection.end = start; + } selection.owner.isShiftingEnabled = true; this.updateInsertPosition(); let blockInfo: ParagraphInfo = this.selection.getParagraphInfo(selection.start); @@ -17746,7 +17792,7 @@ export class Editor { } else if (elementBox instanceof FootnoteElementBox) { this.editorHistory.currentBaseHistoryInfo.insertedText = CONTROL_CHARACTERS.Marker_Start; this.editorHistory.currentBaseHistoryInfo.markerData.push(this.getMarkerData(elementBox)); - } else if (elementBox instanceof BookmarkElementBox || elementBox instanceof EditRangeStartElementBox || elementBox instanceof EditRangeEndElementBox || elementBox instanceof FieldElementBox || elementBox instanceof TextElementBox) { + } else if (elementBox instanceof BookmarkElementBox || elementBox instanceof EditRangeStartElementBox || elementBox instanceof EditRangeEndElementBox || elementBox instanceof FieldElementBox || elementBox instanceof TextElementBox && elementBox.removedIds.length == 0) { this.editorHistory.currentBaseHistoryInfo.markerData.push(this.getMarkerData(elementBox, this.editorHistory.isUndoing)); if (elementBox instanceof FieldElementBox && elementBox.fieldType === 0) { this.editorHistory.currentBaseHistoryInfo.fieldBegin = elementBox; @@ -18697,18 +18743,26 @@ export class Editor { } } if (this.owner.enableTrackChanges && !isNullOrUndefined(revision)) { - if (isNullOrUndefined(markerData)) { - markerData = {}; - } - markerData.revisionId = revision.revisionID; - markerData.revisionType = revision.revisionType; - markerData.author = revision.author; - markerData.date = revision.date; - markerData.skipOperation = skip; - markerData.isAcceptOrReject = isAcceptOrReject; + markerData = this.getRevisionMarkerData(markerData, revision, skip, isAcceptOrReject); } return markerData; } + /** + * @private + * @returns {any} + */ + public getRevisionMarkerData(markerData: any, revision: Revision, skip?: boolean, isAcceptOrReject?: string): any { + if (isNullOrUndefined(markerData)) { + markerData = {}; + } + markerData.revisionId = revision.revisionID; + markerData.revisionType = revision.revisionType; + markerData.author = revision.author; + markerData.date = revision.date; + markerData.skipOperation = skip; + markerData.isAcceptOrReject = isAcceptOrReject; + return markerData; + } /** * @param index * @private diff --git a/controls/documenteditor/src/document-editor/implementation/editor/image-resizer.ts b/controls/documenteditor/src/document-editor/implementation/editor/image-resizer.ts index 8aef2a842c..fb4de2c347 100644 --- a/controls/documenteditor/src/document-editor/implementation/editor/image-resizer.ts +++ b/controls/documenteditor/src/document-editor/implementation/editor/image-resizer.ts @@ -1095,7 +1095,7 @@ export class ImageResizer { this.currentImageElementBox.height = parseFloat(this.imageResizerDiv.style.height) / this.documentHelper.zoomFactor; this.owner.isShiftingEnabled = true; this.owner.editorModule.setOffsetValue(this.owner.selectionModule); - this.documentHelper.layout.reLayoutParagraph(this.currentImageElementBox.line.paragraph, 0, 0); + this.documentHelper.layout.layoutBodyWidgetCollection(this.currentImageElementBox.line.paragraph.index, this.currentImageElementBox.line.paragraph.containerWidget, this.currentImageElementBox.line.paragraph, false); this.updateHistoryForImageResizer(); this.owner.editorModule.reLayout(this.owner.selectionModule, true); this.viewer.updateScrollBars(); diff --git a/controls/documenteditor/src/document-editor/implementation/format/style.ts b/controls/documenteditor/src/document-editor/implementation/format/style.ts index 3ac3ea4a7c..36d74295e2 100644 --- a/controls/documenteditor/src/document-editor/implementation/format/style.ts +++ b/controls/documenteditor/src/document-editor/implementation/format/style.ts @@ -124,6 +124,26 @@ export class WCharacterStyle extends WStyle { this.characterFormat.copyFormat(charStyle.characterFormat); } } +/** + * @private + */ +export class WTableStyle extends WStyle { + public constructor(node?: Object) { + super(); + this.ownerBase = node; + } + /** + * Disposes the internal objects which are maintained. + * @private + */ + public destroy(): void { + this.ownerBase = undefined; + this.name = undefined; + this.next = undefined; + this.basedOn = undefined; + this.link = undefined; + } +} /** * @private */ @@ -160,7 +180,7 @@ export class WStyles { let style: Object = this.collection[parseInt(i.toString(), 10)]; if (style instanceof WCharacterStyle) { (style as WCharacterStyle).clear(); - } else { + } else if (style instanceof WParagraphStyle) { (style as WParagraphStyle).clear(); } } @@ -195,12 +215,14 @@ export class WStyles { for (const style of styles) { const returnStyle: any = {}; const returnStyleObject: any = {}; - if(type == "Paragraph") { + if (type == "Paragraph") { returnStyleObject.paragraphFormat = {}; HelperMethods.writeParagraphFormat(returnStyleObject.paragraphFormat, true, (style as any).paragraphFormat); } - returnStyleObject.characterFormat = {}; - HelperMethods.writeCharacterFormat(returnStyleObject.characterFormat, true, (style as any).characterFormat); + if (type !== "Table") { + returnStyleObject.characterFormat = {}; + HelperMethods.writeCharacterFormat(returnStyleObject.characterFormat, true, (style as any).characterFormat); + } returnStyle.name = style.name; returnStyle.style = JSON.stringify(returnStyleObject); if (!isNullOrUndefined(type)) { @@ -224,8 +246,10 @@ export class WStyles { let style: Object = this.collection[parseInt(i.toString(), 10)]; if (style instanceof WCharacterStyle) { (style as WCharacterStyle).destroy(); - } else { + } else if (style instanceof WParagraphStyle) { (style as WParagraphStyle).destroy(); + } else if (style instanceof WTableStyle) { + (style as WTableStyle).destroy(); } } } diff --git a/controls/documenteditor/src/document-editor/implementation/selection/selection-helper.ts b/controls/documenteditor/src/document-editor/implementation/selection/selection-helper.ts index 5de677dd3f..743770092e 100644 --- a/controls/documenteditor/src/document-editor/implementation/selection/selection-helper.ts +++ b/controls/documenteditor/src/document-editor/implementation/selection/selection-helper.ts @@ -325,6 +325,17 @@ export class TextPosition { } return this.paragraph === textPosition.paragraph; } + /** + * Return true if start and end is in same list + * + * @private + */ + public isInSameListParagraph(textPosition: TextPosition): boolean { + if (isNullOrUndefined(textPosition)) { + throw new Error('textPosition is undefined.'); + } + return this.paragraph.paragraphFormat.listFormat.listId === textPosition.paragraph.paragraphFormat.listFormat.listId; + } /** * Return true is current text position exist before given text position * diff --git a/controls/documenteditor/src/document-editor/implementation/selection/selection.ts b/controls/documenteditor/src/document-editor/implementation/selection/selection.ts index e03613cf00..e5ddb443b1 100644 --- a/controls/documenteditor/src/document-editor/implementation/selection/selection.ts +++ b/controls/documenteditor/src/document-editor/implementation/selection/selection.ts @@ -6805,6 +6805,9 @@ export class Selection { this.extendBackward(); } } else { + if (!isNullOrUndefined(this.owner.imageResizerModule)) { + this.owner.imageResizerModule.selectedImageWidget.clear(); + } this.selectInternal(widget, element, index, caretPosition); } } @@ -11788,7 +11791,6 @@ export class Selection { } } offset += element.length; - } } } diff --git a/controls/documenteditor/src/document-editor/implementation/viewer/layout.ts b/controls/documenteditor/src/document-editor/implementation/viewer/layout.ts index 3fd92f5168..af5b342743 100644 --- a/controls/documenteditor/src/document-editor/implementation/viewer/layout.ts +++ b/controls/documenteditor/src/document-editor/implementation/viewer/layout.ts @@ -2055,8 +2055,8 @@ export class Layout { } if (isNullOrUndefined(bookmrkElement.properties)) { if (!isNullOrUndefined(this.documentHelper.selection) && this.documentHelper.selection.isRenderBookmarkAtEnd(bookmrkElement.reference)) { - var row = bookmrkElement.reference.paragraph.associatedCell.ownerRow; - row.isRenderBookmarkEnd = true; + let cell: TableCellWidget = bookmrkElement.reference.paragraph.associatedCell; + cell.isRenderBookmarkEnd = true; } } else{ if (!isNullOrUndefined(element.paragraph.associatedCell)) { @@ -2076,7 +2076,7 @@ export class Layout { } } if (!isNullOrUndefined(endCell)) { - endCell.isRenderBookmarkEnd = true; + endRow.isRenderBookmarkEnd = true; } } } @@ -6937,7 +6937,7 @@ export class Layout { if (!isRowSpanEnd) { if (this.isVerticalMergedCellContinue(row) && (tableRowWidget.y === viewer.clientArea.y || tableRowWidget.y === this.viewer.clientArea.y + tableRowWidget.ownerTable.headerHeight)) { - this.insertSplittedCellWidgets(viewer, tableWidgets, tableRowWidget, tableRowWidget.indexInOwner - 1); + this.insertSplittedCellWidgets(viewer, tableWidgets, tableRowWidget, tableRowWidget.index - 1); } this.addWidgetToTable(viewer, tableWidgets, rowWidgets, tableRowWidget, footnoteElements); continue; @@ -6989,6 +6989,13 @@ export class Layout { if (isNullOrUndefined(splittedWidget) && tableRowWidget.y === viewer.clientArea.y) { this.addWidgetToTable(viewer, tableWidgets, rowWidgets, tableRowWidget, footnoteElements); } + } else if (heightType === 'AtLeast' && HelperMethods.convertPointToPixel(row.rowFormat.height) > viewer.clientArea.bottom && tableRowWidget.ownerTable.wrapTextAround && tableRowWidget.y - HelperMethods.convertPointToPixel(tableRowWidget.ownerTable.positioning.verticalPosition) === viewer.clientArea.y && tableRowWidget.bodyWidget.firstChild === tableRowWidget.ownerTable) { + splittedWidget = this.splitWidgets(tableRowWidget, viewer, tableWidgets, rowWidgets, splittedWidget, isLastRow, footnoteElements, lineIndexInCell, cellIndex, isMultiColumnSplit); + if (isNullOrUndefined(splittedWidget)) { + this.addWidgetToTable(viewer, tableWidgets, rowWidgets, tableRowWidget, footnoteElements); + count++; + continue; + } } } else if (heightType === 'Exactly' && tableRowWidget.y === viewer.clientArea.y) { this.addWidgetToTable(viewer, tableWidgets, rowWidgets, tableRowWidget, footnoteElements); @@ -7449,7 +7456,6 @@ export class Layout { if (!isNullOrUndefined(rowWidget)) { let left: number = rowWidget.x; let tableWidth: number = 0; - let cellIndex: number = 0; let cellspace: number = 0; let linestyle: boolean = false; tableWidth = HelperMethods.convertPointToPixel(rowWidget.ownerTable.tableHolder.tableWidth); @@ -7462,10 +7468,18 @@ export class Layout { i--; continue; } + // Bug 871725: Empty cell widget must be inserted if the table split into next page. + if (tableCollection.length == 1) { + break; + } const length: number = rowWidget.childWidgets.length; this.insertEmptySplittedCellWidget(rowWidget, tableCollection, left, i, previousRowIndex); if (length < rowWidget.childWidgets.length) { - if (i === cellIndex) { + if (isNullOrUndefined(rowWidget.previousRenderedWidget) || !(rowWidget.previousRenderedWidget instanceof TableRowWidget)) { + break; + } + let prevRowWidget: TableRowWidget = rowWidget.previousRenderedWidget as TableRowWidget; + if ((prevRowWidget.lastChild as TableCellWidget).columnIndex === (rowWidget.lastChild as TableCellWidget).columnIndex && (rowWidget.ownerTable.tableFormat.allowAutoFit ? (prevRowWidget.lastChild as TableCellWidget).cellFormat.columnSpan === 1 : true)) { break; } i--; @@ -7476,6 +7490,9 @@ export class Layout { if (cellspace > 0 || cellWidget.columnIndex === cellWidget.ownerTable.tableHolder.columns.length - 1 || cellWidget.index === (cellWidget.containerWidget as TableRowWidget).childWidgets.length - 1) { if (!cellWidget.ownerTable.tableFormat.allowAutoFit) { + const leftBorderWidth: number = HelperMethods.convertPointToPixel(TableCellWidget.getCellLeftBorder(cellWidget).getLineWidth()); + const rightBorderWidth: number = HelperMethods.convertPointToPixel(TableCellWidget.getCellRightBorder(cellWidget).getLineWidth()); + cellWidget.rightBorderWidth = !cellWidget.ownerTable.isBidiTable ? rightBorderWidth : leftBorderWidth; left += cellWidget.rightBorderWidth; } if (!this.isInsertTable()) { @@ -7483,7 +7500,6 @@ export class Layout { } } left -= (isRightStyleNone && !linestyle) ? 0 : (cellWidget.rightBorderWidth); - cellIndex++; if (i === rowWidget.childWidgets.length - 1 && Math.round(left) < Math.round(rowWidget.x + tableWidth)) { if (this.insertRowSpannedWidget(rowWidget, viewer, left, i + 1)) { continue; @@ -8944,8 +8960,7 @@ export class Layout { if (!isNullOrUndefined(table.previousWidget) || table.isInHeaderFooter || table.isInsideTable) { this.viewer.clientActiveArea = clientActiveAreaForTableWrap.clone(); this.viewer.clientArea = clientAreaForTableWrap.clone(); - let position: TablePosition = table.positioning; - if (this.viewer.clientActiveArea.height < table.height && table.width >= this.viewer.clientActiveArea.width && position.verticalAlignment == "None" && position.horizontalAlignment == "Left" && position.horizontalOrigin == "Margin" && position.verticalOrigin == "Margin" && position.horizontalPosition == 0 && position.verticalPosition <= 0) { + if (!table.isLayouted && this.viewer.clientActiveArea.height < table.height && table.width >= this.viewer.clientActiveArea.width) { this.moveBlocksToNextPage(table.previousWidget as BlockWidget, false); } } else { @@ -9536,7 +9551,7 @@ export class Layout { } // if (viewer instanceof PageLayoutViewer) { this.documentHelper.removeEmptyPages(); - this.updateFieldElements(); + this.updateFieldElements(reLayout); const firstPage = this.documentHelper.pages[0] if(firstPage.bodyWidgets[0].sectionIndex > 0) { let page = firstPage; @@ -9578,26 +9593,33 @@ export class Layout { } } - public updateFieldElements(): void { + public updateFieldElements(reLayout?: boolean): void { for (let i: number = 0; i < this.documentHelper.fields.length; i++) { const fieldBegin: FieldElementBox = this.documentHelper.fields[i]; if(this.viewer instanceof PageLayoutViewer || (this.viewer instanceof WebLayoutViewer && !(fieldBegin.line.paragraph.bodyWidget instanceof HeaderFooterWidget))){ if (!isNullOrUndefined(this.documentHelper.selection)) { const fieldCode: string = this.documentHelper.selection.getFieldCode(fieldBegin); - if (!isNullOrUndefined(fieldCode) && (fieldCode.toLowerCase().match('numpages') || fieldCode.toLowerCase().match('sectionpages')) && !isNullOrUndefined(fieldBegin.fieldSeparator)) { + const regex: RegExp = /^(?!.*\bhyperlink\b).*\bpage\b.*$/; + if (!isNullOrUndefined(fieldCode) && (fieldCode.toLowerCase().match('numpages') || fieldCode.toLowerCase().match('sectionpages') || (regex.test(fieldCode.toLowerCase()) && reLayout)) && !isNullOrUndefined(fieldBegin.fieldSeparator)) { const textElement: FieldTextElementBox = fieldBegin.fieldSeparator.nextNode as FieldTextElementBox; if (!isNullOrUndefined(textElement)) { const prevPageNum: string = textElement.text; - textElement.text = this.documentHelper.pages.length.toString(); const paragraph: ParagraphWidget = fieldBegin.line.paragraph; - if (!isNullOrUndefined(paragraph.bodyWidget) && !isNullOrUndefined(paragraph.bodyWidget.page) && paragraph.bodyWidget.page.index !== -1 - && prevPageNum !== textElement.text) { - const lineIndex: number = paragraph.childWidgets.indexOf(fieldBegin.line); - const elementIndex: number = fieldBegin.line.children.indexOf(textElement); - if (paragraph.isInsideTable) { - this.reLayoutParagraph(paragraph, lineIndex, elementIndex); + if (!isNullOrUndefined(paragraph.bodyWidget) && !isNullOrUndefined(paragraph.bodyWidget.page) && paragraph.bodyWidget.page.index !== -1) { + if (regex.test(fieldCode.toLowerCase())) { + let index: number = paragraph.bodyWidget.page.index + 1; + textElement.text = index.toString(); } else { - this.reLayoutLine(paragraph, lineIndex, false, false, true); + textElement.text = this.documentHelper.pages.length.toString(); + } + if (prevPageNum !== textElement.text) { + const lineIndex: number = paragraph.childWidgets.indexOf(fieldBegin.line); + const elementIndex: number = fieldBegin.line.children.indexOf(textElement); + if (paragraph.isInsideTable) { + this.reLayoutParagraph(paragraph, lineIndex, elementIndex); + } else { + this.reLayoutLine(paragraph, lineIndex, false, false, true); + } } } } diff --git a/controls/documenteditor/src/document-editor/implementation/viewer/page.ts b/controls/documenteditor/src/document-editor/implementation/viewer/page.ts index 58197a88b1..aef042cbcb 100644 --- a/controls/documenteditor/src/document-editor/implementation/viewer/page.ts +++ b/controls/documenteditor/src/document-editor/implementation/viewer/page.ts @@ -4746,7 +4746,7 @@ export abstract class ElementBox { * @private */ public contentControlProperties: ContentControlProperties; - + /** * @private */ diff --git a/controls/documenteditor/src/document-editor/implementation/viewer/render.ts b/controls/documenteditor/src/document-editor/implementation/viewer/render.ts index f3cf4e2d0d..8b2671c06a 100644 --- a/controls/documenteditor/src/document-editor/implementation/viewer/render.ts +++ b/controls/documenteditor/src/document-editor/implementation/viewer/render.ts @@ -865,12 +865,7 @@ export class Renderer { let lastPara: ParagraphWidget = this.documentHelper.selection.getLastParagraph(widget) as ParagraphWidget; let lastLine: LineWidget = lastPara.lastChild as LineWidget; let position: Point = this.documentHelper.selection.getEndPosition(lastPara); - if (this.documentHelper.owner.documentEditorSettings.showHiddenMarks && !this.isPrinting) { - let xLeft = this.documentHelper.textHelper.getWidth(String.fromCharCode(164), lastLine.paragraph.characterFormat) + position.x; - this.renderBookmark(this.getScaledValue(xLeft, 1), this.getScaledValue(position.y, 2), this.getScaledValue(lastLine.height - lastLine.margin.bottom), 1); - } else { - this.renderBookmark(this.getScaledValue(position.x, 1), this.getScaledValue(position.y, 2), this.getScaledValue(lastLine.height - lastLine.margin.bottom), 1); - } + this.renderBookmark(this.getScaledValue(position.x, 1), this.getScaledValue(position.y, 2), this.getScaledValue(lastLine.height - lastLine.margin.bottom), 1); } } } @@ -2052,6 +2047,7 @@ export class Renderer { tabString = '-'; break; case 'Underscore': + case 'Single': tabString = '_'; break; } diff --git a/controls/documenteditor/src/document-editor/implementation/viewer/sfdt-reader.ts b/controls/documenteditor/src/document-editor/implementation/viewer/sfdt-reader.ts index 772bac825b..fe97b498fc 100644 --- a/controls/documenteditor/src/document-editor/implementation/viewer/sfdt-reader.ts +++ b/controls/documenteditor/src/document-editor/implementation/viewer/sfdt-reader.ts @@ -5,7 +5,7 @@ import { WListLevel } from '../list/list-level'; import { WAbstractList } from '../list/abstract-list'; import { WLevelOverride } from '../list/level-override'; import { WCharacterFormat, WListFormat, WParagraphFormat, WCellFormat, WTableFormat, WSectionFormat, WRowFormat, WColumnFormat } from '../format/index'; -import { WBorder, WBorders, WShading, WCharacterStyle, WParagraphStyle, WStyles, WStyle, WTabStop } from '../format/index'; +import { WBorder, WBorders, WShading, WCharacterStyle, WParagraphStyle, WStyles, WStyle, WTabStop, WTableStyle } from '../format/index'; import { LayoutViewer, DocumentHelper } from './viewer'; import { Widget, LineWidget, ParagraphWidget, ImageElementBox, BodyWidget, TextElementBox, TableCellWidget, @@ -310,7 +310,7 @@ export class SfdtReader { revisionCheck = false; } } - if (revisionCheck) { + if (revisionCheck && !this.documentHelper.owner.editorModule.isRemoteAction) { revisions.push(revision); } } @@ -341,6 +341,9 @@ export class SfdtReader { revision.range.push(item); } item.revisions.push(revision); + if (this.isPaste && this.documentHelper.owner.editorModule.isRemoteAction && item instanceof WRowFormat) { + this.documentHelper.owner.editorModule.remotePasteRevision.push(revision); + } } } } @@ -446,6 +449,10 @@ export class SfdtReader { wStyle = new WCharacterStyle(); wStyle.type = 'Character'; } + if (this.getStyleType(style[typeProperty[this.keywordIndex]]) === 'Table') { + wStyle = new WTableStyle(); + wStyle.type = 'Table'; + } if (!isNullOrUndefined(style[nameProperty[this.keywordIndex]])) { wStyle.name = style[nameProperty[this.keywordIndex]]; } @@ -469,7 +476,7 @@ export class SfdtReader { } else { if (wStyle.type === 'Paragraph') { styleString = JSON.parse('{"type":"Paragraph","name":"Normal","next":"Normal"}'); - } else { + } else if (wStyle.type === 'Character') { styleString = JSON.parse('{"type": "Character","name": "Default Paragraph Font"}'); } } @@ -549,7 +556,7 @@ export class SfdtReader { if (!isNullOrUndefined(resetKeyIndex) && resetKeyIndex) { this.keywordIndex = keyIndex; } - if(!isNullOrUndefined(wStyle)) { + if (!isNullOrUndefined(wStyle) && wStyle.type !== 'Table') { this.documentHelper.addToStylesMap(wStyle); } } @@ -733,7 +740,7 @@ export class SfdtReader { if (block[inlinesProperty[this.keywordIndex]].length > 0) { hasValidElmts = this.parseParagraph(block[inlinesProperty[this.keywordIndex]], paragraph, writeInlineFormat, undefined, isFootnoteEndnote && i === 0); if (block.hasOwnProperty(isCreatedUsingHtmlSpanTagProperty[this.keywordIndex])) { - paragraph.isCreatedUsingHtmlSpanTag = block[isCreatedUsingHtmlSpanTagProperty[this.keywordIndex]]; + paragraph.isCreatedUsingHtmlSpanTag = HelperMethods.parseBoolValue(block[isCreatedUsingHtmlSpanTagProperty[this.keywordIndex]]); } } if (!(isSectionBreak && block === data[data.length - 1] && block[inlinesProperty[this.keywordIndex]].length === 0 && !hasValidElmts)) { @@ -1102,7 +1109,7 @@ export class SfdtReader { isContentControl = true; } if (data.hasOwnProperty(isCreatedUsingHtmlSpanTagProperty[this.keywordIndex])) { - paragraph.isCreatedUsingHtmlSpanTag = data[isCreatedUsingHtmlSpanTagProperty[this.keywordIndex]]; + paragraph.isCreatedUsingHtmlSpanTag = HelperMethods.parseBoolValue(data[isCreatedUsingHtmlSpanTagProperty[this.keywordIndex]]); } let hasValidElmts: boolean = false; let revision: Revision; @@ -2913,6 +2920,8 @@ export class SfdtReader { return 'Paragraph'; case 1: return 'Character'; + case 2: + return 'Table'; default: return styleType as StyleType; } diff --git a/controls/documenteditor/src/document-editor/implementation/viewer/viewer.ts b/controls/documenteditor/src/document-editor/implementation/viewer/viewer.ts index c04dbeb10b..b451e639d3 100644 --- a/controls/documenteditor/src/document-editor/implementation/viewer/viewer.ts +++ b/controls/documenteditor/src/document-editor/implementation/viewer/viewer.ts @@ -2400,7 +2400,7 @@ export class DocumentHelper { if (isNullOrUndefined(formField)) { formField = this.selection.getCurrentFormField(); } - if (!this.isDocumentProtected && this.owner.enableFormField) { + if (!this.isDocumentProtected && this.owner.enableFormField && !this.owner.isReadOnlyMode) { const formatType: FormFieldType = this.selection.getFormFieldType(formField); if (formatType) { if (formatType.toString() !== '') { @@ -3043,8 +3043,8 @@ export class DocumentHelper { if (isNullOrUndefined(touchOffsetValues)) { touchOffsetValues = event.changedTouches[0]; } - let offsetX: number = touchOffsetValues.pageX - offset.left; - let offsetY: number = touchOffsetValues.pageY - offset.top; + let offsetX: number = touchOffsetValues.clientX - offset.left; + let offsetY: number = touchOffsetValues.clientY - offset.top; return new Point(offsetX, offsetY); } /** diff --git a/controls/documenteditor/src/document-editor/implementation/writer/sfdt-export.ts b/controls/documenteditor/src/document-editor/implementation/writer/sfdt-export.ts index 9ce253d949..fdc33efe5d 100644 --- a/controls/documenteditor/src/document-editor/implementation/writer/sfdt-export.ts +++ b/controls/documenteditor/src/document-editor/implementation/writer/sfdt-export.ts @@ -41,6 +41,7 @@ export class SfdtExport { private startColumnIndex: number = undefined; private endColumnIndex: number = undefined; private lists: number[] = undefined; + private images: number[] = undefined; private document: any = undefined; private writeInlineStyles: boolean = undefined; private nextBlock: any; @@ -105,6 +106,7 @@ export class SfdtExport { this.startLine = undefined; this.endLine = undefined; this.lists = undefined; + this.images = undefined; this.document = undefined; this.endCell = undefined; this.startColumnIndex = undefined; @@ -331,6 +333,7 @@ export class SfdtExport { */ public Initialize(): void { this.lists = []; + this.images = []; this.document = {}; this.document.optimizeSfdt = this.owner.documentEditorSettings.optimizeSfdt; this.document[sectionsProperty[this.keywordIndex]] = []; @@ -943,6 +946,7 @@ export class SfdtExport { this.writeChart(element, inline); } else if (element instanceof ImageElementBox) { inline[imageStringProperty[this.keywordIndex]] = element.imageString; + this.images.push(parseInt(element.imageString, 10)); inline[metaFileImageStringProperty[this.keywordIndex]] = element.metaFileImageString; inline[isMetaFileProperty[this.keywordIndex]] = HelperMethods.getBoolInfo(element.isMetaFile, this.keywordIndex); inline[isCompressedProperty[this.keywordIndex]] = element.isCompressed; @@ -2001,6 +2005,9 @@ export class SfdtExport { wStyle[typeProperty[this.keywordIndex]] = this.keywordIndex == 1 ? this.getStyleTypeEnumValue(style.type) : style.type; wStyle[characterFormatProperty[this.keywordIndex]] = this.writeCharacterFormat((style as any).characterFormat, this.keywordIndex); } + if (style.type === 'Table') { + wStyle[typeProperty[this.keywordIndex]] = this.keywordIndex == 1 ? this.getStyleTypeEnumValue(style.type) : style.type; + } if (!isNullOrUndefined(style.basedOn)) { wStyle[basedOnProperty[this.keywordIndex]] = style.basedOn.name; } @@ -2052,10 +2059,11 @@ export class SfdtExport { } public writeImages(documentHelper: DocumentHelper): void { this.document[imagesProperty[this.keywordIndex]] = {}; - documentHelper.images.keys.forEach(key => { - let base64ImageString: string[] = this.documentHelper.images.get(key); + for (let i = 0; i < this.images.length; i++) { + let key: number = this.images[i]; + let base64ImageString: string[] = documentHelper.images.get(key); this.document[imagesProperty[this.keywordIndex]][key] = base64ImageString; - }); + } } private writeComment(comments: CommentElementBox): any { let comment: any = {}; @@ -2397,6 +2405,8 @@ export class SfdtExport { return 0; case 'Character': return 1; + case 'Table': + return 2; } } private getProtectionTypeEnumValue(protectionType: ProtectionType): number { @@ -2872,6 +2882,7 @@ export class SfdtExport { */ public destroy(): void { this.lists = undefined; + this.images = undefined; this.endLine = undefined; this.startLine = undefined; this.endOffset = undefined; diff --git a/controls/documenteditor/src/document-editor/implementation/writer/word-export.ts b/controls/documenteditor/src/document-editor/implementation/writer/word-export.ts index 5516582212..203bc25b5f 100644 --- a/controls/documenteditor/src/document-editor/implementation/writer/word-export.ts +++ b/controls/documenteditor/src/document-editor/implementation/writer/word-export.ts @@ -4664,6 +4664,11 @@ export class WordExport { // } // SerializeDocxProps(tempDocxProps, 'tblStyleRowBandSize'); // SerializeDocxProps(tempDocxProps, 'tblStyleColBandSize'); + if (!isNullOrUndefined(table[tableFormatProperty[this.keywordIndex]][styleNameProperty[this.keywordIndex]])) { + writer.writeStartElement(undefined, "tblStyle", this.wNamespace); + writer.writeAttributeString('w', 'val', this.wNamespace, table[tableFormatProperty[this.keywordIndex]][styleNameProperty[this.keywordIndex]]); + writer.writeEndElement(); + } this.serializeTablePositioning(writer, table); this.serializeTableWidth(writer, table); this.serializeTableAlignment(writer, table[tableFormatProperty[this.keywordIndex]]); @@ -6148,7 +6153,7 @@ export class WordExport { for (let i: number = 0; i < this.mStyles.length; i++) { let style: any = this.mStyles[i]; writer.writeStartElement(undefined, 'style', this.wNamespace); - let type: string = style[typeProperty[this.keywordIndex]] === (this.keywordIndex == 1 ? 0 : 'Paragraph') ? 'paragraph' : 'character'; + let type: string = this.getStyleType(style[typeProperty[this.keywordIndex]]); writer.writeAttributeString('w', 'type', this.wNamespace, type); writer.writeAttributeString('w', 'styleId', this.wNamespace, style[nameProperty[this.keywordIndex]]); //name @@ -6185,7 +6190,9 @@ export class WordExport { writer.writeEndElement(); } // let value = (style.characterFormat as WCharacterFormat).newgetCharacterFormat(); - this.serializeCharacterFormat(writer, style[characterFormatProperty[this.keywordIndex]]); + if (style[typeProperty[this.keywordIndex]] !== (this.keywordIndex == 1 ? 2 : 'Table')) { + this.serializeCharacterFormat(writer, style[characterFormatProperty[this.keywordIndex]]); + } writer.writeEndElement(); //end of Style } } @@ -6334,6 +6341,18 @@ export class WordExport { } return color; } + private getStyleType(styleType: any): string { + switch (styleType) { + case 'Character': + case 1: + return 'character'; + case 'Table': + case 2: + return 'table'; + default: + return 'paragraph'; + } + } // Get the underline style as string private getUnderlineStyle(underlineStyle: number | string): string { switch (underlineStyle) { diff --git a/controls/drawings/CHANGELOG.md b/controls/drawings/CHANGELOG.md index 2acfa8a2a3..bdb5c8b8a9 100644 --- a/controls/drawings/CHANGELOG.md +++ b/controls/drawings/CHANGELOG.md @@ -2,7 +2,7 @@ ## [Unreleased] -## 25.1.35 (2024-03-15) +## 25.1.37 (2024-03-26) ### Drawings diff --git a/controls/dropdowns/CHANGELOG.md b/controls/dropdowns/CHANGELOG.md index b64be12415..051719328c 100644 --- a/controls/dropdowns/CHANGELOG.md +++ b/controls/dropdowns/CHANGELOG.md @@ -2,6 +2,18 @@ ## [Unreleased] +## 25.1.37 (2024-03-26) + +### MultiSelect + +#### Bug Fixes + +- `#I560783` - Fixed issue where clearing the searched value would automatically select another value. + +- `#I524283` - Fixed issue where popup was not aligned properly when opening on top of the component. + +- `#I565659` - Fixed an issue in Multiselect Checkbox mode where the height of the dropdown input would change when selecting and unselecting items. + ## 25.1.35 (2024-03-15) ### ComboBox diff --git a/controls/dropdowns/package.json b/controls/dropdowns/package.json index 87bc01be1c..dc22636958 100644 --- a/controls/dropdowns/package.json +++ b/controls/dropdowns/package.json @@ -1,6 +1,6 @@ { "name": "@syncfusion/ej2-dropdowns", - "version": "18.66.23", + "version": "25.1.35", "description": "Essential JS 2 DropDown Components", "author": "Syncfusion Inc.", "license": "SEE LICENSE IN license", @@ -56,13 +56,9 @@ "ej2-multiselect", "ej2-combobox" ], - "repository": { - "type": "git", - "url": "https://github.com/syncfusion/ej2-javascript-ui-controls" - }, "scripts": { "build": "gulp build", "test": "gulp test" }, "typings": "index.d.ts" -} +} \ No newline at end of file diff --git a/controls/dropdowns/spec/multi-select/checkbox-selection.spec.ts b/controls/dropdowns/spec/multi-select/checkbox-selection.spec.ts index 9de695d0bc..66ba1a70f5 100644 --- a/controls/dropdowns/spec/multi-select/checkbox-selection.spec.ts +++ b/controls/dropdowns/spec/multi-select/checkbox-selection.spec.ts @@ -3437,6 +3437,73 @@ describe('EJ2-44211- The focus class maintained after move the focus to another }, 800); }); }); +describe('875197', () => { + let listObj: MultiSelect; + let count: number = 0; + let mouseEventArgs: any = { preventDefault: function () { }, target: null, stopPropagation: function () {} }; + let element: HTMLInputElement = createElement('input', { id: 'multiselect', attrs: { type: "text" } }); + let datasource: { [key: string]: Object }[] = [ + { Name: 'Australia', Code: 'AU' }, + { Name: 'Bermuda', Code: 'BM' }, + { Name: 'Canada', Code: 'CA' }, + { Name: 'Cameroon', Code: 'CM' }, + { Name: 'Denmark', Code: 'DK' }, + { Name: 'France', Code: 'FR' }, + { Name: 'Finland', Code: 'FI' }, + { Name: 'Germany', Code: 'DE' }, + { Name: 'Greenland', Code: 'GL' }, + { Name: 'Hong Kong', Code: 'HK' }, + { Name: 'India', Code: 'IN' }, + { Name: 'Italy', Code: 'IT' }, + { Name: 'Japan', Code: 'JP' }, + ]; + let originalTimeout: number; + beforeAll(() => { + for (let i = 0; i < 27; i++) { + const brElement = document.createElement('br'); + document.body.appendChild(brElement); + } + document.body.appendChild(element); + listObj = new MultiSelect({ + dataSource: datasource, + fields: { text: 'Name', value: 'Code' }, + showDropDownIcon: true, + showSelectAll: true, + allowFiltering: true, + mode: 'CheckBox', + }); + listObj.appendTo(element); + }); + afterAll(() => { + for (let i = 0; i < 27; i++) { + const brElement = document.querySelector('br'); + if (brElement) { + brElement.remove(); + } + } + if (element) { + listObj.destroy(); + element.remove(); + } + }); + it('when click the serach text to prevent the list selection', () => { + mouseEventArgs.type = 'mousedown'; + mouseEventArgs.target = (listObj).overAllWrapper; + (listObj).wrapperClick(mouseEventArgs); + (listObj).checkBoxSelectionModule.filterInput.value = "g"; + keyboardEventArgs.altKey = false; + keyboardEventArgs.keyCode = 71; + (listObj).keyDownStatus = true; + (listObj).onInput(keyboardEventArgs); + (listObj).keyUp(keyboardEventArgs); + (listObj).checkBoxSelectionModule.clearText(mouseEventArgs); + mouseEventArgs.type = 'mouseup'; + mouseEventArgs.target = (listObj).popupWrapper; + (listObj).checkBoxSelectionModule.preventListSelection(mouseEventArgs); + (listObj).onMouseClick(mouseEventArgs); + expect((listObj).list.querySelectorAll('.e-active').length).toBe(0); + }); +}); describe('EJ2-54401- Select all checkbox is not displayed properly while selecting an item from the list ', () => { let listObj: any; let checkObj: any; @@ -3533,4 +3600,4 @@ describe('EJ2-54401- Select all checkbox is not displayed properly while selecti done(); }, 450); }); -}); \ No newline at end of file +}); diff --git a/controls/dropdowns/src/multi-select/checkbox-selection.ts b/controls/dropdowns/src/multi-select/checkbox-selection.ts index 067cd27eb7..25a1f26720 100644 --- a/controls/dropdowns/src/multi-select/checkbox-selection.ts +++ b/controls/dropdowns/src/multi-select/checkbox-selection.ts @@ -39,6 +39,7 @@ export class CheckBoxSelection { public list: HTMLElement; private activeLi: HTMLElement[] = []; private activeEle: HTMLElement[] = []; + private boundPreventListSelection: () => void; public constructor(parent?: IMulitSelect) { this.parent = parent; this.removeEventListener(); @@ -326,14 +327,22 @@ export class CheckBoxSelection { if (this.parent.allowFiltering && (this.parent.targetInputElement as HTMLInputElement).value === '') { this.parent.search(null); } - this.parent.refreshPopup(); this.parent.refreshListItems(null); + this.parent.refreshPopup(); (this.clearIconElement as HTMLElement).style.visibility = 'hidden'; this.filterInput.focus(); this.setReorder(e); + this.boundPreventListSelection = this.preventListSelection.bind(this); + this.parent.popupWrapper.addEventListener('mouseup', this.boundPreventListSelection, true); e.preventDefault(); } + private preventListSelection(e: MouseEvent): void { + e.stopPropagation(); + this.parent.popupWrapper.removeEventListener('mouseup', this.boundPreventListSelection, true); + this.boundPreventListSelection = null; + } + private setDeviceSearchBox(): void { this.parent.popupObj.element.classList.add(device); this.parent.popupObj.element.classList.add(mobileFilter); diff --git a/controls/dropdowns/src/multi-select/multi-select-model.d.ts b/controls/dropdowns/src/multi-select/multi-select-model.d.ts index c582fd414c..6ee5777284 100644 --- a/controls/dropdowns/src/multi-select/multi-select-model.d.ts +++ b/controls/dropdowns/src/multi-select/multi-select-model.d.ts @@ -1,4 +1,4 @@ -import { DropDownBase, SelectEventArgs, dropDownBaseClasses, PopupEventArgs, FilteringEventArgs } from '../drop-down-base/drop-down-base';import { FocusEventArgs, BeforeOpenEventArgs, FilterType, FieldSettings, ResultData } from '../drop-down-base/drop-down-base';import { FieldSettingsModel } from '../drop-down-base/drop-down-base-model';import { Popup, createSpinner, showSpinner, hideSpinner } from '@syncfusion/ej2-popups';import { IInput, FloatLabelType, Input } from '@syncfusion/ej2-inputs';import { attributes, setValue, SanitizeHtmlHelper, getValue } from '@syncfusion/ej2-base';import { NotifyPropertyChanges, extend } from '@syncfusion/ej2-base';import { EventHandler, Property, Event, compile, L10n, EmitType, KeyboardEventArgs } from '@syncfusion/ej2-base';import { Animation, AnimationModel, Browser, prepend, Complex } from '@syncfusion/ej2-base';import { Search } from '../common/incremental-search';import { append, addClass, removeClass, closest, detach, remove, select, selectAll } from '@syncfusion/ej2-base';import { getUniqueID, formatUnit, isNullOrUndefined, isUndefined, ModuleDeclaration } from '@syncfusion/ej2-base';import { DataManager, Query, Predicate, JsonAdaptor, DataOptions } from '@syncfusion/ej2-data';import { SortOrder } from '@syncfusion/ej2-lists';import { createFloatLabel, removeFloating, floatLabelFocus, floatLabelBlur, encodePlaceholder } from './float-label'; +import { DropDownBase, SelectEventArgs, dropDownBaseClasses, PopupEventArgs, FilteringEventArgs } from '../drop-down-base/drop-down-base';import { FocusEventArgs, BeforeOpenEventArgs, FilterType, FieldSettings, ResultData } from '../drop-down-base/drop-down-base';import { FieldSettingsModel } from '../drop-down-base/drop-down-base-model';import { isCollide, Popup, createSpinner, showSpinner, hideSpinner } from '@syncfusion/ej2-popups';import { IInput, FloatLabelType, Input } from '@syncfusion/ej2-inputs';import { attributes, setValue, SanitizeHtmlHelper, getValue } from '@syncfusion/ej2-base';import { NotifyPropertyChanges, extend } from '@syncfusion/ej2-base';import { EventHandler, Property, Event, compile, L10n, EmitType, KeyboardEventArgs } from '@syncfusion/ej2-base';import { Animation, AnimationModel, Browser, prepend, Complex } from '@syncfusion/ej2-base';import { Search } from '../common/incremental-search';import { append, addClass, removeClass, closest, detach, remove, select, selectAll } from '@syncfusion/ej2-base';import { getUniqueID, formatUnit, isNullOrUndefined, isUndefined, ModuleDeclaration } from '@syncfusion/ej2-base';import { DataManager, Query, Predicate, JsonAdaptor, DataOptions } from '@syncfusion/ej2-data';import { SortOrder } from '@syncfusion/ej2-lists';import { createFloatLabel, removeFloating, floatLabelFocus, floatLabelBlur, encodePlaceholder } from './float-label'; import {visualMode,MultiSelectChangeEventArgs,RemoveEventArgs,ISelectAllEventArgs,TaggingEventArgs,CustomValueEventArgs} from "./multi-select"; import {DropDownBaseModel} from "../drop-down-base/drop-down-base-model"; diff --git a/controls/dropdowns/src/multi-select/multi-select.ts b/controls/dropdowns/src/multi-select/multi-select.ts index 811a495f09..5641ee9cca 100644 --- a/controls/dropdowns/src/multi-select/multi-select.ts +++ b/controls/dropdowns/src/multi-select/multi-select.ts @@ -3,7 +3,7 @@ import { DropDownBase, SelectEventArgs, dropDownBaseClasses, PopupEventArgs, FilteringEventArgs } from '../drop-down-base/drop-down-base'; import { FocusEventArgs, BeforeOpenEventArgs, FilterType, FieldSettings, ResultData } from '../drop-down-base/drop-down-base'; import { FieldSettingsModel } from '../drop-down-base/drop-down-base-model'; -import { Popup, createSpinner, showSpinner, hideSpinner } from '@syncfusion/ej2-popups'; +import { isCollide, Popup, createSpinner, showSpinner, hideSpinner } from '@syncfusion/ej2-popups'; import { IInput, FloatLabelType, Input } from '@syncfusion/ej2-inputs'; import { attributes, setValue, SanitizeHtmlHelper, getValue } from '@syncfusion/ej2-base'; import { NotifyPropertyChanges, extend } from '@syncfusion/ej2-base'; @@ -3391,6 +3391,7 @@ export class MultiSelect extends DropDownBase implements IInput { } } }); + this.checkCollision(this.popupWrapper); this.popupContentElement = this.popupObj.element.querySelector('.e-content'); if (this.mode === 'CheckBox' && Browser.isDevice && this.allowFiltering) { @@ -3401,6 +3402,15 @@ export class MultiSelect extends DropDownBase implements IInput { } } } + private checkCollision(popupEle: HTMLElement): void { + if (!(this.mode === 'CheckBox' && Browser.isDevice && this.allowFiltering)) { + const collision: string[] = isCollide(popupEle); + if (collision.length > 0) { + popupEle.style.marginTop = -parseInt(getComputedStyle(popupEle).marginTop, 10) + 'px'; + } + this.popupObj.resolveCollision(); + } + } private setHeaderTemplate(): void { let compiledString: Function; if (this.header) { diff --git a/controls/dropdowns/styles/multi-select/_fluent-definition.scss b/controls/dropdowns/styles/multi-select/_fluent-definition.scss index 6cc123e956..db4c79a8b8 100644 --- a/controls/dropdowns/styles/multi-select/_fluent-definition.scss +++ b/controls/dropdowns/styles/multi-select/_fluent-definition.scss @@ -241,6 +241,6 @@ $ddl-small-clear-icon-width: 12px !default; .e-bigger .e-multiselect.e-control-container .e-multi-select-wrapper.e-down-icon .e-clear-icon, .e-bigger.e-multiselect.e-control-container .e-multi-select-wrapper.e-down-icon .e-clear-icon { - margin-top: -1.5em; + margin-top: -1.6em; } } diff --git a/controls/dropdowns/styles/multi-select/_material-dark-definition.scss b/controls/dropdowns/styles/multi-select/_material-dark-definition.scss index 9d2f8af0e6..32c4dacba4 100644 --- a/controls/dropdowns/styles/multi-select/_material-dark-definition.scss +++ b/controls/dropdowns/styles/multi-select/_material-dark-definition.scss @@ -228,3 +228,14 @@ $ddl-multiselect-filled-float-input-min-height-bigger: 36px !default; $ddl-multiselect-filled-floatlabel-fontsize-bigger: 12px !default; $filled-multiselect-chip-bg-color: $grey-800 !default; $filled-multiselect-chip-hover-bg-color: $grey-700 !default; + +@include export-module('multiselect-material-dark') { + .e-multiselect.e-input-group.e-checkbox .e-multi-select-wrapper input[type = 'text'] { + padding: 1px 0; + } + + .e-small .e-multiselect.e-input-group.e-checkbox .e-multi-select-wrapper input[type = 'text'], + .e-small.e-multiselect.e-input-group.e-checkbox .e-multi-select-wrapper input[type = 'text'] { + padding: 0; + } +} diff --git a/controls/dropdowns/styles/multi-select/_material-definition.scss b/controls/dropdowns/styles/multi-select/_material-definition.scss index fce1c99ecc..e5b6d482c9 100644 --- a/controls/dropdowns/styles/multi-select/_material-definition.scss +++ b/controls/dropdowns/styles/multi-select/_material-definition.scss @@ -221,3 +221,14 @@ $outline-multiselect-disabled-font-color: rgba($grey-light-font, .38) !default; $outline-multiselect-disabled-chip-bg-color: $grey-100 !default; $filled-multiselect-chip-bg-color: darken($grey-300, 7%) !default; $filled-multiselect-chip-hover-bg-color: darken($grey-300, 7%) !default; + +@include export-module('multiselect-material') { + .e-multiselect.e-input-group.e-checkbox .e-multi-select-wrapper input[type = 'text'] { + padding: 1px 0; + } + + .e-small .e-multiselect.e-input-group.e-checkbox .e-multi-select-wrapper input[type = 'text'], + .e-small.e-multiselect.e-input-group.e-checkbox .e-multi-select-wrapper input[type = 'text'] { + padding: 0; + } +} diff --git a/controls/ej2/package.json b/controls/ej2/package.json index 2e9bb2cd83..b156c9d6f8 100644 --- a/controls/ej2/package.json +++ b/controls/ej2/package.json @@ -1,6 +1,6 @@ { "name": "@syncfusion/ej2", - "version": "17.417.0", + "version": "25.5.0", "description": "A modern JavaScript UI toolkit that has been built from the ground up to be lightweight, responsive, modular and touch friendly. It is written in TypeScript and has no external dependencies.", "author": "Syncfusion Inc.", "license": "SEE LICENSE IN license", diff --git a/controls/excelexport/CHANGELOG.md b/controls/excelexport/CHANGELOG.md index d22d496af1..7981a40f64 100644 --- a/controls/excelexport/CHANGELOG.md +++ b/controls/excelexport/CHANGELOG.md @@ -2,15 +2,7 @@ ## [Unreleased] -## 24.2.4 (2024-02-06) - -### Excel Export - -#### Bug Fixes - -- Fixed the new line character after end row being rendered improperly in CSV export. - -## 24.1.41 (2023-12-18) +## 25.1.35 (2024-03-15) ### Excel Export @@ -24,6 +16,8 @@ - Fixed the text with double quotes being rendered improperly in CSV export. +- Fixed the New line character being rendered improperly in CSV export. + ## 21.1.35 (2023-03-23) ### Excel Export diff --git a/controls/excelexport/package.json b/controls/excelexport/package.json index 469a55b00d..2c832a1c02 100644 --- a/controls/excelexport/package.json +++ b/controls/excelexport/package.json @@ -1,6 +1,6 @@ { "name": "@syncfusion/ej2-excel-export", - "version": "24.2.4", + "version": "25.1.35", "description": "Essential Javascript 2 Excel Export Library", "author": "Syncfusion Inc.", "license": "SEE LICENSE IN license", @@ -12,6 +12,7 @@ "@syncfusion/ej2-compression": "*" }, "devDependencies": { + "@syncfusion/ej2-staging": "^1.0.1", "@types/chai": "3.4.28", "@types/es6-promise": "0.0.28", "@types/jasmine": "2.5.43", diff --git a/controls/filemanager/package.json b/controls/filemanager/package.json index 08362521da..9e84fc9252 100644 --- a/controls/filemanager/package.json +++ b/controls/filemanager/package.json @@ -1,6 +1,6 @@ { "name": "@syncfusion/ej2-filemanager", - "version": "18.28.1", + "version": "25.1.35", "description": "Essential JS 2 FileManager Component", "author": "Syncfusion Inc.", "license": "SEE LICENSE IN license", diff --git a/controls/gantt/CHANGELOG.md b/controls/gantt/CHANGELOG.md index 8c7db611dd..4311be7203 100644 --- a/controls/gantt/CHANGELOG.md +++ b/controls/gantt/CHANGELOG.md @@ -2,6 +2,29 @@ ## [Unreleased] +## 25.1.37 (2024-03-26) + +### GanttChart + +#### Bug fixes + +- `#F187206` - The delete action not working in remote data when `timezone` using in sample. +- `#I566491` - The exception is thrown when the resource ID mapping is empty issue has been fixed. +- `#I565418` - Start date defaulting to incorrect value when remove the start Date in add dialog. +- `#I566632` - Duration calculations are incorrect in edit or add dialog in decimals issue has been fixed. +- `#I565751` - The chart does not refresh when any record is edited by cell editing issue has been fixed. +- `#I566333` - Gantt chart disappeared while insert action with `timlineVirtualization` issue has been fixed. +- `#F186355` - Taskbar template not showing in resource view issue has been fixed. +- `#I562492` - `actionBegin` arguments miss the last record while dragging issue has been fixed. +- `#I556547` - Top and bottom tier shows null when using custom zooming level issue has been fixed. +- `#I566539` - Console error occurs while saving data in add dialog box with validation rule issue has been +fixed. +- `#I553748` - Timeline dates validated wrongly after cell editing with timeline virtualization enabled issue has been fixed. +- `#I565439` - Work calculations are incorrect for parent task in project view issue has been fixed. +- `#I553710`,`#I565824` - Weekends are not highlighted while `timlineVirtualization` is enabled issue has been fixed. +- `#I565359` - When `allowEditing` is disabled in a resource view, a console error is thrown issue has been fixed. +- `#I560166` - The context menu using "add child" for any task, dependency line validation is not working properly. + ## 25.1.35 (2024-03-15) ### GanttChart diff --git a/controls/gantt/package.json b/controls/gantt/package.json index a54b2d3bc4..3f03a20d4b 100644 --- a/controls/gantt/package.json +++ b/controls/gantt/package.json @@ -1,6 +1,6 @@ { "name": "@syncfusion/ej2-gantt", - "version": "24.2.7", + "version": "25.1.35", "description": "Essential JS 2 Gantt Component", "author": "Syncfusion Inc.", "license": "SEE LICENSE IN license", diff --git a/controls/gantt/spec/action/dialog-edit.spec.ts b/controls/gantt/spec/action/dialog-edit.spec.ts index df22a534ba..d82c909aa5 100644 --- a/controls/gantt/spec/action/dialog-edit.spec.ts +++ b/controls/gantt/spec/action/dialog-edit.spec.ts @@ -5117,3 +5117,448 @@ describe('Dialog editing - predecessor Tab with decimal', () => { } }); }); +describe('CR-875373:Start date defaulting to incorrect value when remove the startDate in add dialog issues', function () { + let ganttObj: Gantt; + beforeAll(function (done) { + ganttObj = createGantt({ + dataSource: [ + { + TaskID: 1, + TaskName: 'Product Concept', + StartDate: new Date('04/02/2019'), + EndDate: new Date('04/21/2019'), + subtasks: [ + { TaskID: 2, TaskName: 'Defining the product and its usage', StartDate: new Date('04/02/2019'), Duration: 3, Progress: 30 }, + { TaskID: 3, TaskName: 'Defining target audience', StartDate: new Date('04/02/2019'), Duration: 3 }, + { TaskID: 4, TaskName: 'Prepare product sketch and notes', StartDate: new Date('04/02/2019'), Duration: 3, Predecessor: "2", Progress: 30 }, + ] + } + ], + allowSorting: true, + allowSelection: true, + taskFields: { + id: 'TaskID', + name: 'TaskName', + startDate: 'StartDate', + duration: 'Duration', + progress: 'Progress', + endDate: 'EndDate', + child: 'subtasks', + }, + editSettings: { + allowAdding: true, + allowEditing: true, + allowDeleting: true, + allowTaskbarEditing: true, + showDeleteConfirmDialog: true + }, + toolbar: ['Add', 'Edit', 'Update', 'Delete', 'Cancel', 'Indent', 'Outdent'], + }, done); + }); + afterAll(function () { + if (ganttObj) { + destroyGantt(ganttObj); + } + }) + beforeEach(function (done) { + setTimeout(done, 1000); + }); +it('Verifying endDate after dialog add without startDate', () => { + ganttObj.openAddDialog(); + ganttObj.dataBind(); + let startDate: any = (document.getElementById(ganttObj.element.id + 'StartDate')).ej2_instances[0]; + startDate.value = ''; + startDate.dataBind(); + let saveRecord: HTMLElement = document.querySelector('#' + ganttObj.element.id + '_dialog > div.e-footer-content > button.e-control.e-btn.e-lib.e-primary.e-flat') as HTMLElement; + triggerMouseEvent(saveRecord, 'click'); + expect(ganttObj.getFormatedDate(ganttObj.flatData[0].ganttProperties.endDate, 'M/d/yyyy')).toEqual('4/2/2019'); + }); +}); + describe('Duration column Dialog editing with decimal', () => { + let ganttObj: Gantt; + let numericParams = { params: { decimals: 2 } }; + beforeAll((done: Function) => { + ganttObj = createGantt({ + dataSource: [{ TaskID: 5, TaskName: 'Concept Approval', StartDate: new Date('04/02/2019'), Duration: 1 }], + allowSorting: true, + allowReordering: true, + enableContextMenu: true, + taskFields: { + id: 'TaskID', + name: 'TaskName', + startDate: 'StartDate', + duration: 'Duration', + progress: 'Progress', + dependency: 'Predecessor', + baselineStartDate: "BaselineStartDate", + baselineEndDate: "BaselineEndDate", + child: 'subtasks', + indicators: 'Indicators' + }, + renderBaseline: true, + baselineColor: 'red', + editSettings: { + allowAdding: true, + allowEditing: true, + allowDeleting: true, + allowTaskbarEditing: true, + showDeleteConfirmDialog: true + }, + columns: [ + { field: 'TaskID', headerText: 'Task ID' }, + { field: 'TaskName', headerText: 'Task Name', allowReordering: false }, + { field: 'StartDate', headerText: 'Start Date', allowSorting: false }, + { field: 'Duration', headerText: 'Duration',edit:numericParams,editType:'numericedit' }, + { field: 'Progress', headerText: 'Progress', allowFiltering: false }, + { field: 'CustomColumn', headerText: 'CustomColumn' } + ], + toolbar: ['Add', 'Edit', 'Update', 'Delete', 'Cancel', 'ExpandAll', 'CollapseAll', 'Search', 'ZoomIn', 'ZoomOut', 'ZoomToFit', + 'PrevTimeSpan', 'NextTimeSpan', 'ExcelExport', 'CsvExport', 'PdfExport'], + allowExcelExport: true, + allowPdfExport: true, + allowSelection: true, + allowRowDragAndDrop: true, + selectedRowIndex: 1, + splitterSettings: { + position: "50%", + }, + selectionSettings: { + mode: 'Row', + type: 'Single', + enableToggle: false + }, + tooltipSettings: { + showTooltip: true + }, + filterSettings: { + type: 'Menu' + }, + allowFiltering: true, + gridLines: "Both", + showColumnMenu: true, + highlightWeekends: true, + timelineSettings: { + showTooltip: true, + topTier: { + unit: 'Week', + format: 'dd/MM/yyyy' + }, + bottomTier: { + unit: 'Day', + count: 1 + } + }, + eventMarkers: [ + { + day: '04/10/2019', + cssClass: 'e-custom-event-marker', + label: 'Project approval and kick-off' + } + ], + holidays: [{ + from: "04/04/2019", + to: "04/05/2019", + label: " Public holidays", + cssClass: "e-custom-holiday" + }, + { + from: "04/12/2019", + to: "04/12/2019", + label: " Public holiday", + cssClass: "e-custom-holiday" + }], + searchSettings: { fields: ['TaskName', 'Duration'] + }, + labelSettings: { + leftLabel: 'TaskID', + rightLabel: 'Task Name: ${taskData.TaskName}', + taskLabel: '${Progress}%' + }, + allowResizing: true, + readOnly: false, + taskbarHeight: 20, + rowHeight: 40, + height: '550px', + allowUnscheduledTasks: true, + projectStartDate: new Date('03/25/2019'), + projectEndDate: new Date('05/30/2019'), + }, done); + }); + afterAll(() => { + if (ganttObj) { + destroyGantt(ganttObj); + } + }); + beforeEach((done: Function) => { + setTimeout(done, 500); + ganttObj.openEditDialog('5'); + }); + it('editing with decimal', () => { + ganttObj.actionComplete = (args: any): void => { + if (args.requestType == "save") { + expect(args.data.ganttProperties.duration).toBe(0.25) + } + } + ganttObj.dataBind(); + let durationField: any = document.querySelector('#' + ganttObj.element.id + 'Duration') as HTMLInputElement; + if (durationField) { + let textObj: any = (document.getElementById(ganttObj.element.id + 'Duration')).ej2_instances[0]; + textObj.value = '0.25'; + textObj.dataBind(); + let save: HTMLElement = document.querySelectorAll('#' + ganttObj.element.id + '_dialog > div.e-footer-content > button.e-control')[1] as HTMLElement; + triggerMouseEvent(save, 'click'); + } + }); +}); +describe('Add new record with validation rule', () => { + let ganttObj: Gantt; + beforeAll((done: Function) => { + ganttObj = createGantt({ + dataSource: [], + allowSorting: true, + allowReordering: true, + enableContextMenu: true, + taskFields: { + id: 'TaskID', + name: 'TaskName', + startDate: 'StartDate', + duration: 'Duration', + progress: 'Progress', + dependency: 'Predecessor', + baselineStartDate: "BaselineStartDate", + baselineEndDate: "BaselineEndDate", + child: 'subtasks', + indicators: 'Indicators' + }, + renderBaseline: true, + baselineColor: 'red', + editSettings: { + allowAdding: true, + allowEditing: true, + allowDeleting: true, + allowTaskbarEditing: true, + showDeleteConfirmDialog: true + }, + columns: [ + { field: 'TaskID', headerText: 'Task ID' }, + { field: 'TaskName', headerText: 'Task Name', allowReordering: false }, + { field: 'StartDate', headerText: 'Start Date', allowSorting: false }, + { field: 'Duration', headerText: 'Duration', allowEditing: false }, + { field: 'Progress', headerText: 'Progress', allowFiltering: false }, + { + field: 'CustomColumn', headerText: 'CustomColumn', editType: 'numericedit', validationRules: { + number: true, + min: 20, required: true + } + } + ], + editDialogFields: [ + { type: 'General', headerText: 'General' }, + { type: 'Dependency' }, + { type: 'Resources' }, + { type: 'Notes' } + ], + addDialogFields: [ + { type: 'General', headerText: 'General',fields:['TaskID','TaskName','CustomColumn'] }, + { type: 'Dependency' }, + { type: 'Resources' }, + { type: 'Notes' } + ], + sortSettings: { + columns: [{ field: 'TaskID', direction: 'Ascending' }, + { field: 'TaskName', direction: 'Ascending' }] + }, + toolbar: ['Add', 'Edit', 'Update', 'Delete', 'Cancel', 'ExpandAll', 'CollapseAll', 'Search', 'ZoomIn', 'ZoomOut', 'ZoomToFit', + 'PrevTimeSpan', 'NextTimeSpan', 'ExcelExport', 'CsvExport', 'PdfExport'], + allowExcelExport: true, + allowPdfExport: true, + allowSelection: true, + allowRowDragAndDrop: true, + selectedRowIndex: 1, + splitterSettings: { + position: "50%", + // columnIndex: 4 + }, + selectionSettings: { + mode: 'Row', + type: 'Single', + enableToggle: false + }, + tooltipSettings: { + showTooltip: true + }, + filterSettings: { + type: 'Excel' + }, + allowFiltering: true, + gridLines: "Both", + showColumnMenu: true, + highlightWeekends: true, + timelineSettings: { + showTooltip: true, + topTier: { + unit: 'Week', + format: 'dd/MM/yyyy' + }, + bottomTier: { + unit: 'Day', + count: 1 + } + }, + eventMarkers: [ + { + day: '04/10/2019', + cssClass: 'e-custom-event-marker', + label: 'Project approval and kick-off' + } + ], + holidays: [{ + from: "04/04/2019", + to: "04/05/2019", + label: " Public holidays", + cssClass: "e-custom-holiday" + + }, + { + from: "04/12/2019", + to: "04/12/2019", + label: " Public holiday", + cssClass: "e-custom-holiday" + + }], + searchSettings: + { + fields: ['TaskName', 'Duration'] + }, + labelSettings: { + leftLabel: 'TaskID', + rightLabel: 'Task Name: ${taskData.TaskName}', + taskLabel: '${Progress}%' + }, + allowResizing: true, + readOnly: false, + taskbarHeight: 20, + rowHeight: 40, + height: '550px', + allowUnscheduledTasks: true, + projectStartDate: new Date('03/25/2019'), + projectEndDate: new Date('05/30/2019'), + }, done); + }); + afterAll(() => { + if (ganttObj) { + destroyGantt(ganttObj); + } + }); + beforeEach((done: Function) => { + setTimeout(done, 500); + ganttObj.openAddDialog(); + }); + it('add record', () => { + ganttObj.actionComplete = (args: any): void => { + if (args.requestType == "refresh") { + expect(ganttObj.currentViewData.length).toBe(0); + } + } + ganttObj.dataBind(); + let saveRecord: HTMLElement = document.querySelector('#' + ganttObj.element.id + '_dialog > div.e-footer-content > button.e-control.e-btn.e-lib.e-primary.e-flat') as HTMLElement; + triggerMouseEvent(saveRecord, 'click'); + }); +}); +describe('Edit cell with timeline virtualization', () => { + let ganttObj: Gantt; + let SelfReferenceData = [ + { TaskID: 1, TaskName: 'Project Initiation', StartDate: new Date('04/02/2019'), EndDate: new Date('04/21/2019'), Notes: 'xxxxx' }, + { TaskID: 2, TaskName: 'Identify Site location Empty note', StartDate: new Date('03/29/2019'), Duration: 4, Progress: 50, ParentId: 1, Notes: '' }, + { TaskID: 3, TaskName: 'Perform Soil test', StartDate: new Date('04/02/2019'), Duration: 4, Progress: 50, ParentId: 1 }, + { TaskID: 4, TaskName: 'Soil test approval', StartDate: new Date('04/02/2019'), Duration: 4, Progress: 50, Predecessor: '2', ParentId: 1 }, + { TaskID: 5, TaskName: 'Project Estimation', StartDate: new Date('04/02/2019'), EndDate: new Date('04/21/2019') }, + { TaskID: 6, TaskName: 'Develop floor plan for estimation', StartDate: new Date('04/04/2019'), Duration: 3, Progress: 50, ParentId: 5 }, + { TaskID: 7, TaskName: 'List materials_info', StartDate: new Date('04/04/2019'), Duration: 3, Progress: 50, ParentId: 5, Notes: 'yyyyyyy' }, + { TaskID: 8, TaskName: 'Estimation approval', StartDate: new Date('04/04/2019'), Duration: 3, Progress: 50, ParentId: 5 } + ]; + beforeAll((done: Function) => { + ganttObj = createGantt( + { + dataSource: SelfReferenceData, + enableTimelineVirtualization: true, + allowExcelExport: true, + height: '450px', + enableImmutableMode: true, + highlightWeekends: true, + allowResizing: true, + showColumnMenu: true, + allowFiltering: true, + allowSorting: true, + allowReordering: true, + allowRowDragAndDrop: true, + taskFields: { + id: 'TaskID', + name: 'TaskName', + startDate: 'StartDate', + endDate: 'EndDate', + duration: 'Duration', + progress: 'Progress', + dependency: 'Predecessor', + notes: 'Notes', + parentID: 'ParentId' + }, + editSettings: { + allowAdding: true, + allowEditing: true, + allowDeleting: true, + allowTaskbarEditing: true, + showDeleteConfirmDialog: true + }, + editDialogFields: [ + { type: 'General' }, + { type: 'Dependency' }, + { type: 'Notes' }, + ], + treeColumnIndex: 1, + toolbar: ['Add', 'Edit', 'Delete', 'CriticalPath', 'ExcelExport', 'PdfExport', { text: 'Quick Filter', id: 'Quick Filter' }, { text: 'Clear Filter', id: 'Clear Filter' }], + columns: [ + { field: 'TaskID', width: 50 }, + { field: 'TaskName', width: 250 }, + { field: 'StartDate' }, + { field: 'EndDate' }, + { field: 'Duration' }, + { field: 'Predecessor' }, + { field: 'Progress' }, + { field: 'Notes' }, + ], + selectionSettings: { + type: 'Multiple' + }, + splitterSettings: { + columnIndex: 4 + }, + enableContextMenu: true, + projectStartDate: new Date('03/24/2019'), + projectEndDate: new Date('07/06/2019') + }, done); + }); + afterAll(() => { + if (ganttObj) { + destroyGantt(ganttObj); + } + }); + beforeEach((done: Function) => { + ganttObj.openEditDialog(1); + setTimeout(done, 500); + }); + it('edit start date cell', () => { + ganttObj.actionComplete = (args: any): void => { + if(args.action === "DialogEditing") + expect(ganttObj.getFormatedDate(ganttObj.timelineModule.timelineEndDate, 'MM/dd/yyyy')).toBe('04/07/2024'); + }; + let StartDate: any = document.querySelector('#' + ganttObj.element.id + 'StartDate') as HTMLInputElement; + if (StartDate) { + let SD: any = (document.getElementById(ganttObj.element.id + 'StartDate')).ej2_instances[0]; + SD.value = new Date('03/22/2024'); + SD.dataBind(); + let save: HTMLElement = document.querySelectorAll('#' + ganttObj.element.id + '_dialog > div.e-footer-content > button.e-control')[0] as HTMLElement; + triggerMouseEvent(save, 'click'); + } + }); +}); \ No newline at end of file diff --git a/controls/gantt/spec/base/predecessor.spec.ts b/controls/gantt/spec/base/predecessor.spec.ts index d21cbd5a52..5521db0a01 100644 --- a/controls/gantt/spec/base/predecessor.spec.ts +++ b/controls/gantt/spec/base/predecessor.spec.ts @@ -1051,3 +1051,151 @@ describe('AlphaID predecessor', () => { destroyGantt(ganttObj); }); }); +describe('predecessor validation', () => { + let ganttObj: Gantt; + var projectNewData = [ + { + TaskID: 1, + TaskName: 'Product Concept', + StartDate: new Date('04/02/2019'), + EndDate: new Date('04/21/2019'), + subtasks: [ + { TaskID: 4, TaskName: 'Prepare product sketch and notes', StartDate: new Date('04/02/2019'), Duration: 3, Predecessor: "2", Progress: 30 }, + ] + }, + { TaskID: 5, TaskName: 'Concept Approval', StartDate: new Date('04/02/2019'), Duration: 0, Predecessor: "3,4" }, + { + TaskID: 6, + TaskName: 'Market Research', + StartDate: new Date('04/02/2019'), + EndDate: new Date('04/21/2019'), + subtasks: [ + { + TaskID: 7, + TaskName: 'Demand Analysis', + StartDate: new Date('04/04/2019'), + EndDate: new Date('04/21/2019'), + subtasks: [ + { TaskID: 9, TaskName: 'Market opportunity analysis', StartDate: new Date('04/04/2019'), Duration: 4, Predecessor: "5" } + ] + }, + { TaskID: 10, TaskName: 'Competitor Analysis', StartDate: new Date('04/04/2019'), Duration: 4, Predecessor: "7,8", Progress: 30 }, + ] + }, + ]; + beforeAll((done: Function) => { + ganttObj = createGantt( + { + dataSource: projectNewData, + allowSorting: true, + allowReordering: true, + enableContextMenu: true, + taskFields: { + id: 'TaskID', + name: 'TaskName', + startDate: 'StartDate', + duration: 'Duration', + progress: 'Progress', + dependency: 'Predecessor', + baselineStartDate: "BaselineStartDate", + baselineEndDate: "BaselineEndDate", + child: 'subtasks', + indicators: 'Indicators' + }, + renderBaseline: true, + baselineColor: 'red', + editSettings: { + allowAdding: true, + allowEditing: true, + allowDeleting: true, + allowTaskbarEditing: true, + showDeleteConfirmDialog: true + }, + columns: [ + { field: 'TaskID', headerText: 'Task ID' }, + { field: 'TaskName', headerText: 'Task Name', allowReordering: false }, + { field: 'StartDate', headerText: 'Start Date', allowSorting: false }, + { field: 'Duration', headerText: 'Duration', allowEditing: false }, + { field: 'Progress', headerText: 'Progress', allowFiltering: false }, + { field: 'CustomColumn', headerText: 'CustomColumn' } + ], + sortSettings: { + columns: [{ field: 'TaskID', direction: 'Ascending' }, + { field: 'TaskName', direction: 'Ascending' }] + }, + toolbar: ['Add', 'Edit', 'Update', 'Delete', 'Cancel', 'ExpandAll', 'CollapseAll', 'Search', 'ZoomIn', 'ZoomOut', 'ZoomToFit', + 'PrevTimeSpan', 'NextTimeSpan', 'ExcelExport', 'CsvExport', 'PdfExport'], + allowExcelExport: true, + allowPdfExport: true, + allowSelection: true, + allowRowDragAndDrop: true, + selectedRowIndex: 1, + splitterSettings: { + position: "50%", + }, + selectionSettings: { + mode: 'Row', + type: 'Single', + enableToggle: false + }, + tooltipSettings: { + showTooltip: true + }, + filterSettings: { + type: 'Menu' + }, + allowFiltering: true, + gridLines: "Both", + showColumnMenu: true, + highlightWeekends: true, + timelineSettings: { + showTooltip: true, + topTier: { + unit: 'Week', + format: 'dd/MM/yyyy' + }, + bottomTier: { + unit: 'Day', + count: 1 + } + }, + holidays: [{ + from: "04/04/2019", + to: "04/05/2019", + label: " Public holidays", + cssClass: "e-custom-holiday" + }, + { + from: "04/12/2019", + to: "04/12/2019", + label: " Public holiday", + cssClass: "e-custom-holiday" + }], + allowResizing: true, + readOnly: false, + taskbarHeight: 20, + rowHeight: 40, + height: '550px', + allowUnscheduledTasks: true, + projectStartDate: new Date('03/25/2019'), + projectEndDate: new Date('05/30/2019'), + }, done); + }); + beforeEach((done: Function) => { + setTimeout(done, 500); + }); + it('Check predecessor length', (done) => { + ganttObj.taskbarEdited = (args: any) => { + expect(ganttObj.getFormatedDate(ganttObj.currentViewData[4].ganttProperties.startDate, 'MM/dd/yyyy')).toBe('04/02/2019'); + }; + ganttObj.dataBind(); + let dragElement: HTMLElement = ganttObj.element.querySelector('#' + ganttObj.element.id + 'GanttTaskTableBody > tr:nth-child(2) > td > div.e-taskbar-main-container > div.e-taskbar-right-resizer.e-icon') as HTMLElement; + triggerMouseEvent(dragElement, 'mousedown', dragElement.offsetLeft, dragElement.offsetTop); + triggerMouseEvent(dragElement, 'mousemove', -100, 0); + triggerMouseEvent(dragElement, 'mouseup'); + done(); + }); + afterAll(() => { + destroyGantt(ganttObj); + }); +}); \ No newline at end of file diff --git a/controls/gantt/spec/base/resource-view.spec.ts b/controls/gantt/spec/base/resource-view.spec.ts index 1dc3719a20..c8c61dad42 100644 --- a/controls/gantt/spec/base/resource-view.spec.ts +++ b/controls/gantt/spec/base/resource-view.spec.ts @@ -3098,3 +3098,92 @@ describe("Project view duration editing", () => { expect(ganttObj.currentViewData[2].ganttProperties.duration).toBe(null); }); }); +describe('CR-Task:875889-Exception when resource ID mapping is empty', () => { + let ganttObj: Gantt; + let resourceCollection = [ + { resourceId: 1, resourceName: 'Martin Tamer', resourceGroup: 'Planning Team' }, + ]; + beforeAll((done: Function) => { + ganttObj = createGantt( + { + dataSource: [ + { + TaskID: 1, + TaskName: 'Project initiation', + StartDate: new Date('03/29/2019'), + EndDate: new Date('04/02/2019'), + subtasks: [] + }, + { + TaskID: 5, + TaskName: 'Project estimation', + StartDate: new Date('03/29/2019'), + EndDate: new Date('04/21/2019') + } + ], + resources: resourceCollection, + viewType: 'ResourceView', + showOverAllocation: true, + enableContextMenu: true, + allowSorting: true, + allowReordering: true, + taskFields: { + id: 'TaskID', + name: 'TaskName', + startDate: 'StartDate', + endDate: 'EndDate', + duration: 'Duration', + progress: 'Progress', + dependency: 'Predecessor', + resourceInfo: 'resources', + child: 'subtasks' + }, + resourceFields: { + id: 'resourceId' + }, + editSettings: { + allowAdding: true, + allowEditing: true, + allowDeleting: true, + allowTaskbarEditing: true, + showDeleteConfirmDialog: true + }, + columns: [ + { field: 'TaskID', visible: false }, + { field: 'TaskName', headerText: 'Name', width: 250 }, + { field: 'work', headerText: 'Work' }, + { field: 'Progress' }, + { field: 'resourceGroup', headerText: 'Group' }, + { field: 'StartDate' }, + { field: 'Duration' }, + ], + splitterSettings: { + columnIndex: 3 + }, + timelineSettings: { + showTooltip: true, + topTier: { + unit: 'Week', + format: 'dd/MM/yyyy' + }, + bottomTier: { + unit: 'Day', + count: 1 + } + }, + allowResizing: true, + allowFiltering: true, + allowSelection: true, + highlightWeekends: true, + treeColumnIndex: 1, + height: '550px' + }, done); + }); + it('Checking unassigned childrecords length', () => { + expect(ganttObj.currentViewData[1].ganttProperties.taskName).toBe("Unassigned Task"); + expect(ganttObj.currentViewData[1].childRecords.length).toBe(2); + }); + afterAll(() => { + destroyGantt(ganttObj); + }); +}); \ No newline at end of file diff --git a/controls/gantt/spec/renderer/nonworking-day.spec.ts b/controls/gantt/spec/renderer/nonworking-day.spec.ts index ba31a1302a..aaffd49e9b 100644 --- a/controls/gantt/spec/renderer/nonworking-day.spec.ts +++ b/controls/gantt/spec/renderer/nonworking-day.spec.ts @@ -1,10 +1,14 @@ /** * Gantt base spec */ -import { Gantt, DayMarkers } from '../../src/index'; +import { Gantt, DayMarkers, Edit, Toolbar, ContextMenu, Sort, VirtualScroll,Selection } from '../../src/index'; import * as cls from '../../src/gantt/base/css-constants'; import { baselineData,projectData } from '../base/data-source.spec'; -import { createGantt, destroyGantt } from '../base/gantt-util.spec'; +import { createGantt, destroyGantt,triggerMouseEvent } from '../base/gantt-util.spec'; +interface EJ2Instance extends HTMLElement { + ej2_instances: Object[]; +} + describe('Gantt spec for non -working-day', () => { describe('Gantt base module', () => { Gantt.Inject(DayMarkers); @@ -152,4 +156,116 @@ describe('Gantt spec for non -working-day', () => { ]; }); }); -}); \ No newline at end of file +}); +describe('874399 - weekend is not visible', function () { + Gantt.Inject(Edit, Toolbar,ContextMenu,Sort,Selection, VirtualScroll); + let ganttObj: Gantt; + let data: Object[] = [ + { TaskID: 1, TaskName: 'Project Initiation_1', StartDate: new Date('04/02/2019'), EndDate: new Date('04/21/2019') }, + { TaskID: 2, TaskName: 'Identify Site location_1', StartDate: new Date('03/29/2019'), Duration: 6, Progress: 70, ParentId: 1 }, + { TaskID: 3, TaskName: 'Perform Soil test_1', StartDate: new Date('04/02/2019'), Duration: 7, Progress: 70, ParentId: 1 }, + { TaskID: 4, TaskName: 'Soil test approval_1', StartDate: new Date('04/02/2019'), Duration: 8, Progress: 70, Predecessor: '2', ParentId: 1 }, + { TaskID: 5, TaskName: 'Project Estimation_1', StartDate: new Date('04/02/2019'), EndDate: new Date('04/21/2019') }, + { TaskID: 6, TaskName: 'Develop floor plan for estimation_1', StartDate: new Date('04/04/2019'), Duration: 9, Progress: 70, ParentId: 5 }, + { TaskID: 7, TaskName: 'List materials_1', StartDate: new Date('04/04/2019'), Duration: 3, Progress: 70, ParentId: 5 }, + { TaskID: 8, TaskName: 'Estimation approval_1', StartDate: new Date('04/04/2019'), Duration: 3, Progress: 70, ParentId: 5 } + ]; + + beforeAll(function (done) { + ganttObj = createGantt({ + dataSource: data, + enableContextMenu: true, + taskFields: { + id: 'TaskID', + name: 'TaskName', + startDate: 'StartDate', + endDate: 'EndDate', + duration: 'Duration', + progress: 'Progress', + dependency: 'Predecessor', + notes: 'Notes', + parentID: 'ParentId' + }, + editSettings: { + allowAdding: true, + allowEditing: true, + allowDeleting: true, + allowTaskbarEditing: true, + showDeleteConfirmDialog: true + }, + columns: [ + { field: 'TaskID' }, + { field: 'TaskName', width: 80 }, + { field: 'StartDate', width: 120 }, + { field: 'EndDate', width: 120 }, + { field: 'Duration', width: 90 }, + { field: 'TaskType', visible: false } + ], + enableTimelineVirtualization:true, + sortSettings: { + columns: [{ field: 'TaskID', direction: 'Ascending' }, + { field: 'TaskName', direction: 'Ascending' }] + }, + splitterSettings: { + columnIndex: 4 + }, + toolbar: ['Add', 'Edit', 'Update', 'Delete', 'Cancel', 'ExpandAll', 'CollapseAll', 'Search', 'ZoomIn', 'ZoomOut', 'ZoomToFit',], + allowSelection: true, + allowRowDragAndDrop: true, + selectedRowIndex: 1, + selectionSettings: { + mode: 'Row', + type: 'Single', + enableToggle: false + }, + tooltipSettings: { + showTooltip: true + }, + filterSettings: { + type: 'Menu' + }, + allowFiltering: true, + gridLines: "Both", + showColumnMenu: true, + highlightWeekends: true, + timelineSettings: { + showTooltip: true, + topTier: { + unit: 'Week', + format: 'dd/MM/yyyy' + }, + bottomTier: { + unit: 'Day', + count: 1 + } + }, + allowResizing: true, + readOnly: false, + taskbarHeight: 20, + rowHeight: 40, + height: '550px', + allowUnscheduledTasks: true, + projectStartDate: new Date('03/24/2019'), + projectEndDate: new Date('07/06/2024') + }, done); + }); + afterAll(function () { + if (ganttObj) { + destroyGantt(ganttObj); + } + }); + beforeEach((done: Function) => { + setTimeout(done, 1000); + }); + it('editing startdate', () => { + ganttObj.dataBind(); + let startDate: HTMLElement = ganttObj.element.querySelector('#treeGrid' + ganttObj.element.id + '_gridcontrol_content_table > tbody > tr:nth-child(1) > td:nth-child(4)') as HTMLElement; + triggerMouseEvent(startDate, 'dblclick'); + let input: any = (document.querySelector('#treeGrid' + ganttObj.element.id + '_gridcontrolStartDate')as any).ej2_instances[0]; + input.value = new Date('03/04/2024'); + let element: HTMLElement = ganttObj.element.querySelector('#treeGrid' + ganttObj.element.id + '_gridcontrol_content_table > tbody > tr:nth-child(3) > td:nth-child(2)') as HTMLElement; + triggerMouseEvent(element, 'click'); + ganttObj.selectRow(0); + expect(ganttObj.ganttChartModule.chartBodyContent.querySelector(`.${cls.weekend}`)['style'].left).toBe('0px'); + }); +}); \ No newline at end of file diff --git a/controls/gantt/src/gantt/actions/connector-line-edit.ts b/controls/gantt/src/gantt/actions/connector-line-edit.ts index 60a8a98402..e1287fa69e 100644 --- a/controls/gantt/src/gantt/actions/connector-line-edit.ts +++ b/controls/gantt/src/gantt/actions/connector-line-edit.ts @@ -685,14 +685,15 @@ export class ConnectorLineEdit { } else if (args.validateMode.preserveLinkWithEditing) { let connectedTaskId:any; if (this.parent.UpdateOffsetOnTaskbarEdit) { - this.calculateOffset(ganttRecord); let taskId:any = ganttRecord.ganttProperties.taskId; - ganttRecord.ganttProperties.predecessor.forEach(predecessor => { - if (taskId == predecessor.from) { - connectedTaskId = predecessor.to - return - } - }); + if (ganttRecord.ganttProperties.predecessor) { + ganttRecord.ganttProperties.predecessor.forEach(predecessor => { + if (taskId == predecessor.from) { + connectedTaskId = predecessor.to + return + } + }); + } } this.parent.editModule.updateEditedTask(args.editEventArgs); this.processPredecessors(connectedTaskId) diff --git a/controls/gantt/src/gantt/actions/dependency.ts b/controls/gantt/src/gantt/actions/dependency.ts index 1873eeea21..3fc47e1298 100644 --- a/controls/gantt/src/gantt/actions/dependency.ts +++ b/controls/gantt/src/gantt/actions/dependency.ts @@ -903,6 +903,9 @@ export class Dependency { || id.toString() === predecessor.from) && (!validationOn || validationOn === 'predecessor')) { this.validateChildGanttRecord(parentGanttRecord, record); + if (this.parent.editModule['editedRecord'] && this.parent.editModule['editedRecord'].hasChildRecords && !this.parent.editModule['editedRecord'].parentItem) { + this.isValidatedParentTaskID = record.ganttProperties.taskId; + } } } @@ -933,6 +936,13 @@ export class Dependency { } if (validationOn !== 'predecessor' && this.parent.isValidationEnabled) { this.validateChildGanttRecord(parentGanttRecord, record); + if (record && record.hasChildRecords && this.isValidatedParentTaskID != record.ganttProperties.taskId) { + this.updateChildItems(record); + this.isValidatedParentTaskID = record.ganttProperties.taskId; + } + if (this.parent.editModule['editedRecord'] && this.parent.editModule['editedRecord'].hasChildRecords && !this.parent.editModule['editedRecord'].parentItem) { + this.isValidatedParentTaskID = record.ganttProperties.taskId; + } } else if (!record.ganttProperties.isAutoSchedule && this.parent.UpdateOffsetOnTaskbarEdit) { this.parent.connectorLineEditModule['calculateOffset'](record); @@ -941,15 +951,20 @@ export class Dependency { if (record) { this.validatePredecessor(record, undefined, 'successor'); } continue; } - if (record) { + if (record) { if (this.parent.editModule.isFirstCall) { this.storeId = JSON.parse(JSON.stringify(this.parent.ids)); this.parent.editModule.isFirstCall = false } - let index: any = (this.storeId && this.storeId.indexOf(record[this.parent.taskFields.id].toString()) !== -1) ? this.storeId.indexOf(record[this.parent.taskFields.id].toString()) : -1; - if (index !== -1) { - this.storeId = this.storeId.slice(0, index).concat(this.storeId.slice(index + 1)); - this.validatePredecessor(record, undefined, 'successor'); + if (this.storeId) { + let index = (this.storeId.indexOf(record[this.parent.taskFields.id].toString()) !== -1) ? this.storeId.indexOf(record[this.parent.taskFields.id].toString()) : -1; + if (index !== -1) { + this.storeId = this.storeId.slice(0, index).concat(this.storeId.slice(index + 1)); + this.validatePredecessor(record, undefined, 'successor'); + } + } + else { + this.validatePredecessor(record, undefined, 'successor'); } } } @@ -969,12 +984,24 @@ export class Dependency { } } else if ((!record.hasChildRecords && taskBarModule.taskBarEditAction == 'ChildDrag') || - (record.hasChildRecords && taskBarModule.taskBarEditAction == 'ParentDrag')) { + (record.hasChildRecords && (taskBarModule.taskBarEditAction == 'ChildDrag' || taskBarModule.taskBarEditAction == 'ParentDrag'))) { this.updateChildItems(record); this.isValidatedParentTaskID = record.ganttProperties.taskId; } - if (!ganttProp.hasChildRecords) { + if (record.parentItem) { this.parent.dataOperation.updateParentItems(record, true); + const parentData: IGanttData = this.parent.getParentTask(record.parentItem); + const index: number = (this.storeId && this.storeId.indexOf(parentData[this.parent.taskFields.id].toString()) !== -1) ? this.storeId.indexOf(parentData[this.parent.taskFields.id].toString()) : -1; + if (parentData.ganttProperties.predecessor && parentData.ganttProperties.predecessor.length > 0 && index !== -1) { + for (let i: number = 0; i < parentData.ganttProperties.predecessor.length; i++) { + if (parentData.ganttProperties.predecessor[i as number].to != parentData.ganttProperties.taskId.toString()) { + const childRec: IGanttData = this.parent.flatData[this.parent.ids.indexOf(parentData.ganttProperties.predecessor[i as number].to)]; + if (childRec) { + this.validateChildGanttRecord(record, childRec); + } + } + } + } } } else if (record && record.hasChildRecords && this.isValidatedParentTaskID != record.ganttProperties.taskId && !ganttProp) { @@ -1063,6 +1090,9 @@ export class Dependency { this.validatedChildItems.push(childRecords[i as number]); } } + if (!this.parent.isLoad && childRecords[i as number].ganttProperties.predecessor && childRecords[i as number].ganttProperties.predecessor.length > 0) { + this.validatePredecessor(childRecords[i as number],[], ''); + } } } if (childRecords.length) { diff --git a/controls/gantt/src/gantt/actions/dialog-edit.ts b/controls/gantt/src/gantt/actions/dialog-edit.ts index a0dcdefbf8..be8006ed5c 100644 --- a/controls/gantt/src/gantt/actions/dialog-edit.ts +++ b/controls/gantt/src/gantt/actions/dialog-edit.ts @@ -73,11 +73,8 @@ export class DialogEdit { private storeColumn:any; private taskfields:any; private storeValidTab:any; - private singleTab:boolean; private storeDependencyTab:HTMLElement; private storeResourceTab:HTMLElement; - private isAddingDialog : boolean; - private isEditingDialog :boolean; private firstOccuringTab:string; private numericOrString: any; private types: IDependencyEditData[]; @@ -490,112 +487,62 @@ export class DialogEdit { target.style.pointerEvents = 'none'; if ((this.localeObj.getConstant('cancel')).toLowerCase() === (e.target as HTMLInputElement).innerText.trim().toLowerCase()) { if (this.dialog && !this.dialogObj.isDestroyed) { + this.CustomformObj = null; + this.formObj = null; this.dialogObj.hide(); this.dialogClose(); } } else { - if (this.singleTab && this.CustomformObj) { + if (this.CustomformObj) { if (!this.CustomformObj.validate()) { target.style.pointerEvents = ''; return; } - } else { - if (this.CustomformObj) { - if (this.isAddingDialog ) { - if (this.parent.addDialogFields.length > 1 && this.parent.addDialogFields[0].type == "Custom" && !this.formObj) { - if (!this.CustomformObj.validate()) { - target.style.pointerEvents = ''; - return; - } - } - } else if (this.isEditingDialog ) { - if (this.parent.editDialogFields.length > 1 && this.parent.editDialogFields[0].type == "Custom" && !this.formObj) { - if (!this.CustomformObj.validate()) { - target.style.pointerEvents = ''; - return; - } - } - } - if (!this.formObj.validate() && !this.CustomformObj.validate()) { + } + if (this.formObj) { + let formValid = this.formObj.validate(); + if (!formValid) { + target.style.pointerEvents = ''; + return; + } + } + if (this.storeDependencyTab || this.firstOccuringTab === "Dependency") { + let dependencyTab; + if (this.firstOccuringTab === "Dependency") { + let element = (e.target as Element).closest('#' + this.parent.element.id + '_dialog'); + dependencyTab = element.querySelector('.e-gridform'); + } else { + dependencyTab = this.storeDependencyTab.querySelector('.e-gridform'); + } + if (dependencyTab) { + let dependencyTabValid = dependencyTab['ej2_instances'][0].validate(); + if (!dependencyTabValid) { target.style.pointerEvents = ''; return; } } - if (this.formObj) { - let formValid = this.formObj.validate(); - if (this.storeDependencyTab) { - let dependencyTab = this.storeDependencyTab.querySelector('.e-gridform'); - if (dependencyTab) { - let dependencyTabValid = dependencyTab['ej2_instances'][0].validate(); - if (!formValid || !dependencyTabValid) { - target.style.pointerEvents = ''; - return; - } - } - } - if (this.storeResourceTab) { - let resourceTab = this.storeResourceTab.querySelector('.e-gridform'); - if (resourceTab) { - let resourceTabValid = resourceTab['ej2_instances'][0].validate(); - if (!formValid || !resourceTabValid) { - target.style.pointerEvents = ''; - return; - } - } - } - if (!formValid) { + } + if (this.storeResourceTab || this.firstOccuringTab === "Resources") { + let resourceTab; + if (this.firstOccuringTab === "Resources") { + let element = (e.target as Element).closest('#' + this.parent.element.id + '_dialog'); + resourceTab = element.querySelector('.e-gridform'); + } else { + resourceTab = this.storeResourceTab.querySelector('.e-gridform'); + } + + if (resourceTab) { + let resourceTabValid = resourceTab['ej2_instances'][0].validate(); + if (!resourceTabValid) { target.style.pointerEvents = ''; return; } - } else if (this.storeDependencyTab || this.firstOccuringTab == "Dependency") { - if (this.firstOccuringTab == "Dependency") { - let element = (e.target as Element).closest('#'+this.parent.element.id+'_dialog'); - let dependencyTab = element.querySelector('.e-gridform'); - if (dependencyTab) { - let dependencyTabValid = dependencyTab['ej2_instances'][0].validate(); - if (!dependencyTabValid) { - target.style.pointerEvents = ''; - return; - } - } - } else { - let dependencyTab = this.storeDependencyTab.querySelector('.e-gridform'); - if (dependencyTab) { - let dependencyTabValid = dependencyTab['ej2_instances'][0].validate(); - if (!dependencyTabValid) { - target.style.pointerEvents = ''; - return; - } - } - } - } else if (this.storeResourceTab || this.firstOccuringTab == "Resources") { - if (this.firstOccuringTab == "Resources") { - let element = (e.target as Element).closest('#'+this.parent.element.id+'_dialog'); - let resourceTab = element.querySelector('.e-gridform'); - if (resourceTab) { - let resourceTabValid = resourceTab['ej2_instances'][0].validate(); - if (!resourceTabValid) { - target.style.pointerEvents = ''; - return; - } - } - } else { - let resourceTab = this.storeResourceTab.querySelector('.e-gridform'); - if (resourceTab) { - let resourceTabValid = resourceTab['ej2_instances'][0].validate(); - if (!resourceTabValid) { - target.style.pointerEvents = ''; - return; - } - } - } } } this.initiateDialogSave(); + this.CustomformObj = null; + this.formObj = null; target.style.pointerEvents = 'auto'; - this.singleTab = false; - this.isAddingDialog = false; - this.isEditingDialog = false; } } /** @@ -821,6 +768,8 @@ export class DialogEdit { const columns: any[] = this.parent.treeGrid.grid.getColumns(); const isValidateColumn: boolean = columns.some(obj => obj.validationRules); if (isValidateColumn) { + this.CustomformObj = null; + this.formObj = null; this.changeFormObj(actionCompleteArgs.element, false); } this.parent.trigger('actionComplete', actionCompleteArgs, (actionCompleteArg: CObject) => { @@ -873,92 +822,50 @@ export class DialogEdit { this.validateColumn(this.storeColumn, this.taskfields, this.storeValidTab); } - if ((this.isFromAddDialog || this.isFromEditDialog) && this.isSingleCustomTab()) { - isCustomTab = true; - this.singleTab = true; - } - if (this.isFromAddDialog) { - if (this.parent.addDialogFields.length > 1) { - if (this.parent.addDialogFields[0].type === 'Resources' - || this.parent.addDialogFields[0].type === 'Dependency') { - this.firstOccuringTab = this.parent.addDialogFields[0].type; - } - } - if (this.parent.addDialogFields.length == 1) { - this.firstOccuringTab = this.parent.addDialogFields[0].type; - } - } - else if (this.isFromEditDialog) { - if (this.parent.editDialogFields.length > 1) { - if (this.parent.editDialogFields[0].type === 'Resources' - || this.parent.editDialogFields[0].type === 'Dependency') { - this.firstOccuringTab = this.parent.editDialogFields[0].type; - } + if (this.isFromAddDialog && this.parent.addDialogFields && this.parent.addDialogFields.length > 0) { + const firstFieldType = this.parent.addDialogFields[0].type; + if (firstFieldType === 'Resources' || firstFieldType === 'Dependency') { + this.firstOccuringTab = firstFieldType; } - if (this.parent.editDialogFields.length == 1) { - this.firstOccuringTab = this.parent.editDialogFields[0].type; + } else if (this.isFromEditDialog && this.parent.editDialogFields && this.parent.editDialogFields.length > 0) { + const firstFieldType = this.parent.editDialogFields[0].type; + if (firstFieldType === 'Resources' || firstFieldType === 'Dependency') { + this.firstOccuringTab = firstFieldType; } } - if (this.isFromEditDialog) { - if (this.parent.editDialogFields.length > 1) { - if (this.parent.editDialogFields[0].type == 'Custom') { - isCustomTab = true - } - } - } - if (this.isFromAddDialog) { - if (this.parent.addDialogFields.length > 1) { - if (this.parent.addDialogFields[0].type == 'Custom') { - isCustomTab = true - } - } - } - if (isCustomTab) { - this.CustomformObj = (actionCompleteArgs as HTMLElement).querySelector('.e-edit-form-row'); - if (this.CustomformObj === null) { - return; - } - - let validationRulesArray: { [key: string]: { [rule: string]: any } } = {}; - - for (let i: number = 0; i < this.customFieldColumn.length; i++) { - const column = this.customFieldColumn[parseInt(i.toString(), 10)]; - if (!column.visible) { - continue; + if (!this.CustomformObj || !this.formObj) { + const customFieldColumns = this.customFieldColumn; + const taskFieldColumns = this.taskFieldColumn; + if (!this.CustomformObj && customFieldColumns && customFieldColumns.length > 0) { + const validationRulesArray: { [key: string]: { [rule: string]: any } } = {}; + for (let i:number = 0; i < customFieldColumns.length; i++) { + const customColumn = customFieldColumns[i as number]; // Rename the variable + if (customColumn.visible && customColumn.validationRules) { + validationRulesArray[customColumn.field] = customColumn.validationRules; + } } - if (column.validationRules) { - validationRulesArray[column.field] = column.validationRules; + if (Object.keys(validationRulesArray).length > 0) { + this.CustomformObj = actionCompleteArgs.querySelector('#'+this.parent.element.id+'Custom0TabContainer') as HTMLElement; + if (this.CustomformObj) { + this.CustomformObj = this.createFormObj(this.CustomformObj, validationRulesArray); + } } } - - if (Object.keys(validationRulesArray).length > 0) { - this.CustomformObj = this.createFormObj(this.CustomformObj, validationRulesArray); - } - } else { - this.formObj = (actionCompleteArgs as HTMLElement).querySelector('.e-edit-form-row'); - if (this.formObj === null) { - return; - } - - let validationRulesArray: { [key: string]: { [rule: string]: any } } = {}; - - for (let i: number = 0; i < this.taskFieldColumn.length; i++) { - const column = this.taskFieldColumn[parseInt(i.toString(), 10)]; - if (!column.visible) { - continue; + if (!this.formObj && taskFieldColumns && taskFieldColumns.length > 0) { + const validationRulesArray: { [key: string]: { [rule: string]: any } } = {}; + for (let i:number = 0; i < taskFieldColumns.length; i++) { + const taskColumn = taskFieldColumns[i as number]; // Rename the variable + if (taskColumn.visible && taskColumn.validationRules) { + validationRulesArray[taskColumn.field] = taskColumn.validationRules; + } } - if (column.validationRules) { - validationRulesArray[column.field] = column.validationRules; + if (Object.keys(validationRulesArray).length > 0) { + this.formObj = actionCompleteArgs.querySelector('#'+this.parent.element.id+'GeneralTabContainer') as HTMLElement; + if (this.formObj) { + this.formObj = this.createFormObj(this.formObj, validationRulesArray); + } } } - - if (Object.keys(validationRulesArray).length > 0) { - this.formObj = this.createFormObj(this.formObj, validationRulesArray); - } - } - if (this.isFromAddDialog == true || this.isFromEditDialog) { - this.isAddingDialog = this.isFromAddDialog - this.isEditingDialog = this.isFromEditDialog } this.isFromAddDialog = false; this.isFromEditDialog = false; @@ -976,44 +883,24 @@ export class DialogEdit { return null; } - private isSingleCustomTab(): boolean { - const dialogFields = this.isFromAddDialog - ? this.parent.addDialogFields - : this.parent.editDialogFields; - - return this.isFromAddDialog || this.isFromEditDialog - ? dialogFields.length === 1 && dialogFields[0].type === 'Custom' - : false; - } - private validateColumn(storeColumn: any, taskfields: any, storeValidTab: any) { - storeColumn.forEach((column: { field: any }) => { - const field = column.field; - let isValueMatching = false; - const taskfieldValues: (string | number)[] = []; - if (this.parent.customColumns.indexOf(field) === -1) { - isValueMatching = true; - } - if (isValueMatching) { - if ((this.isFromAddDialog || this.isFromEditDialog) && storeValidTab) { - if (storeValidTab.some((obj: { fields: string }) => obj.fields.includes(column.field))) { - this.taskFieldColumn.push(column); - } + if (storeValidTab) { + storeValidTab.forEach((element: any) => { + const targetArray = element.type === "General" ? this.taskFieldColumn : this.customFieldColumn; + element.fields.forEach((field: any) => { + targetArray.push(this.parent.getColumnByField(field, storeColumn)); + }); + }); + } else { + storeColumn.forEach((column: { field: any }) => { + if (column.field.includes(this.parent.customColumns)) { + this.customFieldColumn.push(column); } else { this.taskFieldColumn.push(column); } - } else { - if ((this.isFromAddDialog || this.isFromEditDialog) && storeValidTab) { - if (storeValidTab.some((obj: { fields: string }) => obj.fields.includes(column.field))) { - this.customFieldColumn.push(column); - } - } else { - this.customFieldColumn.push(column); - } - } - }); + }); + } } - private createFormObj(form: HTMLFormElement, rules: { [name: string]: { [rule: string]: Object } }): FormValidator { return new FormValidator(form, { rules: rules, @@ -1234,11 +1121,9 @@ export class DialogEdit { numeric.min = 0; numeric.max = 100; } - if (taskSettings.work === column.field) { - numeric.change = (args: CObject): void => { - this.validateScheduleFields(args, column, ganttObj); - }; - } + numeric.change = (args: CObject): void => { + this.validateScheduleFields(args, column, ganttObj); + }; fieldsModel[column.field] = numeric; break; } @@ -1387,30 +1272,41 @@ export class DialogEdit { let tempValue: string | Date | number; const taskField: TaskFieldsModel = this.parent.taskFields; if (col.editType === 'stringedit') { - const textBox: TextBox = (dialog.querySelector('#' + ganttId + columnName)).ej2_instances[0]; - tempValue = !isNullOrUndefined(col.edit) && !isNullOrUndefined(col.edit.read) ? (col.edit.read as () => void)() : - !isNullOrUndefined(col.valueAccessor) ? (col.valueAccessor as Function) (columnName, ganttObj.editModule.dialogModule.editedRecord, col) : // eslint-disable-line - this.parent.dataOperation.getDurationString(ganttProp.duration, ganttProp.durationUnit); - if (textBox.value !== tempValue.toString() && taskField.duration === columnName) { - textBox.value = tempValue as string; - textBox.dataBind(); - } else if (taskField.startDate === columnName || taskField.endDate === columnName) { - textBox.value = taskField.startDate === columnName ? ganttProp.startDate.toString() : ganttProp.endDate.toString(); - textBox.dataBind(); + const element = dialog.querySelector('#' + ganttId + columnName); + if (element) { + const textBox = (element).ej2_instances[0]; + if (textBox) { + tempValue = !isNullOrUndefined(col.edit) && !isNullOrUndefined(col.edit.read) ? (col.edit.read as () => void)() : + !isNullOrUndefined(col.valueAccessor) ? (col.valueAccessor as Function)(columnName, ganttObj.editModule.dialogModule.editedRecord, col) : + this.parent.dataOperation.getDurationString(ganttProp.duration, ganttProp.durationUnit); + if (textBox.value !== tempValue.toString() && taskField.duration === columnName) { + textBox.value = tempValue as string; + textBox.dataBind(); + } else if (taskField.startDate === columnName || taskField.endDate === columnName) { + textBox.value = taskField.startDate === columnName ? ganttProp.startDate.toString() : ganttProp.endDate.toString(); + textBox.dataBind(); + } + } } } else if (col.editType === 'datepickeredit' || col.editType === 'datetimepickeredit') { - const picker: DatePicker = col.editType === 'datepickeredit' ? - (dialog.querySelector('#' + ganttId + columnName)).ej2_instances[0] : - (dialog.querySelector('#' + ganttId + columnName)).ej2_instances[0]; - tempValue = ganttProp[ganttField as string]; - if (((isNullOrUndefined(picker.value)) && !isNullOrUndefined(tempValue)) || - (isNullOrUndefined(tempValue) && !isNullOrUndefined(picker.value)) || - (picker.value !== tempValue && !isNullOrUndefined(picker.value) && !isNullOrUndefined(tempValue) - && picker.value.toString() !== tempValue.toString())) { - picker.value = tempValue as Date; - picker.dataBind(); - } - } else if (col.editType === 'numericedit') { + const element = dialog.querySelector('#' + ganttId + columnName); + if (element) { + const picker = col.editType === 'datepickeredit' ? + ((element).ej2_instances[0]) : + ((element).ej2_instances[0]); + if (picker) { + tempValue = ganttProp[ganttField as string]; + if (((isNullOrUndefined(picker.value)) && !isNullOrUndefined(tempValue)) || + (isNullOrUndefined(tempValue) && !isNullOrUndefined(picker.value)) || + (picker.value !== tempValue && !isNullOrUndefined(picker.value) && !isNullOrUndefined(tempValue) + && picker.value.toString() !== tempValue.toString())) { + picker.value = tempValue as Date; + picker.dataBind(); + } + } + } + } + else if (col.editType === 'numericedit') { const numericTextBox: NumericTextBox = ( dialog.querySelector('#' + ganttId + columnName)).ej2_instances[0]; tempValue = ganttProp[ganttField as string]; @@ -2755,7 +2651,12 @@ export class DialogEdit { inputModel.checked = false; } } else { - inputModel.value = ganttData[column.field]; + if (!this.parent.taskFields[column.field] && column.editType == 'numericedit' && (ganttData[column.field] === "" || ganttData[column.field] === 0)) { + inputModel.value = 0; + } + else { + inputModel.value = ganttData[column.field]; + } } } if (!isNullOrUndefined(column.edit) && isNullOrUndefined(column.edit.params)) { @@ -3003,6 +2904,7 @@ export class DialogEdit { /** * If any update on edited task do it here */ + this.parent.editModule['editedRecord'] = this.rowData; this.parent.dataOperation.updateWidthLeft(this.rowData); const editArgs: ITaskbarEditedEventArgs = { data: this.rowData, @@ -3010,6 +2912,7 @@ export class DialogEdit { }; this.parent.editModule.initiateUpdateAction(editArgs); } else { + this.parent.editModule['editedRecord'] = this.addedRecord; if (this.parent.viewType === 'ResourceView') { const newRecords: Object = extend({}, this.addedRecord, true); if (newRecords[this.parent.taskFields.resourceInfo].length) { @@ -3084,15 +2987,10 @@ export class DialogEdit { controlObj.value = valueString; } const column: GanttColumnModel = ganttObj.columnByField[fieldName as string]; - if (fieldName == this.parent.taskFields.duration) { - if (parseInt(this.rowData[fieldName as string]) != parseInt(controlObj.value as string)) { - this.disableUndo = true; - } - } - else { - if (this.rowData[fieldName as string] != controlObj.value) { - this.disableUndo = true; - } + if (fieldName === this.parent.taskFields.duration ? + parseInt(this.rowData[fieldName as string]) !== parseInt(controlObj.value as string) : + this.rowData[fieldName as string] !== controlObj.value) { + this.disableUndo = true; } if (!isNullOrUndefined(column.edit) && isNullOrUndefined(column.edit.params)) { let read: Function = column.edit.read as Function; diff --git a/controls/gantt/src/gantt/actions/edit.ts b/controls/gantt/src/gantt/actions/edit.ts index 5c3095fd2f..f5b1dca529 100644 --- a/controls/gantt/src/gantt/actions/edit.ts +++ b/controls/gantt/src/gantt/actions/edit.ts @@ -66,6 +66,7 @@ export class Edit { public taskbarEditModule: TaskbarEdit; public dialogModule: DialogEdit; public isResourceTaskDeleted: boolean = false; + private editedRecord: IGanttData; constructor(parent?: Gantt) { this.parent = parent; this.parent.predecessorModule.validatedChildItems = []; @@ -932,6 +933,7 @@ export class Edit { */ public updateEditedTask(args: ITaskbarEditedEventArgs): void { const ganttRecord: IGanttData = args.data; + this.editedRecord = ganttRecord; if (this.parent.autoCalculateDateScheduling) { this.updateParentChildRecord(ganttRecord); } @@ -941,6 +943,9 @@ export class Edit { const child: IGanttData = this.parent.predecessorModule.validatedChildItems[i as number]; if (child.ganttProperties.predecessor && child.ganttProperties.predecessor.length > 0) { this.parent.editedTaskBarItem = child; + if (!this.isValidatedEditedRecord) { + this.isFirstCall = true; + } this.parent.predecessorModule.validatePredecessor(child, [], ''); } } @@ -956,12 +961,12 @@ export class Edit { this.parent.predecessorModule.validatePredecessor(ganttRecord, [], ''); } this.isValidatedEditedRecord = false; - this.parent.predecessorModule.isValidatedParentTaskID = ''; } - if (this.parent.allowParentDependency && ganttRecord.hasChildRecords && this.parent.previousRecords[ganttRecord.uniqueID].ganttProperties.startDate && + if (this.parent.allowParentDependency && this.parent.predecessorModule.isValidatedParentTaskID != ganttRecord.ganttProperties.taskId && ganttRecord.hasChildRecords && this.parent.previousRecords[ganttRecord.uniqueID].ganttProperties.startDate && (args.action === "DrawConnectorLine")) { this.parent.predecessorModule['updateChildItems'](ganttRecord); } + this.parent.predecessorModule.isValidatedParentTaskID = ''; if(this.parent.undoRedoModule && this.parent.undoRedoModule['isUndoRedoPerformed']) { for (let i: number = 0; i < ganttRecord.childRecords.length; i++) { if (ganttRecord.childRecords[i as number].ganttProperties.predecessor) { @@ -1683,20 +1688,10 @@ export class Edit { for (let j: number = 0; j < this.dialogModule['indexes'].deletedIndexes.length; j++) { if (this.dialogModule['indexes'].deletedIndexes[j as number].data.parentUniqueID == draggedRecord.parentUniqueID && draggedRecord.ganttProperties.taskId == this.dialogModule['indexes'].deletedIndexes[j as number].data.ganttProperties.taskId) { let toIndex: number = this.dialogModule['indexes'].deletedIndexes[j as number].index; - if (this.dialogModule['indexes'].deletedIndexes[j as number].position == 'above') { - childRecordsLength = toIndex; - } - else { - childRecordsLength = toIndex + 1; - } + this.dialogModule['indexes'].deletedIndexes[j as number].position == 'above' ? (childRecordsLength = toIndex) : (childRecordsLength = toIndex + 1); for (let i: number = 0; i < droppedRecord.childRecords.length; i++) { if ('T' + droppedRecord.childRecords[i as number].ganttProperties.taskId == this.dialogModule['indexes'].deletedIndexes[j as number].id) { - if (this.dialogModule['indexes'].deletedIndexes[j as number].position == 'above') { - spliceIndex = i; - } - else { - spliceIndex = i + 1; - } + this.dialogModule['indexes'].deletedIndexes[j as number].position == 'above' ? (spliceIndex = i) : spliceIndex = i + 1; break; } } @@ -2305,7 +2300,9 @@ export class Edit { if (isRemoteData(this.parent.dataSource)) { const data: DataManager = this.parent.dataSource as DataManager; if (this.parent.timezone) { - updateDates(eventArg.modifiedTaskData as IGanttData, this.parent); + (eventArg.modifiedRecords as IGanttData[]).forEach((modifiedRecord: IGanttData) => { + updateDates(modifiedRecord, this.parent); + }); } const updatedData: object = { deletedRecords: getTaskData(eventArg.data as IGanttData[], null, null, this.parent), // to check @@ -3948,7 +3945,7 @@ export class Edit { const fromRecord: IGanttData = this.parent.getRecordByID(droppedRec.ganttProperties.predecessor[count as number].from); const toRecord: IGanttData = this.parent.getRecordByID(droppedRec.ganttProperties.predecessor[count as number].to); const validPredecessor: boolean = this.parent.connectorLineEditModule.validateParentPredecessor(fromRecord, toRecord); - if (droppedRec.ganttProperties.predecessor && !validPredecessor) { + if (droppedRec.ganttProperties.predecessor && (!validPredecessor || !this.parent.allowParentDependency)) { this.parent.editModule.removePredecessorOnDelete(droppedRec); droppedRec.ganttProperties.predecessor.splice(count, 1); droppedRec.ganttProperties.predecessorsName = null; diff --git a/controls/gantt/src/gantt/actions/rowdragdrop.ts b/controls/gantt/src/gantt/actions/rowdragdrop.ts index 238de6e285..1723eae77b 100644 --- a/controls/gantt/src/gantt/actions/rowdragdrop.ts +++ b/controls/gantt/src/gantt/actions/rowdragdrop.ts @@ -386,7 +386,7 @@ export class RowDD { const fromRecord: IGanttData = this.parent.getRecordByID(droppedRecord.ganttProperties.predecessor[count as number].from); const toRecord: IGanttData = this.parent.getRecordByID(droppedRecord.ganttProperties.predecessor[count as number].to) const validPredecessor: boolean = this.parent.connectorLineEditModule.validateParentPredecessor(fromRecord, toRecord); - if (droppedRecord.ganttProperties.predecessor && !validPredecessor) { + if (droppedRecord.ganttProperties.predecessor && (!validPredecessor || !this.parent.allowParentDependency)) { this.parent.editModule.removePredecessorOnDelete(droppedRecord); droppedRecord.ganttProperties.predecessor.splice(0, 1); if (droppedRecord.ganttProperties.predecessorsName) { @@ -733,6 +733,7 @@ export class RowDD { this.parent.setRecordValue('level' , this.draggedRecord.level , this.draggedRecord); this.updateChildRecordLevel(draggedRecord, level); } + droppedRecord.expanded = true; } } private deleteDragRow(): void { diff --git a/controls/gantt/src/gantt/actions/undo-redo.ts b/controls/gantt/src/gantt/actions/undo-redo.ts index 0fe22de1de..56d0bce4ce 100644 --- a/controls/gantt/src/gantt/actions/undo-redo.ts +++ b/controls/gantt/src/gantt/actions/undo-redo.ts @@ -271,7 +271,7 @@ export class UndoRedo { } } else if (updateAction['action'] === 'Add') { - const isShowDeleteConfirmDialog: boolean = extend([], this.parent.editSettings.showDeleteConfirmDialog, [], true)[0]; + const isShowDeleteConfirmDialog: boolean = extend([], [this.parent.editSettings.showDeleteConfirmDialog], [], true)[0]; this.parent.editSettings.showDeleteConfirmDialog = false; let deleteRec: IGanttData = updateAction['addedRecords']; if (this.parent.viewType === 'ResourceView' && updateAction['addedRecords'].length == 1 && (updateAction['addedRecords'][0] as IGanttData).parentItem) { @@ -534,7 +534,7 @@ export class UndoRedo { } } } - const isShowDeleteConfirmDialog: boolean = extend([], this.parent.editSettings.showDeleteConfirmDialog, [], true)[0]; + const isShowDeleteConfirmDialog: boolean = extend([], [this.parent.editSettings.showDeleteConfirmDialog], [], true)[0]; this.parent.editSettings.showDeleteConfirmDialog = false; this.parent.deleteRecord(updateAction['deleteRecords']); this.parent.editSettings.showDeleteConfirmDialog = isShowDeleteConfirmDialog; diff --git a/controls/gantt/src/gantt/base/date-processor.ts b/controls/gantt/src/gantt/base/date-processor.ts index 4f843560e5..611d24f493 100644 --- a/controls/gantt/src/gantt/base/date-processor.ts +++ b/controls/gantt/src/gantt/base/date-processor.ts @@ -114,7 +114,13 @@ export class DateProcessor { if (hour > this.parent.defaultEndTime) { this.setTime(this.parent.defaultEndTime, cloneEndDate); } else if (hour <= this.parent.defaultStartTime && !validateAsMilestone) { - cloneEndDate.setDate(cloneEndDate.getDate() - 1); + let taskfields = this.parent.taskFields; + if(this.parent.editModule && this.parent.editModule['editedRecord'] && (!this.parent.editModule['editedRecord'][taskfields.startDate] && this.parent.editModule['editedRecord'][taskfields.endDate])) { + cloneEndDate.setDate(cloneEndDate.getDate()); + } + else { + cloneEndDate.setDate(cloneEndDate.getDate() - 1); + } this.setTime(this.parent.defaultEndTime, cloneEndDate); } else if (hour > this.parent.defaultStartTime && hour < this.parent.defaultEndTime) { for (let index: number = 0; index < this.parent.workingTimeRanges.length; index++) { diff --git a/controls/gantt/src/gantt/base/gantt-chart.ts b/controls/gantt/src/gantt/base/gantt-chart.ts index 9bc34a7553..27c60df8e0 100644 --- a/controls/gantt/src/gantt/base/gantt-chart.ts +++ b/controls/gantt/src/gantt/base/gantt-chart.ts @@ -330,8 +330,15 @@ export class GanttChart { const wrapper1: HTMLElement = getValue('wrapper', this.parent.ganttChartModule.virtualRender); const treegridVirtualHeight: string = (this.parent.treeGrid.element.getElementsByClassName('e-virtualtable')[0] as HTMLElement).style.transform; const virtualTable: string = (document.getElementsByClassName('e-virtualtable')[1] as HTMLElement).style.transform; - if (this.parent.enableTimelineVirtualization && virtualTable !== "") { - const translateXValue: string = virtualTable.match(/translate.*\((.+)\)/)[1].split(', ')[0]; + if (this.parent.enableTimelineVirtualization) { + let translateXValue: string; + if (virtualTable !== "") { + translateXValue = virtualTable.match(/translate.*\((.+)\)/)[1].split(', ')[0]; + } + else { + const chartTransform: string = (this.parent.ganttChartModule.scrollElement.getElementsByClassName('e-virtualtable')[0] as HTMLElement).style.transform; + translateXValue = chartTransform.match(/translate.*\((.+)\)/)[1].split(', ')[0]; + } const translateYValue: string = treegridVirtualHeight.match(/translate.*\((.+)\)/)[1].split(', ')[1]; wrapper1.style.transform = `translate(${translateXValue}, ${translateYValue})`; } diff --git a/controls/gantt/src/gantt/base/gantt-model.d.ts b/controls/gantt/src/gantt/base/gantt-model.d.ts index c2c077c3fe..dd29e47563 100644 --- a/controls/gantt/src/gantt/base/gantt-model.d.ts +++ b/controls/gantt/src/gantt/base/gantt-model.d.ts @@ -129,7 +129,7 @@ export interface GanttModel extends ComponentModel{ /** * `undoRedoActions` Defines action items that retain for undo and redo operation. * - * @default null + * @default ['Sorting','Add','ColumnReorder','ColumnResize','ColumnState','Delete','Edit','Filtering','Indent','Outdent','NextTimeSpan','PreviousTimeSpan','RowDragAndDrop','Search'] */ undoRedoActions?: GanttAction[]; diff --git a/controls/gantt/src/gantt/base/gantt.ts b/controls/gantt/src/gantt/base/gantt.ts index 90fe5d32be..63b2b05de1 100644 --- a/controls/gantt/src/gantt/base/gantt.ts +++ b/controls/gantt/src/gantt/base/gantt.ts @@ -453,9 +453,9 @@ export class Gantt extends Component /** * `undoRedoActions` Defines action items that retain for undo and redo operation. * - * @default null + * @default ['Sorting','Add','ColumnReorder','ColumnResize','ColumnState','Delete','Edit','Filtering','Indent','Outdent','NextTimeSpan','PreviousTimeSpan','RowDragAndDrop','Search'] */ - @Property() + @Property(['Sorting','Add','ColumnReorder','ColumnResize','ColumnState','Delete','Edit','Filtering','Indent','Outdent','NextTimeSpan','PreviousTimeSpan','RowDragAndDrop','Search']) public undoRedoActions: GanttAction[]; /** @@ -2574,7 +2574,7 @@ export class Gantt extends Component }, { topTier: { unit: 'Week', format: 'MMM dd, yyyy', count: 1 }, - bottomTier: { unit: 'Day', format: null, count: 1 }, timelineUnitSize: 33, level: 11, + bottomTier: { unit: 'Day', format: 'd', count: 1 }, timelineUnitSize: 33, level: 11, timelineViewMode: 'Week', weekStartDay: _WeekStartDay, updateTimescaleView: true, weekendBackground: null, showTooltip: true }, { @@ -3587,7 +3587,7 @@ export class Gantt extends Component zoomToFit: 'Zoom to fit', excelExport: 'Excel export', csvExport: 'CSV export', - pdfExport: 'Pdf export', + pdfExport: 'PDF export', expandAll: 'Expand all', collapseAll: 'Collapse all', nextTimeSpan: 'Next timespan', diff --git a/controls/gantt/src/gantt/base/task-processor.ts b/controls/gantt/src/gantt/base/task-processor.ts index bc4971e1d0..e4af6f61ee 100644 --- a/controls/gantt/src/gantt/base/task-processor.ts +++ b/controls/gantt/src/gantt/base/task-processor.ts @@ -161,8 +161,9 @@ export class TaskProcessor extends DateProcessor { this.constructResourceViewDataSource(resources, hierarchicalData, unassignedTasks); if (unassignedTasks.length > 0) { const record: Object = {}; + let resourceName: string = this.parent.resourceFields.name || 'resourceName'; record[this.parent.resourceFields.id] = 0; - record[this.parent.resourceFields.name] = this.parent.localeObj.getConstant('unassignedTask'); + record[resourceName as string] = this.parent.localeObj.getConstant('unassignedTask'); record[this.parent.taskFields.child] = unassignedTasks; resources.push(record); } @@ -385,7 +386,12 @@ export class TaskProcessor extends DateProcessor { } } else if (!isNullOrUndefined(data[resourceFields.id])) { id = data[resourceFields.id]; - name = data[resourceFields.name]; + if (isNullOrUndefined(data[resourceFields.name]) && data['resourceName'] === "Unassigned Task"){ + name = data['resourceName']; + } + else{ + name = data[resourceFields.name]; + } this.addTaskData(ganttData, data, false); } this.parent.setRecordValue('taskId', id, ganttProperties, true); @@ -697,7 +703,7 @@ export class TaskProcessor extends DateProcessor { public updateWorkWithDuration(ganttData: IGanttData): void { const resources: Object[] = ganttData.ganttProperties.resourceInfo; let work: number = 0; - if (!isNullOrUndefined(resources)) { + if (!isNullOrUndefined(resources) && !ganttData.hasChildRecords) { const resourcesLength: number = resources.length; let index: number; let resourceUnit: number; @@ -1342,11 +1348,11 @@ export class TaskProcessor extends DateProcessor { this.parent.ganttChartModule.scrollObject['isSetScrollLeft'])) && !isFromTimelineVirtulization) { isValid = false; } - if (!this.parent.editModule && this.parent.enableTimelineVirtualization && isValid && !this.parent.timelineModule['performedTimeSpanAction']) { + if (this.parent.enableTimelineVirtualization && isValid && !this.parent.timelineModule['performedTimeSpanAction']) { leftValueForStartDate = (this.parent.enableTimelineVirtualization && this.parent.ganttChartModule.scrollObject.element.scrollLeft != 0) ? this.parent.ganttChartModule.scrollObject.getTimelineLeft() : null; } - const timelineStartDate: Date = (this.parent.editModule && this.parent.enableTimelineVirtualization && !isNullOrUndefined(leftValueForStartDate)) + const timelineStartDate: Date = (this.parent.enableTimelineVirtualization && !isNullOrUndefined(leftValueForStartDate)) ? new Date((this.parent.timelineModule['dateByLeftValue'](leftValueForStartDate)).toString()) : new Date(this.parent.timelineModule.timelineStartDate); if (timelineStartDate) { let leftValue: number = (date.getTime() - timelineStartDate.getTime()) / (1000 * 60 * 60 * 24) * this.parent.perDayWidth; diff --git a/controls/gantt/src/gantt/base/utils.ts b/controls/gantt/src/gantt/base/utils.ts index 76e9f9a405..56beadf418 100644 --- a/controls/gantt/src/gantt/base/utils.ts +++ b/controls/gantt/src/gantt/base/utils.ts @@ -126,18 +126,20 @@ export function getTaskData( */ export function updateDates(record: IGanttData, parent: Gantt): void { // let startDate: Date = (record as IGanttData).taskData[parent.taskFields.startDate]; - (record as IGanttData).taskData[parent.taskFields.startDate] = parent.dateValidationModule.remove( - (record as IGanttData).ganttProperties.startDate, parent.timezone); - if (parent.taskFields.endDate != null) { - (record as IGanttData).taskData[parent.taskFields.endDate] = parent.dateValidationModule.remove( - (record as IGanttData).ganttProperties.endDate, parent.timezone); - } - if (parent.taskFields.baselineEndDate || parent.taskFields.baselineStartDate) { - (record as IGanttData).taskData[parent.taskFields.baselineStartDate] = parent.dateValidationModule.remove( - (record as IGanttData).ganttProperties.baselineStartDate, parent.timezone); + if (record && !isNullOrUndefined((record as IGanttData).ganttProperties)) { + (record as IGanttData).taskData[parent.taskFields.startDate] = parent.dateValidationModule.remove( + (record as IGanttData).ganttProperties.startDate, parent.timezone); + if (parent.taskFields.endDate != null) { + (record as IGanttData).taskData[parent.taskFields.endDate] = parent.dateValidationModule.remove( + (record as IGanttData).ganttProperties.endDate, parent.timezone); + } + if (parent.taskFields.baselineEndDate || parent.taskFields.baselineStartDate) { + (record as IGanttData).taskData[parent.taskFields.baselineStartDate] = parent.dateValidationModule.remove( + (record as IGanttData).ganttProperties.baselineStartDate, parent.timezone); - (record as IGanttData).taskData[parent.taskFields.baselineEndDate] = parent.dateValidationModule.remove( - (record as IGanttData).ganttProperties.baselineEndDate, parent.timezone); + (record as IGanttData).taskData[parent.taskFields.baselineEndDate] = parent.dateValidationModule.remove( + (record as IGanttData).ganttProperties.baselineEndDate, parent.timezone); + } } return null; } diff --git a/controls/gantt/src/gantt/export/pdf-taskbar.ts b/controls/gantt/src/gantt/export/pdf-taskbar.ts index ed6d2418af..6029dda775 100644 --- a/controls/gantt/src/gantt/export/pdf-taskbar.ts +++ b/controls/gantt/src/gantt/export/pdf-taskbar.ts @@ -107,7 +107,6 @@ export class PdfGanttTaskbarCollection { public indicators: IIndicator[]; public labelSettings: ILabel; public taskbarTemplate : ITemplateDetails; - public rightLabelBoundsTemplates: number; public previousWidthofLeftValue: number; public previousWidthofLeftImage: number; public totalLeftWidth: number; @@ -187,29 +186,19 @@ export class PdfGanttTaskbarCollection { let font: PdfFont = new PdfStandardFont(this.fontFamily, 9, PdfFontStyle.Regular); const fontColor: PdfPen = null; const fontBrush: PdfBrush = new PdfSolidBrush(this.progressFontColor); - let customizedFont : PdfFont; - let customizedFontBrush : PdfBrush; - let customizedFontColor : PdfPen; - if(!isNullOrUndefined(taskbar.taskbarTemplate.value)){ - if(taskbar.taskbarTemplate.fontStyle.fontFamily && taskbar.taskbarTemplate.fontStyle.fontSize){ - customizedFont = new PdfStandardFont(taskbar.taskbarTemplate.fontStyle.fontFamily,taskbar.taskbarTemplate.fontStyle.fontSize,taskbar.taskbarTemplate.fontStyle.fontStyle); - }else{ - customizedFont = font; - } - if(taskbar.taskbarTemplate.fontStyle.fontColor){ - customizedFontBrush = new PdfSolidBrush(taskbar.taskbarTemplate.fontStyle.fontColor); - } - else{ - customizedFontBrush = fontBrush; - } - if(taskbar.taskbarTemplate.fontStyle.fontBrush){ - customizedFontColor = new PdfPen(taskbar.taskbarTemplate.fontStyle.fontBrush); - } - else{ - customizedFontColor = fontColor; - } - } - if (!isNullOrUndefined(this.parent.pdfExportModule['helper']['exportProps'].ganttStyle) && + let customizedFont: PdfFont; + let customizedFontBrush: PdfBrush; + let customizedFontColor: PdfPen; + customizedFont = !isNullOrUndefined(taskbar.taskbarTemplate.value) && taskbar.taskbarTemplate.fontStyle && taskbar.taskbarTemplate.fontStyle.fontFamily && taskbar.taskbarTemplate.fontStyle.fontSize + ? new PdfStandardFont(taskbar.taskbarTemplate.fontStyle.fontFamily, taskbar.taskbarTemplate.fontStyle.fontSize, taskbar.taskbarTemplate.fontStyle.fontStyle) + : font; + customizedFontBrush = !isNullOrUndefined(taskbar.taskbarTemplate.value) && taskbar.taskbarTemplate.fontStyle && taskbar.taskbarTemplate.fontStyle.fontColor + ? new PdfSolidBrush(taskbar.taskbarTemplate.fontStyle.fontColor) + : fontBrush; + customizedFontColor = !isNullOrUndefined(taskbar.taskbarTemplate.value) && taskbar.taskbarTemplate.fontStyle && taskbar.taskbarTemplate.fontStyle.fontBrush + ? new PdfPen(taskbar.taskbarTemplate.fontStyle.fontBrush) + : fontColor; + if (!isNullOrUndefined(this.parent.pdfExportModule['helper']['exportProps'].ganttStyle) && this.parent.pdfExportModule['helper']['exportProps'].ganttStyle.font) { font = this.parent.pdfExportModule['helper']['exportProps'].ganttStyle.font; } diff --git a/controls/gantt/src/gantt/renderer/chart-rows.ts b/controls/gantt/src/gantt/renderer/chart-rows.ts index 0a18f09474..b32889c343 100644 --- a/controls/gantt/src/gantt/renderer/chart-rows.ts +++ b/controls/gantt/src/gantt/renderer/chart-rows.ts @@ -39,6 +39,7 @@ export class ChartRows extends DateProcessor { private refreshedTr: Element[] = []; private refreshedData: IGanttData[] = []; private isUpdated: boolean = true; + private taskBaselineTemplateNode: NodeList = null; constructor(ganttObj?: Gantt) { super(ganttObj); this.parent = ganttObj; @@ -1506,25 +1507,42 @@ export class ChartRows extends DateProcessor { */ public getGanttChartRow(i: number, tempTemplateData: IGanttData): Node { this.templateData = tempTemplateData; - let taskBaselineTemplateNode: NodeList = null; const parentTrNode: NodeList = this.getTableTrNode(i); const leftLabelNode: NodeList = this.getLeftLabelNode(i); - const taskbarContainerNode: NodeList = this.taskbarContainer(); + let taskbarContainerNode: NodeList | NodeList[] = this.taskbarContainer(); (taskbarContainerNode[0]).setAttribute('aria-label', this.generateAriaLabel(this.templateData)); (taskbarContainerNode[0]).setAttribute('rowUniqueId', this.templateData.ganttProperties.rowUniqueID); let connectorLineLeftNode: NodeList; - if (!this.templateData.hasChildRecords && !this.parent.allowParentDependency) { - const connectorLineLeftNode: NodeList = this.getLeftPointNode(); + let connectorLineRightNode: NodeList; + connectorLineLeftNode = this.getLeftPointNode(); + if ((this.templateData.ganttProperties.isAutoSchedule && this.parent.viewType === 'ProjectView') || !this.templateData.hasChildRecords) { taskbarContainerNode[0].appendChild([].slice.call(connectorLineLeftNode)[0]); } - else if (this.parent.allowParentDependency) { - connectorLineLeftNode = this.getLeftPointNode(); - if ((this.templateData.ganttProperties.isAutoSchedule && this.parent.viewType === 'ProjectView') || !this.templateData.hasChildRecords) { - taskbarContainerNode[0].appendChild([].slice.call(connectorLineLeftNode)[0]); - } - } if (this.templateData.hasChildRecords) { - const parentTaskbarTemplateNode: NodeList = this.getParentTaskbarNode(i, taskbarContainerNode); + let parentTaskbarTemplateNode: NodeList; + if (!this.parent.enableMultiTaskbar || (this.parent.enableMultiTaskbar && this.templateData.expanded)) { + parentTaskbarTemplateNode = this.getParentTaskbarNode(i, taskbarContainerNode); + } + else { + taskbarContainerNode = []; + for (let j: number = 0; j < this.templateData.childRecords.length; j++) { + this.templateData = this.templateData.childRecords[j as number]; + let taskbarContainerNode1: NodeList | NodeList[] = this.taskbarContainer(); + (taskbarContainerNode1[0]).setAttribute('aria-label', this.generateAriaLabel(this.templateData)); + (taskbarContainerNode1[0]).setAttribute('rowUniqueId', this.templateData.ganttProperties.rowUniqueID); + if (!this.parent.allowParentDependency) { + connectorLineLeftNode = this.getLeftPointNode(); + taskbarContainerNode1[0].appendChild([].slice.call(connectorLineLeftNode)[0]); + } + else { + connectorLineLeftNode = this.getLeftPointNode(); + if ((this.templateData.ganttProperties.isAutoSchedule) || !this.templateData.hasChildRecords) { + taskbarContainerNode1[0].appendChild([].slice.call(connectorLineLeftNode)[0]); + } + } + this.appendChildTaskbars(tempTemplateData,i,taskbarContainerNode1,connectorLineRightNode,taskbarContainerNode); + } + } if (!this.templateData.ganttProperties.isAutoSchedule) { const manualTaskbar: NodeList = this.getManualTaskbar(); if (!isNullOrUndefined(manualTaskbar[0])) { @@ -1533,130 +1551,68 @@ export class ChartRows extends DateProcessor { const connectorLineRightNode: NodeList = this.getRightPointNode(); manualTaskbar[0].appendChild([].slice.call(connectorLineRightNode)[0]); } - taskbarContainerNode[0].appendChild([].slice.call(manualTaskbar)[0]); + (taskbarContainerNode[0] as any).appendChild([].slice.call(manualTaskbar)[0]); } } if ((this.templateData.ganttProperties.autoDuration !== 0) && !this.templateData.ganttProperties.isMilestone && parentTaskbarTemplateNode && parentTaskbarTemplateNode.length > 0) { append(parentTaskbarTemplateNode, taskbarContainerNode[0] as Element); } else if((this.templateData.ganttProperties.duration === 0 && this.templateData.ganttProperties.isMilestone && this.templateData.ganttProperties.isAutoSchedule)){ - const milestoneTemplateNode: NodeList = this.getMilestoneNode(i, taskbarContainerNode); + const milestoneTemplateNode: NodeList = this.getMilestoneNode(i, taskbarContainerNode as NodeList); if (milestoneTemplateNode && milestoneTemplateNode.length > 0) { append(milestoneTemplateNode, taskbarContainerNode[0] as Element); } } if (this.parent.renderBaseline && this.templateData.ganttProperties.baselineStartDate && this.templateData.ganttProperties.baselineEndDate) { - taskBaselineTemplateNode = ((this.templateData.ganttProperties.baselineStartDate.getTime() === this.templateData.ganttProperties.baselineEndDate.getTime()) || ( + this.taskBaselineTemplateNode = ((this.templateData.ganttProperties.baselineStartDate.getTime() === this.templateData.ganttProperties.baselineEndDate.getTime()) || ( (!isNullOrUndefined(this.templateData.ganttProperties.baselineStartDate) && !isNullOrUndefined(this.templateData.ganttProperties.startDate) && (this.templateData.ganttProperties.baselineStartDate.getTime() === this.templateData.ganttProperties.startDate.getTime())) && (!isNullOrUndefined(this.templateData.ganttProperties.baselineEndDate) && !isNullOrUndefined(this.templateData.ganttProperties.endDate) && (this.templateData.ganttProperties.baselineEndDate.getTime() === this.templateData.ganttProperties.endDate.getTime())) && this.templateData.ganttProperties.isMilestone)) ? this.getMilestoneBaselineNode() : this.getTaskBaselineNode(); } - } else if (this.templateData.ganttProperties.isMilestone) { - const milestoneTemplateNode: NodeList = this.getMilestoneNode(i, taskbarContainerNode); - if (milestoneTemplateNode && milestoneTemplateNode.length > 0) { - append(milestoneTemplateNode, taskbarContainerNode[0] as Element); + if (!this.parent.enableMultiTaskbar || (this.parent.enableMultiTaskbar && this.templateData.expanded)) { + if (this.parent.allowParentDependency && ((this.templateData.ganttProperties.isAutoSchedule && this.parent.viewType === 'ProjectView') || !this.templateData.hasChildRecords)) { + connectorLineRightNode = this.getRightPointNode(); + (taskbarContainerNode[0] as any).appendChild([].slice.call(connectorLineRightNode)[0]); } - if (this.parent.renderBaseline && this.templateData.ganttProperties.baselineStartDate && - this.templateData.ganttProperties.baselineEndDate) { - taskBaselineTemplateNode = ((this.templateData.ganttProperties.baselineStartDate.getTime() === this.templateData.ganttProperties.baselineEndDate.getTime()) || ( - (!isNullOrUndefined(this.templateData.ganttProperties.baselineStartDate) && !isNullOrUndefined(this.templateData.ganttProperties.startDate) && (this.templateData.ganttProperties.baselineStartDate.getTime() === this.templateData.ganttProperties.startDate.getTime())) - && (!isNullOrUndefined(this.templateData.ganttProperties.baselineEndDate) && !isNullOrUndefined(this.templateData.ganttProperties.endDate) && (this.templateData.ganttProperties.baselineEndDate.getTime() === this.templateData.ganttProperties.endDate.getTime())) && - this.templateData.ganttProperties.isMilestone)) - ? this.getMilestoneBaselineNode() : this.getTaskBaselineNode(); + else if (!this.parent.allowParentDependency) { + connectorLineRightNode = this.getRightPointNode(); + (taskbarContainerNode[0] as any).appendChild([].slice.call(connectorLineRightNode)[0]); } - } else { - const scheduledTask: Boolean = isScheduledTask(this.templateData.ganttProperties);// eslint-disable-line - let childTaskbarProgressResizeNode: NodeList = null; let childTaskbarRightResizeNode: NodeList = null; - let childTaskbarLeftResizeNode: NodeList = null; - if (!isNullOrUndefined(scheduledTask)) { - if (scheduledTask || this.templateData.ganttProperties.duration) { - if (scheduledTask && (isNullOrUndefined(this.templateData.ganttProperties.segments) - || this.templateData.ganttProperties.segments.length <= 0)) { - childTaskbarProgressResizeNode = this.childTaskbarProgressResizer(); - childTaskbarLeftResizeNode = this.childTaskbarLeftResizer(); - childTaskbarRightResizeNode = this.childTaskbarRightResizer(); - } - } - const childTaskbarTemplateNode: NodeList = this.getChildTaskbarNode(i, taskbarContainerNode); - if (childTaskbarLeftResizeNode) { - taskbarContainerNode[0].appendChild([].slice.call(childTaskbarLeftResizeNode)[0]); - } - if (childTaskbarTemplateNode && childTaskbarTemplateNode.length > 0) { - if (this.templateData.ganttProperties.segments && this.templateData.ganttProperties.segments.length > 0) { - const length: number = this.templateData.ganttProperties.segments.length; - const connector: string = ('
'); - let segmentConnector: NodeList = null; - segmentConnector = this.createDivElement(connector); - taskbarContainerNode[0].appendChild([].slice.call(segmentConnector)[0]); - for (let i: number = 0; i < length; i++) { - append(childTaskbarTemplateNode, taskbarContainerNode[0] as Element); - } - } else { - append(childTaskbarTemplateNode, taskbarContainerNode[0] as Element); - } - } - if (childTaskbarProgressResizeNode) { - taskbarContainerNode[0].appendChild([].slice.call(childTaskbarProgressResizeNode)[0]); - } - if (childTaskbarRightResizeNode) { - taskbarContainerNode[0].appendChild([].slice.call(childTaskbarRightResizeNode)[0]); - } - } - if (this.parent.renderBaseline && this.templateData.ganttProperties.baselineStartDate && - this.templateData.ganttProperties.baselineEndDate) { - taskBaselineTemplateNode = ((this.templateData.ganttProperties.baselineStartDate.getTime() === this.templateData.ganttProperties.baselineEndDate.getTime()) || ( - (!isNullOrUndefined(this.templateData.ganttProperties.baselineStartDate) && !isNullOrUndefined(this.templateData.ganttProperties.startDate) && (this.templateData.ganttProperties.baselineStartDate.getTime() === this.templateData.ganttProperties.startDate.getTime())) - && (!isNullOrUndefined(this.templateData.ganttProperties.baselineEndDate) && !isNullOrUndefined(this.templateData.ganttProperties.endDate) && (this.templateData.ganttProperties.baselineEndDate.getTime() === this.templateData.ganttProperties.endDate.getTime())) && - this.templateData.ganttProperties.isMilestone)) - ? this.getMilestoneBaselineNode() : this.getTaskBaselineNode(); - } - } - let connectorLineRightNode: NodeList; - if (this.parent.allowParentDependency && ((this.templateData.ganttProperties.isAutoSchedule && this.parent.viewType === 'ProjectView') || !this.templateData.hasChildRecords)) { - connectorLineRightNode = this.getRightPointNode(); - taskbarContainerNode[0].appendChild([].slice.call(connectorLineRightNode)[0]); } - else if (!this.parent.allowParentDependency) { - connectorLineRightNode = this.getRightPointNode(); - taskbarContainerNode[0].appendChild([].slice.call(connectorLineRightNode)[0]); + } else { + this.appendChildTaskbars(tempTemplateData,i,taskbarContainerNode,connectorLineRightNode); } const rightLabelNode: NodeList = this.getRightLabelNode(i); - parentTrNode[0].childNodes[0].childNodes[0].appendChild([].slice.call(leftLabelNode)[0]); - parentTrNode[0].childNodes[0].childNodes[0].appendChild([].slice.call(taskbarContainerNode)[0]); - if (this.templateData.ganttProperties.indicators && this.templateData.ganttProperties.indicators.length > 0) { - let taskIndicatorNode: NodeList; - let taskIndicatorTextFunction: Function; - let taskIndicatorTextNode: NodeList; - const indicators: IIndicator[] = this.templateData.ganttProperties.indicators; - for (let indicatorIndex: number = 0; indicatorIndex < indicators.length; indicatorIndex++) { - taskIndicatorNode = this.getIndicatorNode(indicators[indicatorIndex as number]); - (taskIndicatorNode[0]).setAttribute('aria-label',indicators[indicatorIndex as number].name); - if (indicators[indicatorIndex as number].name.indexOf('$') > -1 || indicators[indicatorIndex as number].name.indexOf('#') > -1) { - taskIndicatorTextFunction = this.templateCompiler(indicators[indicatorIndex as number].name); - taskIndicatorTextNode = taskIndicatorTextFunction( - extend({ index: i }, this.templateData), this.parent, 'indicatorLabelText'); - } else { - const text: HTMLElement = createElement('Text'); - text.innerHTML = indicators[indicatorIndex as number].name; - if (this.parent.enableHtmlSanitizer && typeof (indicators[indicatorIndex as number].name) === 'string') { - indicators[indicatorIndex as number].name = SanitizeHtmlHelper.sanitize(indicators[indicatorIndex as number].name); - } - taskIndicatorTextNode = text.childNodes; - } - taskIndicatorNode[0].appendChild([].slice.call(taskIndicatorTextNode)[0]); - (taskIndicatorNode[0] as HTMLElement).title = - !isNullOrUndefined(indicators[indicatorIndex as number].tooltip) ? indicators[indicatorIndex as number].tooltip : ''; - parentTrNode[0].childNodes[0].childNodes[0].appendChild([].slice.call(taskIndicatorNode)[0]); + if (this.parent.enableMultiTaskbar && this.templateData.hasChildRecords && !this.templateData.expanded) { + const collapseParent: HTMLElement = createElement('div', { + className: 'e-collapse-parent' + }); + parentTrNode[0].childNodes[0].childNodes[0].appendChild(collapseParent); + for (let j:number = 0; j < taskbarContainerNode.length; j++) { + addClass([taskbarContainerNode[j as number] as HTMLElement], 'collpse-parent-border'); + parentTrNode[0].childNodes[0].childNodes[0].childNodes[0].appendChild([].slice.call(taskbarContainerNode)[j as number]); + } + parentTrNode[0].childNodes[0].childNodes[0].appendChild([].slice.call(leftLabelNode)[0]); + if (this.templateData.ganttProperties.indicators && this.templateData.ganttProperties.indicators.length > 0) { + this.appendIndicators(i,parentTrNode); } } - if (rightLabelNode && rightLabelNode.length > 0) { - parentTrNode[0].childNodes[0].childNodes[0].appendChild([].slice.call(rightLabelNode)[0]); + else { + parentTrNode[0].childNodes[0].childNodes[0].appendChild([].slice.call(leftLabelNode)[0]); + parentTrNode[0].childNodes[0].childNodes[0].appendChild([].slice.call(taskbarContainerNode)[0]); + if (this.templateData.ganttProperties.indicators && this.templateData.ganttProperties.indicators.length > 0) { + this.appendIndicators(i,parentTrNode); + } + if (rightLabelNode && rightLabelNode.length > 0) { + parentTrNode[0].childNodes[0].childNodes[0].appendChild([].slice.call(rightLabelNode)[0]); + } } - if (!isNullOrUndefined(taskBaselineTemplateNode)) { - parentTrNode[0].childNodes[0].childNodes[0].appendChild([].slice.call(taskBaselineTemplateNode)[0]); + if (!isNullOrUndefined(this.taskBaselineTemplateNode)) { + parentTrNode[0].childNodes[0].childNodes[0].appendChild([].slice.call(this.taskBaselineTemplateNode)[0]); } + this.taskBaselineTemplateNode = null; const tRow: Node = parentTrNode[0].childNodes[0]; this.setAriaRowIndex(tempTemplateData, tRow); return tRow; @@ -1717,6 +1673,112 @@ export class ChartRows extends DateProcessor { } } } + + private appendIndicators(i: number, parentTrNode: NodeList) { + let taskIndicatorNode: NodeList; + let taskIndicatorTextFunction: Function; + let taskIndicatorTextNode: NodeList; + const indicators: IIndicator[] = this.templateData.ganttProperties.indicators; + for (let indicatorIndex: number = 0; indicatorIndex < indicators.length; indicatorIndex++) { + taskIndicatorNode = this.getIndicatorNode(indicators[indicatorIndex as number]); + (taskIndicatorNode[0]).setAttribute('aria-label', indicators[indicatorIndex as number].name); + if (indicators[indicatorIndex as number].name.indexOf('$') > -1 || indicators[indicatorIndex as number].name.indexOf('#') > -1) { + taskIndicatorTextFunction = this.templateCompiler(indicators[indicatorIndex as number].name); + taskIndicatorTextNode = taskIndicatorTextFunction( + extend({ index: i }, this.templateData), this.parent, 'indicatorLabelText'); + } else { + const text: HTMLElement = createElement('Text'); + text.innerHTML = indicators[indicatorIndex as number].name; + if (this.parent.enableHtmlSanitizer && typeof (indicators[indicatorIndex as number].name) === 'string') { + indicators[indicatorIndex as number].name = SanitizeHtmlHelper.sanitize(indicators[indicatorIndex as number].name); + } + taskIndicatorTextNode = text.childNodes; + } + taskIndicatorNode[0].appendChild([].slice.call(taskIndicatorTextNode)[0]); + (taskIndicatorNode[0] as HTMLElement).title = + !isNullOrUndefined(indicators[indicatorIndex as number].tooltip) ? indicators[indicatorIndex as number].tooltip : ''; + parentTrNode[0].childNodes[0].childNodes[0].appendChild([].slice.call(taskIndicatorNode)[0]); + } + } + + private appendChildTaskbars(tempTemplateData: IGanttData, i: number, taskbarContainerNode: NodeList, connectorLineRightNode: NodeList, taskbarCollection?: NodeList | NodeList[]) { + if (this.templateData.ganttProperties.isMilestone) { + const milestoneTemplateNode: NodeList = this.getMilestoneNode(i, taskbarContainerNode); + if (milestoneTemplateNode && milestoneTemplateNode.length > 0) { + append(milestoneTemplateNode, taskbarContainerNode[0] as Element); + } + if (this.parent.renderBaseline && this.templateData.ganttProperties.baselineStartDate && + this.templateData.ganttProperties.baselineEndDate) { + this.taskBaselineTemplateNode = ((this.templateData.ganttProperties.baselineStartDate.getTime() === this.templateData.ganttProperties.baselineEndDate.getTime()) || ( + (!isNullOrUndefined(this.templateData.ganttProperties.baselineStartDate) && !isNullOrUndefined(this.templateData.ganttProperties.startDate) && (this.templateData.ganttProperties.baselineStartDate.getTime() === this.templateData.ganttProperties.startDate.getTime())) + && (!isNullOrUndefined(this.templateData.ganttProperties.baselineEndDate) && !isNullOrUndefined(this.templateData.ganttProperties.endDate) && (this.templateData.ganttProperties.baselineEndDate.getTime() === this.templateData.ganttProperties.endDate.getTime())) && + this.templateData.ganttProperties.isMilestone)) + ? this.getMilestoneBaselineNode() : this.getTaskBaselineNode(); + } + if (taskbarCollection) { + (taskbarCollection as any).push(taskbarContainerNode[0]); + this.templateData = tempTemplateData; + } + } else { + const scheduledTask: Boolean = isScheduledTask(this.templateData.ganttProperties);// eslint-disable-line + let childTaskbarProgressResizeNode: NodeList = null; let childTaskbarRightResizeNode: NodeList = null; + let childTaskbarLeftResizeNode: NodeList = null; + if (!isNullOrUndefined(scheduledTask)) { + if (scheduledTask || this.templateData.ganttProperties.duration) { + if (scheduledTask && (isNullOrUndefined(this.templateData.ganttProperties.segments) + || this.templateData.ganttProperties.segments.length <= 0)) { + childTaskbarProgressResizeNode = this.childTaskbarProgressResizer(); + childTaskbarLeftResizeNode = this.childTaskbarLeftResizer(); + childTaskbarRightResizeNode = this.childTaskbarRightResizer(); + } + } + const childTaskbarTemplateNode: NodeList = this.getChildTaskbarNode(i, taskbarContainerNode); + if (childTaskbarLeftResizeNode) { + taskbarContainerNode[0].appendChild([].slice.call(childTaskbarLeftResizeNode)[0]); + } + if (childTaskbarTemplateNode && childTaskbarTemplateNode.length > 0) { + if (this.templateData.ganttProperties.segments && this.templateData.ganttProperties.segments.length > 0) { + const length: number = this.templateData.ganttProperties.segments.length; + const connector: string = ('
'); + let segmentConnector: NodeList = null; + segmentConnector = this.createDivElement(connector); + taskbarContainerNode[0].appendChild([].slice.call(segmentConnector)[0]); + for (let i: number = 0; i < length; i++) { + append(childTaskbarTemplateNode, taskbarContainerNode[0] as Element); + } + } else { + append(childTaskbarTemplateNode, taskbarContainerNode[0] as Element); + } + } + if (childTaskbarProgressResizeNode) { + taskbarContainerNode[0].appendChild([].slice.call(childTaskbarProgressResizeNode)[0]); + } + if (childTaskbarRightResizeNode) { + taskbarContainerNode[0].appendChild([].slice.call(childTaskbarRightResizeNode)[0]); + } + } + if (this.parent.renderBaseline && this.templateData.ganttProperties.baselineStartDate && + this.templateData.ganttProperties.baselineEndDate) { + this.taskBaselineTemplateNode = ((this.templateData.ganttProperties.baselineStartDate.getTime() === this.templateData.ganttProperties.baselineEndDate.getTime()) || ( + (!isNullOrUndefined(this.templateData.ganttProperties.baselineStartDate) && !isNullOrUndefined(this.templateData.ganttProperties.startDate) && (this.templateData.ganttProperties.baselineStartDate.getTime() === this.templateData.ganttProperties.startDate.getTime())) + && (!isNullOrUndefined(this.templateData.ganttProperties.baselineEndDate) && !isNullOrUndefined(this.templateData.ganttProperties.endDate) && (this.templateData.ganttProperties.baselineEndDate.getTime() === this.templateData.ganttProperties.endDate.getTime())) && + this.templateData.ganttProperties.isMilestone)) + ? this.getMilestoneBaselineNode() : this.getTaskBaselineNode(); + } + } + if (this.parent.allowParentDependency && ((this.templateData.ganttProperties.isAutoSchedule && this.parent.viewType === 'ProjectView') || !this.templateData.hasChildRecords)) { + connectorLineRightNode = this.getRightPointNode(); + (taskbarContainerNode[0] as any).appendChild([].slice.call(connectorLineRightNode)[0]); + } + else if (!this.parent.allowParentDependency) { + connectorLineRightNode = this.getRightPointNode(); + (taskbarContainerNode[0] as any).appendChild([].slice.call(connectorLineRightNode)[0]); + } + if (taskbarCollection) { + (taskbarCollection as any).push(taskbarContainerNode[0]); + this.templateData = tempTemplateData; + } + } /** * * @param {Element} trElement . @@ -2211,7 +2273,10 @@ export class ChartRows extends DateProcessor { else { index = this.parent.currentViewData.indexOf(items[i as number]); } - this.refreshRow(index, isValidateRange, isUndoRedo); + if (!this.parent.enableMultiTaskbar || + (this.parent.enableMultiTaskbar && (items[i as number].expanded || !this.parent.isLoad))) { + this.refreshRow(index, isValidateRange, isUndoRedo); + } } this.parent.ganttChartModule.updateLastRowBottomWidth(); } diff --git a/controls/gantt/src/gantt/renderer/timeline.ts b/controls/gantt/src/gantt/renderer/timeline.ts index fbb30b463d..152a167b73 100644 --- a/controls/gantt/src/gantt/renderer/timeline.ts +++ b/controls/gantt/src/gantt/renderer/timeline.ts @@ -722,7 +722,7 @@ export class Timeline { if (this.parent.enableTimelineVirtualization && (this.wholeTimelineWidth > this.parent.element.offsetWidth * 3)) { for (let count: number = 0; count < loopCount; count++) { table = createElement('table', { className: cls.timelineHeaderTableContainer, styles: 'display: block;' }); - table.setAttribute('role', 'presentation'); + table.setAttribute('role', 'none'); thead = createElement('thead', { className: cls.timelineHeaderTableBody, styles: 'display:block; border-collapse:collapse' }); tr = createElement('tr', { innerHTML: this.createTimelineTemplate(tier) }); td = createElement('td'); @@ -752,7 +752,7 @@ export class Timeline { else { for (let count: number = 0; count < loopCount; count++) { table = createElement('table', { className: cls.timelineHeaderTableContainer, styles: 'display: block;' }); - table.setAttribute('role', 'presentation'); + table.setAttribute('role', 'none'); thead = createElement('thead', { className: cls.timelineHeaderTableBody, styles: 'display:block; border-collapse:collapse' }); tr = createElement('tr', { innerHTML: this.createTimelineTemplate(tier) }); td = createElement('td'); @@ -1462,7 +1462,7 @@ export class Timeline { startDate = new Date(startDate.getFullYear(), startDate.getMonth(), 1); endDate = new Date(endDate.getFullYear(), endDate.getMonth() + 1, 0); } else if (tierMode === 'Week') { - const dayIndex: number = this.parent.timelineModule.customTimelineSettings.weekStartDay; + const dayIndex: number = !isNullOrUndefined(this.customTimelineSettings.weekStartDay) ? this.parent.timelineModule.customTimelineSettings.weekStartDay : 0; const roundOffStartDate: number = startDate.getDay() < dayIndex ? (startDate.getDate()) - (7 - dayIndex + startDate.getDay()) : (startDate.getDate()) - startDate.getDay() + dayIndex; @@ -1634,7 +1634,7 @@ export class Timeline { } else { minStartDate = this.timelineStartDate; } - if (!isNullOrUndefined(maxEndLeft) && (maxEndLeft >= (this.totalTimelineWidth - this.bottomTierCellWidth) || + if (!isNullOrUndefined(maxEndLeft) && (maxEndLeft >= ((this.parent.enableTimelineVirtualization ? this.wholeTimelineWidth : this.totalTimelineWidth) - this.bottomTierCellWidth) || maxEndLeft >= validEndLeft)) { isChanged = isChanged === 'prevTimeSpan' ? 'both' : 'nextTimeSpan'; maxEndDate = maxEndDate < this.timelineEndDate ? this.timelineEndDate : maxEndDate; diff --git a/controls/gantt/src/gantt/renderer/virtual-content-render.ts b/controls/gantt/src/gantt/renderer/virtual-content-render.ts index 14735f5226..4577c36b2e 100644 --- a/controls/gantt/src/gantt/renderer/virtual-content-render.ts +++ b/controls/gantt/src/gantt/renderer/virtual-content-render.ts @@ -45,6 +45,22 @@ export class VirtualContentRenderer { */ public adjustTable(): void { const content: HTMLElement = this.parent.treeGrid.getContent().querySelector('.e-content').querySelector('.e-virtualtable'); - this.parent.ganttChartModule.virtualRender.wrapper.style.transform = content.style.transform; + if (this.parent.enableTimelineVirtualization) { + const virtualTable: string = (document.getElementsByClassName('e-virtualtable')[1] as HTMLElement).style.transform; + const treegridVirtualHeight: string = (this.parent.treeGrid.element.getElementsByClassName('e-virtualtable')[0] as HTMLElement).style.transform; + let translateXValue: string; + if (virtualTable !== "") { + translateXValue = virtualTable.match(/translate.*\((.+)\)/)[1].split(', ')[0]; + } + else { + const chartTransform: string = (this.parent.ganttChartModule.scrollElement.getElementsByClassName('e-virtualtable')[0] as HTMLElement).style.transform; + translateXValue = chartTransform.match(/translate.*\((.+)\)/)[1].split(', ')[0]; + } + const translateYValue: string = treegridVirtualHeight.match(/translate.*\((.+)\)/)[1].split(', ')[1]; + this.parent.ganttChartModule.virtualRender.wrapper.style.transform = `translate(${translateXValue}, ${translateYValue})`; + } + else { + this.parent.ganttChartModule.virtualRender.wrapper.style.transform = content.style.transform; + } } } diff --git a/controls/grids/CHANGELOG.md b/controls/grids/CHANGELOG.md index 89e7d9b712..1ceb223125 100644 --- a/controls/grids/CHANGELOG.md +++ b/controls/grids/CHANGELOG.md @@ -2,6 +2,22 @@ ## [Unreleased] +## 25.1.37 (2024-03-26) + +### Grid + +#### Bug fixes + +- `#I565411` - Resolved the issue where the action complete event was not being triggered in the Infinite Scrolling. +- `#I553471` - The issue of misleading text in Grid toolbar search when focus is lost has been resolved. +- `#I562553` - The `getRows` method returning undefined for all the rows in the `rowTemplate` feature has been resolved. +- `#I559289` - Resolved the issue where the validation message displayed immediately upon opening the dropdown instead of on focus out. +- `#I558905` - The issue of white spaces being shown when the virtual scroll grid's height is set in pixels has been resolved. +- `#I566680` - Resolved the Hierarchy grid export issue when the child grid's enable filter property is enabled. +- `#I540683` - The issue of a script error being thrown when the last column contains auto width and `minWidth` in a Grid with `allowResizing` has been fixed. +- `#I558576` - Resolved the issue where the sorting `popup` did not open when clicking the sort icon has been fixed. +- `#FB51479` - The issue where the cell was automatically saved when opening the dropdown in Batch edit mode has been fixed. + ## 25.1.35 (2024-03-15) ### Grid diff --git a/controls/grids/package.json b/controls/grids/package.json index 724a7a9013..0fbf1d6d97 100644 --- a/controls/grids/package.json +++ b/controls/grids/package.json @@ -1,6 +1,6 @@ { "name": "@syncfusion/ej2-grids", - "version": "1.193.11", + "version": "25.1.35", "description": "Feature-rich JavaScript datagrid (datatable) control with built-in support for editing, filtering, grouping, paging, sorting, and exporting to Excel.", "author": "Syncfusion Inc.", "license": "SEE LICENSE IN license", diff --git a/controls/grids/spec/grid/actions/batch.edit.spec.ts b/controls/grids/spec/grid/actions/batch.edit.spec.ts index 209796a953..cd80540521 100644 --- a/controls/grids/spec/grid/actions/batch.edit.spec.ts +++ b/controls/grids/spec/grid/actions/batch.edit.spec.ts @@ -4647,6 +4647,44 @@ describe('EJ2-72030 - Batch Edited cell value not saved during tab out from last gridObj = null; }); }); +describe('EJ2-871057 - Add button is not getting focused on first click in Batch Editing Sample =>', () => { + let gridObj: Grid; + let batchAdd: (args: any) => void; + beforeAll((done: Function) => { + gridObj = createGrid( + { + dataSource: data.slice(0, 5), + editSettings: { allowEditing: true, allowAdding: true, allowDeleting: true, mode: 'Batch' }, + allowPaging: true, + toolbar: ['Add', 'Delete', 'Update', 'Cancel'], + columns: [ + { field: 'OrderID', isPrimaryKey: true, headerText: 'Order ID', textAlign: 'Right', validationRules: { required: true, number: true }, width: 120 }, + { field: 'CustomerID', headerText: 'Customer ID', validationRules: { required: true }, width: 140 }, + { field: 'Freight', headerText: 'Freight', textAlign: 'Right', editType: 'numericedit', width: 120, format: 'C2', validationRules: { required: true } }, + ], + }, done); + }); + + it('Add record 1', (done: Function) => { + batchAdd = (args?: Object): void => { + expect(gridObj.element.querySelectorAll('.e-insertedrow').length).toBe(1); + gridObj.batchAdd = null; + done(); + }; + gridObj.batchAdd = batchAdd; + gridObj.addRecord(); + }); + it('Add record 2', (done: Function) => { + gridObj.addRecord(); + expect(gridObj.element.querySelectorAll('.e-insertedrow').length).toBe(1); + done(); + }); + + afterAll(() => { + destroy(gridObj); + gridObj = batchAdd = null; + }); +}); describe('EJ2-837195 - Inline and Batch Edit mode behave differently when column.allowEditing is false =>', () => { let gridObj: Grid; diff --git a/controls/grids/spec/grid/actions/infinite-scroll.spec.ts b/controls/grids/spec/grid/actions/infinite-scroll.spec.ts index 84a17407e3..c05ca5fa80 100644 --- a/controls/grids/spec/grid/actions/infinite-scroll.spec.ts +++ b/controls/grids/spec/grid/actions/infinite-scroll.spec.ts @@ -288,15 +288,19 @@ describe('Infinite scroll cache mode compare with other features => ', () => { it('Single sort orderID asc testing', (done: Function) => { expect(gridObj.pageSettings.currentPage).not.toBe(1); let actionComplete = (args: any): any => { - expect(cols[0].querySelectorAll('.e-ascending').length).toBe(1); - expect(sortSettings.columns[0].field).toBe('FIELD2'); - expect(sortSettings.columns[0].direction).toBe('Ascending'); - expect(gridObj.getContent().firstElementChild.scrollTop).toBe(0); - expect(gridObj.pageSettings.currentPage).toBe(1); - expect((gridObj.infiniteScrollModule as any).infiniteCache[4]).toBeUndefined(); - expect((gridObj.infiniteScrollModule as any).infiniteCache[3]).toBeDefined(); - expect(gridObj.getHeaderContent().querySelectorAll('.e-columnheader')[0].querySelectorAll('.e-sortnumber').length).toBe(0); - done(); + if (args.requestType === 'infiniteScroll') { + expect(gridObj.pageSettings.currentPage).toBe(4); + } else { + expect(cols[0].querySelectorAll('.e-ascending').length).toBe(1); + expect(sortSettings.columns[0].field).toBe('FIELD2'); + expect(sortSettings.columns[0].direction).toBe('Ascending'); + expect(gridObj.getContent().firstElementChild.scrollTop).toBe(0); + expect(gridObj.pageSettings.currentPage).toBe(1); + expect((gridObj.infiniteScrollModule as any).infiniteCache[4]).toBeUndefined(); + expect((gridObj.infiniteScrollModule as any).infiniteCache[3]).toBeDefined(); + expect(gridObj.getHeaderContent().querySelectorAll('.e-columnheader')[0].querySelectorAll('.e-sortnumber').length).toBe(0); + done(); + } }; let actionBegin = (args: any): any => { expect(args.target).not.toBeNull(); diff --git a/controls/grids/spec/grid/actions/search.spec.ts b/controls/grids/spec/grid/actions/search.spec.ts index 5ba3d23126..c4f054a573 100644 --- a/controls/grids/spec/grid/actions/search.spec.ts +++ b/controls/grids/spec/grid/actions/search.spec.ts @@ -468,6 +468,50 @@ describe('Search module=>', () => { }); }); + describe('EJ2-872387- Misleading Text in Grid Toolbar Search when Focus is Lost', () => { + let gridObj: Grid; + let actionBegin: (args?: Object) => void; + beforeAll((done: Function) => { + gridObj = createGrid( + { + dataSource: data.slice(0,5), + toolbar: ['Search'], + columns: [ + { + field: 'OrderID', + headerText: 'OrderID', + width: 140, + }, + { + field: 'CustomerID', + headerText: 'Customer Name', + width: 140, + }, + ], + height: 350, + }, done); + }); + it('search on focus out', (done: Function) => { + actionBegin = (args: any): void => { + if (args.requestType === 'searching') { + expect(args.searchString).toBe('vinet'); + gridObj.actionBegin = null; + done(); + } + }; + gridObj.actionBegin = actionBegin; + let searchBar: HTMLInputElement = gridObj.element.querySelector('#' + gridObj.element.id + '_searchbar'); + searchBar.focus(); + searchBar.value = 'vinet'; + gridObj.element.querySelector('th').click(); + searchBar = null; + }); + afterAll(() => { + destroy(gridObj); + gridObj = actionBegin = null; + }); + }); + describe('846444 - Searching value with Trailing Zero not working', () => { let gridObj: Grid; let actionComplete: () => void; diff --git a/controls/grids/spec/grid/actions/virtualscroll.spec.ts b/controls/grids/spec/grid/actions/virtualscroll.spec.ts index fe26975085..62dba703e3 100644 --- a/controls/grids/spec/grid/actions/virtualscroll.spec.ts +++ b/controls/grids/spec/grid/actions/virtualscroll.spec.ts @@ -1575,3 +1575,24 @@ describe('EJ2-859411-Scroll using the down arrow key by focusing the template, t grid = dataBound = null; }); }); + +describe('EJ2-873384-When the Grid height is set in pixels, whitespaces are shown while scrolling up and down', () => { + let grid: Grid; + beforeAll((done: Function) => { + grid = createGrid( + { + dataSource: largeDataset.slice(0,60), + columns: [{ field: 'Field0', headerText: 'Field0', width: 120 }], + enableVirtualization: true, + height: '600px' + },done); + }); + it('ensure pageSize', (done: Function) => { + expect(grid.pageSettings.pageSize).toBeGreaterThan(12); + done(); + }); + afterAll(() => { + destroy(grid); + grid = null; + }); +}); diff --git a/controls/grids/spec/grid/base/grid.spec.ts b/controls/grids/spec/grid/base/grid.spec.ts index 6770528752..58e99e0766 100644 --- a/controls/grids/spec/grid/base/grid.spec.ts +++ b/controls/grids/spec/grid/base/grid.spec.ts @@ -2257,3 +2257,35 @@ describe('dateonly =>', () => { gridObj = null; }); }); + +// used for code coverage +describe('EJ2-871826: Error when using Stacked Header with Column Template and updating dataSource dynamically in React =>', () => { + let gridObj: Grid; + beforeAll((done: Function) => { + gridObj = createGrid( + { + dataSource: data.slice(0, 1), + columns: [ + { + field: 'OrderID', isPrimaryKey: true, headerText: 'Order ID', width: 120 + }, + {headerText: 'Details', textAlign: 'Center', columns:[ + {field: 'CustomerID', headerText: 'Customer ID', textAlign: 'Center', width: '80'}, + {field: 'EmployeeID', headerText: 'Employee ID', textAlign: 'Center', width: '60'}, + ]}, + ], + }, done); + }); + it('execute column setProperties method', (done: Function) => { + const col: Column = {headerText: 'Details', textAlign: 'Center', columns:[ + {field: 'CustomerID', headerText: 'Customer ID', textAlign: 'Center', width: '80'}, + {field: 'EmployeeID', headerText: 'Employee ID', textAlign: 'Center', width: '100'}, + ]} as Column; + (gridObj.columns[1] as Column).setProperties(col); + done(); + }); + afterAll(() => { + destroy(gridObj); + gridObj = null; + }); +}); diff --git a/controls/grids/spec/grid/renderer/responsive-dialog-renderer.spec.ts b/controls/grids/spec/grid/renderer/responsive-dialog-renderer.spec.ts index 3d2cd1c8e5..1478a85cd5 100644 --- a/controls/grids/spec/grid/renderer/responsive-dialog-renderer.spec.ts +++ b/controls/grids/spec/grid/renderer/responsive-dialog-renderer.spec.ts @@ -920,4 +920,38 @@ describe('Adaptive renderer', () => { gridObj = null; }); }); + + describe('EJ2-873156 - If Sort option is clicked on the mobile device, Filter Pop up is being Opened', () => { + let gridObj: any; + beforeAll((done: Function) => { + gridObj = createGrid( + { + dataSource: data, + enableAdaptiveUI: true, + rowRenderingMode: 'Vertical', + allowFiltering: true, + allowSorting: true, + allowPaging: true, + filterSettings: { type: 'Excel' }, + height: 400, + columns: [ + { headerText: 'OrderID', field: 'OrderID', isPrimaryKey: true, width: 120 }, + { headerText: 'CustomerID', field: 'CustomerID', width: 120 }, + { headerText: 'EmployeeID', field: 'EmployeeID', width: 120 }, + { headerText: 'ShipCountry', field: 'ShipCountry', width: 120 }, + { headerText: 'ShipCity', field: 'ShipCity', width: 120 }, + ] + }, done); + }); + + it('Ensuring the sorting popup', () => { + (document.getElementsByClassName('e-tbar-btn')[1]as HTMLElement).click(); + expect(document.getElementsByClassName('e-ressortdiv').length).toBe(1); + }); + + afterAll(() => { + destroy(gridObj); + gridObj = null; + }); + }); }); \ No newline at end of file diff --git a/controls/grids/src/grid/actions/batch-edit.ts b/controls/grids/src/grid/actions/batch-edit.ts index 61291c987b..28bf1bd9f5 100644 --- a/controls/grids/src/grid/actions/batch-edit.ts +++ b/controls/grids/src/grid/actions/batch-edit.ts @@ -542,6 +542,9 @@ export class BatchEdit { this.saveCell(); this.parent.notify(events.editNextValCell, {}); } + if (this.validateFormObj()) { + return; + } if (this.initialRender) { const visibleColumns: Column[] = gObj.getVisibleColumns(); for (let i: number = 0; i < visibleColumns.length; i++) { diff --git a/controls/grids/src/grid/actions/data.ts b/controls/grids/src/grid/actions/data.ts index d50e806f4d..0c7cb9454d 100644 --- a/controls/grids/src/grid/actions/data.ts +++ b/controls/grids/src/grid/actions/data.ts @@ -75,11 +75,8 @@ export class Data implements IDataProcessor { gObj.setProperties({ query: new Query() }, true); } else { this.isQueryInvokedFromData = true; - if (gObj.isVue) { - gObj.setProperties({ query: gObj.query instanceof Query ? gObj.query : new Query() }, true); - } - else { - gObj.query = gObj.query instanceof Query ? gObj.query : new Query(); + if (!(gObj.query instanceof Query)) { + gObj.query = new Query(); } } } diff --git a/controls/grids/src/grid/actions/excel-export.ts b/controls/grids/src/grid/actions/excel-export.ts index 0d9cc8b963..f06e04ae7b 100644 --- a/controls/grids/src/grid/actions/excel-export.ts +++ b/controls/grids/src/grid/actions/excel-export.ts @@ -913,7 +913,7 @@ export class ExcelExport { if (!isNullOrUndefined(cell.attributes.index)) { columnsDetails = this.parent.getColumnByIndex(cell.attributes.index as number); } - if (cell.cellType === CellType.DetailFooterIntent || columnsDetails.type === 'checkbox') { + if (cell.cellType === CellType.DetailFooterIntent || columnsDetails.type === 'checkbox' || columnsDetails.commands) { continue; } if ((cell.visible || this.includeHiddenColumn)) { diff --git a/controls/grids/src/grid/actions/filter.ts b/controls/grids/src/grid/actions/filter.ts index f09726e026..87d5c8e48f 100644 --- a/controls/grids/src/grid/actions/filter.ts +++ b/controls/grids/src/grid/actions/filter.ts @@ -387,7 +387,10 @@ export class Filter implements IAction { } private refreshFilterValue(): void { - this.parent.removeEventListener(events.beforeDataBound, this.refreshFilterValueFn); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + if (!isNullOrUndefined((this.parent as any).modelObserver.boundedEvents)) { + this.parent.removeEventListener(events.beforeDataBound, this.refreshFilterValueFn); + } if (this.filterSettings.type === 'FilterBar' && this.filterSettings.columns.length && !this.parent.getCurrentViewRecords().length) { this.initialEnd(); diff --git a/controls/grids/src/grid/actions/infinite-scroll.ts b/controls/grids/src/grid/actions/infinite-scroll.ts index d18fd54a7f..d0348c3c71 100644 --- a/controls/grids/src/grid/actions/infinite-scroll.ts +++ b/controls/grids/src/grid/actions/infinite-scroll.ts @@ -119,6 +119,7 @@ export class InfiniteScroll implements IAction { this.parent.on(events.contentReady, this.selectNewRow, this); this.parent.on(events.captionActionComplete, this.captionActionComplete, this); this.parent.on(events.setVirtualPageQuery, this.setGroupCollapsePageQuery, this); + this.parent.on(events.infiniteScrollComplete, this.onActionComplete, this); this.actionBeginFunction = this.actionBegin.bind(this); this.actionCompleteFunction = this.actionComplete.bind(this); this.dataBoundFunction = this.dataBound.bind(this); @@ -159,6 +160,7 @@ export class InfiniteScroll implements IAction { this.parent.off(events.contentReady, this.selectNewRow); this.parent.off(events.captionActionComplete, this.captionActionComplete); this.parent.off(events.setVirtualPageQuery, this.setGroupCollapsePageQuery); + this.parent.off(events.infiniteScrollComplete, this.onActionComplete); this.parent.removeEventListener(events.actionBegin, this.actionBeginFunction); this.parent.removeEventListener(events.actionComplete, this.actionCompleteFunction); this.parent.removeEventListener(events.dataBound, this.dataBoundFunction); @@ -666,6 +668,18 @@ export class InfiniteScroll implements IAction { } } + /** + * The function used to trigger onActionComplete + * + * @param {NotifyArgs} e - specifies the NotifyArgs + * @returns {void} + * @hidden + */ + public onActionComplete(e: NotifyArgs): void { + const args: Object = { type: events.actionComplete }; + this.parent.trigger(events.actionComplete, extend(e, args)); + } + private resetInfiniteEdit(): void { if (this.parent.enableInfiniteScrolling && this.isNormaledit) { if ((this.parent.editSettings.allowEditing && this.isEdit) || (this.parent.editSettings.allowAdding && this.isAdd)) { diff --git a/controls/grids/src/grid/actions/toolbar.ts b/controls/grids/src/grid/actions/toolbar.ts index 9f25358328..9193d75e0b 100644 --- a/controls/grids/src/grid/actions/toolbar.ts +++ b/controls/grids/src/grid/actions/toolbar.ts @@ -716,6 +716,11 @@ export class Toolbar { private onFocusOut(e: FocusEvent): void { (e.target as HTMLElement).tabIndex = -1; + if (e.target && (e.target as HTMLElement).id === this.parent.element.id + '_searchbar' && + !(e.relatedTarget && ((e.relatedTarget as HTMLElement).id === this.parent.element.id + '_clearbutton' || + (e.relatedTarget as HTMLElement).id === this.parent.element.id + '_searchbutton'))) { + this.search(); + } } private setFocusToolbarItem(element: Element): void { diff --git a/controls/grids/src/grid/actions/virtual-scroll.ts b/controls/grids/src/grid/actions/virtual-scroll.ts index 0b6c9ccf9d..9659497a7a 100644 --- a/controls/grids/src/grid/actions/virtual-scroll.ts +++ b/controls/grids/src/grid/actions/virtual-scroll.ts @@ -45,7 +45,7 @@ export class VirtualScroll implements IAction { const rowHeight: number = this.parent.getRowHeight(); const vHeight: string | number = this.parent.height.toString().indexOf('%') < 0 ? this.parent.height : this.parent.element.getBoundingClientRect().height; - this.blockSize = ~~(vHeight / rowHeight); + this.blockSize = ~~(parseFloat(vHeight.toString()) / rowHeight); const height: number = this.blockSize * 2; const size: number = this.parent.pageSettings.pageSize; this.parent.setProperties({ pageSettings: { pageSize: size < height ? height : size }}, true); diff --git a/controls/grids/src/grid/base/constant.ts b/controls/grids/src/grid/base/constant.ts index a08b9cb77b..9c15c30155 100644 --- a/controls/grids/src/grid/base/constant.ts +++ b/controls/grids/src/grid/base/constant.ts @@ -576,3 +576,5 @@ export const renderResponsiveChangeAction: string = 'render-Responsive-Change-Ac export const renderResponsiveColumnChooserDiv: string = 'render-Responsive-Column-Chooser-Div'; /** @hidden */ export const showAddNewRowFocus: string = 'show-Add-New-Row-Focus'; +/** @hidden */ +export const infiniteScrollComplete: string = 'infinitescroll-complete'; diff --git a/controls/grids/src/grid/base/grid.ts b/controls/grids/src/grid/base/grid.ts index ef988dba54..f2e65dca00 100644 --- a/controls/grids/src/grid/base/grid.ts +++ b/controls/grids/src/grid/base/grid.ts @@ -6529,6 +6529,7 @@ export class Grid extends Component implements INotifyPropertyChang * @returns {void} */ public resetIndentWidth(): void { + if (this.isDestroyed) { return; } if (ispercentageWidth(this)) { this.getHeaderTable().querySelector('.e-emptycell').removeAttribute('indentRefreshed'); this.widthService.setWidthToColumns(); @@ -7346,8 +7347,8 @@ export class Grid extends Component implements INotifyPropertyChang (ariaOwns)) !== (e.target as Element).getAttribute('aria-owns'))) && !this.keyPress && this.isEdit && !Browser.isDevice) { if (this.editSettings.mode === 'Batch' && !(((parentsUntil(relatedTarget, 'e-ddl') || parentsUntil(relatedTarget, 'e-ddt')) && - parentsUntil(relatedTarget, 'e-multi-select-list-wrapper')) && parentsUntil(relatedTarget, 'e-input-group')) - && (parentsUntil(relatedTarget, 'e-uploader') || !(relatedTarget && + (parentsUntil(relatedTarget, 'e-multi-select-list-wrapper') || parentsUntil(relatedTarget, 'e-input-filter'))) && + parentsUntil(relatedTarget, 'e-input-group')) && (parentsUntil(relatedTarget, 'e-uploader') || !(relatedTarget && isNullOrUndefined(parentsUntil(relatedTarget, 'e-input-group'))))) { this.editModule.saveCell(); this.notify(events.editNextValCell, {}); diff --git a/controls/grids/src/grid/models/column.ts b/controls/grids/src/grid/models/column.ts index 64fdc6e4cc..5bd5529ad7 100644 --- a/controls/grids/src/grid/models/column.ts +++ b/controls/grids/src/grid/models/column.ts @@ -633,7 +633,15 @@ export class Column { //Angular two way binding const keys: string[] = Object.keys(column); for (let i: number = 0; i < keys.length; i++) { - this[keys[parseInt(i.toString(), 10)]] = column[keys[parseInt(i.toString(), 10)]]; + if (keys[parseInt(i.toString(), 10)] === 'columns') { + const cols: Column[] = column[keys[parseInt(i.toString(), 10)]]; + for (let j: number = 0; j < cols.length; j++) { + ((this.columns as Column[]).find((col: Column) => { return col.field === cols[parseInt(j.toString(), 10)] + .field; }) as Column).setProperties(cols[parseInt(j.toString(), 10)]); + } + } else { + this[keys[parseInt(i.toString(), 10)]] = column[keys[parseInt(i.toString(), 10)]]; + } //Refresh the react columnTemplates on state change if (this.parent && this.parent.isReact) { if (keys[parseInt(i.toString(), 10)] === 'template') { diff --git a/controls/grids/src/grid/renderer/content-renderer.ts b/controls/grids/src/grid/renderer/content-renderer.ts index aa405bb090..ead739e953 100644 --- a/controls/grids/src/grid/renderer/content-renderer.ts +++ b/controls/grids/src/grid/renderer/content-renderer.ts @@ -488,7 +488,7 @@ export class ContentRender implements IRenderer { thisRef.parent.trigger(events.rowDataBound, arg); if (modelData[parseInt(i.toString(), 10)].isDataRow || (thisRef.parent.enableVirtualization && thisRef.parent.groupSettings.enableLazyLoading)) { - thisRef.rowElements.push(tr); + thisRef.rowElements.push(arg.row); } thisRef.ariaService.setOptions(thisRef.parent.element, { colcount: gObj.getColumns().length.toString() }); @@ -532,7 +532,11 @@ export class ContentRender implements IRenderer { } if (modelData[parseInt(i.toString(), 10)].isDataRow || (this.parent.enableVirtualization && this.parent.groupSettings.enableLazyLoading)) { - this.rowElements.push(tr); + if (!isNullOrUndefined(gObj.rowTemplate) && (gObj.isAngular || gObj.isVue3 || gObj.isVue)) { + this.rowElements.push(trElement ? trElement : tr); + } else { + this.rowElements.push(tr); + } } this.ariaService.setOptions(this.parent.element, { colcount: gObj.getColumns().length.toString() }); } diff --git a/controls/grids/src/grid/renderer/edit-renderer.ts b/controls/grids/src/grid/renderer/edit-renderer.ts index b3442e8847..f9c24981af 100644 --- a/controls/grids/src/grid/renderer/edit-renderer.ts +++ b/controls/grids/src/grid/renderer/edit-renderer.ts @@ -122,13 +122,14 @@ export class EditRender { this.focus.onClick({ target: closest(elem, 'td') }, true); } else { const isFocus: boolean = (this.parent.enableVirtualization || this.parent.enableColumnVirtualization) && this.parent.editSettings.mode === 'Normal' ? false : true; + const focusElement: HTMLElement = elem.parentElement.classList.contains('e-ddl') ? elem.parentElement : elem; if ((isFocus || ((this.parent.enableVirtualization || this.parent.enableColumnVirtualization) && this.parent.editSettings.newRowPosition === 'Bottom' && parentsUntil(elem, literals.addedRow))) && (!this.parent.editSettings.showAddNewRow || (this.parent.editSettings.showAddNewRow && (!parentsUntil(elem, literals.addedRow)) || this.parent.addNewRowFocus))) { - elem.focus(); + focusElement.focus(); } else { // eslint-disable-next-line @typescript-eslint/no-explicit-any - (elem as any).focus({ preventScroll: true }); + (focusElement as any).focus({ preventScroll: true }); } } if (elem.classList.contains('e-defaultcell')) { diff --git a/controls/grids/src/grid/renderer/header-renderer.ts b/controls/grids/src/grid/renderer/header-renderer.ts index 562b893c01..fbd08ac94d 100644 --- a/controls/grids/src/grid/renderer/header-renderer.ts +++ b/controls/grids/src/grid/renderer/header-renderer.ts @@ -727,7 +727,8 @@ export class HeaderRender implements IRenderer { btnObj.appendTo(button); button.onclick = (e: MouseEvent) => { if ((e.target as HTMLElement).classList.contains('e-ressort-btn') - || (e.target as HTMLElement).classList.contains('e-ressort-icon')) { + || (e.target as HTMLElement).classList.contains('e-ressort-icon') || + (e.target as HTMLElement).querySelector('.e-ressort-icon')) { this.parent.showResponsiveCustomSort(); } else { this.parent.showResponsiveCustomFilter(); diff --git a/controls/grids/src/grid/services/focus-strategy.ts b/controls/grids/src/grid/services/focus-strategy.ts index f486bfe6f1..9225658817 100644 --- a/controls/grids/src/grid/services/focus-strategy.ts +++ b/controls/grids/src/grid/services/focus-strategy.ts @@ -1351,6 +1351,10 @@ export class ContentFocus implements IFocus { !info.element.classList.contains('e-detailcell') ? this.getFocusable(info.element) : info.element; info.elementToFocus = info.element.classList.contains('e-detailcell') && info.element.querySelector('.e-childgrid') ? info.element.querySelector('.e-childgrid') : info.elementToFocus; + if (this.parent.editSettings.mode === 'Batch' && this.parent.isEdit && info.elementToFocus.tagName.toLowerCase() === 'input' + && info.elementToFocus.parentElement.classList.contains('e-ddl')) { + info.elementToFocus = info.elementToFocus.parentElement; + } info.outline = true; info.uid = info.element.parentElement.getAttribute('data-uid'); return info; diff --git a/controls/grids/styles/excel-filter/_layout.scss b/controls/grids/styles/excel-filter/_layout.scss index e322c122bf..910338407f 100644 --- a/controls/grids/styles/excel-filter/_layout.scss +++ b/controls/grids/styles/excel-filter/_layout.scss @@ -2,6 +2,9 @@ /*! Excel-Filter layout */ .sf-grid .e-excelfilter { + &.e-dialog .e-dlg-content { + padding-left: 0; + } .e-dlg-content { overflow: visible; padding: 0; @@ -471,7 +474,7 @@ user-select: none; } - & :not(.sf-grid) .e-dialog .e-dlg-content { + &.e-dialog .e-dlg-content { padding-left: $grid-checkbox-content-padding-left; @if $grid-xlfl-skin == 'material3' { padding-right: 16px; diff --git a/controls/grids/styles/grid/_material3-definition.scss b/controls/grids/styles/grid/_material3-definition.scss index 8c49d8c506..08bf9b1125 100644 --- a/controls/grids/styles/grid/_material3-definition.scss +++ b/controls/grids/styles/grid/_material3-definition.scss @@ -599,9 +599,9 @@ $grid-group-drop-area-hover-border-color: $border-dark !default; $grid-adatptive-apply-btn-disable: $primary-bg-color-disabled !default; $grid-sortnumber-font-size: 11px !default; $grid-reorderarrow-margin-top: -6px !default; -$grid-reorder-arrow-top-margin: -1px !default; +$grid-reorder-arrow-top-margin: 1px !default; $grid-reorder-downarrow-top-margin: -2px !default; -$grid-reorder-virtualarrow-top-margin: 1px !default; +$grid-reorder-virtualarrow-top-margin: -.45px !default; $grid-reorder-virtualdownarrow-top-margin: -2px !default; $grid-reorderdownarrow-margin-top: 3.7px !default; $grid-bigger-toolbar-icon-size: 20px !default; diff --git a/controls/imageeditor/CHANGELOG.md b/controls/imageeditor/CHANGELOG.md index bb69431493..79d5065895 100644 --- a/controls/imageeditor/CHANGELOG.md +++ b/controls/imageeditor/CHANGELOG.md @@ -2,6 +2,14 @@ ## [Unreleased] +## 25.1.37 (2024-03-26) + +### Image Editor + +#### Bug Fixes + +- `#I565340` - The issue with "Script error thrown when attempting to reopen a base64 URL using a custom toolbar in mobile mode" has been resolved. + ## 25.1.35 (2024-03-15) ### Image Editor diff --git a/controls/imageeditor/package.json b/controls/imageeditor/package.json index 7b762a7990..a53c1902d3 100644 --- a/controls/imageeditor/package.json +++ b/controls/imageeditor/package.json @@ -1,6 +1,6 @@ { "name": "@syncfusion/ej2-image-editor", - "version": "23.1.39", + "version": "25.1.35", "description": "Essential JS 2 ImageEditor", "author": "Syncfusion Inc.", "license": "SEE LICENSE IN license", diff --git a/controls/imageeditor/spec/image-editor.spec.ts b/controls/imageeditor/spec/image-editor.spec.ts index 49c82a6c7d..ebf0336073 100644 --- a/controls/imageeditor/spec/image-editor.spec.ts +++ b/controls/imageeditor/spec/image-editor.spec.ts @@ -8137,5 +8137,75 @@ describe('ImageEditor', () => { done(); }, 100); }); + it('Coverage improvement - random combinations 1', (done) => { + imageEditor = new ImageEditor({ + height: '450px', + toolbarUpdating: function (args) { + if (args.toolbarType === 'rectangle') { + args.toolbarItems = ['strokeColor']; + } + } + }, '#image-editor'); + imageEditor.open('https://www.shutterstock.com/image-photo/linked-together-life-cropped-shot-600w-2149264221.jpg'); + setTimeout(() => { + setTimeout(() => { + imageEditor.cloneShape('shape_1'); + imageEditor.getShapeSettings(); + }, 500); + imageEditor.drawImage('https://www.shutterstock.com/image-photo/linked-together-life-cropped-shot-600w-2149264221.jpg', 500, 100, 200, 80, false, 90); + done(); + }, 100); + }); + it('Coverage improvement - random combinations 2', (done) => { + imageEditor = new ImageEditor({ + height: '450px', + zoomSettings: { minZoomFactor: 0.1, maxZoomFactor: 50 }, + toolbarUpdating: function (args) { + if (args.toolbarType === 'rectangle') { + args.toolbarItems = ['strokeColor']; + } + } + }, '#image-editor'); + imageEditor.open('https://www.shutterstock.com/image-photo/linked-together-life-cropped-shot-600w-2149264221.jpg'); + setTimeout(() => { + imageEditor.drawRectangle(350, 200, 650, 400, 15, 'red', 'green'); + imageEditor.select('custom'); + imageEditor.rotate(90); + imageEditor.zoom(1.5); + imageEditor.crop(); + imageEditor.reset(); + imageEditor.drawText(350, 100, 'Syncfusion', 'Arial', 70, true, true, '#40e040'); + imageEditor.selectShape('shape_1'); + imageEditor.enableTextEditing(); + imageEditor.reset(); + imageEditor.select('custom'); + imageEditor.rotate(90); + imageEditor.crop(); + imageEditor.straightenImage(10); + imageEditor.select('custom'); + imageEditor.select('3:4'); + imageEditor.crop(); + imageEditor.reset(); + imageEditor.zoom(0.8); + const finetuneBtn: any = document.querySelectorAll('#image-editor_adjustment')[0]; + finetuneBtn.click(); + setTimeout(() => {}); + imageEditor.reset(); + imageEditor.drawText(350, 100, 'Syncfusion', 'Arial', 70, true, true, '#40e040', true); + imageEditor.enableTextEditing(); + imageEditor.reset(); + imageEditor.drawLine(350, 300, 300, 100, 20, 'red'); + expect(imageEditor.objColl[imageEditor.objColl.length - 1].shape).toEqual('line'); + imageEditor.drawArrow(350, 300, 300, 100, 20, 'red', 'Arrow', 'SolidArrow'); + expect(imageEditor.objColl[imageEditor.objColl.length - 1].shape).toEqual('arrow'); + imageEditor.drawPath([{x: 400, y: 300}, {x: 600, y: 400}, {x: 700, y: 300}], 20, 'red'); + expect(imageEditor.objColl[imageEditor.objColl.length - 1].shape).toEqual('path'); + imageEditor.getShapeSettings(); + imageEditor.getImageFilter('Invert'); + imageEditor.canUndo(); + imageEditor.canRedo(); + done(); + }, 100); + }); }); }); diff --git a/controls/imageeditor/src/image-editor/action/shape.ts b/controls/imageeditor/src/image-editor/action/shape.ts index 1918b81420..6d8e8cb06e 100644 --- a/controls/imageeditor/src/image-editor/action/shape.ts +++ b/controls/imageeditor/src/image-editor/action/shape.ts @@ -720,6 +720,8 @@ export class Shape { endX: parent.activeObj.activePoint.startX + parent.activeObj.activePoint.width, endY: parent.activeObj.activePoint.startY + parent.activeObj.activePoint.height, width: selectionSettings.width, height: selectionSettings.height }; + parent.activeObj.activePoint.endX = parent.activeObj.activePoint.startX + parent.activeObj.activePoint.width; + parent.activeObj.activePoint.endY = parent.activeObj.activePoint.startY + parent.activeObj.activePoint.height; } private updateShapeChangeEventArgs(shapeSettings: ShapeSettings): void { @@ -775,6 +777,7 @@ export class Shape { if (shapeSettings.degree) { parent.activeObj.rotatedAngle = shapeSettings.degree * (Math.PI / 180); } + this.updateFontRatio(parent.activeObj); break; case 'rectangle': case 'image': diff --git a/controls/imageeditor/src/image-editor/action/transform.ts b/controls/imageeditor/src/image-editor/action/transform.ts index ceed16d2ae..975cbd358b 100644 --- a/controls/imageeditor/src/image-editor/action/transform.ts +++ b/controls/imageeditor/src/image-editor/action/transform.ts @@ -1453,8 +1453,9 @@ export class Transform { toolbarHeight = obj['toolbarHeight']; } } - if (Browser.isDevice && straightenObj['bool']) { - cxtTbarHeight = parent.element.querySelector('#' + parent.element.id + '_contextualToolbarArea').clientHeight; + const ctxTbarArea: HTMLElement = parent.element.querySelector('#' + parent.element.id + '_contextualToolbarArea'); + if (Browser.isDevice && straightenObj['bool'] && ctxTbarArea) { + cxtTbarHeight = ctxTbarArea.clientHeight; } parent.notify('toolbar', { prop: 'setToolbarHeight', value: {height: toolbarHeight }}); if (Browser.isDevice) { diff --git a/controls/imageeditor/src/image-editor/action/undo-redo.ts b/controls/imageeditor/src/image-editor/action/undo-redo.ts index 3f4703107a..1578ff6336 100644 --- a/controls/imageeditor/src/image-editor/action/undo-redo.ts +++ b/controls/imageeditor/src/image-editor/action/undo-redo.ts @@ -736,26 +736,6 @@ export class UndoRedo { parent.currObjType.isCustomCrop = false; } - private getImageAction(operation: string): string { - if (['brightness', 'contrast', 'saturation', 'opacity', 'blur', 'hue'].indexOf(operation) !== -1) { - return 'FinetuneApplied'; - } else if (['chrome', 'cold', 'warm', 'grayscale', 'blackandwhite', 'sepia', 'invert'].indexOf(operation) !== -1) { - return 'FilterApplied'; - } else if (operation === 'frame') { - return 'FrameApplied'; - } else if (operation === 'resize') { - return 'ImageResized'; - } else if (['deleteFreehandDrawing', 'deleteObj'].indexOf(operation) !== -1) { - return 'ShapeDeleted'; - } else if (operation === 'crop') { - return 'Cropped'; - } else if (['shapeInsert', 'freehanddraw', 'freehand-draw'].indexOf(operation) !== -1) { - return 'ShapeInserted'; - } else { - return 'ShapeCustomized'; - } - } - private updateUrc(operation: string, previousObj: CurrentObject, previousObjColl: SelectionPoint[], previousPointColl: Point[], previousSelPointColl: Point[], previousCropObj: CurrentObject, previousText?: string, diff --git a/controls/imageeditor/src/image-editor/base/image-editor.ts b/controls/imageeditor/src/image-editor/base/image-editor.ts index 49f727a0f4..95770ba406 100644 --- a/controls/imageeditor/src/image-editor/base/image-editor.ts +++ b/controls/imageeditor/src/image-editor/base/image-editor.ts @@ -1196,7 +1196,7 @@ export class ImageEditor extends Component implements INotifyPro private createDropUploader(): void { const uploadObj: Uploader = new Uploader({ - dropArea: document.getElementsByClassName('e-canvas-wrapper')[0] as HTMLElement, + dropArea: this.element.getElementsByClassName('e-canvas-wrapper')[0] as HTMLElement, allowedExtensions: '.jpg, .jpeg, .png,.svg', multiple: false, selected: (args: ChangeEventArgs) => { @@ -1485,7 +1485,8 @@ export class ImageEditor extends Component implements INotifyPro private notifyResetForAllModules(): void { const modules: ModuleDeclaration[] = this.requiredModules(); for (let i: number = 0; i < modules.length; i++) { - this.notify(modules[i as number].member, { prop: 'reset', onPropertyChange: false}); + const module: string = modules[i as number].member; + this.notify(module === 'toolbar-module' ? 'toolbar' : module, { prop: 'reset', onPropertyChange: false}); } } @@ -2293,8 +2294,19 @@ export class ImageEditor extends Component implements INotifyPro this.notify('shape', { prop: 'selectShape', onPropertyChange: false, value: {id: setting.id, obj: obj }}); this.notify('selection', { prop: 'getFreehandDrawEditing', onPropertyChange: false, value: {obj: freehandObj }}); if (obj['isSelected']) { + const tempFontSize: number = this.activeObj.textSettings.fontSize; this.notify('shape', { prop: 'updateShapeChangeEventArgs', onPropertyChange: false, value: {shapeSettings: setting }}); + if (this.activeObj.shape === 'text' && tempFontSize) { + const diff: number = this.activeObj.textSettings.fontSize - tempFontSize; + if (diff !== 0) { + this.activeObj.activePoint.height += diff; + this.activeObj.activePoint.startY -= (diff / 2); + this.activeObj.activePoint.endY += (diff / 2); + this.notify('draw', { prop: 'updateActiveObject', onPropertyChange: false, value: { actPoint: this.activeObj.activePoint, obj: this.activeObj, + isMouseMove: null, x: null, y: null } }); + } + } const activeObj: SelectionPoint = extend({}, this.activeObj, {}, true) as SelectionPoint; this.notify('shape', { prop: 'refreshActiveObj', onPropertyChange: false }); this.notify('draw', { prop: 'render-image', value: { isMouseWheel: null, isPreventClearRect: null, isFrame: null } }); diff --git a/controls/imageeditor/src/image-editor/renderer/toolbar.ts b/controls/imageeditor/src/image-editor/renderer/toolbar.ts index ba42e74dba..c4684d91c9 100644 --- a/controls/imageeditor/src/image-editor/renderer/toolbar.ts +++ b/controls/imageeditor/src/image-editor/renderer/toolbar.ts @@ -275,12 +275,6 @@ export class ToolbarModule { case 'setSelectedFreehandColor': this.selFhdColor = args.value['color']; break; - case 'getCurrentFilter': - args.value['obj']['currentFilter'] = parent.currentFilter; - break; - case 'setCurrentFilter': - parent.currentFilter = args.value['filter']; - break; case 'setInitialAdjustmentValue': parent.initialAdjustmentValue = args.value['value']; break; @@ -303,9 +297,6 @@ export class ToolbarModule { case 'refreshSlider': this.refreshSlider(); break; - case 'renderSlider': - this.renderSlider(args.value['type']); - break; case 'getCurrAdjustmentValue': parent.getCurrAdjustmentValue(args.value['type']); break; @@ -315,18 +306,6 @@ export class ToolbarModule { case 'refreshShapeDrawing': this.refreshShapeDrawing(); break; - case 'getCropToolbar': - args.value['obj']['isCropToolbar'] = parent.isCropToolbar; - break; - case 'getPrevCurrSelectionPoint': - args.value['obj']['prevCurrSelectionPoint'] = parent.prevCurrSelectionPoint; - break; - case 'setPrevCurrSelectionPoint': - parent.prevCurrSelectionPoint = args.value['point']; - break; - case 'updateCropTransformItems': - parent.updateCropTransformItems(); - break; case 'setEnableDisableUndoRedo': this.preventEnableDisableUr = args.value['isPrevent']; break; @@ -373,7 +352,7 @@ export class ToolbarModule { private reset(): void { const parent: ImageEditor = this.parent; - this.defToolbarItems = []; this.toolbarHeight = 46; parent.prevCurrSelectionPoint = null; + this.toolbarHeight = 46; parent.prevCurrSelectionPoint = null; this.zoomBtnHold = null; this.currToolbar = ''; parent.cxtTbarHeight = null; this.currentToolbar = 'main'; this.selFhdColor = '#42a5f5'; parent.currentFilter = ''; this.preventZoomBtn = parent.isCropToolbar = this.preventEnableDisableUr = this.isFrameToolbar = false; diff --git a/controls/imageeditor/styles/image-editor/_layout.scss b/controls/imageeditor/styles/image-editor/_layout.scss index 2e0a883d05..963894beca 100644 --- a/controls/imageeditor/styles/image-editor/_layout.scss +++ b/controls/imageeditor/styles/image-editor/_layout.scss @@ -512,6 +512,9 @@ .e-ie-finetune-slider-wrap { top: calc(50% - 15px) !important; /* stylelint-disable-line declaration-no-important */ + @if $skin-name == 'Material3' or $skin-name == 'tailwind' { + top: calc(50% - 14px) !important; /* stylelint-disable-line declaration-no-important */ + } } .e-transparency-slider-wrap { diff --git a/controls/inputs/CHANGELOG.md b/controls/inputs/CHANGELOG.md index 0ad612b7aa..4ae60b15ba 100644 --- a/controls/inputs/CHANGELOG.md +++ b/controls/inputs/CHANGELOG.md @@ -2,7 +2,7 @@ ## [Unreleased] -## 25.1.35 (2024-03-15) +## 25.1.37 (2024-03-26) ### Signature diff --git a/controls/kanban/CHANGELOG.md b/controls/kanban/CHANGELOG.md index 1cd9f8dce3..2d2cfb150e 100644 --- a/controls/kanban/CHANGELOG.md +++ b/controls/kanban/CHANGELOG.md @@ -2,6 +2,32 @@ ## [Unreleased] +## 25.1.35 (2024-03-15) + +### Kanban + +#### Bug Fixes + +- `#I525892` - Now, the card template works properly upon drag-and-drop action in Kanban with remote data. + +- `#I550208` - Now, the swimlane header template will work properly in the mobile view. + +## 24.1.47 (2024-01-23) + +### Kanban + +#### Bug Fixes + +- `#I544423` - Now, the Kanban column header title shows properly when the column is collapsed. + +## 24.1.46 (2024-01-17) + +### Kanban + +#### Bug Fixes + +- `#I535989` - Now, drop clone works properly when slowly dragging and dropping the cards in the last position in the Kanban column. + ## 24.1.41 (2023-12-18) ### Kanban @@ -16,9 +42,9 @@ #### Bug Fixes -`#I513537` - Now, localization of the "Cards" text was done. When you drag the multiple cards, it show up. +- `#I513537` - Now, localization of the "Cards" text was done. When you drag the multiple cards, it show up. -`#I515897` - Now, when the `cancel` argument is set to true in the `dialogClose` event in the kanban, it works properly. +- `#I515897` - Now, when the `cancel` argument is set to true in the `dialogClose` event in the kanban, it works properly. ## 23.1.36 (2023-09-15) @@ -26,9 +52,9 @@ #### Bug Fixes -`#I492818` - Now, fast scrolling from top to bottom of the column continuously works properly. +- `#I492818` - Now, fast scrolling from top to bottom of the column continuously works properly. -`#I495751` - Now, dragging a card to the first position in the Kanban works properly without flickering. +- `#I495751` - Now, dragging a card to the first position in the Kanban works properly without flickering. ## 22.1.34 (2023-06-21) @@ -44,7 +70,7 @@ #### Bug Fixes -`#F181441` - Resolved a issue, where a console error was thrown when dragging into the column header on the Kanban. +- `#F181441` - Resolved a issue, where a console error was thrown when dragging into the column header on the Kanban. ## 21.1.37 (2023-03-29) diff --git a/controls/kanban/package.json b/controls/kanban/package.json index 5cbd93bdce..902b87dbb1 100644 --- a/controls/kanban/package.json +++ b/controls/kanban/package.json @@ -1,6 +1,6 @@ { "name": "@syncfusion/ej2-kanban", - "version": "23.1.36", + "version": "25.1.35", "author": "Syncfusion Inc.", "license": "SEE LICENSE IN license", "main": "./dist/ej2-kanban.umd.min.js", diff --git a/controls/kanban/src/kanban/base/layout-render.ts b/controls/kanban/src/kanban/base/layout-render.ts index bf91c48563..c46acd9c74 100644 --- a/controls/kanban/src/kanban/base/layout-render.ts +++ b/controls/kanban/src/kanban/base/layout-render.ts @@ -231,9 +231,18 @@ export class LayoutRender extends MobileLayout { private initializeSwimlaneTree(): void { if (this.parent.swimlaneSettings.keyField && this.parent.isAdaptive && this.parent.kanbanData.length !== 0) { + const swimlaneHeaderName: HTMLElement = this.parent.element.querySelector('.' + cls.TOOLBAR_SWIMLANE_NAME_CLASS); this.swimlaneRow = [this.kanbanRows[this.swimlaneIndex]]; this.renderSwimlaneTree(); - this.parent.element.querySelector('.' + cls.TOOLBAR_SWIMLANE_NAME_CLASS).innerHTML = this.swimlaneRow[0].textField; + if (this.parent.swimlaneSettings.template) { + const cardCount: number = this.swimlaneData[this.swimlaneRow[0].keyField].length; + const templateArgs: HeaderArgs = extend({}, this.swimlaneRow[0], { count: cardCount }, true) as HeaderArgs; + const swimlaneTemplate: HTMLElement[] = this.parent.templateParser( + this.parent.swimlaneSettings.template)(templateArgs, this.parent, 'swimlaneTemplate', '', false); + swimlaneHeaderName.appendChild(swimlaneTemplate[0]); + } else { + swimlaneHeaderName.innerHTML = this.swimlaneRow[0].textField; + } } } diff --git a/controls/layouts/CHANGELOG.md b/controls/layouts/CHANGELOG.md index 69aded70a3..6f4fa7e009 100644 --- a/controls/layouts/CHANGELOG.md +++ b/controls/layouts/CHANGELOG.md @@ -2,13 +2,31 @@ ## [Unreleased] -## 24.2.4 (2024-02-06) +## 25.1.37 (2024-03-26) -### Dashboard Layout +### DashboardLayout #### Bug Fixes -- `#FB49154` - The console error in the Dashboard Layout component when resizing the browser window has been resolved. +- `#I564184` - Resolved the issue with the dynamically added panel not being persisted when `enablePersistence` is enabled in the Dashboard Layout. + +## 25.1.35 (2024-03-15) + +### Timeline + +The Timeline control enables users to display a series of data in chronological order, providing a visually compelling and user-friendly experience. This showcases user activities, tracking progress, narrating historical timelines, and more. + +#### Key features + +- **Orientation** - Display items in a horizontal or vertical orientation. + +- **Opposite content** - Display additional information opposite to the item content. + +- **Items alignment** - Items' content and opposite content can be aligned - before, after, alternate, or alternate reverse. + +- **Reverse timeline** - Shows the timeline items in the reverse order. + +- **Templates** - Customize the default appearance, including styling the dot item, templated content, and more. ## 20.4.48 (2023-02-01) diff --git a/controls/layouts/README.md b/controls/layouts/README.md index 41ef0bdabd..9743f8a012 100644 --- a/controls/layouts/README.md +++ b/controls/layouts/README.md @@ -2,7 +2,7 @@ # ej2-layouts -The layout package contains cards, avatars, splitter and Dashboard Layout controls. +The layout package contains cards, avatars, splitter, timeline and Dashboard Layout controls. * The `card` is a small container in which user can show defined content in specific structure. @@ -10,6 +10,8 @@ The layout package contains cards, avatars, splitter and Dashboard Layout contro * The `splitter` is container control which used to construct different layouts using multiple and nested panes. +* The `timeline` is a tool for displaying chronological information effortlessly within your application. It offers a visually compelling and user-friendly experience for showcasing user activities, tracking progress, or narrating historical timelines. + * The `DashboardLayout` is a grid structured layout control that helps to create a dashboard with panels. Panels hold the UI components and allow resize, reorder, drag-n-drop, remove and add options. This allows users to easily place the components at the desired position within the grid layout. ![Layout](https://ej2.syncfusion.com/products/images/layout/readme.png) @@ -42,6 +44,10 @@ Following list of controls are available in the package * [Getting Started](https://ej2.syncfusion.com/documentation/splitter/getting-started/?utm_source=npm&utm_medium=listing&utm_campaign=javascript-layout-npm) * [View Online Demos](https://ej2.syncfusion.com/demos/?utm_source=npm&utm_medium=listing&utm_campaign=javascript-layout-npm#/material/splitter/default.html) +* [JavaScript Timeline](https://www.syncfusion.com/javascript-ui-controls/js-timeline?utm_source=npm&utm_medium=listing&utm_campaign=javascript-layout-npm) - Used to build timelines to showcases user activities, tracking progress, narrating historical timelines, and more. + * [Getting Started](https://ej2.syncfusion.com/documentation/timeline/getting-started/?utm_source=npm&utm_medium=listing&utm_campaign=javascript-layout-npm) + * [View Online Demos](https://ej2.syncfusion.com/demos/?utm_source=npm&utm_medium=listing&utm_campaign=javascript-layout-npm#/material/timeline/default.html) + * [JavaScript Dashboard Layout](https://www.syncfusion.com/javascript-ui-controls/js-dashboard-layout?utm_source=npm&utm_medium=listing&utm_campaign=javascript-layout-npm) - Used to build dashboards with panels that holds the UI components and allow resize, reorder, drag-n-drop, remove and add options. * [Getting Started](https://ej2.syncfusion.com/documentation/dashboard-layout/getting-started/?utm_source=npm&utm_medium=listing&utm_campaign=javascript-layout-npm) * [View Online Demos](https://ej2.syncfusion.com/demos/?utm_source=npm&utm_medium=listing&utm_campaign=javascript-layout-npm#/material/dashboard-layout/default.html) @@ -70,6 +76,7 @@ These components are available in following list of: ## Key Features * Card + * [Header](https://ej2.syncfusion.com/demos/?utm_source=npm&utm_medium=listing&utm_campaign=javascript-layout-npm#/material/card/basic.html) - Header supports to include title, subtitle along with image. * [Images and Title](https://ej2.syncfusion.com/demos/?utm_source=npm&utm_medium=listing&utm_campaign=javascript-layout-npm#/material/card/reveal.html) - Support to include images with customizable caption positions in it. @@ -90,6 +97,7 @@ These components are available in following list of: * xlarge * Splitter + * [Multiple Panes](https://ej2.syncfusion.com/demos/?utm_source=npm&utm_medium=listing&utm_campaign=javascript-layout-npm#/material/splitter/default.html) - Provided an option to configure more than two panes. * [Resizable Panes](https://ej2.syncfusion.com/demos/?utm_source=npm&utm_medium=listing&utm_campaign=javascript-layout-npm#/material/splitter/code-editor-layout.html) - Supports resizable to adjust its pane size dynamically. @@ -100,15 +108,27 @@ These components are available in following list of: * [Nested Panes](https://ej2.syncfusion.com/demos/?utm_source=npm&utm_medium=listing&utm_campaign=javascript-layout-npm#/material/splitter/code-editor-layout.html) - Another splitter can be integrated within panes to create a complex layout. - * Dashboard Layout +* Timeline + + * [Orientation](https://ej2.syncfusion.com/demos/?utm_source=npm&utm_medium=listing&utm_campaign=javascript-layout-npm#/material/timeline/api.html) - Display items in a horizontal or vertical orientation. + + * [Opposite content](https://ej2.syncfusion.com/demos/?utm_source=npm&utm_medium=listing&utm_campaign=javascript-layout-npm#/material/timeline/api.html) - Display additional information opposite to the item content. + + * [Items alignment](https://ej2.syncfusion.com/demos/?utm_source=npm&utm_medium=listing&utm_campaign=javascript-layout-npm#/material/timeline/api.html) - Items content and opposite content can be aligned - before, after, alternate, or alternate reverse. + + * [Reverse timeline](https://ej2.syncfusion.com/demos/?utm_source=npm&utm_medium=listing&utm_campaign=javascript-layout-npm#/material/timeline/api.html) - Shows the timeline items in the reverse order. + + * [Templates](https://ej2.syncfusion.com/demos/?utm_source=npm&utm_medium=listing&utm_campaign=javascript-layout-npm#/material/timeline/template.html) - Customize the default appearance, including styling the dot item, templated content, and more. + +* Dashboard Layout - * [Drag and Drop](https://ej2.syncfusion.com/demos/?utm_source=npm&utm_medium=listing&utm_campaign=javascript-layout-npm#/material/dashboardlayout/properties.html) - Allows drag and drop of panels at the desired location within the dashboard. + * [Drag and Drop](https://ej2.syncfusion.com/demos/?utm_source=npm&utm_medium=listing&utm_campaign=javascript-layout-npm#/material/dashboardlayout/properties.html) - Allows drag and drop of panels at the desired location within the dashboard. - * [Floating](https://ej2.syncfusion.com/demos/?utm_source=npm&utm_medium=listing&utm_campaign=javascript-layout-npm#/material/dashboardlayout/properties.html) - Floats the panels upward when the dragging option is enabled. + * [Floating](https://ej2.syncfusion.com/demos/?utm_source=npm&utm_medium=listing&utm_campaign=javascript-layout-npm#/material/dashboardlayout/properties.html) - Floats the panels upward when the dragging option is enabled. - * [Resizing](https://ej2.syncfusion.com/demos/?utm_source=npm&utm_medium=listing&utm_campaign=javascript-layout-npm#/material/dashboardlayout/properties.html) - Support to resize the panels in any direction as per the requirement. + * [Resizing](https://ej2.syncfusion.com/demos/?utm_source=npm&utm_medium=listing&utm_campaign=javascript-layout-npm#/material/dashboardlayout/properties.html) - Support to resize the panels in any direction as per the requirement. - * [MediaQuery](https://ej2.syncfusion.com/demos/?utm_source=npm&utm_medium=listing&utm_campaign=javascript-layout-npm#/material/dashboardlayout/default.html) - Allows the panels to be stacked when the specified resolution is met. + * [MediaQuery](https://ej2.syncfusion.com/demos/?utm_source=npm&utm_medium=listing&utm_campaign=javascript-layout-npm#/material/dashboardlayout/default.html) - Allows the panels to be stacked when the specified resolution is met. ## Support diff --git a/controls/layouts/package.json b/controls/layouts/package.json index 249ca95eb4..15505c412c 100644 --- a/controls/layouts/package.json +++ b/controls/layouts/package.json @@ -1,6 +1,6 @@ { "name": "@syncfusion/ej2-layouts", - "version": "24.2.4", + "version": "25.1.35", "description": "A package of Essential JS 2 layout pure CSS components such as card and avatar. The card is used as small container to show content in specific structure, whereas the avatars are icons, initials or figures representing particular person.", "author": "Syncfusion Inc.", "license": "SEE LICENSE IN license", @@ -11,6 +11,7 @@ "@syncfusion/ej2-base": "*" }, "devDependencies": { + "@syncfusion/ej2-staging": "^1.0.1", "@types/chai": "^3.4.28", "@types/jasmine": "2.8.9", "@types/jasmine-ajax": "^3.1.27", diff --git a/controls/layouts/spec/dashboard-layout.spec.ts b/controls/layouts/spec/dashboard-layout.spec.ts index 6caf7d6e6e..7ef4c2dba2 100644 --- a/controls/layouts/spec/dashboard-layout.spec.ts +++ b/controls/layouts/spec/dashboard-layout.spec.ts @@ -314,7 +314,7 @@ describe('GridLayout', () => { cellSpacing: [10, 10], showGridLines: true, panels: [{ - id: "one", sizeX: 2, sizeY: 1, row: 1, col: 0, header: "
", cssClass:"test" + id: "one", sizeX: 2, sizeY: 1, row: 1, col: 0, header: "
",cssClass:"test" }, { id: "two", sizeX: 2, sizeY: 1, row: 0, col: 0, content: "
2
" @@ -726,7 +726,6 @@ describe('GridLayout', () => { expect(gridLayOut.element.children[0].offsetHeight).toBe(101); expect(gridLayOut.element.offsetHeight).toBe(gridLayOut.element.children[0].offsetHeight); }); - it('addpanel with root element height with mediaquery null test case ', () => { let content = generateTemplate('0'); gridLayOut = new DashboardLayout({ @@ -8047,4 +8046,4 @@ describe('GridLayout', () => { detach(ele); } }); - }); + }); \ No newline at end of file diff --git a/controls/layouts/spec/splitter.spec.ts b/controls/layouts/spec/splitter.spec.ts index 91eb1a967d..b230cce75c 100644 --- a/controls/layouts/spec/splitter.spec.ts +++ b/controls/layouts/spec/splitter.spec.ts @@ -5252,8 +5252,8 @@ describe('Splitter Control', () => { splitterObj.paneSettings[0].size = '200px'; splitterObj.dataBind(); expect(splitterObj.allPanes[0].style.flexBasis).toBe('200px'); - expect(splitterObj.allPanes[1].style.flexBasis).toBe('149px'); - expect(splitterObj.allPanes[2].style.flexBasis).toBe('149px'); + expect(splitterObj.allPanes[1].style.flexBasis).toBe(''); + expect(splitterObj.allPanes[2].style.flexBasis).toBe(''); splitterObj.destroy(); }); it('Check flexible pane size -Pixel -one flexible pane', function () { @@ -5266,7 +5266,7 @@ describe('Splitter Control', () => { splitterObj.dataBind(); expect(splitterObj.allPanes[0].style.flexBasis).toBe('200px'); expect(splitterObj.allPanes[1].style.flexBasis).toBe('100px'); - expect(splitterObj.allPanes[2].style.flexBasis).toBe('298px'); + expect(splitterObj.allPanes[2].style.flexBasis).toBe(''); splitterObj.destroy(); }); it('Check flexible pane size -Percentage - Two flexible pane', function () { @@ -5278,8 +5278,8 @@ describe('Splitter Control', () => { splitterObj.paneSettings[0].size = '50%'; splitterObj.dataBind(); expect(splitterObj.allPanes[0].style.flexBasis).toBe('50%'); - expect(splitterObj.allPanes[1].style.flexBasis).toBe('124px'); - expect(splitterObj.allPanes[2].style.flexBasis).toBe('124px'); + expect(splitterObj.allPanes[1].style.flexBasis).toBe(''); + expect(splitterObj.allPanes[2].style.flexBasis).toBe(''); splitterObj.destroy(); }); it('Check flexible pane size -Percentage - one flexible pane', function () { @@ -5292,7 +5292,7 @@ describe('Splitter Control', () => { splitterObj.dataBind(); expect(splitterObj.allPanes[0].style.flexBasis).toBe('50%'); expect(splitterObj.allPanes[1].style.flexBasis).toBe('20%'); - expect(splitterObj.allPanes[2].style.flexBasis).toBe('248px'); + expect(splitterObj.allPanes[2].style.flexBasis).toBe(''); splitterObj.destroy(); }); }); diff --git a/controls/layouts/spec/timeline.spec.ts b/controls/layouts/spec/timeline.spec.ts new file mode 100644 index 0000000000..6d15b4b5d8 --- /dev/null +++ b/controls/layouts/spec/timeline.spec.ts @@ -0,0 +1,843 @@ +import { createElement, remove, isNullOrUndefined} from '@syncfusion/ej2-base'; +import { Timeline, TimelineItemModel, TimelineRenderingEventArgs } from '../src/timeline/index'; +import { getMemoryProfile, inMB, profile } from './common.spec'; + +describe('Timeline', () => { + beforeAll(() => { + const isDef: any = (o: any) => o !== undefined && o !== null; + if (!isDef(window.performance)) { + console.log('Unsupported environment, window.performance.memory is unavailable'); + this.skip(); // skips test (in Chai) + return; + } + }); + + describe('DOM', () => { + let timeline: Timeline; + let timelineElement: HTMLElement; + + beforeEach(() => { + timelineElement = createElement('div', { id: 'timeline'}); + document.body.appendChild(timelineElement); + }); + + afterEach(() => { + if (timeline) { + timeline.destroy(); + timeline = undefined; + } + remove(timelineElement); + }); + + it('Default timeline testing', () => { + timeline = new Timeline({ + items: [{}, {}, {}, {}] + }); + timeline.appendTo('#timeline'); + expect(timelineElement.classList.contains('e-timeline')).toEqual(true); + expect(timelineElement.classList.contains('.e-vertical') != null).toEqual(true); + expect(timelineElement.querySelector('.e-timeline-items') != null).toEqual(true); + expect(timelineElement.querySelectorAll('.e-timeline-item').length).toBe(4); + expect(timelineElement.querySelectorAll('.e-dot').length).toBe(4); + timeline.orientation = 'horizontal'; + timeline.dataBind(); + expect(timelineElement.classList.contains('.e-vertical')).toEqual(false); + expect(timelineElement.classList.contains('.e-horizontal') != null).toEqual(true); + timeline.orientation = 'vertical'; + timeline.dataBind(); + expect(timelineElement.classList.contains('.e-vertical') != null).toEqual(true); + expect(timelineElement.classList.contains('.e-horizontal')).toEqual(false); + }); + + it('Horizontal timeline testing', () => { + timeline = new Timeline({ + items: [{}, {}, {}, {}] + }); + timeline.appendTo('#timeline'); + expect(timelineElement.classList.contains('e-timeline')).toEqual(true); + expect(timelineElement.classList.contains('.e-horizontal') != null).toEqual(true); + expect(timelineElement.querySelector('.e-timeline-items') != null).toEqual(true); + expect(timelineElement.querySelectorAll('.e-timeline-item').length).toBe(4); + expect(timelineElement.querySelectorAll('.e-dot').length).toBe(4); + }); + + it('Custom Icon', () => { + const customData: TimelineItemModel[] = [ + {dotCss: 'e-icons e-people'}, + {dotCss: 'e-icons e-signature'}, + {dotCss: 'e-icons e-location'}, + {dotCss: 'e-icons e-cut'} + ]; + timeline = new Timeline({ items: customData }); + timeline.appendTo('#timeline'); + expect(timelineElement.classList.contains('e-timeline')).toEqual(true); + expect(timelineElement.classList.contains('.e-vertical') != null).toEqual(true); + expect(timelineElement.querySelector('.e-timeline-items') != null).toEqual(true); + expect(timelineElement.querySelectorAll('.e-timeline-item').length).toBe(4); + expect(timelineElement.querySelectorAll('.e-dot').length).toBe(4); + const liElementArray: any = timelineElement.querySelectorAll('.e-dot'); + expect((liElementArray[0] as HTMLElement).classList.contains('e-people')).toEqual(true); + expect((liElementArray[1] as HTMLElement).classList.contains('e-signature')).toEqual(true); + expect((liElementArray[2] as HTMLElement).classList.contains('e-location')).toEqual(true); + expect((liElementArray[3] as HTMLElement).classList.contains('e-cut')).toEqual(true); + }); + + it('Get component name testing', () => { + timeline = new Timeline({items: [{}, {}, {}, {}]}); + timeline.appendTo('#timeline'); + expect(timeline.getModuleName()).toEqual('timeline'); + }); + + it('Timeline testing with Persistence', () => { + timeline = new Timeline({ + items: [{}, {}, {}, {}], + enablePersistence: true + }); + timeline.appendTo('#timeline'); + expect(timelineElement.classList.contains('e-timeline')).toEqual(true); + expect(timelineElement.classList.contains('.e-timeline') != null).toEqual(true); + }); + + it('Generic div Element ID generation', () => { + timeline = new Timeline({ + items: [{}, {}, {}, {}] + }); + const timelineEle1 = createElement('div', {}); + document.body.appendChild(timelineEle1); + timeline.appendTo(timelineEle1); + expect(timelineEle1.getAttribute('id') != timelineElement.getAttribute('id')).toEqual(true); + expect(isNullOrUndefined(timelineEle1.id)).toBe(false); + timeline.destroy(); + timeline = undefined; + remove(timelineEle1); + }); + }); + + describe('DOM Properties', () => { + let timeline: Timeline; + let timelineElement: HTMLElement; + + beforeEach(() => { + timelineElement = createElement('div', { id: 'timeline'}); + document.body.appendChild(timelineElement); + }); + + afterEach(() => { + if (timeline) { + timeline.destroy(); + timeline = undefined; + } + remove(timelineElement); + }); + + it('cssClass', () => { + timeline = new Timeline({ + items: [{}, {}, {}, {}], + cssClass: 'testClass' + }); + timeline.appendTo('#timeline'); + expect(timelineElement.classList.contains('.e-vertical') != null).toEqual(true); + expect(timelineElement.querySelector('.e-timeline-items') != null).toEqual(true); + expect(timelineElement.querySelectorAll('.e-timeline-item').length).toBe(4); + expect(timelineElement.querySelectorAll('.e-dot').length).toBe(4); + expect(timelineElement.classList.contains('testClass')).toBe(true); + timeline.cssClass = 'newClass'; + timeline.dataBind(); + expect(timelineElement.classList.contains('newClass')).toBe(true); + expect(timelineElement.classList.contains('testClass')).toBe(false); + }); + + it('Item with cssClass', () => { + const customData: TimelineItemModel[] = [ + {dotCss: 'e-icons e-people', cssClass: 'testClass'}, + {dotCss: 'e-icons e-signature'}, + {dotCss: 'e-icons e-location'}, + {dotCss: 'e-icons e-cut'} + ]; + timeline = new Timeline({ items: customData }); + timeline.appendTo('#timeline'); + expect(timelineElement.classList.contains('e-timeline')).toEqual(true); + expect(timelineElement.classList.contains('.e-vertical') != null).toEqual(true); + expect(timelineElement.querySelector('.e-timeline-items') != null).toEqual(true); + expect(timelineElement.querySelectorAll('.e-timeline-item').length).toBe(4); + expect(timelineElement.querySelectorAll('.e-dot').length).toBe(4); + expect(timelineElement.querySelector('.e-timeline-item').classList).toContain('testClass'); + }); + + it('Item with disabled', () => { + const customData: TimelineItemModel[] = [ + {dotCss: 'e-icons e-people', disabled: true}, + {dotCss: 'e-icons e-signature'}, + {dotCss: 'e-icons e-location'}, + {dotCss: 'e-icons e-cut'} + ]; + timeline = new Timeline({ items: customData }); + timeline.appendTo('#timeline'); + expect(timelineElement.classList.contains('e-timeline')).toEqual(true); + expect(timelineElement.classList.contains('.e-vertical') != null).toEqual(true); + expect(timelineElement.querySelector('.e-timeline-items') != null).toEqual(true); + expect(timelineElement.querySelectorAll('.e-timeline-item').length).toBe(4); + expect(timelineElement.querySelectorAll('.e-dot').length).toBe(4); + expect(timelineElement.querySelector('.e-timeline-item').classList).toContain('e-item-disabled'); + timeline.items[0].disabled = false; + timeline.dataBind(); + expect(timelineElement.querySelector('.e-timeline-item').classList.contains('e-item-disabled')).toBe(false); + }); + + it('RTL', () => { + timeline = new Timeline({ + items: [{}, {}, {}, {}], + enableRtl: true + }); + timeline.appendTo('#timeline'); + expect(timelineElement.classList.contains('e-timeline')).toEqual(true); + expect(timelineElement.classList.contains('.e-vertical') != null).toEqual(true); + expect(timelineElement.querySelector('.e-timeline-items') != null).toEqual(true); + expect(timelineElement.querySelectorAll('.e-timeline-item').length).toBe(4); + expect(timelineElement.querySelectorAll('.e-dot').length).toBe(4); + expect(timelineElement.classList.contains('e-rtl')).toEqual(true); + timeline.enableRtl = false; + timeline.dataBind(); + expect(timelineElement.classList.contains('e-rtl')).toEqual(false); + timeline.enableRtl = true; + timeline.dataBind(); + expect(timelineElement.classList.contains('e-rtl')).toEqual(true); + }); + + it('text content', () => { + const customData: TimelineItemModel[] = [ + {content: 'Ordered'}, + {content: 'Processing'}, + {content: 'Shipped'}, + {content: 'Delivered'} + ]; + timeline = new Timeline({ items: customData }); + timeline.appendTo('#timeline'); + expect(timelineElement.classList.contains('e-timeline')).toEqual(true); + expect(timelineElement.classList.contains('e-align-after')).toEqual(true); + expect(timelineElement.classList.contains('.e-vertical') != null).toEqual(true); + expect(timelineElement.querySelector('.e-timeline-items') != null).toEqual(true); + expect(timelineElement.querySelectorAll('.e-timeline-item').length).toBe(4); + expect(timelineElement.querySelectorAll('.e-dot').length).toBe(4); + const liElementArray: any = timelineElement.querySelectorAll('.e-content'); + expect((liElementArray[0] as HTMLElement).innerText).toEqual('Ordered'); + expect((liElementArray[1] as HTMLElement).innerText).toEqual('Processing'); + expect((liElementArray[2] as HTMLElement).innerText).toEqual('Shipped'); + expect((liElementArray[3] as HTMLElement).innerText).toEqual('Delivered'); + }); + + it('text content with reverse feature', () => { + const customData: TimelineItemModel[] = [ + {content: 'Ordered'}, + {content: 'Processing'}, + {content: 'Shipped'}, + {content: 'Delivered'} + ]; + timeline = new Timeline({ items: customData, reverse: true }); + timeline.appendTo('#timeline'); + expect(timelineElement.classList.contains('e-timeline')).toEqual(true); + expect(timelineElement.classList.contains('e-timeline-reverse')).toEqual(true); + expect(timelineElement.classList.contains('e-align-after')).toEqual(true); + expect(timelineElement.classList.contains('.e-vertical') != null).toEqual(true); + expect(timelineElement.querySelector('.e-timeline-items') != null).toEqual(true); + expect(timelineElement.querySelectorAll('.e-timeline-item').length).toBe(4); + expect(timelineElement.querySelectorAll('.e-dot').length).toBe(4); + const liElementArray: any = timelineElement.querySelectorAll('.e-content'); + expect((liElementArray[0] as HTMLElement).innerText).toEqual('Ordered'); + expect((liElementArray[1] as HTMLElement).innerText).toEqual('Processing'); + expect((liElementArray[2] as HTMLElement).innerText).toEqual('Shipped'); + expect((liElementArray[3] as HTMLElement).innerText).toEqual('Delivered'); + timeline.reverse = false; + timeline.dataBind(); + expect(timelineElement.classList.contains('e-timeline-reverse')).toEqual(false); + timeline.reverse = true; + timeline.dataBind(); + expect(timelineElement.classList.contains('e-timeline-reverse')).toEqual(true); + }); + + it('custom icon with text content', () => { + const customData: TimelineItemModel[] = [ + {dotCss: 'e-icons e-people', content: 'Ordered'}, + {dotCss: 'e-icons e-signature', content: 'Processing'}, + {dotCss: 'e-icons e-location', content: 'Shipped'}, + {dotCss: 'e-icons e-cut', content: 'Delivered'} + ]; + timeline = new Timeline({ items: customData }); + timeline.appendTo('#timeline'); + expect(timelineElement.classList.contains('e-timeline')).toEqual(true); + expect(timelineElement.classList.contains('e-align-after')).toEqual(true); + expect(timelineElement.classList.contains('.e-vertical') != null).toEqual(true); + expect(timelineElement.querySelector('.e-timeline-items') != null).toEqual(true); + expect(timelineElement.querySelectorAll('.e-timeline-item').length).toBe(4); + expect(timelineElement.querySelectorAll('.e-dot').length).toBe(4); + expect(timelineElement.querySelectorAll('.e-content').length).toBe(4); + const liIconElementArray: any = timelineElement.querySelectorAll('.e-dot'); + expect((liIconElementArray[0] as HTMLElement).classList.contains('e-people')).toEqual(true); + expect((liIconElementArray[1] as HTMLElement).classList.contains('e-signature')).toEqual(true); + expect((liIconElementArray[2] as HTMLElement).classList.contains('e-location')).toEqual(true); + expect((liIconElementArray[3] as HTMLElement).classList.contains('e-cut')).toEqual(true); + const liElementArray: any = timelineElement.querySelectorAll('.e-content'); + expect((liElementArray[0] as HTMLElement).innerText).toEqual('Ordered'); + expect((liElementArray[1] as HTMLElement).innerText).toEqual('Processing'); + expect((liElementArray[2] as HTMLElement).innerText).toEqual('Shipped'); + expect((liElementArray[3] as HTMLElement).innerText).toEqual('Delivered'); + }); + + it('text content with before position', () => { + const customData: TimelineItemModel[] = [ + {content: 'Ordered'}, + {content: 'Processing'}, + {content: 'Shipped'}, + {content: 'Delivered'} + ]; + timeline = new Timeline({ items: customData, align: 'before' }); + timeline.appendTo('#timeline'); + expect(timelineElement.classList.contains('e-timeline')).toEqual(true); + expect(timelineElement.classList.contains('e-align-before')).toEqual(true); + expect(timelineElement.classList.contains('.e-vertical') != null).toEqual(true); + expect(timelineElement.querySelector('.e-timeline-items') != null).toEqual(true); + expect(timelineElement.querySelectorAll('.e-timeline-item').length).toBe(4); + expect(timelineElement.querySelectorAll('.e-dot').length).toBe(4); + const liElementArray: any = timelineElement.querySelectorAll('.e-content'); + expect((liElementArray[0] as HTMLElement).innerText).toEqual('Ordered'); + expect((liElementArray[1] as HTMLElement).innerText).toEqual('Processing'); + expect((liElementArray[2] as HTMLElement).innerText).toEqual('Shipped'); + expect((liElementArray[3] as HTMLElement).innerText).toEqual('Delivered'); + }); + + it('text content with alternate position', () => { + const customData: TimelineItemModel[] = [ + {content: 'Ordered'}, + {content: 'Processing'}, + {content: 'Shipped'}, + {content: 'Delivered'} + ]; + timeline = new Timeline({ items: customData, align: 'alternate' }); + timeline.appendTo('#timeline'); + expect(timelineElement.classList.contains('e-timeline')).toEqual(true); + expect(timelineElement.classList.contains('e-align-alternate')).toEqual(true); + expect(timelineElement.classList.contains('.e-vertical') != null).toEqual(true); + expect(timelineElement.querySelector('.e-timeline-items') != null).toEqual(true); + expect(timelineElement.querySelectorAll('.e-timeline-item').length).toBe(4); + expect(timelineElement.querySelectorAll('.e-dot').length).toBe(4); + const liElementArray: any = timelineElement.querySelectorAll('.e-content'); + expect((liElementArray[0] as HTMLElement).innerText).toEqual('Ordered'); + expect((liElementArray[1] as HTMLElement).innerText).toEqual('Processing'); + expect((liElementArray[2] as HTMLElement).innerText).toEqual('Shipped'); + expect((liElementArray[3] as HTMLElement).innerText).toEqual('Delivered'); + }); + + it('text content with alternate reverse position', () => { + const customData: TimelineItemModel[] = [ + {content: 'Ordered'}, + {content: 'Processing'}, + {content: 'Shipped'}, + {content: 'Delivered'} + ]; + timeline = new Timeline({ items: customData, align: 'alternatereverse' }); + timeline.appendTo('#timeline'); + expect(timelineElement.classList.contains('e-timeline')).toEqual(true); + expect(timelineElement.classList.contains('e-align-alternatereverse')).toEqual(true); + expect(timelineElement.classList.contains('.e-vertical') != null).toEqual(true); + expect(timelineElement.querySelector('.e-timeline-items') != null).toEqual(true); + expect(timelineElement.querySelectorAll('.e-timeline-item').length).toBe(4); + expect(timelineElement.querySelectorAll('.e-dot').length).toBe(4); + const liElementArray: any = timelineElement.querySelectorAll('.e-content'); + expect((liElementArray[0] as HTMLElement).innerText).toEqual('Ordered'); + expect((liElementArray[1] as HTMLElement).innerText).toEqual('Processing'); + expect((liElementArray[2] as HTMLElement).innerText).toEqual('Shipped'); + expect((liElementArray[3] as HTMLElement).innerText).toEqual('Delivered'); + }); + + it('text content with opposite content', () => { + const customData: TimelineItemModel[] = [ + {oppositeContent: '09:30 am', content: 'Ordered'}, + {oppositeContent: '10:30 am', content: 'Processing'}, + {oppositeContent: '11:30 am', content: 'Shipped'}, + {oppositeContent: '12:30 am', content: 'Delivered'} + ]; + timeline = new Timeline({ items: customData }); + timeline.appendTo('#timeline'); + expect(timelineElement.classList.contains('e-timeline')).toEqual(true); + expect(timelineElement.classList.contains('e-align-after')).toEqual(true); + expect(timelineElement.classList.contains('.e-vertical') != null).toEqual(true); + expect(timelineElement.querySelector('.e-timeline-items') != null).toEqual(true); + expect(timelineElement.querySelectorAll('.e-timeline-item').length).toBe(4); + expect(timelineElement.querySelectorAll('.e-opposite-content').length).toBe(4); + expect(timelineElement.querySelectorAll('.e-dot').length).toBe(4); + expect(timelineElement.querySelectorAll('.e-content').length).toBe(4); + const liIconElementArray: any = timelineElement.querySelectorAll('.e-opposite-content'); + expect((liIconElementArray[0] as HTMLElement).innerText).toEqual('09:30 am'); + expect((liIconElementArray[1] as HTMLElement).innerText).toEqual('10:30 am'); + expect((liIconElementArray[2] as HTMLElement).innerText).toEqual('11:30 am'); + expect((liIconElementArray[3] as HTMLElement).innerText).toEqual('12:30 am'); + const liElementArray: any = timelineElement.querySelectorAll('.e-content'); + expect((liElementArray[0] as HTMLElement).innerText).toEqual('Ordered'); + expect((liElementArray[1] as HTMLElement).innerText).toEqual('Processing'); + expect((liElementArray[2] as HTMLElement).innerText).toEqual('Shipped'); + expect((liElementArray[3] as HTMLElement).innerText).toEqual('Delivered'); + }); + + it('timeline content as js renderer ', () => { + const customData: TimelineItemModel[] = [ + {content: '#itemContent'}, + {content: 'Processing'}, + {content: 'Shipped'}, + {content: 'Delivered'} + ]; + let content = 'Ordered'; + const renderer = createElement('script', { id: 'itemContent', innerHTML: content }); + renderer.setAttribute('type', 'text/x-jsrender'); + document.body.appendChild(renderer); + timeline = new Timeline({ items: customData }); + timeline.appendTo('#timeline'); + const liElementArray: any = timelineElement.querySelectorAll('.e-content'); + expect((liElementArray[0] as HTMLElement).innerText).toEqual('Ordered'); + expect((liElementArray[1] as HTMLElement).innerText).toEqual('Processing'); + expect((liElementArray[2] as HTMLElement).innerText).toEqual('Shipped'); + expect((liElementArray[3] as HTMLElement).innerText).toEqual('Delivered'); + content = null; + remove(renderer); + }); + + it('timeline content with opposite content as js renderer ', () => { + const customData: TimelineItemModel[] = [ + {oppositeContent: '#itemContent', content: 'Ordered'}, + {oppositeContent: '10:30 am', content: 'Processing'}, + {oppositeContent: '11:30 am', content: 'Shipped'}, + {oppositeContent: '12:30 am', content: 'Delivered'} + ]; + let oppositeContent = '09:30 am'; + const renderer = createElement('script', { id: 'itemContent', innerHTML: oppositeContent }); + renderer.setAttribute('type', 'text/x-jsrender'); + document.body.appendChild(renderer); + timeline = new Timeline({ items: customData }); + timeline.appendTo('#timeline'); + const liIconElementArray: any = timelineElement.querySelectorAll('.e-opposite-content'); + expect((liIconElementArray[0] as HTMLElement).innerText).toEqual('09:30 am'); + expect((liIconElementArray[1] as HTMLElement).innerText).toEqual('10:30 am'); + expect((liIconElementArray[2] as HTMLElement).innerText).toEqual('11:30 am'); + expect((liIconElementArray[3] as HTMLElement).innerText).toEqual('12:30 am'); + oppositeContent = null; + remove(renderer); + }); + + it('text content with opposite content with before', () => { + const customData: TimelineItemModel[] = [ + {oppositeContent: '09:30 am', content: 'Ordered'}, + {oppositeContent: '10:30 am', content: 'Processing'}, + {oppositeContent: '11:30 am', content: 'Shipped'}, + {oppositeContent: '12:30 am', content: 'Delivered'} + ]; + timeline = new Timeline({ items: customData, align: 'before' }); + timeline.appendTo('#timeline'); + expect(timelineElement.classList.contains('e-timeline')).toEqual(true); + expect(timelineElement.classList.contains('e-align-before')).toEqual(true); + expect(timelineElement.classList.contains('.e-vertical') != null).toEqual(true); + expect(timelineElement.querySelector('.e-timeline-items') != null).toEqual(true); + expect(timelineElement.querySelectorAll('.e-timeline-item').length).toBe(4); + expect(timelineElement.querySelectorAll('.e-opposite-content').length).toBe(4); + expect(timelineElement.querySelectorAll('.e-dot').length).toBe(4); + expect(timelineElement.querySelectorAll('.e-content').length).toBe(4); + const liIconElementArray: any = timelineElement.querySelectorAll('.e-opposite-content'); + expect((liIconElementArray[0] as HTMLElement).innerText).toEqual('09:30 am'); + expect((liIconElementArray[1] as HTMLElement).innerText).toEqual('10:30 am'); + expect((liIconElementArray[2] as HTMLElement).innerText).toEqual('11:30 am'); + expect((liIconElementArray[3] as HTMLElement).innerText).toEqual('12:30 am'); + const liElementArray: any = timelineElement.querySelectorAll('.e-content'); + expect((liElementArray[0] as HTMLElement).innerText).toEqual('Ordered'); + expect((liElementArray[1] as HTMLElement).innerText).toEqual('Processing'); + expect((liElementArray[2] as HTMLElement).innerText).toEqual('Shipped'); + expect((liElementArray[3] as HTMLElement).innerText).toEqual('Delivered'); + }); + + it('text content with opposite content with alternate', () => { + const customData: TimelineItemModel[] = [ + {oppositeContent: '09:30 am', content: 'Ordered'}, + {oppositeContent: '10:30 am', content: 'Processing'}, + {oppositeContent: '11:30 am', content: 'Shipped'}, + {oppositeContent: '12:30 am', content: 'Delivered'} + ]; + timeline = new Timeline({ items: customData, align: 'alternate' }); + timeline.appendTo('#timeline'); + expect(timelineElement.classList.contains('e-timeline')).toEqual(true); + expect(timelineElement.classList.contains('e-align-alternate')).toEqual(true); + expect(timelineElement.classList.contains('.e-vertical') != null).toEqual(true); + expect(timelineElement.querySelector('.e-timeline-items') != null).toEqual(true); + expect(timelineElement.querySelectorAll('.e-timeline-item').length).toBe(4); + expect(timelineElement.querySelectorAll('.e-opposite-content').length).toBe(4); + expect(timelineElement.querySelectorAll('.e-dot').length).toBe(4); + expect(timelineElement.querySelectorAll('.e-content').length).toBe(4); + const liIconElementArray: any = timelineElement.querySelectorAll('.e-opposite-content'); + expect((liIconElementArray[0] as HTMLElement).innerText).toEqual('09:30 am'); + expect((liIconElementArray[1] as HTMLElement).innerText).toEqual('10:30 am'); + expect((liIconElementArray[2] as HTMLElement).innerText).toEqual('11:30 am'); + expect((liIconElementArray[3] as HTMLElement).innerText).toEqual('12:30 am'); + const liElementArray: any = timelineElement.querySelectorAll('.e-content'); + expect((liElementArray[0] as HTMLElement).innerText).toEqual('Ordered'); + expect((liElementArray[1] as HTMLElement).innerText).toEqual('Processing'); + expect((liElementArray[2] as HTMLElement).innerText).toEqual('Shipped'); + expect((liElementArray[3] as HTMLElement).innerText).toEqual('Delivered'); + }); + + it('text content with opposite content with alternate reverse', () => { + const customData: TimelineItemModel[] = [ + {oppositeContent: '09:30 am', content: 'Ordered'}, + {oppositeContent: '10:30 am', content: 'Processing'}, + {oppositeContent: '11:30 am', content: 'Shipped'}, + {oppositeContent: '12:30 am', content: 'Delivered'} + ]; + timeline = new Timeline({ items: customData, align: 'alternatereverse' }); + timeline.appendTo('#timeline'); + expect(timelineElement.classList.contains('e-timeline')).toEqual(true); + expect(timelineElement.classList.contains('e-align-alternatereverse')).toEqual(true); + expect(timelineElement.classList.contains('.e-vertical') != null).toEqual(true); + expect(timelineElement.querySelector('.e-timeline-items') != null).toEqual(true); + expect(timelineElement.querySelectorAll('.e-timeline-item').length).toBe(4); + expect(timelineElement.querySelectorAll('.e-opposite-content').length).toBe(4); + expect(timelineElement.querySelectorAll('.e-dot').length).toBe(4); + expect(timelineElement.querySelectorAll('.e-content').length).toBe(4); + const liIconElementArray: any = timelineElement.querySelectorAll('.e-opposite-content'); + expect((liIconElementArray[0] as HTMLElement).innerText).toEqual('09:30 am'); + expect((liIconElementArray[1] as HTMLElement).innerText).toEqual('10:30 am'); + expect((liIconElementArray[2] as HTMLElement).innerText).toEqual('11:30 am'); + expect((liIconElementArray[3] as HTMLElement).innerText).toEqual('12:30 am'); + const liElementArray: any = timelineElement.querySelectorAll('.e-content'); + expect((liElementArray[0] as HTMLElement).innerText).toEqual('Ordered'); + expect((liElementArray[1] as HTMLElement).innerText).toEqual('Processing'); + expect((liElementArray[2] as HTMLElement).innerText).toEqual('Shipped'); + expect((liElementArray[3] as HTMLElement).innerText).toEqual('Delivered'); + }); + + it('text content', () => { + const customData: TimelineItemModel[] = [ + {content: 'Ordered'}, + {content: 'Processing'}, + {content: 'Shipped'}, + {content: 'Delivered'} + ]; + timeline = new Timeline({ items: customData, orientation: 'horizontal' }); + timeline.appendTo('#timeline'); + expect(timelineElement.classList.contains('e-timeline')).toEqual(true); + expect(timelineElement.classList.contains('e-align-after')).toEqual(true); + expect(timelineElement.classList.contains('.e-horizontal') != null).toEqual(true); + expect(timelineElement.querySelector('.e-timeline-items') != null).toEqual(true); + expect(timelineElement.querySelectorAll('.e-timeline-item').length).toBe(4); + expect(timelineElement.querySelectorAll('.e-dot').length).toBe(4); + const liElementArray: any = timelineElement.querySelectorAll('.e-content'); + expect((liElementArray[0] as HTMLElement).innerText).toEqual('Ordered'); + expect((liElementArray[1] as HTMLElement).innerText).toEqual('Processing'); + expect((liElementArray[2] as HTMLElement).innerText).toEqual('Shipped'); + expect((liElementArray[3] as HTMLElement).innerText).toEqual('Delivered'); + }); + + it('custom icon with text content', () => { + const customData: TimelineItemModel[] = [ + {dotCss: 'e-icons e-people', content: 'Ordered'}, + {dotCss: 'e-icons e-signature', content: 'Processing'}, + {dotCss: 'e-icons e-location', content: 'Shipped'}, + {dotCss: 'e-icons e-cut', content: 'Delivered'} + ]; + timeline = new Timeline({ items: customData, orientation: 'horizontal' }); + timeline.appendTo('#timeline'); + expect(timelineElement.classList.contains('e-timeline')).toEqual(true); + expect(timelineElement.classList.contains('e-align-after')).toEqual(true); + expect(timelineElement.classList.contains('.e-horizontal') != null).toEqual(true); + expect(timelineElement.querySelector('.e-timeline-items') != null).toEqual(true); + expect(timelineElement.querySelectorAll('.e-timeline-item').length).toBe(4); + expect(timelineElement.querySelectorAll('.e-dot').length).toBe(4); + expect(timelineElement.querySelectorAll('.e-content').length).toBe(4); + const liIconElementArray: any = timelineElement.querySelectorAll('.e-dot'); + expect((liIconElementArray[0] as HTMLElement).classList.contains('e-people')).toEqual(true); + expect((liIconElementArray[1] as HTMLElement).classList.contains('e-signature')).toEqual(true); + expect((liIconElementArray[2] as HTMLElement).classList.contains('e-location')).toEqual(true); + expect((liIconElementArray[3] as HTMLElement).classList.contains('e-cut')).toEqual(true); + const liElementArray: any = timelineElement.querySelectorAll('.e-content'); + expect((liElementArray[0] as HTMLElement).innerText).toEqual('Ordered'); + expect((liElementArray[1] as HTMLElement).innerText).toEqual('Processing'); + expect((liElementArray[2] as HTMLElement).innerText).toEqual('Shipped'); + expect((liElementArray[3] as HTMLElement).innerText).toEqual('Delivered'); + }); + + it('text content with before position', () => { + const customData: TimelineItemModel[] = [ + {content: 'Ordered'}, + {content: 'Processing'}, + {content: 'Shipped'}, + {content: 'Delivered'} + ]; + timeline = new Timeline({ items: customData, align: 'before', orientation: 'horizontal' }); + timeline.appendTo('#timeline'); + expect(timelineElement.classList.contains('e-timeline')).toEqual(true); + expect(timelineElement.classList.contains('e-align-before')).toEqual(true); + expect(timelineElement.classList.contains('.e-horizontal') != null).toEqual(true); + expect(timelineElement.querySelector('.e-timeline-items') != null).toEqual(true); + expect(timelineElement.querySelectorAll('.e-timeline-item').length).toBe(4); + expect(timelineElement.querySelectorAll('.e-dot').length).toBe(4); + const liElementArray: any = timelineElement.querySelectorAll('.e-content'); + expect((liElementArray[0] as HTMLElement).innerText).toEqual('Ordered'); + expect((liElementArray[1] as HTMLElement).innerText).toEqual('Processing'); + expect((liElementArray[2] as HTMLElement).innerText).toEqual('Shipped'); + expect((liElementArray[3] as HTMLElement).innerText).toEqual('Delivered'); + }); + + it('text content with alternate position', () => { + const customData: TimelineItemModel[] = [ + {content: 'Ordered'}, + {content: 'Processing'}, + {content: 'Shipped'}, + {content: 'Delivered'} + ]; + timeline = new Timeline({ items: customData, align: 'alternate', orientation: 'horizontal' }); + timeline.appendTo('#timeline'); + expect(timelineElement.classList.contains('e-timeline')).toEqual(true); + expect(timelineElement.classList.contains('e-align-alternate')).toEqual(true); + expect(timelineElement.classList.contains('.e-horizontal') != null).toEqual(true); + expect(timelineElement.querySelector('.e-timeline-items') != null).toEqual(true); + expect(timelineElement.querySelectorAll('.e-timeline-item').length).toBe(4); + expect(timelineElement.querySelectorAll('.e-dot').length).toBe(4); + const liElementArray: any = timelineElement.querySelectorAll('.e-content'); + expect((liElementArray[0] as HTMLElement).innerText).toEqual('Ordered'); + expect((liElementArray[1] as HTMLElement).innerText).toEqual('Processing'); + expect((liElementArray[2] as HTMLElement).innerText).toEqual('Shipped'); + expect((liElementArray[3] as HTMLElement).innerText).toEqual('Delivered'); + }); + + it('text content with alternate reverse position', () => { + const customData: TimelineItemModel[] = [ + {content: 'Ordered'}, + {content: 'Processing'}, + {content: 'Shipped'}, + {content: 'Delivered'} + ]; + timeline = new Timeline({ items: customData, align: 'alternatereverse', orientation: 'horizontal' }); + timeline.appendTo('#timeline'); + expect(timelineElement.classList.contains('e-timeline')).toEqual(true); + expect(timelineElement.classList.contains('e-align-alternatereverse')).toEqual(true); + expect(timelineElement.classList.contains('.e-horizontal') != null).toEqual(true); + expect(timelineElement.querySelector('.e-timeline-items') != null).toEqual(true); + expect(timelineElement.querySelectorAll('.e-timeline-item').length).toBe(4); + expect(timelineElement.querySelectorAll('.e-dot').length).toBe(4); + const liElementArray: any = timelineElement.querySelectorAll('.e-content'); + expect((liElementArray[0] as HTMLElement).innerText).toEqual('Ordered'); + expect((liElementArray[1] as HTMLElement).innerText).toEqual('Processing'); + expect((liElementArray[2] as HTMLElement).innerText).toEqual('Shipped'); + expect((liElementArray[3] as HTMLElement).innerText).toEqual('Delivered'); + }); + + it('text content with opposite content', () => { + const customData: TimelineItemModel[] = [ + {oppositeContent: '09:30 am', content: 'Ordered'}, + {oppositeContent: '10:30 am', content: 'Processing'}, + {oppositeContent: '11:30 am', content: 'Shipped'}, + {oppositeContent: '12:30 am', content: 'Delivered'} + ]; + timeline = new Timeline({ items: customData, orientation: 'horizontal' }); + timeline.appendTo('#timeline'); + expect(timelineElement.classList.contains('e-timeline')).toEqual(true); + expect(timelineElement.classList.contains('e-align-after')).toEqual(true); + expect(timelineElement.classList.contains('.e-horizontal') != null).toEqual(true); + expect(timelineElement.querySelector('.e-timeline-items') != null).toEqual(true); + expect(timelineElement.querySelectorAll('.e-timeline-item').length).toBe(4); + expect(timelineElement.querySelectorAll('.e-opposite-content').length).toBe(4); + expect(timelineElement.querySelectorAll('.e-dot').length).toBe(4); + expect(timelineElement.querySelectorAll('.e-content').length).toBe(4); + const liIconElementArray: any = timelineElement.querySelectorAll('.e-opposite-content'); + expect((liIconElementArray[0] as HTMLElement).innerText).toEqual('09:30 am'); + expect((liIconElementArray[1] as HTMLElement).innerText).toEqual('10:30 am'); + expect((liIconElementArray[2] as HTMLElement).innerText).toEqual('11:30 am'); + expect((liIconElementArray[3] as HTMLElement).innerText).toEqual('12:30 am'); + const liElementArray: any = timelineElement.querySelectorAll('.e-content'); + expect((liElementArray[0] as HTMLElement).innerText).toEqual('Ordered'); + expect((liElementArray[1] as HTMLElement).innerText).toEqual('Processing'); + expect((liElementArray[2] as HTMLElement).innerText).toEqual('Shipped'); + expect((liElementArray[3] as HTMLElement).innerText).toEqual('Delivered'); + }); + + it('text content with opposite content with before', () => { + const customData: TimelineItemModel[] = [ + {oppositeContent: '09:30 am', content: 'Ordered'}, + {oppositeContent: '10:30 am', content: 'Processing'}, + {oppositeContent: '11:30 am', content: 'Shipped'}, + {oppositeContent: '12:30 am', content: 'Delivered'} + ]; + timeline = new Timeline({ items: customData, align: 'before', orientation: 'horizontal' }); + timeline.appendTo('#timeline'); + expect(timelineElement.classList.contains('e-timeline')).toEqual(true); + expect(timelineElement.classList.contains('e-align-before')).toEqual(true); + expect(timelineElement.classList.contains('.e-horizontal') != null).toEqual(true); + expect(timelineElement.querySelector('.e-timeline-items') != null).toEqual(true); + expect(timelineElement.querySelectorAll('.e-timeline-item').length).toBe(4); + expect(timelineElement.querySelectorAll('.e-opposite-content').length).toBe(4); + expect(timelineElement.querySelectorAll('.e-dot').length).toBe(4); + expect(timelineElement.querySelectorAll('.e-content').length).toBe(4); + const liIconElementArray: any = timelineElement.querySelectorAll('.e-opposite-content'); + expect((liIconElementArray[0] as HTMLElement).innerText).toEqual('09:30 am'); + expect((liIconElementArray[1] as HTMLElement).innerText).toEqual('10:30 am'); + expect((liIconElementArray[2] as HTMLElement).innerText).toEqual('11:30 am'); + expect((liIconElementArray[3] as HTMLElement).innerText).toEqual('12:30 am'); + const liElementArray: any = timelineElement.querySelectorAll('.e-content'); + expect((liElementArray[0] as HTMLElement).innerText).toEqual('Ordered'); + expect((liElementArray[1] as HTMLElement).innerText).toEqual('Processing'); + expect((liElementArray[2] as HTMLElement).innerText).toEqual('Shipped'); + expect((liElementArray[3] as HTMLElement).innerText).toEqual('Delivered'); + }); + + it('text content with opposite content with alternate', () => { + const customData: TimelineItemModel[] = [ + {oppositeContent: '09:30 am', content: 'Ordered'}, + {oppositeContent: '10:30 am', content: 'Processing'}, + {oppositeContent: '11:30 am', content: 'Shipped'}, + {oppositeContent: '12:30 am', content: 'Delivered'} + ]; + timeline = new Timeline({ items: customData, align: 'alternate', orientation: 'horizontal' }); + timeline.appendTo('#timeline'); + expect(timelineElement.classList.contains('e-timeline')).toEqual(true); + expect(timelineElement.classList.contains('e-align-alternate')).toEqual(true); + expect(timelineElement.classList.contains('.e-horizontal') != null).toEqual(true); + expect(timelineElement.querySelector('.e-timeline-items') != null).toEqual(true); + expect(timelineElement.querySelectorAll('.e-timeline-item').length).toBe(4); + expect(timelineElement.querySelectorAll('.e-opposite-content').length).toBe(4); + expect(timelineElement.querySelectorAll('.e-dot').length).toBe(4); + expect(timelineElement.querySelectorAll('.e-content').length).toBe(4); + const liIconElementArray: any = timelineElement.querySelectorAll('.e-opposite-content'); + expect((liIconElementArray[0] as HTMLElement).innerText).toEqual('09:30 am'); + expect((liIconElementArray[1] as HTMLElement).innerText).toEqual('10:30 am'); + expect((liIconElementArray[2] as HTMLElement).innerText).toEqual('11:30 am'); + expect((liIconElementArray[3] as HTMLElement).innerText).toEqual('12:30 am'); + const liElementArray: any = timelineElement.querySelectorAll('.e-content'); + expect((liElementArray[0] as HTMLElement).innerText).toEqual('Ordered'); + expect((liElementArray[1] as HTMLElement).innerText).toEqual('Processing'); + expect((liElementArray[2] as HTMLElement).innerText).toEqual('Shipped'); + expect((liElementArray[3] as HTMLElement).innerText).toEqual('Delivered'); + }); + + it('text content with opposite content with alternate reverse', () => { + const customData: TimelineItemModel[] = [ + {oppositeContent: '09:30 am', content: 'Ordered'}, + {oppositeContent: '10:30 am', content: 'Processing'}, + {oppositeContent: '11:30 am', content: 'Shipped'}, + {oppositeContent: '12:30 am', content: 'Delivered'} + ]; + timeline = new Timeline({ items: customData, align: 'alternatereverse', orientation: 'horizontal' }); + timeline.appendTo('#timeline'); + expect(timelineElement.classList.contains('e-timeline')).toEqual(true); + expect(timelineElement.classList.contains('e-align-alternatereverse')).toEqual(true); + expect(timelineElement.classList.contains('.e-horizontal') != null).toEqual(true); + expect(timelineElement.querySelector('.e-timeline-items') != null).toEqual(true); + expect(timelineElement.querySelectorAll('.e-timeline-item').length).toBe(4); + expect(timelineElement.querySelectorAll('.e-opposite-content').length).toBe(4); + expect(timelineElement.querySelectorAll('.e-dot').length).toBe(4); + expect(timelineElement.querySelectorAll('.e-content').length).toBe(4); + const liIconElementArray: any = timelineElement.querySelectorAll('.e-opposite-content'); + expect((liIconElementArray[0] as HTMLElement).innerText).toEqual('09:30 am'); + expect((liIconElementArray[1] as HTMLElement).innerText).toEqual('10:30 am'); + expect((liIconElementArray[2] as HTMLElement).innerText).toEqual('11:30 am'); + expect((liIconElementArray[3] as HTMLElement).innerText).toEqual('12:30 am'); + const liElementArray: any = timelineElement.querySelectorAll('.e-content'); + expect((liElementArray[0] as HTMLElement).innerText).toEqual('Ordered'); + expect((liElementArray[1] as HTMLElement).innerText).toEqual('Processing'); + expect((liElementArray[2] as HTMLElement).innerText).toEqual('Shipped'); + expect((liElementArray[3] as HTMLElement).innerText).toEqual('Delivered'); + }); + + it('created Property', () => { + let isCreated: boolean = false; + timeline = new Timeline({ + items: [{}, {}, {}, {}], + created: () => { isCreated = true; } + }); + timeline.appendTo('#timeline'); + const liElementArray: any = timelineElement.querySelectorAll('.e-timeline-item'); + expect(isCreated).toEqual(true); + }); + + it('beforeItemRender Property', () => { + let count: number = 0; + timeline = new Timeline({ + items: [{}, {}, {}, {}], + beforeItemRender: (e: TimelineRenderingEventArgs) => { + count++; + expect(e.element.classList).toContain('e-timeline-item'); + } + }); + timeline.appendTo('#timeline'); + expect(timelineElement.querySelectorAll('.e-timeline-item').length).toBe(4); + expect(count).toBe(4); + }); + + it('timeline with template support', () => { + const customData: TimelineItemModel[] = [ + {dotCss: 'e-icons e-folder', content: 'Item 1'}, + {dotCss: 'e-icons e-folder', content: 'Item 2'}, + {dotCss: 'e-icons e-folder', content: 'Item 3'}, + {dotCss: 'e-icons e-folder', content: 'Item 4'} + ]; + timeline = new Timeline({ items: customData, template: '${itemIndex}' }); + timeline.appendTo('#timeline'); + const liElementArray: any = timelineElement.querySelectorAll('.e-timeline-item'); + expect(timelineElement.querySelectorAll('.e-timeline-item').length).toBe(4); + expect((liElementArray[0] as HTMLElement).innerText).toEqual('0'); + expect((liElementArray[1] as HTMLElement).innerText).toEqual('1'); + expect((liElementArray[2] as HTMLElement).innerText).toEqual('2'); + expect((liElementArray[3] as HTMLElement).innerText).toEqual('3'); + timeline.template = 'Item ${itemIndex}'; + timeline.dataBind(); + expect((liElementArray[0] as HTMLElement).innerText).toEqual('Item 0'); + expect((liElementArray[1] as HTMLElement).innerText).toEqual('Item 1'); + expect((liElementArray[2] as HTMLElement).innerText).toEqual('Item 2'); + expect((liElementArray[3] as HTMLElement).innerText).toEqual('Item 3'); + }); + + it('timeline Template as js renderer ', () => { + const customData: TimelineItemModel[] = [ + {dotCss: 'e-icons e-folder', content: 'Item 1'}, + {dotCss: 'e-icons e-folder', content: 'Item 2'}, + {dotCss: 'e-icons e-folder', content: 'Item 3'}, + {dotCss: 'e-icons e-folder', content: 'Item 4'} + ]; + let template = 'Item ${itemIndex}'; + const renderer = createElement('script', { id: 'itemTemp', innerHTML: template }); + renderer.setAttribute('type', 'text/x-jsrender'); + document.body.appendChild(renderer); + timeline = new Timeline({ items: customData, template: '#itemTemp' }); + timeline.appendTo('#timeline'); + const liElementArray: any = timelineElement.querySelectorAll('.e-timeline-item'); + expect(timelineElement.querySelector('.e-timeline-item').firstElementChild.classList).toContain('tempContent'); + expect((liElementArray[0] as HTMLElement).innerText).toEqual('Item 0'); + expect((liElementArray[1] as HTMLElement).innerText).toEqual('Item 1'); + expect((liElementArray[2] as HTMLElement).innerText).toEqual('Item 2'); + expect((liElementArray[3] as HTMLElement).innerText).toEqual('Item 3'); + template = null; + remove(renderer); + }); + + it('timeline Template as HTMLElement ', () => { + const customData: TimelineItemModel[] = [ + {dotCss: 'e-icons e-folder', content: 'Item 1'}, + {dotCss: 'e-icons e-folder', content: 'Item 2'}, + {dotCss: 'e-icons e-folder', content: 'Item 3'}, + {dotCss: 'e-icons e-folder', content: 'Item 4'} + ]; + const template = 'Item ${itemIndex}'; + const tempContent = createElement('div', { id: 'itemTemp', className: 'tempContent', innerHTML: template }); + document.body.appendChild(tempContent); + timeline = new Timeline({ items: customData, template: '#itemTemp' }); + timeline.appendTo('#timeline'); + expect(document.querySelector('.tempContent') === null).toEqual(false); + timeline.template = '#labelTemp1'; + timeline.dataBind(); + remove(tempContent); + }); + + it('memory leak', () => { + profile.sample(); + const average: any = inMB(profile.averageChange); + // check average change in memory samples to not be over 10MB + expect(average).toBeLessThan(10); + const memory: any = inMB(getMemoryProfile()); + // check the final memory usage against the first usage, there should be little change if everything was properly deallocated + expect(memory).toBeLessThan(profile.samples[0] + 0.25); + }); + }); +}); diff --git a/controls/layouts/src/dashboard-layout/dashboard-layout.ts b/controls/layouts/src/dashboard-layout/dashboard-layout.ts index 043e060a32..76f86093f0 100644 --- a/controls/layouts/src/dashboard-layout/dashboard-layout.ts +++ b/controls/layouts/src/dashboard-layout/dashboard-layout.ts @@ -3269,6 +3269,7 @@ export class DashboardLayout extends Component implements INotifyPr if (Array.isArray(getValue(key, this)) && key === 'panels') { // eslint-disable-next-line this.mergePanels(dataObj[key], this[key]); + (this)[key as keyof DashboardLayout] = dataObj[key as keyof DashboardLayout]; } } } diff --git a/controls/layouts/src/index.ts b/controls/layouts/src/index.ts index c7c4ac22d5..484b8db25a 100644 --- a/controls/layouts/src/index.ts +++ b/controls/layouts/src/index.ts @@ -3,4 +3,4 @@ */ export * from './splitter/index'; export * from './dashboard-layout/index'; - +export * from './timeline/index'; diff --git a/controls/layouts/src/splitter/splitter.ts b/controls/layouts/src/splitter/splitter.ts index 529516eb1f..ef49612f50 100644 --- a/controls/layouts/src/splitter/splitter.ts +++ b/controls/layouts/src/splitter/splitter.ts @@ -554,7 +554,9 @@ export class Splitter extends Component { - staticPaneWidth - (this.border * 2); const avgDiffWidth: number = flexPaneWidth / flexPaneIndexes.length; for (let j: number = 0; j < flexPaneIndexes.length; j++) { - this.allPanes[flexPaneIndexes[j as number]].style.flexBasis = avgDiffWidth + 'px'; + if (this.allPanes[flexPaneIndexes[j as number]].style.flexBasis !== '') { + this.allPanes[flexPaneIndexes[j as number]].style.flexBasis = avgDiffWidth + 'px'; + } } this.allPanes[index as number].classList.add(STATIC_PANE); } @@ -1359,7 +1361,9 @@ export class Splitter extends Component { if (paneCount - 1 === i) { const staticPaneCount: number = this.element.querySelectorAll('.' + STATIC_PANE).length; if (staticPaneCount === paneCount) { - removeClass([this.allPanes[i as number]], STATIC_PANE); + if (this.allPanes[i as number].style.flexBasis === '') { + removeClass([this.allPanes[i as number]], STATIC_PANE); + } } } } diff --git a/controls/layouts/src/timeline/index.ts b/controls/layouts/src/timeline/index.ts new file mode 100644 index 0000000000..28e7925b83 --- /dev/null +++ b/controls/layouts/src/timeline/index.ts @@ -0,0 +1,3 @@ +/** Timeline export modules */ +export * from './timeline'; +export * from './timeline-model'; diff --git a/controls/layouts/src/timeline/timeline-model.d.ts b/controls/layouts/src/timeline/timeline-model.d.ts new file mode 100644 index 0000000000..ef468bf984 --- /dev/null +++ b/controls/layouts/src/timeline/timeline-model.d.ts @@ -0,0 +1,140 @@ +import { Component, INotifyPropertyChanged, ChildProperty, Collection, BaseEventArgs, Event, EmitType, NotifyPropertyChanges, Property, getUniqueID, addClass, attributes, isNullOrUndefined, select, compile, remove, removeClass, append } from '@syncfusion/ej2-base'; +import {TimelineOrientation,TimelineAlign,TimelineRenderingEventArgs} from "./timeline"; +import {ComponentModel} from '@syncfusion/ej2-base'; + +/** + * Interface for a class TimelineItem + */ +export interface TimelineItemModel { + + /** + * Defines one or more CSS classes to include an icon or image in the Timeline item. + * + * @default '' + */ + dotCss?: string; + + /** + * Defines the text content or template for the Timeline item. The current itemIndex passed as context to build the content. + * + * @default '' + * @angularType string | object + * @reactType string | function | JSX.Element + * @vueType string | function + * @aspType string + */ + content?: string | Function; + + /** + * Defines the additional text content or template to be displayed opposite side of the item. The current itemIndex passed as context to build the content. + * + * @default '' + * @angularType string | object + * @reactType string | function | JSX.Element + * @vueType string | function + * @aspType string + */ + oppositeContent?: string | Function; + + /** + * Defines whether to enable or disable the timeline item. + * + * @default false + */ + disabled?: boolean; + + /** + * Defines the CSS class to customize the Timeline item appearance. + * + * @default '' + */ + cssClass?: string; + +} + +/** + * Interface for a class Timeline + */ +export interface TimelineModel extends ComponentModel{ + + /** + * Defines the orientation type of the Timeline. + * + * The possible values are: + * * Horizontal + * * vertical + * + * {% codeBlock src='timeline/orientation/index.md' %}{% endcodeBlock %} + * + * @isenumeration true + * @default TimelineOrientation.Vertical + * @asptype TimelineOrientation + */ + orientation?: string | TimelineOrientation; + + /** + * Defines the alignment of item content within the Timeline. + * + * The possible values are: + * * Before + * * After + * * Alternate + * * AlternateReverse + * + * {% codeBlock src='timeline/align/index.md' %}{% endcodeBlock %} + * + * @isenumeration true + * @default TimelineAlign.After + * @asptype TimelineAlign + */ + align?: string | TimelineAlign; + + /** + * Defines the list of items. + * + * @default [] + */ + items?: TimelineItemModel[]; + + /** + * Defines the CSS class to customize the Timeline appearance. + * + * @default '' + */ + cssClass?: string; + + /** + * Defines whether to show the timeline items in reverse order or not. + * + * @default false + */ + reverse?: boolean; + + /** + * Defines the template content for each timeline item. The template context will contain the item model. + * + * {% codeBlock src='timeline/template/index.md' %}{% endcodeBlock %} + * + * @default '' + * @angularType string | object + * @reactType string | function | JSX.Element + * @vueType string | function + * @aspType string + */ + template?: string | Function; + + /** + * Event callback that is raised after rendering the timeline. + * + * @event created + */ + created?: EmitType; + + /** + * Event triggers before rendering each item. + * + * @event beforeItemRender + */ + beforeItemRender?: EmitType; + +} \ No newline at end of file diff --git a/controls/layouts/src/timeline/timeline.ts b/controls/layouts/src/timeline/timeline.ts new file mode 100644 index 0000000000..f298919b8c --- /dev/null +++ b/controls/layouts/src/timeline/timeline.ts @@ -0,0 +1,484 @@ +import { Component, INotifyPropertyChanged, ChildProperty, Collection, BaseEventArgs, Event, EmitType, NotifyPropertyChanges, Property, getUniqueID, addClass, attributes, isNullOrUndefined, select, compile, remove, removeClass, append } from '@syncfusion/ej2-base'; +import { TimelineModel, TimelineItemModel } from '../timeline'; + +const ITEMLISTCONTAINER: string = 'e-timeline-items'; +const ITEMCONTAINER: string = 'e-timeline-item'; +const OPPOSITECONTENT: string = 'e-opposite-content'; +const DOTCONTAINER: string = 'e-dot-item'; +const DOTCONTENT: string = 'e-dot'; +const CONTENT: string = 'e-content'; +const ITEMCONNECTOR: string = 'e-connector'; +const VERTICAL: string = 'e-vertical'; +const HORIZONTAL: string = 'e-horizontal'; +const TIMELINEREVERSE: string = 'e-timeline-reverse'; +const RTL: string = 'e-rtl'; +const DISABLED: string = 'e-item-disabled'; +const TEMPLATE: string = 'e-item-template'; + +/** + * Defines the orientation type of the Timeline. + */ +export enum TimelineOrientation { + /** + * Items are displayed horizontally. + */ + Horizontal = 'Horizontal', + /** + * Items are displayed vertically. + */ + Vertical = 'Vertical' +} + +/** + * Specifies the alignment of item content within the Timeline. + */ +export enum TimelineAlign { + /** + * Aligns item content to the top and opposite content to the bottom when the Timeline is in a horizontal orientation, or the content to the left and opposite content to the right when the Timeline is in a vertical orientation. + */ + Before = 'Before', + /** + * Aligns item content to the bottom and opposite content to the top when the Timeline is in a horizontal orientation, or the content to the right and opposite content to the left when the Timeline is in a vertical orientation. + */ + After = 'After', + /** + * Aligns item content alternatively, regardless of the Timeline's orientation. + */ + Alternate = 'Alternate', + /** + * Aligns item content in alternate reverse, regardless of the Timeline's orientation. + */ + AlternateReverse = 'AlternateReverse' +} + +/** + * Specifies the items of the Timeline. + */ +export class TimelineItem extends ChildProperty { + /** + * Defines one or more CSS classes to include an icon or image in the Timeline item. + * + * @default '' + */ + @Property('') + public dotCss: string; + + /** + * Defines the text content or template for the Timeline item. The current itemIndex passed as context to build the content. + * + * @default '' + * @angularType string | object + * @reactType string | function | JSX.Element + * @vueType string | function + * @aspType string + */ + @Property('') + public content: string | Function; + + /** + * Defines the additional text content or template to be displayed opposite side of the item. The current itemIndex passed as context to build the content. + * + * @default '' + * @angularType string | object + * @reactType string | function | JSX.Element + * @vueType string | function + * @aspType string + */ + @Property('') + public oppositeContent: string | Function; + + /** + * Defines whether to enable or disable the timeline item. + * + * @default false + */ + @Property(false) + public disabled: boolean; + + /** + * Defines the CSS class to customize the Timeline item appearance. + * + * @default '' + */ + @Property('') + public cssClass: string; +} + +/** + * Provides information about beforeItemRender event callback. + */ +export interface TimelineRenderingEventArgs extends BaseEventArgs { + /** + * Provides the timeline element. + */ + element: HTMLElement; + + /** + * Provides the index of the current item. + */ + index: number; +} + +/** + * The Timeline component presents a series of events or activities in chronological order, allowing users to track the progression of time. + * + * ```html + *
+ * ``` + * ```typescript + * + * ``` + */ +@NotifyPropertyChanges +export class Timeline extends Component implements INotifyPropertyChanged { + + /** + * Defines the orientation type of the Timeline. + * + * The possible values are: + * * Horizontal + * * vertical + * + * {% codeBlock src='timeline/orientation/index.md' %}{% endcodeBlock %} + * + * @isenumeration true + * @default TimelineOrientation.Vertical + * @asptype TimelineOrientation + */ + @Property(TimelineOrientation.Vertical) + public orientation: string | TimelineOrientation; + + /** + * Defines the alignment of item content within the Timeline. + * + * The possible values are: + * * Before + * * After + * * Alternate + * * AlternateReverse + * + * {% codeBlock src='timeline/align/index.md' %}{% endcodeBlock %} + * + * @isenumeration true + * @default TimelineAlign.After + * @asptype TimelineAlign + */ + @Property(TimelineAlign.After) + public align: string | TimelineAlign; + + /** + * Defines the list of items. + * + * @default [] + */ + @Collection([], TimelineItem) + public items: TimelineItemModel[]; + + /** + * Defines the CSS class to customize the Timeline appearance. + * + * @default '' + */ + @Property('') + public cssClass: string; + + /** + * Defines whether to show the timeline items in reverse order or not. + * + * @default false + */ + @Property(false) + public reverse: boolean; + + /** + * Defines the template content for each timeline item. The template context will contain the item model. + * + * {% codeBlock src='timeline/template/index.md' %}{% endcodeBlock %} + * + * @default '' + * @angularType string | object + * @reactType string | function | JSX.Element + * @vueType string | function + * @aspType string + */ + @Property('') + public template: string | Function; + + /** + * Event callback that is raised after rendering the timeline. + * + * @event created + */ + @Event() + public created: EmitType; + + /** + * Event triggers before rendering each item. + * + * @event beforeItemRender + */ + @Event() + public beforeItemRender: EmitType; + + /* Private variables */ + private timelineListEle: HTMLElement; + private templateFunction: Function; + private isReact?: boolean; + + /** + * * Constructor for creating the Timeline component. + * + * @param {TimelineModel} options - Specifies the Timeline model. + * @param {string | HTMLElement} element - Specifies the element to render as component. + * @private + */ + constructor(options?: TimelineModel, element?: string | HTMLElement) { + super(options, element); + } + + protected preRender(): void { + if (!this.element.id) { this.element.id = getUniqueID('e-' + this.getModuleName()); } + } + + /** + * To get component name. + * + * @returns {string} - Module Name + * @private + */ + public getModuleName(): string { + return 'timeline'; + } + + /** + * This method is abstract member of the Component. + * + * @private + * @returns {string} + */ + protected getPersistData(): string { + return this.addOnPersist([]); + } + + protected render(): void { + attributes(this.element, { 'role': 'navigation', 'aria-label': this.element.id }); + this.timelineListEle = this.createElement('ol', { className: ITEMLISTCONTAINER }); + this.updateOrientation(); + this.updateCssClass(this.cssClass); + this.updateAlign(); + this.updateReverse(); + this.updateRtl(); + this.updateTemplateFunction(); + this.renderItems(); + this.element.appendChild(this.timelineListEle); + } + + + + protected updateOrientation(): void { + const orientation = this.orientation.toLowerCase(); + if (orientation === 'horizontal' || orientation === 'vertical') { + this.element.classList.remove(HORIZONTAL, VERTICAL); + this.element.classList.add('e-' + orientation); + } + } + + protected updateCssClass(addCss: string, removeCss: string = ""): void { + let cssClasses: string[]; + if (removeCss) { + cssClasses = removeCss.trim().split(' '); + this.element.classList.remove(...cssClasses); + } + if (addCss) { + cssClasses = addCss.trim().split(' '); + this.element.classList.add(...cssClasses); + } + } + + protected updateRtl(): void { + this.element.classList[this.enableRtl ? 'add' : 'remove'](RTL); + } + + protected updateAlign(): void { + const align: string = this.align.toLowerCase(); + if (align === 'before' || align === 'after' || align === 'alternate' || align === 'alternatereverse') { + this.element.classList.remove('e-align-before', 'e-align-after', 'e-align-alternate', 'e-align-alternatereverse'); + this.element.classList.add('e-align-' + align); + } + } + + protected updateReverse(): void { + this.element.classList[this.reverse ? 'add' : 'remove'](TIMELINEREVERSE); + } + + private renderItems(): void { + for (let index: number = 0; index < this.items.length; index++) { + const item: TimelineItemModel = this.items[parseInt(index.toString(), 10)]; + const timelineItem: HTMLElement = this.createElement('li', { className: ITEMCONTAINER + ' ' + ITEMCONNECTOR }); + if (!this.template) { + const oppositeTextEle = this.createElement('div', { className: OPPOSITECONTENT }); + if (item.oppositeContent) { + const oppositeCtn: string | Function = this.getTemplateFunction(item.oppositeContent); + if (typeof oppositeCtn === 'string') { + oppositeTextEle.innerText = oppositeCtn; + } else { + append(oppositeCtn({ item: item, itemIndex: index }), oppositeTextEle); + } + } + timelineItem.appendChild(oppositeTextEle); + const dotContainer: HTMLElement = this.createElement('div', { className: DOTCONTAINER }); + const dotEleCss: string = item.dotCss ? DOTCONTENT + ' ' + item.dotCss.trim() : DOTCONTENT; + const dotEle: HTMLElement = this.createElement('div', { className: dotEleCss }); + dotContainer.appendChild(dotEle); + timelineItem.appendChild(dotContainer); + const contentEle = this.createElement('div', { className: CONTENT }); + if (item.content){ + const ctn: string | Function = this.getTemplateFunction(item.content); + if (typeof ctn === 'string') { + contentEle.innerText = ctn; + } else { + append(ctn({ item: item, itemIndex: index }), contentEle); + } + } + timelineItem.appendChild(contentEle); + if (item.cssClass) { timelineItem.classList.add(...item.cssClass.trim().split(' ')); } + if (item.disabled) { timelineItem.classList.add(DISABLED); } + } + else { + this.renderItemContent(index, false, timelineItem); + } + const eventArgs: TimelineRenderingEventArgs = { element: timelineItem, index: index }; + this.trigger('beforeItemRender', eventArgs, (args: TimelineRenderingEventArgs) => { this.timelineListEle.appendChild(args.element); }); + + } + } + + private updateTemplateFunction(): void { + this.templateFunction = this.template ? this.getTemplateFunction(this.template, false) as Function : null; + } + + private renderItemContent(index: number, isrerender: boolean, timelineItem?: HTMLElement): void { + const listItems: NodeListOf = this.timelineListEle.querySelectorAll('li'); + if (isrerender) { + this.removeItemContent(listItems[parseInt((index).toString(), 10)] as HTMLElement); + } + if (this.template) { + isrerender ? listItems[parseInt((index).toString(), 10)].classList.add(TEMPLATE) : + timelineItem.classList.add(TEMPLATE); + const item: TimelineItemModel = this.items[parseInt(index.toString(), 10)]; + append(this.templateFunction({ item: item, itemIndex: index }, this, 'timelineTemplate', (this.element.id + '_timelineTemplate'), this.isStringTemplate), isrerender ? listItems[parseInt((index).toString(), 10)] : timelineItem); + } + this.renderReactTemplates(); + } + + private removeItemContent(ele: HTMLElement): void { + ele.classList.remove(TEMPLATE); + const firstChild: HTMLElement = ele.firstElementChild as HTMLElement; + for (let i: number = 0; i < ele.childElementCount; i++) { + firstChild.remove(); + } + } + + /** + * Gets template content based on the template property value. + * + * @param {string | Function} template - Template property value. + * @returns {Function} - Return template function. + * @hidden + */ + private getTemplateFunction(template: string | Function, notCompile: boolean = true): string | Function { + if (typeof template === 'string') { + let content: string = ''; + try { + const tempEle: HTMLElement = select(template); + if (tempEle) { + //Return innerHTML incase of jsrenderer script else outerHTML + content = tempEle.tagName === 'SCRIPT' ? tempEle.innerHTML : tempEle.outerHTML; + notCompile = false; + } else { + content = template; + } + } catch (e) { + content = template; + } + return notCompile ? content : compile(content); + } else { + /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ + return compile(template as any); + } + } + + private removeItemElements(): void { + const listItems: NodeListOf = this.timelineListEle.querySelectorAll('li'); + for (let i: number = 0; i < listItems.length; i++) { + remove(listItems[parseInt(i.toString(), 10)]); + } + } + + private updateElementClassArray(): void { + const classArray: string[] = [RTL, 'e-align-before', 'e-align-after', 'e-outline', 'e-fill', 'e-align-alternate', + 'e-align-alternatereverse', TIMELINEREVERSE, HORIZONTAL, VERTICAL]; + removeClass([this.element], classArray); + } + + private updateContent(): void { + if (this.isReact) { this.clearTemplate(['timelineTemplate']); } + for (let i: number = 0; i < this.items.length; i++) { + this.renderItemContent(i, true); + } + } + + public destroy(): void { + super.destroy(); + // unwires the events and detach the li elements + this.removeItemElements(); + this.element.removeAttribute("role"); + this.element.removeAttribute("aria-label"); + this.clearTemplate(); + if (this.timelineListEle) { remove(this.timelineListEle); } + this.timelineListEle = null; + this.updateElementClassArray(); + } + + /** + * Called internally if any of the property value changed. + * + * @param {TimelineModel} newProp - Specifies new properties + * @param {TimelineModel} oldProp - Specifies old properties + * @returns {void} + * @private + */ + public onPropertyChanged(newProp: TimelineModel, oldProp?: TimelineModel): void { + for (const prop of Object.keys(newProp)) { + switch (prop) { + case 'items': { + this.removeItemElements(); + this.renderItems(); + break; + } + case 'orientation': + this.updateOrientation(); + break; + case 'align': + this.updateAlign(); + break; + case 'enableRtl': + this.updateRtl(); + break; + case 'cssClass': + this.updateCssClass(newProp.cssClass, oldProp.cssClass); + break; + case 'reverse': + this.element.classList[this.reverse ? 'add' : 'remove'](TIMELINEREVERSE); + break; + case 'template': + this.updateTemplateFunction(); + this.updateContent(); + break; + } + } + } +} diff --git a/controls/layouts/styles/avatar/_bds-definition.scss b/controls/layouts/styles/avatar/_bds-definition.scss new file mode 100644 index 0000000000..60f45fff52 --- /dev/null +++ b/controls/layouts/styles/avatar/_bds-definition.scss @@ -0,0 +1,32 @@ +// Avatar Base +$avatar-base-border-radius: 8px !default; +$avatar-base-background-color: $content-bg-color-alt3 !default; +$avatar-base-font-size: $text-sm !default; +$avatar-base-width: 40px !default; +$avatar-base-height: 40px !default; +$avatar-base-line-height: 22px !default; +$avatar-base-text-color: $content-text-color-alt2 !default; +$avatar-base-font-weight: $font-weight-medium !default; +$avatar-base-img-height: 100% !default; + +// Circle Avatar +$avatar-circle-border-radius: 50% !default; + +// Avatar size +$avatar-xsmall-font-size: $text-xxs !default; +$avatar-small-font-size: $text-xs !default; +$avatar-large-font-size: $text-lg !default; +$avatar-xlarge-font-size: $text-xl !default; + +// Avatar Line Height +$avatar-base-xsmall-line-height: 16px !default; +$avatar-base-small-line-height: 18px !default; +$avatar-base-large-line-height: 28px !default; +$avatar-base-xlarge-line-height: 28px !default; + +//border +$avatar-border: $avatar-border-color !default; + +.e-avatar { + border: 1px solid $avatar-border; +} diff --git a/controls/layouts/styles/bootstrap-dark.scss b/controls/layouts/styles/bootstrap-dark.scss index 86b78aee96..91bf827fcf 100644 --- a/controls/layouts/styles/bootstrap-dark.scss +++ b/controls/layouts/styles/bootstrap-dark.scss @@ -2,3 +2,4 @@ @import 'card/bootstrap-dark.scss'; @import 'splitter/bootstrap-dark.scss'; @import 'dashboard-layout/bootstrap-dark.scss'; +@import 'timeline/bootstrap-dark.scss'; diff --git a/controls/layouts/styles/bootstrap.scss b/controls/layouts/styles/bootstrap.scss index 743dc105ea..0d75254e48 100644 --- a/controls/layouts/styles/bootstrap.scss +++ b/controls/layouts/styles/bootstrap.scss @@ -2,3 +2,4 @@ @import 'card/bootstrap.scss'; @import 'splitter/bootstrap.scss'; @import 'dashboard-layout/bootstrap.scss'; +@import 'timeline/bootstrap.scss'; diff --git a/controls/layouts/styles/bootstrap4.scss b/controls/layouts/styles/bootstrap4.scss index 9eb19329ac..652ea44e7f 100644 --- a/controls/layouts/styles/bootstrap4.scss +++ b/controls/layouts/styles/bootstrap4.scss @@ -2,3 +2,4 @@ @import 'card/bootstrap4.scss'; @import 'splitter/bootstrap4.scss'; @import 'dashboard-layout/bootstrap4.scss'; +@import 'timeline/bootstrap4.scss'; diff --git a/controls/layouts/styles/bootstrap5-dark.scss b/controls/layouts/styles/bootstrap5-dark.scss index 312b75d869..9e20ef2b1b 100644 --- a/controls/layouts/styles/bootstrap5-dark.scss +++ b/controls/layouts/styles/bootstrap5-dark.scss @@ -2,3 +2,4 @@ @import 'card/bootstrap5-dark.scss'; @import 'splitter/bootstrap5-dark.scss'; @import 'dashboard-layout/bootstrap5-dark.scss'; +@import 'timeline/bootstrap5-dark.scss'; diff --git a/controls/layouts/styles/bootstrap5.scss b/controls/layouts/styles/bootstrap5.scss index b376db493a..9dcf752280 100644 --- a/controls/layouts/styles/bootstrap5.scss +++ b/controls/layouts/styles/bootstrap5.scss @@ -2,3 +2,4 @@ @import 'card/bootstrap5.scss'; @import 'splitter/bootstrap5.scss'; @import 'dashboard-layout/bootstrap5.scss'; +@import 'timeline/bootstrap5.scss'; diff --git a/controls/layouts/styles/card/_bds-definition.scss b/controls/layouts/styles/card/_bds-definition.scss new file mode 100644 index 0000000000..dec2091893 --- /dev/null +++ b/controls/layouts/styles/card/_bds-definition.scss @@ -0,0 +1,123 @@ +//Layout Variables Start +$card-img-brdr-radious: 50% !default; +$card-brdr-radious: 6px !default; +$card-action-btn-txt-transform: none !default; + +// Font +$card-header-font: $text-sm !default; +$card-title-font: 16px !default; +$card-action-btn-icon-font: $text-sm !default; +$card-action-btn-font: $text-sm !default; +$card-content-font-size: $text-sm !default; +$card-content-bigger-font-size: $text-base !default; +$card-header-title-font: $text-sm !default; +$card-header-sub-title-font: $text-sm !default; + +// Mouse +$card-content-line-height: $leading-normal !default; +$card-nrml-lheight: 24px !default; +$card-nrml-mheight: 36px !default; +$card-header-padding: 18px !default; +$card-header-lheight: $leading-tight !default; +$card-title-nrml-padding: 18px !default; +$card-title-nrml-lheight: $leading-normal !default; +$card-hor-image-margin: 2px !default; +$card-sep-margin: 12px 0 !default; +$card-header-minheight: 22.5px !default; +$card-header-nrml-padding: 18px 18px 12px 18px !default; +$card-header-txt-nrml-padding: 0 0 0 18px !default; +$card-header-txt-title-lheight: $leading-normal !default; +$card-header-txt-title-nrml-padding: 2px 0 0 !default; +$card-header-txt-subtitle-lheight: $leading-normal !default; +$card-header-image-width: 46px !default; +$card-header-image-height: 46px !default; +$card-image-mheight: 112.5px !default; +$card-image-title-lheight: 30px !default; +$card-image-title-mheight: 30px !default; +$card-action-nrml-vertical-padding: 18px 18px !default; +$card-action-nrml-btn-vertical-margin: 0 0 6px 0 !default; +$card-action-btn-nrml-height: 30px !default; +$card-action-btn-nrml-margin: 0 0 0 6px !default; +$card-action-btn-nrml-padding: 0 6px !default; +$card-nrml-content-padding: 0 18px 12px 18px !default; +$card-header-txt-nrml-title-font: $text-sm !default; +$card-header-txt-nrml-subtitle-font: $text-sm !default; +$card-image-title-font: $text-base !default; +$card-action-btn-icon-width: 24px !default; //Need to check UI +$card-action-btn-icon-height: 24px !default; //Need to check UI +// Touch +$card-bigger-lheight: 48px !default; +$card-bigger-mheight: 48px !default; +$card-header-bigger-padding: 24px 24px 16px 24px !default; +$card-header-bigger-lheight: $leading-normal !default; +$card-title-bigger-margin: 8px 24px 16px 24px !default; +$card-title-bigger-lheight: $leading-normal !default; +$card-hor-image-bigger-margin: 2px !default; +$card-sep-bigger-margin: 16px 0 !default; +$card-header-bigger-minheight: 30px !default; +$card-header-bigger-padding: 24px !default; +$card-header-txt-bigger-padding: 0 0 0 24px !default; +$card-header-txt-title-bigger-lheight: $leading-normal !default; +$card-header-txt-title-bigger-padding: 4px 0 0 !default; +$card-header-txt-subtitle-bigger-lheight: $leading-normal !default; +$card-header-image-bigger-width: 46px !default; +$card-header-image-bigger-height: 50px !default; +$card-image-bigger-mheight: 150px !default; +$card-image-title-bigger-lheight: $leading-tight !default; //Need to check UI +$card-image-title-bigger-mheight: 40px !default; //Need to check UI +$card-img-title-bigger-padding: 12px 16px !default; +$card-action-bigger-padding: 24px 24px !default; +$card-action-bigger-vertical-padding: 12px 24px 24px 24px !default; +$card-action-bigger-btn-vertical-margin: 0 0 8px 0 !default;//Need to check UI +$card-action-btn-bigger-height: 38px !default; +$card-action-btn-bigger-margin: 0 0 0 8px !default; +$card-action-btn-bigger-padding: 0 6px !default; +$card-bigger-content-padding: 0 24px 16px 24px !default; +$card-header-txt-bigger-title-font: $text-sm !default; +$card-header-txt-bigger-subtitle-font: $text-base !default; +$card-image-title-bigger-font: 18px !default; +$card-action-btn-bigger-icon-width: 24px !default; //Need to chek UI +$card-action-btn-bigger-icon-height: 24px !default; //Need to check UI +$card-image-title-nrml-padding: 8px 16px !default; +$card-action-nrml-padding: 18px 18px !default; +$card-highlight-color: $primary !default; + +//Layout Variables End + +//Theme Variables Start +$card-bg-color: $content-bg-color !default; +$card-focus-bg-color: $content-bg-color-alt2 !default; +$card-hover-bg-color: $content-bg-color-alt2 !default; +$card-active-bg-color: $content-bg-color-alt2 !default; +$card-focus-brdr-color: $border-light !default; +$card-hover-brdr-color: $border-light !default; +$card-active-brdr-color: $border-light !default; +$card-brdr-size: 1px !default; +$card-brdr-type: solid !default; +$card-brdr-color: $border-light !default; +$card-sep-brdr-size: 1px !default; +$card-sep-brdr-type: solid !default; +$card-sep-brdr-color: $border-light !default; +$card-image-title-color: $content-text-color !default; +$card-image-title-bg: $overlay-bg-color !default; +$card-action-btn-bg-color: $transparent !default; +$card-action-btn-font-color: $primary !default; +$card-action-btn-border: 1px solid $primary !default; +$card-action-btn-hover-bg: $primary !default; +$card-action-btn-hover-border: 1px solid $secondary-border-color-hover !default; +$card-action-btn-hover-font: $white !default; +$card-action-btn-focus-bg: $secondary-bg-color-focus !default; +$card-action-btn-focus-border: 1px solid !default; +$card-action-btn-focus-font: $secondary-text-color-focus !default; +$card-action-btn-pressed-bg: $secondary-bg-color-pressed !default; +$card-action-btn-pressed-border: 1px solid $secondary-border-color-pressed !default; +$card-action-btn-pressed-font: $secondary-text-color-pressed !default; +$card-font-color: $content-text-color !default; +$card-header-txt-title-color: $content-text-color !default; +$card-header-txt-subtitle-color: $content-text-color-alt2 !default; +$card-content-font-color: $content-text-color-alt1 !default; +$card-box-shadow: $shadow !default; +$card-action-btn-icon-color: $primary !default; //Doubt +$card-hover-box-shadow: $shadow !default; + +//Theme Variables End diff --git a/controls/layouts/styles/card/_layout.scss b/controls/layouts/styles/card/_layout.scss index 19259da124..6c5a120500 100644 --- a/controls/layouts/styles/card/_layout.scss +++ b/controls/layouts/styles/card/_layout.scss @@ -177,14 +177,14 @@ &:hover { @if $skin-name == 'fabric' or $skin-name == 'highcontrast' { - border-width: 2px; + border-width: 1px; padding: 1px; } } &:active { @if $skin-name == 'fabric' or $skin-name == 'highcontrast' { - border-width: 2px; + border-width: 1px; padding: 0; } } diff --git a/controls/layouts/styles/card/_theme.scss b/controls/layouts/styles/card/_theme.scss index 27dd12f981..a3f04226c0 100644 --- a/controls/layouts/styles/card/_theme.scss +++ b/controls/layouts/styles/card/_theme.scss @@ -61,25 +61,45 @@ .e-card-btn, a { - background-color: $card-action-btn-bg-color; + @if $skin-name == 'Material3' { + background: $card-action-btn-bg-color; + } + @else { + background-color: $card-action-btn-bg-color; + } border: $card-action-btn-border; color: $card-action-btn-font-color; outline: 0; &:hover { - background-color: $card-action-btn-hover-bg; + @if $skin-name == 'Material3' { + background: $card-action-btn-hover-bg; + } + @else { + background-color: $card-action-btn-hover-bg; + } border: $card-action-btn-hover-border; color: $card-action-btn-hover-font; } &:focus { - background-color: $card-action-btn-focus-bg; + @if $skin-name == 'Material3' { + background: $card-action-btn-focus-bg; + } + @else { + background-color: $card-action-btn-focus-bg; + } border: $card-action-btn-focus-border; color: $card-action-btn-focus-font; } &:active { - background-color: $card-action-btn-pressed-bg; + @if $skin-name == 'Material3' { + background: $card-action-btn-pressed-bg; + } + @else { + background-color: $card-action-btn-pressed-bg; + } border: $card-action-btn-pressed-border; color: $card-action-btn-pressed-font; } diff --git a/controls/layouts/styles/dashboard-layout/_bds-definition.scss b/controls/layouts/styles/dashboard-layout/_bds-definition.scss new file mode 100644 index 0000000000..07fd690672 --- /dev/null +++ b/controls/layouts/styles/dashboard-layout/_bds-definition.scss @@ -0,0 +1,109 @@ +/*! component's theme wise override tailwind-definitions and variables */ + +// Generic +$icon-zero: 0 !default; +$icon-border-radius: 50% !default; +$grid-layout-position: relative !default; +$panel-position: absolute !default; +$panel-box-sizing: border-box !default; + +// Header styles + +// Mouse +$panel-header-height: 38px !default; +$panel-header-padding: 8px 18px !default; +$panel-header-line-height: 22px !default; +$panel-header-bg-color: $content-bg-color-alt1 !default; +$panel-header-color: $content-text-color !default; +$panel-header-font-size: $text-sm !default; +$panel-header-font-weight: $font-weight-medium !default; +$panel-header-border-bottom: none !default; +$panel-header-border-radius: 6px !default; +$panel-header-font-family: $font-family !default; +$panel-header-white-space: nowrap !default; +$panel-header-overflow: hidden !default; +$panel-header-text-overflow: ellipsis !default; + +// Touch +$panel-bigger-header-height: 40px !default; +$panel-bigger-header-padding: 8px 18px !default; +$panel-bigger-header-line-height: 24px !default; +$panel-bigger-header-color: $content-text-color !default; +$panel-bigger-header-font-size: $text-base !default; +$panel-bigger-header-font-weight: $font-weight-medium !default; +$panel-bigger-content-padding: 16px !default; + +// Panel styles +$panel-border: 1px solid $border-light !default; +$panel-border-radius: 6px !default; +$panel-full-height: 100% !default; +$panel-full-width: 100% !default; +$panel-hover-border: 1px solid $border-dark !default; +$panel-active-border: 1px solid $primary-border-color !default; +$panel-content-padding: 12px !default; + +// icons styles +$panel-resize-one-dimensional-icon-height: 8px !default; +$panel-resize-one-dimensional-icon-width: 8px !default; +$panel-resize-one-dimensional-icon-background: none !default; +$panel-resize-one-dimensional-icon-border: none !default; +$panel-resize-one-dimensional-icon-shadow: none !default; + +$panel-resize-two-dimensional-icon-height: 8px !default; +$panel-resize-two-dimensional-icon-width: 8px !default; +$panel-resize-two-dimensional-inner-icon-height: 8px !default; +$panel-resize-two-dimensional-inner-icon-width: 8px !default; +$panel-resize-two-dimensional-icon-background: none !default; +$panel-resize-two-dimensional-icon-color: $icon-color-disabled !default; +$panel-resize-two-dimensional-icon-border: none !default; +$panel-resize-two-dimensional-icon-shadow: none !default; + +// south-east-icon-styles +$panel-south-east-icon-right: 2px !default; +$panel-south-east-icon-bottom: 2px !default; + +// south-west-icon-style +$panel-south-west-icon-left: 2px !default; +$panel-south-west-icon-bottom: 2px !default; + +// north-east-icon-styles +$panel-north-east-icon-right: 2px !default; +$panel-north-east-icon-top: 2px !default; + +// north-west-icon-styles +$panel-north-west-icon-left: 2px !default; +$panel-north-west-icon-top: 2px !default; + +// droppable area border +$panel-drop-border-radius: 6px !default; + +// dragging element style +$panel-dragging-cursor: move !default; +$panel-drag-prevent: none !default; + +// Blazor ContentTemplate styles +$panel-content-template-height: inherit !default; +$panel-content-template-width: inherit !default; + +/* stylelint-disable */ +$panel-dragging-zindex: 1111 !important !default; +$panel-drag-prevent: none !default; + +$element-width-complete: 100% !important !default; + +// colors + +// Panel styles +$panel-active-background: $content-bg-color-alt2 !default; +$panel-hover-box-shadow: $shadow-lg !default; +$panel-active-drag-box-shadow: $shadow !default; +$panel-background: $content-bg-color !default; +$panel-box-shadow: $shadow; + +// droppable area border +$panel-drop-background: $content-bg-color-alt3 !default; +$panel-drop-border: 1px $border-dark dashed !default; + +//gridlines +$gridline-background: $content-bg-color-alt1 !default; +$gridline-border: $border !default; diff --git a/controls/layouts/styles/dashboard-layout/icons/_bds.scss b/controls/layouts/styles/dashboard-layout/icons/_bds.scss new file mode 100644 index 0000000000..f4de358297 --- /dev/null +++ b/controls/layouts/styles/dashboard-layout/icons/_bds.scss @@ -0,0 +1,80 @@ +@include export-module('dashboardlayout-bds-icons') { + .e-dashboardlayout.e-control { + & .e-panel { + + & .e-resize.e-single, + & .e-resize.e-double { + &.e-east { + height: 100%; + padding: 20px 0; + right: 1px; + top: 0; + width: 12px; + } + + &.e-west { + height: 100%; + left: 0; + padding: 20px 0; + top: 0; + width: 12px; + } + + &.e-north { + height: 12px; + padding: 0 20px; + top: 1px; + width: 100%; + } + + &.e-south { + bottom: 1px; + height: 12px; + padding: 0 20px; + width: 100%; + } + + &.e-south-east { + bottom: 0; + right: 1px; + z-index: 10; + } + + &.e-north-west { + left: 2px; + top: 2px; + z-index: 10; + } + + &.e-north-east { + right: 2px; + top: 2px; + z-index: 10; + } + + &.e-south-west { + bottom: 1px; + left: 1px; + z-index: 10; + } + + &.e-south-east::before { + bottom: 4px; + content: '\e761'; + font-size: 12px; + position: absolute; + right: 4px; + } + + &.e-south-west::before { + bottom: 4px; + content: '\e761'; + font-size: 12px; + left: 4px; + position: absolute; + transform: rotateY(180deg); + } + } + } + } +} diff --git a/controls/layouts/styles/fabric-dark.scss b/controls/layouts/styles/fabric-dark.scss index c1d36397b8..4de7155757 100644 --- a/controls/layouts/styles/fabric-dark.scss +++ b/controls/layouts/styles/fabric-dark.scss @@ -2,3 +2,4 @@ @import 'card/fabric-dark.scss'; @import 'splitter/fabric-dark.scss'; @import 'dashboard-layout/fabric-dark.scss'; +@import 'timeline/fabric-dark.scss'; diff --git a/controls/layouts/styles/fabric.scss b/controls/layouts/styles/fabric.scss index b1eebe4061..e1c3db6c8f 100644 --- a/controls/layouts/styles/fabric.scss +++ b/controls/layouts/styles/fabric.scss @@ -2,3 +2,4 @@ @import 'card/fabric.scss'; @import 'splitter/fabric.scss'; @import 'dashboard-layout/fabric.scss'; +@import 'timeline/fabric.scss'; diff --git a/controls/layouts/styles/fluent-dark.scss b/controls/layouts/styles/fluent-dark.scss index f4006cffe5..b5a569beb9 100644 --- a/controls/layouts/styles/fluent-dark.scss +++ b/controls/layouts/styles/fluent-dark.scss @@ -2,3 +2,4 @@ @import 'card/fluent-dark.scss'; @import 'splitter/fluent-dark.scss'; @import 'dashboard-layout/fluent-dark.scss'; +@import 'timeline/fluent-dark.scss'; diff --git a/controls/layouts/styles/fluent.scss b/controls/layouts/styles/fluent.scss index 626403675d..bf9617dbaf 100644 --- a/controls/layouts/styles/fluent.scss +++ b/controls/layouts/styles/fluent.scss @@ -2,3 +2,4 @@ @import 'card/fluent.scss'; @import 'splitter/fluent.scss'; @import 'dashboard-layout/fluent.scss'; +@import 'timeline/fluent.scss'; diff --git a/controls/layouts/styles/highcontrast-light.scss b/controls/layouts/styles/highcontrast-light.scss index 235335b7d3..796450f169 100644 --- a/controls/layouts/styles/highcontrast-light.scss +++ b/controls/layouts/styles/highcontrast-light.scss @@ -2,3 +2,4 @@ @import 'card/highcontrast-light.scss'; @import 'splitter/highcontrast-light.scss'; @import 'dashboard-layout/highcontrast-light.scss'; +@import 'timeline/highcontrast-light.scss'; diff --git a/controls/layouts/styles/highcontrast.scss b/controls/layouts/styles/highcontrast.scss index 06d8eddc61..afeb425280 100644 --- a/controls/layouts/styles/highcontrast.scss +++ b/controls/layouts/styles/highcontrast.scss @@ -2,3 +2,4 @@ @import 'card/highcontrast.scss'; @import 'splitter/highcontrast.scss'; @import 'dashboard-layout/highcontrast.scss'; +@import 'timeline/highcontrast.scss'; diff --git a/controls/layouts/styles/material-dark.scss b/controls/layouts/styles/material-dark.scss index 8b69150f0f..80da9957d1 100644 --- a/controls/layouts/styles/material-dark.scss +++ b/controls/layouts/styles/material-dark.scss @@ -2,3 +2,4 @@ @import 'card/material-dark.scss'; @import 'splitter/material-dark.scss'; @import 'dashboard-layout/material-dark.scss'; +@import 'timeline/material-dark.scss'; diff --git a/controls/layouts/styles/material.scss b/controls/layouts/styles/material.scss index 7542594b55..dcc57aa65b 100644 --- a/controls/layouts/styles/material.scss +++ b/controls/layouts/styles/material.scss @@ -2,3 +2,4 @@ @import 'card/material.scss'; @import 'splitter/material.scss'; @import 'dashboard-layout/material.scss'; +@import 'timeline/material.scss'; diff --git a/controls/layouts/styles/material3-dark.scss b/controls/layouts/styles/material3-dark.scss index 01f3b57e63..33812b9eff 100644 --- a/controls/layouts/styles/material3-dark.scss +++ b/controls/layouts/styles/material3-dark.scss @@ -4,3 +4,4 @@ @import 'card/material3-dark.scss'; @import 'splitter/material3-dark.scss'; @import 'dashboard-layout/material3-dark.scss'; +@import 'timeline/material3-dark.scss'; diff --git a/controls/layouts/styles/material3.scss b/controls/layouts/styles/material3.scss index 2dead8ad6b..e2b264cb30 100644 --- a/controls/layouts/styles/material3.scss +++ b/controls/layouts/styles/material3.scss @@ -4,3 +4,4 @@ @import 'card/material3.scss'; @import 'splitter/material3.scss'; @import 'dashboard-layout/material3.scss'; +@import 'timeline/material3.scss'; diff --git a/controls/layouts/styles/splitter/_bds-definition.scss b/controls/layouts/styles/splitter/_bds-definition.scss new file mode 100644 index 0000000000..ccc513b2f7 --- /dev/null +++ b/controls/layouts/styles/splitter/_bds-definition.scss @@ -0,0 +1,31 @@ +//Layout Variables Start +$splitpane-font-size: $text-sm !default; +$splitpane-font-weight: $font-weight-normal !default; +$splitpane-font-family: $font-family !default; +$splitbar-icon-size: 14px !default; +$splitbar-icon-gap: 18px !default; +$bigger-splitbar-icon-gap: 20px !default; +$bigger-splitpane-font-size: $text-base !default; +$bigger-splitbar-icon-size: 16px !default; + +//Layout Variables End + +//Theme Variables Start +$navigation-arrow-background: $transparent !default; +$navigation-icon-background-hover: $primary !default; +$navigation-icon-border-hover: $primary !default; +$splitter-border-color: $border-light !default; +$splitter-background-color: $primary-text-color !default; +$splitpane-font-color: $content-text-color !default; +$splitbar-border-color: $border-light !default; +$splitbar-hover-border-color: $primary !default; +$resize-icon-hover-color: $splitbar-hover-border-color !default; +$splitbar-icon-color: $icon-color !default; +$collapse-icon-bg-color: $primary-text-color !default; +$resize-icon-bg-color: $content-bg-color !default; +$split-bar-border-left: 1px solid $border-light !default; +$split-bar-border-right: 1px solid $border-light !default; +$split-bar-hover-border-left: 1px solid $splitbar-hover-border-color !default; +$split-bar-hover-border-right: 1px solid $splitbar-hover-border-color !default; + +//Theme Variables End diff --git a/controls/layouts/styles/splitter/icons/_bds.scss b/controls/layouts/styles/splitter/icons/_bds.scss new file mode 100644 index 0000000000..503910e25b --- /dev/null +++ b/controls/layouts/styles/splitter/icons/_bds.scss @@ -0,0 +1,39 @@ +@include export-module('splitter-material-icons') { + .e-splitter { + .e-split-bar { + &.e-split-bar-horizontal { + .e-resize-handler::before { + content: '\e7e3'; + font-family: 'e-icons'; + font-size: $splitbar-icon-size; + } + } + + &.e-split-bar-vertical { + & .e-resize-handler::before { + content: '\e7fd'; + font-family: 'e-icons'; + font-size: $splitbar-icon-size; + } + } + } + } + + .e-bigger { + .e-splitter { + .e-split-bar { + .e-resize-handler::before { + font-size: $bigger-splitbar-icon-size; + } + } + } + + &.e-splitter { + .e-split-bar { + .e-resize-handler::before { + font-size: $bigger-splitbar-icon-size; + } + } + } + } +} diff --git a/controls/layouts/styles/tailwind-dark.scss b/controls/layouts/styles/tailwind-dark.scss index 609e92a91d..215bd1f88e 100644 --- a/controls/layouts/styles/tailwind-dark.scss +++ b/controls/layouts/styles/tailwind-dark.scss @@ -2,3 +2,4 @@ @import 'card/tailwind-dark.scss'; @import 'splitter/tailwind-dark.scss'; @import 'dashboard-layout/tailwind-dark.scss'; +@import 'timeline/tailwind-dark.scss'; diff --git a/controls/layouts/styles/tailwind.scss b/controls/layouts/styles/tailwind.scss index fd0ec885d2..8389299e52 100644 --- a/controls/layouts/styles/tailwind.scss +++ b/controls/layouts/styles/tailwind.scss @@ -2,3 +2,4 @@ @import 'card/tailwind.scss'; @import 'splitter/tailwind.scss'; @import 'dashboard-layout/tailwind.scss'; +@import 'timeline/tailwind.scss'; diff --git a/controls/layouts/styles/timeline/_all.scss b/controls/layouts/styles/timeline/_all.scss new file mode 100644 index 0000000000..a0fe77dcde --- /dev/null +++ b/controls/layouts/styles/timeline/_all.scss @@ -0,0 +1,2 @@ +@import 'layout.scss'; +@import 'theme.scss'; diff --git a/controls/layouts/styles/timeline/_bds-definition.scss b/controls/layouts/styles/timeline/_bds-definition.scss new file mode 100644 index 0000000000..8b1b27447c --- /dev/null +++ b/controls/layouts/styles/timeline/_bds-definition.scss @@ -0,0 +1,18 @@ +$timeline-dot-size: 16px !default; +$timeline-content-font-size: 14px !default; +$timeline-dot-font-size: 16px !default; +$timeline-opposite-content-font-size: 12px !default; +$timeline-dot-icon-size: 32px !default; + +$timeline-bigger-dot-size: 20px !default; +$timeline-bigger-content-font-size: 16px !default; +$timeline-bigger-opposite-content-font-size: 14px !default; +$timeline-bigger-dot-font-size: 18px !default; +$timeline-bigger-dot-icon-size: 40px !default; + +$timeline-dot-color: $content-text-color-alt2 !default; +$timeline-dot-background-color: $content-bg-color !default; +$timeline-dot-border-color: rgba($border-light) !default; +$timeline-content-font-color: $content-text-color !default; +$timeline-opposite-content-font-color: $content-text-color-alt1 !default; +$timeline-item-disabled-color: $content-text-color-disabled !default; diff --git a/controls/layouts/styles/timeline/_bootstrap-dark-definition.scss b/controls/layouts/styles/timeline/_bootstrap-dark-definition.scss new file mode 100644 index 0000000000..13f13f08da --- /dev/null +++ b/controls/layouts/styles/timeline/_bootstrap-dark-definition.scss @@ -0,0 +1,18 @@ +$timeline-dot-size: 16px !default; +$timeline-content-font-size: 14px !default; +$timeline-dot-font-size: 16px !default; +$timeline-opposite-content-font-size: 12px !default; +$timeline-dot-icon-size: 32px !default; + +$timeline-bigger-dot-size: 20px !default; +$timeline-bigger-content-font-size: 16px !default; +$timeline-bigger-opposite-content-font-size: 14px !default; +$timeline-bigger-dot-font-size: 18px !default; +$timeline-bigger-dot-icon-size: 40px !default; + +$timeline-dot-color: $grey-dark-font !default; +$timeline-dot-background-color: $gray-light !default; +$timeline-dot-border-color: $grey-ad !default; +$timeline-content-font-color: $grey-dark-font !default; +$timeline-opposite-content-font-color: $grey-dark-font !default; +$timeline-item-disabled-color: $grey-lighter !default; diff --git a/controls/layouts/styles/timeline/_bootstrap-definition.scss b/controls/layouts/styles/timeline/_bootstrap-definition.scss new file mode 100644 index 0000000000..cbcecf0bfe --- /dev/null +++ b/controls/layouts/styles/timeline/_bootstrap-definition.scss @@ -0,0 +1,18 @@ +$timeline-dot-size: 16px !default; +$timeline-content-font-size: 14px !default; +$timeline-dot-font-size: 16px !default; +$timeline-opposite-content-font-size: 12px !default; +$timeline-dot-icon-size: 32px !default; + +$timeline-bigger-dot-size: 20px !default; +$timeline-bigger-content-font-size: 16px !default; +$timeline-bigger-opposite-content-font-size: 14px !default; +$timeline-bigger-dot-font-size: 18px !default; +$timeline-bigger-dot-icon-size: 40px !default; + +$timeline-dot-color: $grey-light-font !default; +$timeline-dot-background-color: $grey-white !default; +$timeline-dot-border-color: $grey-e6 !default; +$timeline-content-font-color: $grey-light-font !default; +$timeline-opposite-content-font-color: $grey-light-font !default; +$timeline-item-disabled-color: $grey-ad !default; diff --git a/controls/layouts/styles/timeline/_bootstrap4-definition.scss b/controls/layouts/styles/timeline/_bootstrap4-definition.scss new file mode 100644 index 0000000000..0dbd5c14a7 --- /dev/null +++ b/controls/layouts/styles/timeline/_bootstrap4-definition.scss @@ -0,0 +1,18 @@ +$timeline-dot-size: 16px !default; +$timeline-content-font-size: 14px !default; +$timeline-dot-font-size: 16px !default; +$timeline-opposite-content-font-size: 12px !default; +$timeline-dot-icon-size: 32px !default; + +$timeline-bigger-dot-size: 20px !default; +$timeline-bigger-content-font-size: 16px !default; +$timeline-bigger-opposite-content-font-size: 14px !default; +$timeline-bigger-dot-font-size: 18px !default; +$timeline-bigger-dot-icon-size: 40px !default; + +$timeline-dot-color: $gray-700 !default; +$timeline-dot-background-color: $white !default; +$timeline-dot-border-color: $gray-300 !default; +$timeline-content-font-color: $gray-900 !default; +$timeline-opposite-content-font-color: $gray-900 !default; +$timeline-item-disabled-color: $gray-500 !default; diff --git a/controls/layouts/styles/timeline/_bootstrap5-dark-definition.scss b/controls/layouts/styles/timeline/_bootstrap5-dark-definition.scss new file mode 100644 index 0000000000..941247df8f --- /dev/null +++ b/controls/layouts/styles/timeline/_bootstrap5-dark-definition.scss @@ -0,0 +1 @@ +@import './bootstrap5-definition.scss'; diff --git a/controls/layouts/styles/timeline/_bootstrap5-definition.scss b/controls/layouts/styles/timeline/_bootstrap5-definition.scss new file mode 100644 index 0000000000..c7fb45ae11 --- /dev/null +++ b/controls/layouts/styles/timeline/_bootstrap5-definition.scss @@ -0,0 +1,18 @@ +$timeline-dot-size: 16px !default; +$timeline-content-font-size: 14px !default; +$timeline-dot-font-size: 16px !default; +$timeline-opposite-content-font-size: 12px !default; +$timeline-dot-icon-size: 32px !default; + +$timeline-bigger-dot-size: 20px !default; +$timeline-bigger-content-font-size: 16px !default; +$timeline-bigger-opposite-content-font-size: 14px !default; +$timeline-bigger-dot-font-size: 18px !default; +$timeline-bigger-dot-icon-size: 40px !default; + +$timeline-dot-color: $content-text-color !default; +$timeline-dot-background-color: $content-bg-color !default; +$timeline-dot-border-color: $border-light !default; +$timeline-content-font-color: $content-text-color-alt1 !default; +$timeline-opposite-content-font-color: $content-text-color-alt2 !default; +$timeline-item-disabled-color: $content-text-color-disabled !default; diff --git a/controls/layouts/styles/timeline/_fabric-dark-definition.scss b/controls/layouts/styles/timeline/_fabric-dark-definition.scss new file mode 100644 index 0000000000..d2e768f4ca --- /dev/null +++ b/controls/layouts/styles/timeline/_fabric-dark-definition.scss @@ -0,0 +1,18 @@ +$timeline-dot-size: 16px !default; +$timeline-content-font-size: 14px !default; +$timeline-dot-font-size: 16px !default; +$timeline-opposite-content-font-size: 12px !default; +$timeline-dot-icon-size: 32px !default; + +$timeline-bigger-dot-size: 20px !default; +$timeline-bigger-content-font-size: 16px !default; +$timeline-bigger-opposite-content-font-size: 14px !default; +$timeline-bigger-dot-font-size: 18px !default; +$timeline-bigger-dot-icon-size: 40px !default; + +$timeline-dot-color: $base-font !default; +$timeline-dot-background-color: $neutral-white !default; +$timeline-dot-border-color: $neutral-tertiary-alt !default; +$timeline-content-font-color: $neutral-light-font !default; +$timeline-opposite-content-font-color: $neutral-light-font !default; +$timeline-item-disabled-color: $neutral-tertiary-alt !default; diff --git a/controls/layouts/styles/timeline/_fabric-definition.scss b/controls/layouts/styles/timeline/_fabric-definition.scss new file mode 100644 index 0000000000..7c394d672d --- /dev/null +++ b/controls/layouts/styles/timeline/_fabric-definition.scss @@ -0,0 +1,18 @@ +$timeline-dot-size: 16px !default; +$timeline-content-font-size: 14px !default; +$timeline-dot-font-size: 16px !default; +$timeline-opposite-content-font-size: 12px !default; +$timeline-dot-icon-size: 32px !default; + +$timeline-bigger-dot-size: 20px !default; +$timeline-bigger-content-font-size: 16px !default; +$timeline-bigger-opposite-content-font-size: 14px !default; +$timeline-bigger-dot-font-size: 18px !default; +$timeline-bigger-dot-icon-size: 40px !default; + +$timeline-dot-color: $base-font !default; +$timeline-dot-background-color: $neutral-white !default; +$timeline-dot-border-color: $neutral-quintenaryalt !default; +$timeline-content-font-color: $theme-light-font !default; +$timeline-opposite-content-font-color: $theme-light-font !default; +$timeline-item-disabled-color: $neutral-tertiary !default; diff --git a/controls/layouts/styles/timeline/_fluent-dark-definition.scss b/controls/layouts/styles/timeline/_fluent-dark-definition.scss new file mode 100644 index 0000000000..0548f68542 --- /dev/null +++ b/controls/layouts/styles/timeline/_fluent-dark-definition.scss @@ -0,0 +1 @@ +@import './fluent-definition.scss'; diff --git a/controls/layouts/styles/timeline/_fluent-definition.scss b/controls/layouts/styles/timeline/_fluent-definition.scss new file mode 100644 index 0000000000..881f23cdbc --- /dev/null +++ b/controls/layouts/styles/timeline/_fluent-definition.scss @@ -0,0 +1,18 @@ +$timeline-dot-size: 16px !default; +$timeline-content-font-size: 14px !default; +$timeline-dot-font-size: 16px !default; +$timeline-opposite-content-font-size: 12px !default; +$timeline-dot-icon-size: 32px !default; + +$timeline-bigger-dot-size: 20px !default; +$timeline-bigger-content-font-size: 16px !default; +$timeline-bigger-opposite-content-font-size: 14px !default; +$timeline-bigger-dot-font-size: 18px !default; +$timeline-bigger-dot-icon-size: 40px !default; + +$timeline-dot-color: $content-text-color !default; +$timeline-dot-background-color: $content-bg-color !default; +$timeline-dot-border-color: $border-light !default; +$timeline-content-font-color: $content-text-color-alt1 !default; +$timeline-opposite-content-font-color: $content-text-color-alt1 !default; +$timeline-item-disabled-color: $content-text-color-disabled !default; diff --git a/controls/layouts/styles/timeline/_fusionnew-definition.scss b/controls/layouts/styles/timeline/_fusionnew-definition.scss new file mode 100644 index 0000000000..881f23cdbc --- /dev/null +++ b/controls/layouts/styles/timeline/_fusionnew-definition.scss @@ -0,0 +1,18 @@ +$timeline-dot-size: 16px !default; +$timeline-content-font-size: 14px !default; +$timeline-dot-font-size: 16px !default; +$timeline-opposite-content-font-size: 12px !default; +$timeline-dot-icon-size: 32px !default; + +$timeline-bigger-dot-size: 20px !default; +$timeline-bigger-content-font-size: 16px !default; +$timeline-bigger-opposite-content-font-size: 14px !default; +$timeline-bigger-dot-font-size: 18px !default; +$timeline-bigger-dot-icon-size: 40px !default; + +$timeline-dot-color: $content-text-color !default; +$timeline-dot-background-color: $content-bg-color !default; +$timeline-dot-border-color: $border-light !default; +$timeline-content-font-color: $content-text-color-alt1 !default; +$timeline-opposite-content-font-color: $content-text-color-alt1 !default; +$timeline-item-disabled-color: $content-text-color-disabled !default; diff --git a/controls/layouts/styles/timeline/_highcontrast-definition.scss b/controls/layouts/styles/timeline/_highcontrast-definition.scss new file mode 100644 index 0000000000..025ec5d6a4 --- /dev/null +++ b/controls/layouts/styles/timeline/_highcontrast-definition.scss @@ -0,0 +1,18 @@ +$timeline-dot-size: 16px !default; +$timeline-content-font-size: 14px !default; +$timeline-dot-font-size: 16px !default; +$timeline-opposite-content-font-size: 12px !default; +$timeline-dot-icon-size: 32px !default; + +$timeline-bigger-dot-size: 20px !default; +$timeline-bigger-content-font-size: 16px !default; +$timeline-bigger-opposite-content-font-size: 14px !default; +$timeline-bigger-dot-font-size: 18px !default; +$timeline-bigger-dot-icon-size: 40px !default; + +$timeline-dot-color: $content-font !default; +$timeline-dot-background-color: $bg-base-0 !default; +$timeline-dot-border-color: $border-alt !default; +$timeline-content-font-color: $content-font !default; +$timeline-opposite-content-font-color: $content-font !default; +$timeline-item-disabled-color: $selection-bg !default; diff --git a/controls/layouts/styles/timeline/_highcontrast-light-definition.scss b/controls/layouts/styles/timeline/_highcontrast-light-definition.scss new file mode 100644 index 0000000000..025ec5d6a4 --- /dev/null +++ b/controls/layouts/styles/timeline/_highcontrast-light-definition.scss @@ -0,0 +1,18 @@ +$timeline-dot-size: 16px !default; +$timeline-content-font-size: 14px !default; +$timeline-dot-font-size: 16px !default; +$timeline-opposite-content-font-size: 12px !default; +$timeline-dot-icon-size: 32px !default; + +$timeline-bigger-dot-size: 20px !default; +$timeline-bigger-content-font-size: 16px !default; +$timeline-bigger-opposite-content-font-size: 14px !default; +$timeline-bigger-dot-font-size: 18px !default; +$timeline-bigger-dot-icon-size: 40px !default; + +$timeline-dot-color: $content-font !default; +$timeline-dot-background-color: $bg-base-0 !default; +$timeline-dot-border-color: $border-alt !default; +$timeline-content-font-color: $content-font !default; +$timeline-opposite-content-font-color: $content-font !default; +$timeline-item-disabled-color: $selection-bg !default; diff --git a/controls/layouts/styles/timeline/_layout.scss b/controls/layouts/styles/timeline/_layout.scss new file mode 100644 index 0000000000..d55256c19a --- /dev/null +++ b/controls/layouts/styles/timeline/_layout.scss @@ -0,0 +1,273 @@ +@include export-module('timeline-layout') { + .e-timeline { + --dot-size: #{$timeline-dot-size}; + --dot-outer-space: 0; + --dot-border: 1px; + --connector-size: 1px; + --dot-radius: 50%; + height: inherit; + width: 100%; + + [class ^= 'e-dot ']::before, + .e-dot-item, + .e-dot { + display: flex; + align-items: center; + justify-content: center; + } + + [class ^= 'e-dot ']::before { + min-width: $timeline-dot-icon-size; + min-height: $timeline-dot-icon-size; + border-radius: var(--dot-radius); + } + + .e-timeline-items { + display: inline-flex; + flex-direction: column; + list-style: none; + flex-wrap: nowrap; + padding: 0; + width: inherit; + height: inherit; + } + + .e-timeline-item { + display: flex; + flex-direction: row; + position: relative; + align-items: flex-start; + width: inherit; + height: inherit; + } + + .e-timeline-item.e-connector::after { + top: 0; + bottom: 0; + left: calc(50% - var(--connector-size)); + right: auto; + content: ''; + position: absolute; + z-index: 999; + border-width: var(--connector-size); + border-style: solid; + } + + .e-timeline-item.e-connector.e-item-template::after { + content: unset; + } + + .e-dot-item { + position: relative; + flex: 0 1 calc(var(--dot-size) * 3); + z-index: 1000; + } + + .e-dot { + min-width: var(--dot-size); + min-height: var(--dot-size); + border-radius: var(--dot-radius); + outline: var(--dot-outer-space) solid; + border: var(--dot-border) solid; + font-size: $timeline-dot-font-size; + background-size: cover; + background-position: center; + background-repeat: no-repeat; + } + + .e-opposite-content, + .e-content { + flex: 1 1 50%; + } + + .e-opposite-content { + font-size: $timeline-opposite-content-font-size; + text-align: right; + } + + .e-content { + text-align: left; + font-size: $timeline-content-font-size; + font-weight: 500; + } + + .e-timeline-item:first-child::after { + top: 0; + } + + .e-timeline-item:last-child::after { + bottom: calc(var(--dot-size) * 2); + } + + &.e-vertical { + + &.e-align-before .e-timeline-item, + &.e-align-alternate .e-timeline-item:nth-of-type(even), + &.e-align-alternatereverse .e-timeline-item:nth-of-type(odd) { + flex-direction: row-reverse; + } + + &.e-timeline-reverse .e-timeline-items { + flex-direction: column-reverse; + } + + &.e-align-before .e-timeline-items .e-opposite-content, + &.e-align-after .e-timeline-items .e-content { + text-align: left; + } + + .e-timeline-item.e-connector:last-child::after { + content: unset; + } + + &.e-timeline-reverse { + .e-timeline-item.e-connector:first-child::after { + content: unset; + } + + .e-timeline-item.e-connector:last-child::after { + content: ''; + bottom: 0; + } + } + + &.e-align-after .e-opposite-content, + &.e-align-before .e-content, + &.e-rtl.e-align-after .e-content, + &.e-rtl.e-align-alternate .e-timeline-item:nth-of-type(odd) .e-content, + &.e-rtl.e-align-alternatereverse .e-timeline-item:nth-of-type(even) .e-content, + &.e-align-alternate .e-timeline-item:nth-of-type(even) .e-content, + &.e-align-alternatereverse .e-timeline-item:nth-of-type(odd) .e-content, + &.e-rtl.e-align-alternate .e-timeline-item:nth-of-type(even) .e-opposite-content, + &.e-rtl.e-align-alternatereverse .e-timeline-item:nth-of-type(odd) .e-opposite-content, + &.e-rtl.e-align-before .e-opposite-content, + &.e-align-alternate .e-opposite-content, + &.e-align-alternatereverse .e-opposite-content { + text-align: right; + } + + &.e-align-before .e-opposite-content, + &.e-align-after .e-content, + &.e-rtl.e-align-after .e-opposite-content, + &.e-align-alternate .e-timeline-item:nth-of-type(odd) .e-content, + &.e-align-alternatereverse .e-timeline-item:nth-of-type(even) .e-content, + &.e-align-alternate .e-timeline-item:nth-of-type(even) .e-opposite-content, + &.e-align-alternatereverse .e-timeline-item:nth-of-type(odd) .e-opposite-content, + &.e-rtl.e-align-alternate .e-timeline-item:nth-of-type(even) .e-content, + &.e-rtl.e-align-alternatereverse .e-timeline-item:nth-of-type(odd) .e-content, + &.e-rtl.e-align-before .e-content, + &.e-rtl.e-align-alternate .e-opposite-content, + &.e-rtl.e-align-alternatereverse .e-opposite-content { + text-align: left; + } + + .e-dot-item { + width: calc(var(--dot-size) * 2); + } + } + + &.e-horizontal { + .e-timeline-items { + display: inline-flex; + flex-direction: row; + } + + .e-timeline-item { + height: auto; + } + + &.e-rtl .e-timeline-item.e-connector::after, + &.e-timeline-reverse .e-timeline-item.e-connector::after { + right: calc(50% - var(--connector-size)); + left: auto; + } + + .e-dot-item { + margin: calc((var(--dot-size) * 2) / 2) 0; + } + + .e-timeline-item { + flex-direction: column; + align-items: center; + } + + .e-opposite-content, + .e-content { + display: flex; + } + + &.e-align-alternate .e-timeline-item:nth-of-type(even) .e-content, + &.e-align-alternate .e-timeline-item:nth-of-type(odd) .e-opposite-content, + &.e-align-alternatereverse .e-timeline-item:nth-of-type(odd) .e-content, + &.e-align-alternatereverse .e-timeline-item:nth-of-type(even) .e-opposite-content, + &.e-align-before .e-content { + align-items: flex-end; + } + + &.e-align-alternate .e-timeline-item:nth-of-type(odd) .e-content, + &.e-align-alternate .e-timeline-item:nth-of-type(even) .e-opposite-content, + &.e-align-alternatereverse .e-timeline-item:nth-of-type(even) .e-content, + &.e-align-alternatereverse .e-timeline-item:nth-of-type(odd) .e-opposite-content, + &.e-align-before .e-opposite-content { + align-items: flex-start; + } + + &.e-align-before .e-timeline-item, + &.e-align-alternate .e-timeline-item:nth-of-type(even), + &.e-align-alternatereverse .e-timeline-item:nth-of-type(odd) { + flex-direction: column-reverse; + } + + &.e-timeline-reverse .e-timeline-items { + flex-direction: row-reverse; + } + + .e-timeline-item::after { + width: 100%; + height: 0; + top: calc(50% - var(--connector-size)); + } + + .e-opposite-content { + display: flex; + align-items: flex-end; + text-align: left; + padding: 0; + } + + .e-content { + padding: 0; + } + + .e-timeline-item:last-child::after { + width: auto; + } + } + } + + .e-bigger.e-timeline, + .e-bigger .e-timeline { + [class ^= 'e-dot ']::before { + min-width: $timeline-bigger-dot-icon-size; + min-height: $timeline-bigger-dot-icon-size; + font-size: $timeline-bigger-dot-font-size; + } + + .e-dot-item { + flex: 0 1 calc(var(--dot-size) * 4); + } + + .e-dot { + min-width: $timeline-bigger-dot-size; + min-height: $timeline-bigger-dot-size; + } + + .e-opposite-content { + font-size: $timeline-bigger-opposite-content-font-size; + } + + .e-content { + font-size: $timeline-bigger-content-font-size; + } + } +} diff --git a/controls/layouts/styles/timeline/_material-dark-definition.scss b/controls/layouts/styles/timeline/_material-dark-definition.scss new file mode 100644 index 0000000000..ae1ee57388 --- /dev/null +++ b/controls/layouts/styles/timeline/_material-dark-definition.scss @@ -0,0 +1,18 @@ +$timeline-dot-size: 16px !default; +$timeline-content-font-size: 14px !default; +$timeline-dot-font-size: 16px !default; +$timeline-opposite-content-font-size: 12px !default; +$timeline-dot-icon-size: 32px !default; + +$timeline-bigger-dot-size: 20px !default; +$timeline-bigger-content-font-size: 16px !default; +$timeline-bigger-opposite-content-font-size: 14px !default; +$timeline-bigger-dot-font-size: 18px !default; +$timeline-bigger-dot-icon-size: 40px !default; + +$timeline-dot-color: $grey-400 !default; +$timeline-dot-background-color: $grey-900 !default; +$timeline-dot-border-color: $grey-700 !default; +$timeline-content-font-color: $grey-white !default; +$timeline-opposite-content-font-color: $grey-white !default; +$timeline-item-disabled-color: 'rgba($white, .38)' !default; diff --git a/controls/layouts/styles/timeline/_material-definition.scss b/controls/layouts/styles/timeline/_material-definition.scss new file mode 100644 index 0000000000..6881b07cc1 --- /dev/null +++ b/controls/layouts/styles/timeline/_material-definition.scss @@ -0,0 +1,18 @@ +$timeline-dot-size: 16px !default; +$timeline-content-font-size: 14px !default; +$timeline-dot-font-size: 16px !default; +$timeline-opposite-content-font-size: 12px !default; +$timeline-dot-icon-size: 32px !default; + +$timeline-bigger-dot-size: 20px !default; +$timeline-bigger-content-font-size: 16px !default; +$timeline-bigger-opposite-content-font-size: 14px !default; +$timeline-bigger-dot-font-size: 18px !default; +$timeline-bigger-dot-icon-size: 40px !default; + +$timeline-dot-color: $base-font !default; +$timeline-dot-background-color: $primary-300-font !default; +$timeline-dot-border-color: $grey-200 !default; +$timeline-content-font-color: $base-font !default; +$timeline-opposite-content-font-color: $base-font !default; +$timeline-item-disabled-color: 'rgba($black, .38)' !default; diff --git a/controls/layouts/styles/timeline/_material3-dark-definition.scss b/controls/layouts/styles/timeline/_material3-dark-definition.scss new file mode 100644 index 0000000000..356e259499 --- /dev/null +++ b/controls/layouts/styles/timeline/_material3-dark-definition.scss @@ -0,0 +1 @@ +@import './material3-definition.scss'; diff --git a/controls/layouts/styles/timeline/_material3-definition.scss b/controls/layouts/styles/timeline/_material3-definition.scss new file mode 100644 index 0000000000..7ad84de233 --- /dev/null +++ b/controls/layouts/styles/timeline/_material3-definition.scss @@ -0,0 +1,18 @@ +$timeline-dot-size: 16px !default; +$timeline-content-font-size: 14px !default; +$timeline-dot-font-size: 16px !default; +$timeline-opposite-content-font-size: 12px !default; +$timeline-dot-icon-size: 32px !default; + +$timeline-bigger-dot-size: 20px !default; +$timeline-bigger-content-font-size: 16px !default; +$timeline-bigger-opposite-content-font-size: 14px !default; +$timeline-bigger-dot-font-size: 18px !default; +$timeline-bigger-dot-icon-size: 40px !default; + +$timeline-dot-color: rgba($content-text-color) !default; +$timeline-dot-background-color: rgba($content-bg-color) !default; +$timeline-dot-border-color: rgba($border-light) !default; +$timeline-content-font-color: $content-text-color !default; +$timeline-opposite-content-font-color: $content-text-color-alt1 !default; +$timeline-item-disabled-color: $content-text-color-disabled !default; diff --git a/controls/layouts/styles/timeline/_tailwind-dark-definition.scss b/controls/layouts/styles/timeline/_tailwind-dark-definition.scss new file mode 100644 index 0000000000..b0d3baea85 --- /dev/null +++ b/controls/layouts/styles/timeline/_tailwind-dark-definition.scss @@ -0,0 +1 @@ +@import './tailwind-definition.scss'; diff --git a/controls/layouts/styles/timeline/_tailwind-definition.scss b/controls/layouts/styles/timeline/_tailwind-definition.scss new file mode 100644 index 0000000000..46b6db5fa5 --- /dev/null +++ b/controls/layouts/styles/timeline/_tailwind-definition.scss @@ -0,0 +1,18 @@ +$timeline-dot-size: 16px !default; +$timeline-content-font-size: 14px !default; +$timeline-dot-font-size: 16px !default; +$timeline-opposite-content-font-size: 12px !default; +$timeline-dot-icon-size: 32px !default; + +$timeline-bigger-dot-size: 20px !default; +$timeline-bigger-content-font-size: 16px !default; +$timeline-bigger-opposite-content-font-size: 14px !default; +$timeline-bigger-dot-font-size: 18px !default; +$timeline-bigger-dot-icon-size: 40px !default; + +$timeline-dot-color: $content-text-color-alt2 !default; +$timeline-dot-background-color: $content-bg-color !default; +$timeline-dot-border-color: $border-light !default; +$timeline-content-font-color: $content-text-color !default; +$timeline-opposite-content-font-color: $content-text-color !default; +$timeline-item-disabled-color: $content-text-color-disabled !default; diff --git a/controls/layouts/styles/timeline/_theme.scss b/controls/layouts/styles/timeline/_theme.scss new file mode 100644 index 0000000000..25e31bfa2d --- /dev/null +++ b/controls/layouts/styles/timeline/_theme.scss @@ -0,0 +1,31 @@ +@include export-module('timeline-theme') { + .e-timeline { + .e-dot { + background-color: $timeline-dot-border-color; + border-color: $timeline-dot-border-color; + color: $timeline-dot-color; + outline-color: $timeline-dot-background-color; + } + + &.e-outline .e-dot { + background-color: $timeline-dot-background-color; + } + + .e-timeline-item.e-connector::after { + border-color: $timeline-dot-border-color; + } + + .e-content { + color: $timeline-content-font-color; + } + + .e-opposite-content { + color: $timeline-opposite-content-font-color; + } + + .e-item-disabled .e-content, + .e-item-disabled .e-opposite-content { + color: $timeline-item-disabled-color; + } + } +} diff --git a/controls/layouts/styles/timeline/bootstrap-dark.scss b/controls/layouts/styles/timeline/bootstrap-dark.scss new file mode 100644 index 0000000000..acbfa5aad6 --- /dev/null +++ b/controls/layouts/styles/timeline/bootstrap-dark.scss @@ -0,0 +1,3 @@ +@import 'ej2-base/styles/definition/bootstrap-dark.scss'; +@import 'bootstrap-dark-definition.scss'; +@import 'all.scss'; diff --git a/controls/layouts/styles/timeline/bootstrap.scss b/controls/layouts/styles/timeline/bootstrap.scss new file mode 100644 index 0000000000..7deba5ce06 --- /dev/null +++ b/controls/layouts/styles/timeline/bootstrap.scss @@ -0,0 +1,3 @@ +@import 'ej2-base/styles/definition/bootstrap.scss'; +@import 'bootstrap-definition.scss'; +@import 'all.scss'; diff --git a/controls/layouts/styles/timeline/bootstrap4.scss b/controls/layouts/styles/timeline/bootstrap4.scss new file mode 100644 index 0000000000..500bf9a0e8 --- /dev/null +++ b/controls/layouts/styles/timeline/bootstrap4.scss @@ -0,0 +1,3 @@ +@import 'ej2-base/styles/definition/bootstrap4.scss'; +@import 'bootstrap4-definition.scss'; +@import 'all.scss'; diff --git a/controls/layouts/styles/timeline/bootstrap5-dark.scss b/controls/layouts/styles/timeline/bootstrap5-dark.scss new file mode 100644 index 0000000000..6c2aaa3b0e --- /dev/null +++ b/controls/layouts/styles/timeline/bootstrap5-dark.scss @@ -0,0 +1,3 @@ +@import 'ej2-base/styles/definition/bootstrap5-dark.scss'; +@import 'bootstrap5-dark-definition.scss'; +@import 'all.scss'; diff --git a/controls/layouts/styles/timeline/bootstrap5.scss b/controls/layouts/styles/timeline/bootstrap5.scss new file mode 100644 index 0000000000..85ca966e24 --- /dev/null +++ b/controls/layouts/styles/timeline/bootstrap5.scss @@ -0,0 +1,3 @@ +@import 'ej2-base/styles/definition/bootstrap5.scss'; +@import 'bootstrap5-definition.scss'; +@import 'all.scss'; diff --git a/controls/layouts/styles/timeline/fabric-dark.scss b/controls/layouts/styles/timeline/fabric-dark.scss new file mode 100644 index 0000000000..ae86756a76 --- /dev/null +++ b/controls/layouts/styles/timeline/fabric-dark.scss @@ -0,0 +1,3 @@ +@import 'ej2-base/styles/definition/fabric-dark.scss'; +@import 'fabric-dark-definition.scss'; +@import 'all.scss'; diff --git a/controls/layouts/styles/timeline/fabric.scss b/controls/layouts/styles/timeline/fabric.scss new file mode 100644 index 0000000000..ae925e91a1 --- /dev/null +++ b/controls/layouts/styles/timeline/fabric.scss @@ -0,0 +1,3 @@ +@import 'ej2-base/styles/definition/fabric.scss'; +@import 'fabric-definition.scss'; +@import 'all.scss'; diff --git a/controls/layouts/styles/timeline/fluent-dark.scss b/controls/layouts/styles/timeline/fluent-dark.scss new file mode 100644 index 0000000000..b75529aca4 --- /dev/null +++ b/controls/layouts/styles/timeline/fluent-dark.scss @@ -0,0 +1,3 @@ +@import 'ej2-base/styles/definition/fluent-dark.scss'; +@import 'fluent-dark-definition.scss'; +@import 'all.scss'; diff --git a/controls/layouts/styles/timeline/fluent.scss b/controls/layouts/styles/timeline/fluent.scss new file mode 100644 index 0000000000..b852be2f68 --- /dev/null +++ b/controls/layouts/styles/timeline/fluent.scss @@ -0,0 +1,3 @@ +@import 'ej2-base/styles/definition/fluent.scss'; +@import 'fluent-definition.scss'; +@import 'all.scss'; diff --git a/controls/layouts/styles/timeline/highcontrast-light.scss b/controls/layouts/styles/timeline/highcontrast-light.scss new file mode 100644 index 0000000000..a041dcb7f0 --- /dev/null +++ b/controls/layouts/styles/timeline/highcontrast-light.scss @@ -0,0 +1,3 @@ +@import 'ej2-base/styles/definition/highcontrast-light.scss'; +@import 'highcontrast-light-definition.scss'; +@import 'all.scss'; diff --git a/controls/layouts/styles/timeline/highcontrast.scss b/controls/layouts/styles/timeline/highcontrast.scss new file mode 100644 index 0000000000..feb972b289 --- /dev/null +++ b/controls/layouts/styles/timeline/highcontrast.scss @@ -0,0 +1,3 @@ +@import 'ej2-base/styles/definition/highcontrast.scss'; +@import 'highcontrast-definition.scss'; +@import 'all.scss'; diff --git a/controls/layouts/styles/timeline/material-dark.scss b/controls/layouts/styles/timeline/material-dark.scss new file mode 100644 index 0000000000..25d51ab76e --- /dev/null +++ b/controls/layouts/styles/timeline/material-dark.scss @@ -0,0 +1,3 @@ +@import 'ej2-base/styles/definition/material-dark.scss'; +@import 'material-dark-definition.scss'; +@import 'all.scss'; diff --git a/controls/layouts/styles/timeline/material.scss b/controls/layouts/styles/timeline/material.scss new file mode 100644 index 0000000000..e6ebcd96be --- /dev/null +++ b/controls/layouts/styles/timeline/material.scss @@ -0,0 +1,3 @@ +@import 'ej2-base/styles/definition/material.scss'; +@import 'material-definition.scss'; +@import 'all.scss'; diff --git a/controls/layouts/styles/timeline/material3-dark.scss b/controls/layouts/styles/timeline/material3-dark.scss new file mode 100644 index 0000000000..f83476b154 --- /dev/null +++ b/controls/layouts/styles/timeline/material3-dark.scss @@ -0,0 +1,4 @@ +@import 'ej2-base/styles/definition/material3-dark.scss'; + +@import 'material3-dark-definition.scss'; +@import 'all.scss'; diff --git a/controls/layouts/styles/timeline/material3.scss b/controls/layouts/styles/timeline/material3.scss new file mode 100644 index 0000000000..6e811b6c23 --- /dev/null +++ b/controls/layouts/styles/timeline/material3.scss @@ -0,0 +1,4 @@ +@import 'ej2-base/styles/definition/material3.scss'; + +@import 'material3-definition.scss'; +@import 'all.scss'; diff --git a/controls/layouts/styles/timeline/tailwind-dark.scss b/controls/layouts/styles/timeline/tailwind-dark.scss new file mode 100644 index 0000000000..4f446b0813 --- /dev/null +++ b/controls/layouts/styles/timeline/tailwind-dark.scss @@ -0,0 +1,3 @@ +@import 'ej2-base/styles/definition/tailwind-dark.scss'; +@import 'tailwind-dark-definition.scss'; +@import 'all.scss'; diff --git a/controls/layouts/styles/timeline/tailwind.scss b/controls/layouts/styles/timeline/tailwind.scss new file mode 100644 index 0000000000..b19b9e1f09 --- /dev/null +++ b/controls/layouts/styles/timeline/tailwind.scss @@ -0,0 +1,3 @@ +@import 'ej2-base/styles/definition/tailwind.scss'; +@import 'tailwind-definition.scss'; +@import 'all.scss'; diff --git a/controls/lists/CHANGELOG.md b/controls/lists/CHANGELOG.md index 0de26de626..6232a588b0 100644 --- a/controls/lists/CHANGELOG.md +++ b/controls/lists/CHANGELOG.md @@ -2,7 +2,7 @@ ## [Unreleased] -## 25.1.35 (2024-03-15) +## 25.1.37 (2024-03-26) ### ListBox diff --git a/controls/maps/CHANGELOG.md b/controls/maps/CHANGELOG.md index c8f8445c4f..3be301794f 100644 --- a/controls/maps/CHANGELOG.md +++ b/controls/maps/CHANGELOG.md @@ -8,7 +8,7 @@ ## [Unreleased] -## 25.1.35 (2024-03-15) +## 25.1.37 (2024-03-26) ### Maps diff --git a/controls/navigations/CHANGELOG.md b/controls/navigations/CHANGELOG.md index c127e9bf56..a1012c3979 100644 --- a/controls/navigations/CHANGELOG.md +++ b/controls/navigations/CHANGELOG.md @@ -2,6 +2,20 @@ ## [Unreleased] +## 25.1.37 (2024-03-26) + +### TreeView + +#### Bug Fixes + +- `#I570321` - The issue with the focus on first item in TreeView if it is disabled mode has been resolved. + +### Toolbar + +#### Bug Fixes + +- `#I553624` - An issue with expanded toolbar items not align properly when change the mouse and touch modes has been fixed. + ## 25.1.35 (2024-03-15) ### ContextMenu diff --git a/controls/navigations/package.json b/controls/navigations/package.json index 8e27fe5dd7..cf24bd410c 100644 --- a/controls/navigations/package.json +++ b/controls/navigations/package.json @@ -1,6 +1,6 @@ { "name": "@syncfusion/ej2-navigations", - "version": "22.42.3", + "version": "25.1.35", "description": "A package of Essential JS 2 navigation components such as Tree-view, Tab, Toolbar, Context-menu, and Accordion which is used to navigate from one page to another", "author": "Syncfusion Inc.", "license": "SEE LICENSE IN license", diff --git a/controls/navigations/spec/toolbar.spec.ts b/controls/navigations/spec/toolbar.spec.ts index edac015169..bab23ffbe9 100644 --- a/controls/navigations/spec/toolbar.spec.ts +++ b/controls/navigations/spec/toolbar.spec.ts @@ -13025,6 +13025,68 @@ describe('Hscroll module scrollStep change in beforeCreate', () => { expect(document.activeElement).toBe(firstElement); }); }); + + describe('Extended overflow mode in toolbar items ', () => { + let toolbar: any; + beforeEach((): void => { + toolbar = undefined; + const ele: HTMLElement = createElement('div', { id: 'ej2Toolbar' }); + document.body.appendChild(ele); + }); + afterEach((): void => { + if (toolbar) { + toolbar.destroy(); + } + document.body.innerHTML = ''; + }); + + it('Extended popup width testing', () => { + const element: HTMLElement = document.getElementById('ej2Toolbar'); + toolbar = new Toolbar({ + overflowMode: 'Extended', + width: '200px', + items: [ + { prefixIcon: 'e-cut-icon', tooltipText: 'Cut' }, + { prefixIcon: 'e-copy-icon', tooltipText: 'Copy' }, + { prefixIcon: 'e-paste-icon', tooltipText: 'Paste' }, + { type: 'Separator' }, + { prefixIcon: 'e-bold-icon', tooltipText: 'Bold' }, + { prefixIcon: 'e-underline-icon', tooltipText: 'Underline' }, + { prefixIcon: 'e-italic-icon', tooltipText: 'Italic' }, + { prefixIcon: 'e-color-icon', tooltipText: 'Color-Picker' }, + { type: 'Separator' }, + { prefixIcon: 'e-alignleft-icon', tooltipText: 'Align-Left' }, + { prefixIcon: 'e-alignjustify-icon', tooltipText: 'Align-Justify' }, + { prefixIcon: 'e-alignright-icon', tooltipText: 'Align-Right' }, + { prefixIcon: 'e-aligncenter-icon', tooltipText: 'Align-Center' }, + { type: 'Separator' }, + { prefixIcon: 'e-bullets-icon', tooltipText: 'Bullets' }, + { prefixIcon: 'e-numbering-icon', tooltipText: 'Numbering' }, + { type: 'Separator' }, + { prefixIcon: 'e-ascending-icon', tooltipText: 'Sort A - Z' }, + { prefixIcon: 'e-descending-icon', tooltipText: 'Sort Z - A' }, + { type: 'Separator' }, + { prefixIcon: 'e-upload-icon', tooltipText: 'Upload' }, + { prefixIcon: 'e-download-icon', tooltipText: 'Download' }, + { type: 'Separator' }, + { prefixIcon: 'e-indent-icon', tooltipText: 'Text Indent' }, + { prefixIcon: 'e-outdent-icon', tooltipText: 'Text Outdent' }, + { type: 'Separator' }, + { prefixIcon: 'e-clear-icon', tooltipText: 'Clear' }, + { prefixIcon: 'e-reload-icon', tooltipText: 'Reload' }, + { prefixIcon: 'e-export-icon', tooltipText: 'Export' }, + { type: 'Separator' }, + { prefixIcon: 'e-undo-icon', tooltipText: 'Undo', text: 'Undo' }, + { prefixIcon: 'e-redo-icon', tooltipText: 'Redo', text: 'Redo' } + ] + }); + toolbar.appendTo('#ej2Toolbar'); + let toolbarWidth: number = element.offsetWidth; + let extendedToolbar: HTMLElement = element.querySelector('.e-toolbar-extended'); + extendedToolbar.style.display = "block"; + expect(extendedToolbar.offsetWidth).toEqual(toolbarWidth); + }); + }); it('memory leak', () => { profile.sample(); diff --git a/controls/navigations/src/toolbar/toolbar.ts b/controls/navigations/src/toolbar/toolbar.ts index e954b9d401..09c75462e8 100644 --- a/controls/navigations/src/toolbar/toolbar.ts +++ b/controls/navigations/src/toolbar/toolbar.ts @@ -945,16 +945,23 @@ export class Toolbar extends Component implements INotifyPropertyCh popObj.enableRtl = false; popObj.position = { X: 'right', Y: 'top' }; } - popObj.dataBind(); - popObj.refreshPosition(); - popObj.element.style.top = this.getElementOffsetY() + 'px'; if (this.overflowMode === 'Extended') { popObj.element.style.minHeight = '0px'; + popObj.width = this.getToolbarPopupWidth(this.element); } + popObj.dataBind(); + popObj.refreshPosition(); + popObj.element.style.top = this.getElementOffsetY() + 'px'; popupNav.classList.add(CLS_TBARNAVACT); popObj.show({ name: 'FadeIn', duration: 100 }); } } + + private getToolbarPopupWidth(ele: HTMLElement) { + var eleStyles = window.getComputedStyle(ele); + return parseFloat(eleStyles.width) + ((parseFloat(eleStyles.borderRightWidth)) * 2); + } + /** * To Initialize the control rendering * @@ -1333,7 +1340,7 @@ export class Toolbar extends Component implements INotifyPropertyCh position: this.enableRtl ? { X: 'left', Y: 'top' } : { X: 'right', Y: 'top' } }); if (this.overflowMode === 'Extended') { - popup.width = parseFloat(eleStyles.width) + ((parseFloat(eleStyles.borderRightWidth)) * 2); + popup.width = this.getToolbarPopupWidth(this.element); popup.offsetX = 0; } popup.appendTo(ele); @@ -2309,8 +2316,7 @@ export class Toolbar extends Component implements INotifyPropertyCh } if (this.popObj) { if (this.overflowMode === 'Extended') { - const eleStyles: CSSStyleDeclaration = window.getComputedStyle(this.element); - this.popObj.width = parseFloat(eleStyles.width) + ((parseFloat(eleStyles.borderRightWidth)) * 2); + this.popObj.width = this.getToolbarPopupWidth(this.element); } if (this.tbarAlign) { this.removePositioning(); diff --git a/controls/navigations/src/treeview/treeview.ts b/controls/navigations/src/treeview/treeview.ts index beeecf095d..4cfd5edc55 100644 --- a/controls/navigations/src/treeview/treeview.ts +++ b/controls/navigations/src/treeview/treeview.ts @@ -3620,11 +3620,16 @@ export class TreeView extends Component implements INotifyPropertyC private focusIn(): void { if(!this.mouseDownStatus){ let focusedElement: Element = this.getFocusedNode(); - focusedElement.setAttribute("tabindex","0"); - addClass([focusedElement], FOCUS); - EventHandler.add(focusedElement, 'blur', this.focusOut, this); + if (focusedElement.classList.contains('e-disable')) { + focusedElement.setAttribute("tabindex", "-1"); + this.navigateNode(true); + } else { + focusedElement.setAttribute("tabindex","0"); + addClass([focusedElement], FOCUS); + EventHandler.add(focusedElement, 'blur', this.focusOut, this); + } + this.mouseDownStatus = false; } - this.mouseDownStatus = false; } private focusOut(event: Event): void { diff --git a/controls/notifications/CHANGELOG.md b/controls/notifications/CHANGELOG.md index 1bd9aedbe0..9749283fc4 100644 --- a/controls/notifications/CHANGELOG.md +++ b/controls/notifications/CHANGELOG.md @@ -2,7 +2,7 @@ ## [Unreleased] -## 25.1.35 (2024-03-15) +## 25.1.37 (2024-03-26) ### Toast diff --git a/controls/officechart/CHANGELOG.md b/controls/officechart/CHANGELOG.md index 62e3cc5dd5..0183ca809f 100644 --- a/controls/officechart/CHANGELOG.md +++ b/controls/officechart/CHANGELOG.md @@ -2,7 +2,7 @@ ## [Unreleased] -## 24.1.41 (2023-12-18) +## 25.1.35 (2024-03-15) ### Office Chart diff --git a/controls/officechart/package.json b/controls/officechart/package.json index c918d9006c..7f8b87effb 100644 --- a/controls/officechart/package.json +++ b/controls/officechart/package.json @@ -1,6 +1,6 @@ { "name": "@syncfusion/ej2-office-chart", - "version": "24.2.3", + "version": "25.1.35", "description": "Essential JS 2 Component", "author": "Syncfusion Inc.", "license": "SEE LICENSE IN license", diff --git a/controls/officechart/tsconfig.json b/controls/officechart/tsconfig.json index 188c599658..33ba08d2af 100644 --- a/controls/officechart/tsconfig.json +++ b/controls/officechart/tsconfig.json @@ -18,7 +18,7 @@ "forceConsistentCasingInFileNames": true, "moduleResolution": "node", "suppressImplicitAnyIndexErrors": true, - "lib": ["es5", "es2015", "es2015.promise", "dom"], + "lib": ["es5", "es2015", "es2015.promise", "dom"], "types": ["jasmine","jasmine-ajax","requirejs","chai"] }, "exclude": [ @@ -29,4 +29,4 @@ "test-report" ], "compileOnSave": false - } + } \ No newline at end of file diff --git a/controls/pdf/CHANGELOG.md b/controls/pdf/CHANGELOG.md index bad1ceaea4..4fedd75979 100644 --- a/controls/pdf/CHANGELOG.md +++ b/controls/pdf/CHANGELOG.md @@ -2,6 +2,14 @@ ## [Unreleased] +## 25.1.37 (2024-03-26) + +### PDF Parser + +#### Bug Fixes + +- Resolved an exception encountered while removing all pages from PDF document. + ## 25.1.35 (2024-03-15) ### PDF Parser diff --git a/controls/pdf/package.json b/controls/pdf/package.json index 107fd891ca..03cc5c4f8b 100644 --- a/controls/pdf/package.json +++ b/controls/pdf/package.json @@ -1,6 +1,6 @@ { "name": "@syncfusion/ej2-pdf", - "version": "17.9.0", + "version": "25.1.35", "description": "Feature-rich JavaScript PDF library with built-in support for loading and manipulating PDF document.", "author": "Syncfusion Inc.", "license": "SEE LICENSE IN license", diff --git a/controls/pdf/src/pdf/core/pdf-document.ts b/controls/pdf/src/pdf/core/pdf-document.ts index 9113a2b2ae..cf486670e1 100644 --- a/controls/pdf/src/pdf/core/pdf-document.ts +++ b/controls/pdf/src/pdf/core/pdf-document.ts @@ -558,30 +558,42 @@ export class PdfDocument { pageDictionary.objId = pageReference.toString(); pageDictionary.update('Parent', sectionReference); sectionDictionary.update('Kids', [pageReference]); - const lastPage: PdfPage = this.getPage(pageIndex === this.pageCount ? (pageIndex - 1) : pageIndex); - if (lastPage && lastPage._pageDictionary) { - const parentReference: _PdfReference = lastPage._pageDictionary._get('Parent'); - const parentDictionary: _PdfDictionary = this._crossReference._fetch(parentReference); - if (parentDictionary && parentDictionary.has('Kids')) { - let kids: _PdfReference[] = parentDictionary.get('Kids'); - if (kids) { - if (pageIndex === this.pageCount) { - kids.push(sectionReference); - } else { - const newKids: _PdfReference[] = []; - kids.forEach((entry: _PdfReference) => { - if (entry === lastPage._ref) { - newKids.push(sectionReference); - } - newKids.push(entry); - }); - kids = newKids; - this._updatePageCache(pageIndex); + if (this.pageCount === 0) { + let parentReference: _PdfReference = this._catalog._catalogDictionary._get('Pages'); + if (parentReference && this._catalog._topPagesDictionary) { + this._catalog._topPagesDictionary.update('Kids', [sectionReference]); + sectionDictionary.update('Parent', parentReference); + } else { + this._catalog._catalogDictionary.update('Pages', sectionReference); + } + this._pages = new Map(); + this._pageCount = 1; + } else { + const lastPage: PdfPage = this.getPage(pageIndex === this.pageCount ? (pageIndex - 1) : pageIndex); + if (lastPage && lastPage._pageDictionary) { + const parentReference: _PdfReference = lastPage._pageDictionary._get('Parent'); + const parentDictionary: _PdfDictionary = this._crossReference._fetch(parentReference); + if (parentDictionary && parentDictionary.has('Kids')) { + let kids: _PdfReference[] = parentDictionary.get('Kids'); + if (kids) { + if (pageIndex === this.pageCount) { + kids.push(sectionReference); + } else { + const newKids: _PdfReference[] = []; + kids.forEach((entry: _PdfReference) => { + if (entry === lastPage._ref) { + newKids.push(sectionReference); + } + newKids.push(entry); + }); + kids = newKids; + this._updatePageCache(pageIndex); + } + parentDictionary.update('Kids', kids); + sectionDictionary.update('Parent', parentReference); + this._updatePageCount(parentDictionary, 1); + this._pageCount = this.pageCount + 1; } - parentDictionary.update('Kids', kids); - sectionDictionary.update('Parent', parentReference); - this._updatePageCount(parentDictionary, 1); - this._pageCount = this.pageCount + 1; } } } @@ -713,16 +725,18 @@ export class PdfDocument { } } _removeParent(referenceToRemove: _PdfReference, dictionary: _PdfDictionary): void { - const parentReference: _PdfReference = dictionary._get('Parent'); - const parentDictionary: _PdfDictionary = this._crossReference._fetch(parentReference); - if (parentDictionary && parentDictionary.has('Kids')) { - let kids: _PdfReference[] = parentDictionary.get('Kids'); - if (kids.length === 1 && parentDictionary && parentDictionary.get('Type').name === 'Pages') { - this._removeParent(parentReference, parentDictionary); - } else { - kids = kids.filter((item: _PdfReference) => item !== referenceToRemove); - parentDictionary.update('Kids', kids); - this._updatePageCount(parentDictionary, -1); + if (dictionary.has('Parent')) { + const parentReference: _PdfReference = dictionary._get('Parent'); + const parentDictionary: _PdfDictionary = this._crossReference._fetch(parentReference); + if (parentDictionary && parentDictionary.has('Kids')) { + let kids: _PdfReference[] = parentDictionary.get('Kids'); + if (kids.length === 1 && parentDictionary && parentDictionary.get('Type').name === 'Pages') { + this._removeParent(parentReference, parentDictionary); + } else { + kids = kids.filter((item: _PdfReference) => item !== referenceToRemove); + parentDictionary.update('Kids', kids); + this._updatePageCount(parentDictionary, -1); + } } } } diff --git a/controls/pdfexport/package.json b/controls/pdfexport/package.json index bf43914750..6a12e5d1ec 100644 --- a/controls/pdfexport/package.json +++ b/controls/pdfexport/package.json @@ -1,6 +1,6 @@ { "name": "@syncfusion/ej2-pdf-export", - "version": "0.38.8", + "version": "25.1.35", "description": "Syncfusion TypeScript Component", "author": "Syncfusion Inc.", "license": "SEE LICENSE IN license", diff --git a/controls/pdfexport/spec/syncfusion-pdf-base/main.spec.ts b/controls/pdfexport/spec/syncfusion-pdf-base/main.spec.ts index 43bd9d0dcd..88b39757ca 100644 --- a/controls/pdfexport/spec/syncfusion-pdf-base/main.spec.ts +++ b/controls/pdfexport/spec/syncfusion-pdf-base/main.spec.ts @@ -4975,4 +4975,110 @@ describe('Usability features', () => { }); document.destroy(); }); +}); +describe('Grid issue', () => { + it('872266 - nested grid - issue', (done) => { + let pdfDocument: PdfDocument = new PdfDocument(); + pdfDocument.pageSettings.size = { height: 842, width: 595 }; + pdfDocument.pageSettings.orientation = 1; + let hfont = new PdfStandardFont(2, 13); // font style for headers + // create black brush + let brush = new PdfSolidBrush(new PdfColor(0, 0, 0)); + let bounds = new RectangleF(0, 0, 515, 50); + let header = new PdfPageTemplateElement(bounds); + header.graphics.drawString('Header Text', hfont, null, brush, 0, 0, 100, 50, null); + //Add the header at the top. + let grid2: PdfGrid = new PdfGrid(); + grid2.columns.add(6); + grid2.headers.add(2); + let grid2header_0: PdfGridRow = grid2.headers.getHeader(0); + grid2header_0.cells.getCell(0).value = 'RECEIPT DETAIL'; + grid2header_0.cells.getCell(0).style.font = hfont; + grid2header_0.cells.getCell(0).columnSpan = 6; + let grid2header_1: PdfGridRow = grid2.headers.getHeader(1); + grid2header_1.cells.getCell(0).value = 'Receipt Date'; + grid2header_1.cells.getCell(0).style.font = hfont; + grid2header_1.cells.getCell(1).value = 'Receipt No'; + grid2header_1.cells.getCell(1).style.font = hfont; + grid2header_1.cells.getCell(2).value = 'Sale Bill No'; + grid2header_1.cells.getCell(2).style.font = hfont; + grid2header_1.cells.getCell(3).value = 'Name'; + grid2header_1.cells.getCell(3).style.font = hfont; + grid2header_1.cells.getCell(4).value = 'Payment Mode'; + grid2header_1.cells.getCell(4).style.font = hfont; + grid2header_1.cells.getCell(5).value = 'Amount'; + grid2header_1.cells.getCell(5).style.font = hfont; + for (let i: number = 0; i < 30; i++) { + let grid2row1: PdfGridRow = grid2.rows.addRow(); + grid2row1.cells.getCell(0).value = '15/06/2021'; + grid2row1.cells.getCell(1).value = '15-06-2021/RECP/' + i.toString(); + grid2row1.cells.getCell(2).value = 'Sale-702'; + grid2row1.cells.getCell(3).value = 'Akash Agarwal'; + grid2row1.cells.getCell(4).value = 'IDBI BANK'; + grid2row1.cells.getCell(5).value = '11080'; + } + let aggregateRow2_0: PdfGridRow = grid2.rows.addRow(); + aggregateRow2_0.cells.getCell(0).value = ''; + aggregateRow2_0.cells.getCell(1).value = ''; + aggregateRow2_0.cells.getCell(2).value = ''; + aggregateRow2_0.cells.getCell(3).value = 'Total :'; + aggregateRow2_0.cells.getCell(4).value = ''; + aggregateRow2_0.cells.getCell(5).value = '89,080.00'; + let format2: PdfGridLayoutFormat = new PdfGridLayoutFormat(); + format2.layout = PdfLayoutType.Paginate; + format2.break = PdfLayoutBreakType.FitPage; + pdfDocument.template.top = header; + let page: PdfPage = pdfDocument.pages.add(); + // create a PdfGrid + let grid: PdfGrid = new PdfGrid(); + grid.columns.add(5); + grid.headers.add(1); + let header_1: PdfGridRow = grid.headers.getHeader(0); + header_1.cells.getCell(0).value = 'Bill Date'; + header_1.cells.getCell(0).style.font = hfont; + header_1.cells.getCell(1).value = 'Bill No'; + header_1.cells.getCell(1).style.font = hfont; + header_1.cells.getCell(2).value = 'Bill Amount'; + header_1.cells.getCell(2).style.font = hfont; + header_1.cells.getCell(3).value = 'Cash'; + header_1.cells.getCell(3).style.font = hfont; + header_1.cells.getCell(4).value = 'Card'; + header_1.cells.getCell(4).style.font = hfont; + for (let i: number = 0; i < 50; i++) { + let row_0: PdfGridRow = grid.rows.addRow(); + + if (i == 1) { + row_0.cells.getCell(0).value = grid2; + row_0.cells.getCell(0).columnSpan = 5; + } + else { + row_0.cells.getCell(0).value = '15/06/2021'; + row_0.cells.getCell(1).value = '1048' + i.toString(); + row_0.cells.getCell(2).value = '3070'; + row_0.cells.getCell(3).value = '3070'; + row_0.cells.getCell(4).value = '0'; + } + } + let aggregateRow_0: PdfGridRow = grid.rows.addRow(); + aggregateRow_0.cells.getCell(0).value = ''; + aggregateRow_0.cells.getCell(1).value = ''; + aggregateRow_0.cells.getCell(2).value = '570,310.00'; + aggregateRow_0.cells.getCell(3).value = '344,570.00'; + aggregateRow_0.cells.getCell(4).value = '87,640.00'; + let result: PdfLayoutResult = grid.draw(page, 0, 0); + pdfDocument.save().then((xlBlob: { blobData: Blob }) => { + if (Utils.isDownloadEnabled) { + Utils.download(xlBlob.blobData, '872266_nested_grid.pdf'); + } + let reader: FileReader = new FileReader(); + reader.readAsArrayBuffer(xlBlob.blobData); + reader.onload = (): void => { + if (reader.readyState == 2) { + expect((reader.result as ArrayBuffer).byteLength).toBeGreaterThanOrEqual(0); + done(); + } + } + }); + pdfDocument.destroy(); + }); }); \ No newline at end of file diff --git a/controls/pdfexport/src/implementation/structured-elements/grid/layout/grid-layouter.ts b/controls/pdfexport/src/implementation/structured-elements/grid/layout/grid-layouter.ts index 9dad9712bc..1f0d084fdd 100644 --- a/controls/pdfexport/src/implementation/structured-elements/grid/layout/grid-layouter.ts +++ b/controls/pdfexport/src/implementation/structured-elements/grid/layout/grid-layouter.ts @@ -1590,14 +1590,16 @@ export class PdfGridLayouter extends ElementLayouter { // } } for (let i : number = this.cellStartIndex; i <= this.cellEndIndex; i++) { + let gridColumnWidth: number = this.Grid.columns.getColumn(i).width; let cancelSpans : boolean = ((row.cells.getCell(i).columnSpan + i > this.cellEndIndex + 1) && (row.cells.getCell(i).columnSpan > 1)); - // if (!cancelSpans) { - // for (let k : number = 1; k < row.cells.getCell(i).columnSpan; k++) { - // row.cells.getCell(i + k).isCellMergeContinue = true; - // } - //} - let size : SizeF = new SizeF(this.Grid.columns.getColumn(i).width, this.gridHeight > 0.0 ? this.gridHeight : + if (!cancelSpans) { + for (let k : number = 1; k < row.cells.getCell(i).columnSpan; k++) { + row.cells.getCell(i + k).isCellMergeContinue = true; + gridColumnWidth += this.Grid.columns.getColumn(i + k).width; + } + } + let size : SizeF = new SizeF(gridColumnWidth, this.gridHeight > 0.0 ? this.gridHeight : this.currentPageBounds.height); // if (size.width === 0) { // size = new SizeF(row.cells.getCell(i).width, size.height); diff --git a/controls/pdfexport/src/implementation/structured-elements/grid/pdf-grid-cell.ts b/controls/pdfexport/src/implementation/structured-elements/grid/pdf-grid-cell.ts index b337e8d0e3..42e3015240 100644 --- a/controls/pdfexport/src/implementation/structured-elements/grid/pdf-grid-cell.ts +++ b/controls/pdfexport/src/implementation/structured-elements/grid/pdf-grid-cell.ts @@ -638,7 +638,8 @@ export class PdfGridCell { } if (param.page != childGridResult.page) //&& (isWidthGreaterthanParent != true)) { - childGridResult.bounds.height = this.row.rowBreakHeightValue; + if (this.row.rowBreakHeightValue !== null && typeof this.row.rowBreakHeightValue !== 'undefined') + childGridResult.bounds.height = this.row.rowBreakHeightValue; if(this.row.rowBreakHeight == 0) this.row.NestedGridLayoutResult = childGridResult; else diff --git a/controls/pdfviewer/CHANGELOG.md b/controls/pdfviewer/CHANGELOG.md index 4730e802b2..86aade3ad2 100644 --- a/controls/pdfviewer/CHANGELOG.md +++ b/controls/pdfviewer/CHANGELOG.md @@ -2,6 +2,25 @@ ## [Unreleased] +## 25.1.37 (2024-03-26) + +### PDF Viewer + +#### Bug Fixes + +- `#I562878` - Now, the custom data is not missing from deleted annotation properties when initializing settings with custom data. +- `#I565199` - Now, the annotation custom data is not missing in `AnnotationSelect` events. +- `#I563333` - Now, the comments cannot be edited when the annotation is locked. +- `#I874338` - Now, the locked annotations are preserved properly when importing the annotation. +- `#I561320` - Now, the undo has restored the programmatically deleted annotations. +- `#I566765` - Now, the create and clear buttons are not enabled if the text box is empty in the text signature tab. +- `#I564309` - Now, the script error not occurred when dynamically updating toolbar items without annotation module. +- `#I564643` - Now, the form fields are rendered properly after deleting it from the customer document. + +#### Features + +- `#I531005` - Now, provided the option to turn off the autocomplete option for comments in the comment panel. + ## 25.1.35 (2024-03-15) ### PDF Viewer diff --git a/controls/pdfviewer/package.json b/controls/pdfviewer/package.json index a0ef082406..45cbeb90da 100644 --- a/controls/pdfviewer/package.json +++ b/controls/pdfviewer/package.json @@ -1,6 +1,6 @@ { "name": "@syncfusion/ej2-pdfviewer", - "version": "22.16.57", + "version": "25.1.35", "description": "Essential JS 2 PDF viewer Component", "author": "Syncfusion Inc.", "license": "SEE LICENSE IN license", diff --git a/controls/pdfviewer/src/pdfviewer/annotation/annotation.ts b/controls/pdfviewer/src/pdfviewer/annotation/annotation.ts index f232f2fae8..9599922950 100644 --- a/controls/pdfviewer/src/pdfviewer/annotation/annotation.ts +++ b/controls/pdfviewer/src/pdfviewer/annotation/annotation.ts @@ -2808,7 +2808,10 @@ export class Annotation { } // eslint-disable-next-line - private modifyInCollections(annotationBase: PdfAnnotationBaseModel, property: string): any { + /** + * @private + */ + public modifyInCollections(annotationBase: PdfAnnotationBaseModel, property: string): any { // eslint-disable-next-line let returnObj: any; if (annotationBase.measureType === '' || isNullOrUndefined(annotationBase.measureType)) { diff --git a/controls/pdfviewer/src/pdfviewer/annotation/free-text-annotation.ts b/controls/pdfviewer/src/pdfviewer/annotation/free-text-annotation.ts index 381b7c2546..63b5bc9c4e 100644 --- a/controls/pdfviewer/src/pdfviewer/annotation/free-text-annotation.ts +++ b/controls/pdfviewer/src/pdfviewer/annotation/free-text-annotation.ts @@ -353,6 +353,9 @@ export class FreeTextAnnotation { } // eslint-disable-next-line max-len annotation.AnnotationSettings = annotation.AnnotationSettings ? annotation.AnnotationSettings : this.pdfViewer.annotationModule.updateSettings(this.pdfViewer.freeTextSettings); + if (annotation.IsLocked) { + annotation.AnnotationSettings.isLock = annotation.IsLocked; + } let annot: PdfAnnotationBaseModel; let paddingValue: number = 0.5; let annotationBoundsX: number = !isNullOrUndefined(annotation.Bounds.X) ? annotation.Bounds.X - paddingValue : annotation.Bounds.x; diff --git a/controls/pdfviewer/src/pdfviewer/annotation/ink-annotation.ts b/controls/pdfviewer/src/pdfviewer/annotation/ink-annotation.ts index 4944071343..bfec727360 100644 --- a/controls/pdfviewer/src/pdfviewer/annotation/ink-annotation.ts +++ b/controls/pdfviewer/src/pdfviewer/annotation/ink-annotation.ts @@ -211,7 +211,7 @@ export class InkAnnotation { let isLock: boolean = this.pdfViewer.inkAnnotationSettings.isLock ? this.pdfViewer.inkAnnotationSettings.isLock : this.pdfViewer.annotationSettings.isLock; const author: string = (this.pdfViewer.annotationSettings.author !== 'Guest') ? this.pdfViewer.annotationSettings.author : this.pdfViewer.inkAnnotationSettings.author ? this.pdfViewer.inkAnnotationSettings.author : 'Guest'; const subject: string = (this.pdfViewer.annotationSettings.subject !== "" && !isNullOrUndefined(this.pdfViewer.annotationSettings.subject)) ? this.pdfViewer.annotationSettings.subject : this.pdfViewer.inkAnnotationSettings.subject ? this.pdfViewer.inkAnnotationSettings.subject : 'Ink'; - const customData: object = this.pdfViewer.inkAnnotationSettings.customData; + const customData: object = !isNullOrUndefined(this.pdfViewer.annotationSettings.customData) ?this.pdfViewer.annotationSettings.customData : this.pdfViewer.inkAnnotationSettings.customData ? this.pdfViewer.inkAnnotationSettings.customData : null; const isPrint: boolean = this.pdfViewer.inkAnnotationSettings.isPrint; // eslint-disable-next-line let allowedInteractions: any = this.pdfViewer.inkAnnotationSettings.allowedInteractions ? this.pdfViewer.inkAnnotationSettings.allowedInteractions : this.pdfViewer.annotationSettings.allowedInteractions; diff --git a/controls/pdfviewer/src/pdfviewer/annotation/measure-annotation.ts b/controls/pdfviewer/src/pdfviewer/annotation/measure-annotation.ts index 2bd266d820..88cb609b9d 100644 --- a/controls/pdfviewer/src/pdfviewer/annotation/measure-annotation.ts +++ b/controls/pdfviewer/src/pdfviewer/annotation/measure-annotation.ts @@ -308,6 +308,9 @@ export class MeasureAnnotation { } // eslint-disable-next-line max-len annotation.AnnotationSettings = annotation.AnnotationSettings ? annotation.AnnotationSettings : this.pdfViewer.annotationModule.updateAnnotationSettings(annotation); + if (annotation.IsLocked) { + annotation.AnnotationSettings.isLock = annotation.IsLocked; + } // eslint-disable-next-line max-len annotation.allowedInteractions = annotation.AllowedInteractions ? annotation.AllowedInteractions : this.pdfViewer.annotationModule.updateAnnotationAllowedInteractions(annotation); let isPrint: boolean = annotation.IsPrint; @@ -369,7 +372,7 @@ export class MeasureAnnotation { fontColor: annotation.FontColor, labelBorderColor: annotation.LabelBorderColor, fontSize: annotation.FontSize, labelBounds: annotation.LabelBounds, annotationSelectorSettings: annotation.AnnotationSelectorSettings, annotationSettings: annotationObject.annotationSettings, annotationAddMode: annotation.annotationAddMode, - isPrint: isPrint, isCommentLock: annotationObject.isCommentLock + isPrint: isPrint, isCommentLock: annotationObject.isCommentLock, customData: annotationObject.customData }; this.pdfViewer.annotation.storeAnnotations(pageNumber, annotationObject, '_annotations_shape_measure'); if(this.isAddAnnotationProgramatically) @@ -388,6 +391,10 @@ export class MeasureAnnotation { const annotationObject: IMeasureShapeAnnotation = this.createAnnotationObject(shapeAnnotations); this.pdfViewer.annotationModule.isFormFieldShape = false; this.pdfViewer.annotationModule.storeAnnotations(pageNumber, annotationObject, '_annotations_shape_measure'); + if(shapeAnnotations) + { + shapeAnnotations.customData = annotationObject.customData; + } this.pdfViewer.annotationModule.triggerAnnotationAdd(shapeAnnotations); } } @@ -414,6 +421,7 @@ export class MeasureAnnotation { public setAnnotationType(type: AnnotType): void { let author: string = 'Guest'; let subject: string = ""; + let customData: object; this.updateMeasureproperties(); this.pdfViewerBase.disableTextSelectionMode(); switch (type) { @@ -423,13 +431,14 @@ export class MeasureAnnotation { // eslint-disable-next-line max-len author = (this.pdfViewer.annotationSettings.author !== 'Guest') ? this.pdfViewer.annotationSettings.author : this.pdfViewer.distanceSettings.author ? this.pdfViewer.distanceSettings.author : 'Guest'; subject = (this.pdfViewer.annotationSettings.subject !== "" && !isNullOrUndefined(this.pdfViewer.annotationSettings.subject)) ? this.pdfViewer.annotationSettings.subject : this.pdfViewer.distanceSettings.subject ? this.pdfViewer.distanceSettings.subject : 'Distance calculation'; + customData = !isNullOrUndefined(this.pdfViewer.annotationSettings.customData) ? this.pdfViewer.annotationSettings.customData : this.pdfViewer.distanceSettings.customData ? this.pdfViewer.distanceSettings.customData : null; this.pdfViewer.drawingObject = { sourceDecoraterShapes: this.pdfViewer.annotation.getArrowType(this.distanceStartHead), taregetDecoraterShapes: this.pdfViewer.annotation.getArrowType(this.distanceEndHead), measureType: 'Distance', fillColor: this.distanceFillColor, notes: '', strokeColor: this.distanceStrokeColor, leaderHeight: this.leaderLength, opacity: this.distanceOpacity, thickness: this.distanceThickness, borderDashArray: this.distanceDashArray.toString(), // eslint-disable-next-line max-len - shapeAnnotationType: 'Distance', author: author, subject: subject, isCommentLock: false + shapeAnnotationType: 'Distance', author: author, subject: subject, isCommentLock: false, customData: customData }; this.pdfViewer.tool = 'Distance'; break; @@ -470,11 +479,12 @@ export class MeasureAnnotation { // eslint-disable-next-line max-len author = (this.pdfViewer.annotationSettings.author !== 'Guest') ? this.pdfViewer.annotationSettings.author : this.pdfViewer.radiusSettings.author ? this.pdfViewer.radiusSettings.author : 'Guest'; subject = (this.pdfViewer.annotationSettings.subject !== "" && !isNullOrUndefined(this.pdfViewer.annotationSettings.subject)) ? this.pdfViewer.annotationSettings.subject : this.pdfViewer.radiusSettings.subject ? this.pdfViewer.radiusSettings.subject : 'Radius calculation'; + customData = !isNullOrUndefined(this.pdfViewer.annotationSettings.customData) ? this.pdfViewer.annotationSettings.customData : this.pdfViewer.radiusSettings.customData ? this.pdfViewer.radiusSettings.customData : null; this.pdfViewer.drawingObject = { // eslint-disable-next-line max-len shapeAnnotationType: 'Radius', fillColor: this.radiusFillColor, notes: '', strokeColor: this.radiusStrokeColor, opacity: this.radiusOpacity, thickness: this.radiusThickness, measureType: 'Radius', modifiedDate: modifiedDateRad, borderStyle: '', borderDashArray: '0', - author: author, subject: subject, isCommentLock: false + author: author, subject: subject, isCommentLock: false, customData: customData }; this.pdfViewer.tool = 'DrawTool'; break; diff --git a/controls/pdfviewer/src/pdfviewer/annotation/shape-annotation.ts b/controls/pdfviewer/src/pdfviewer/annotation/shape-annotation.ts index 3351a2c59c..99553ec6bb 100644 --- a/controls/pdfviewer/src/pdfviewer/annotation/shape-annotation.ts +++ b/controls/pdfviewer/src/pdfviewer/annotation/shape-annotation.ts @@ -245,6 +245,9 @@ export class ShapeAnnotation { annotation.AnnotationSelectorSettings = annotation.AnnotationSelectorSettings ? annotation.AnnotationSelectorSettings : this.pdfViewer.annotationSelectorSettings; // eslint-disable-next-line max-len annotation.AnnotationSettings = annotation.AnnotationSettings ? annotation.AnnotationSettings : this.pdfViewer.annotationModule.updateAnnotationSettings(annotation); + if (annotation.IsLocked) { + annotation.AnnotationSettings.isLock = annotation.IsLocked; + } // eslint-disable-next-line max-len annotation.allowedInteractions = annotation.AllowedInteractions ? annotation.AllowedInteractions : this.pdfViewer.annotationModule.updateAnnotationAllowedInteractions(annotation); let left: number = annotation.Bounds.X ? annotation.Bounds.X : annotation.Bounds.x; @@ -311,6 +314,9 @@ export class ShapeAnnotation { else this.pdfViewer.annotationModule.isFormFieldShape = false; this.pdfViewer.annotationModule.storeAnnotations(pageNumber, annotationObject, '_annotations_shape'); + if (shapeAnnotations) { + shapeAnnotations.customData = annotationObject.customData; + } this.pdfViewer.annotationModule.triggerAnnotationAdd(shapeAnnotations); } } @@ -339,6 +345,7 @@ export class ShapeAnnotation { this.pdfViewerBase.disableTextSelectionMode(); let author: string = 'Guest'; let subject: string = ""; + let customData: object; switch (type) { case 'Line': this.currentAnnotationMode = 'Line'; @@ -347,13 +354,14 @@ export class ShapeAnnotation { // eslint-disable-next-line max-len author = (this.pdfViewer.annotationSettings.author !== 'Guest') ? this.pdfViewer.annotationSettings.author : this.pdfViewer.lineSettings.author ? this.pdfViewer.lineSettings.author : 'Guest'; subject = (this.pdfViewer.annotationSettings.subject !== "" && !isNullOrUndefined(this.pdfViewer.annotationSettings.subject)) ? this.pdfViewer.annotationSettings.subject : this.pdfViewer.lineSettings.subject ? this.pdfViewer.lineSettings.subject : 'Line'; + customData = !isNullOrUndefined(this.pdfViewer.annotationSettings.customData) ? this.pdfViewer.annotationSettings.customData : this.pdfViewer.lineSettings.customData ? this.pdfViewer.lineSettings.customData : null; this.pdfViewer.drawingObject = { // eslint-disable-next-line max-len shapeAnnotationType: this.setShapeType('Line'), fillColor: this.lineFillColor, notes: '', strokeColor: this.lineStrokeColor, opacity: this.lineOpacity, thickness: this.lineThickness, modifiedDate: modifiedDateLine, borderDashArray: this.lineDashArray.toString(), // eslint-disable-next-line max-len sourceDecoraterShapes: this.pdfViewer.annotation.getArrowType(this.lineStartHead.toString()), taregetDecoraterShapes: this.pdfViewer.annotation.getArrowType(this.lineEndHead.toString()), - author: author, subject: subject, lineHeadStart: this.lineStartHead, lineHeadEnd: this.lineEndHead, isCommentLock: false + author: author, subject: subject, lineHeadStart: this.lineStartHead, lineHeadEnd: this.lineEndHead, isCommentLock: false, customData: customData }; this.pdfViewer.tool = 'Line'; break; @@ -363,6 +371,7 @@ export class ShapeAnnotation { const modifiedDateArrow: string = this.pdfViewer.annotation.stickyNotesAnnotationModule.getDateAndTime(); author = (this.pdfViewer.annotationSettings.author !== 'Guest') ? this.pdfViewer.annotationSettings.author : this.pdfViewer.arrowSettings.author ? this.pdfViewer.arrowSettings.author : 'Guest'; subject = (this.pdfViewer.annotationSettings.subject !== "" && !isNullOrUndefined(this.pdfViewer.annotationSettings.subject)) ? this.pdfViewer.annotationSettings.subject : this.pdfViewer.arrowSettings.subject ? this.pdfViewer.arrowSettings.subject : 'Arrow'; + customData = !isNullOrUndefined(this.pdfViewer.annotationSettings.customData) ? this.pdfViewer.annotationSettings.customData : this.pdfViewer.arrowSettings.customData ? this.pdfViewer.arrowSettings.customData : null; this.pdfViewer.drawingObject = { shapeAnnotationType: this.setShapeType('Arrow'), opacity: this.arrowOpacity, // eslint-disable-next-line max-len @@ -372,7 +381,7 @@ export class ShapeAnnotation { fillColor: this.arrowFillColor, strokeColor: this.arrowStrokeColor, notes: '', thickness: this.arrowThickness, borderDashArray: this.arrowDashArray.toString(), author: author, subject: subject, // eslint-disable-next-line max-len - modifiedDate: modifiedDateArrow, lineHeadStart: this.arrowStartHead, lineHeadEnd: this.arrowEndHead, isCommentLock: false + modifiedDate: modifiedDateArrow, lineHeadStart: this.arrowStartHead, lineHeadEnd: this.arrowEndHead, isCommentLock: false, customData: customData }; this.pdfViewer.tool = 'Line'; break; @@ -383,11 +392,12 @@ export class ShapeAnnotation { // eslint-disable-next-line max-len author = (this.pdfViewer.annotationSettings.author !== 'Guest') ? this.pdfViewer.annotationSettings.author : this.pdfViewer.rectangleSettings.author ? this.pdfViewer.rectangleSettings.author : 'Guest'; subject = (this.pdfViewer.annotationSettings.subject !== "" && !isNullOrUndefined(this.pdfViewer.annotationSettings.subject)) ? this.pdfViewer.annotationSettings.subject : this.pdfViewer.rectangleSettings.subject ? this.pdfViewer.rectangleSettings.subject : 'Rectangle'; + customData = !isNullOrUndefined(this.pdfViewer.annotationSettings.customData) ? this.pdfViewer.annotationSettings.customData : this.pdfViewer.rectangleSettings.customData ? this.pdfViewer.rectangleSettings.customData : null; this.pdfViewer.drawingObject = { shapeAnnotationType: this.setShapeType('Rectangle'), strokeColor: this.rectangleStrokeColor, fillColor: this.rectangleFillColor, opacity: this.rectangleOpacity, notes: '', thickness: this.rectangleThickness, borderDashArray: '0', modifiedDate: modifiedDateRect, - author: author, subject: subject, isCommentLock: false + author: author, subject: subject, isCommentLock: false, customData: customData }; this.pdfViewer.tool = 'DrawTool'; break; @@ -398,11 +408,12 @@ export class ShapeAnnotation { // eslint-disable-next-line max-len author = (this.pdfViewer.annotationSettings.author !== 'Guest') ? this.pdfViewer.annotationSettings.author : this.pdfViewer.circleSettings.author ? this.pdfViewer.circleSettings.author : 'Guest'; subject = (this.pdfViewer.annotationSettings.subject !== "" && !isNullOrUndefined(this.pdfViewer.annotationSettings.subject)) ? this.pdfViewer.annotationSettings.subject : this.pdfViewer.circleSettings.subject ? this.pdfViewer.circleSettings.subject : 'Circle'; + customData = !isNullOrUndefined(this.pdfViewer.annotationSettings.customData) ? this.pdfViewer.annotationSettings.customData : this.pdfViewer.circleSettings.customData ? this.pdfViewer.circleSettings.customData : null; this.pdfViewer.drawingObject = { shapeAnnotationType: this.setShapeType('Circle'), strokeColor: this.circleStrokeColor, fillColor: this.circleFillColor, opacity: this.circleOpacity, notes: '', thickness: this.circleThickness, borderDashArray: '0', modifiedDate: modifiedDateCir, - author: author, subject: subject, isCommentLock: false + author: author, subject: subject, isCommentLock: false, customData: customData }; this.pdfViewer.tool = 'DrawTool'; break; @@ -413,11 +424,12 @@ export class ShapeAnnotation { // eslint-disable-next-line max-len author = (this.pdfViewer.annotationSettings.author !== 'Guest') ? this.pdfViewer.annotationSettings.author : this.pdfViewer.polygonSettings.author ? this.pdfViewer.polygonSettings.author : 'Guest'; subject = (this.pdfViewer.annotationSettings.subject !== "" && !isNullOrUndefined(this.pdfViewer.annotationSettings.subject)) ? this.pdfViewer.annotationSettings.subject : this.pdfViewer.polygonSettings.subject ? this.pdfViewer.polygonSettings.subject : 'Polygon'; + customData = !isNullOrUndefined(this.pdfViewer.annotationSettings.customData) ? this.pdfViewer.annotationSettings.customData : this.pdfViewer.polygonSettings.customData ? this.pdfViewer.polygonSettings.customData : null; this.pdfViewer.drawingObject = { strokeColor: this.polygonStrokeColor, fillColor: this.polygonFillColor, opacity: this.polygonOpacity, thickness: this.polygonThickness, borderDashArray: '0', notes: '', author: author, subject: subject, - modifiedDate: modifiedDatePolygon, borderStyle: '', isCommentLock: false + modifiedDate: modifiedDatePolygon, borderStyle: '', isCommentLock: false, customData: customData }; this.pdfViewer.tool = 'Polygon'; break; diff --git a/controls/pdfviewer/src/pdfviewer/annotation/stamp-annotation.ts b/controls/pdfviewer/src/pdfviewer/annotation/stamp-annotation.ts index 3419a2a7e6..6e061b3f11 100644 --- a/controls/pdfviewer/src/pdfviewer/annotation/stamp-annotation.ts +++ b/controls/pdfviewer/src/pdfviewer/annotation/stamp-annotation.ts @@ -136,10 +136,12 @@ export class StampAnnotation { // eslint-disable-next-line public renderStampAnnotations(stampAnnotations: any, pageNumber: number, canvass?: any, isImport?: boolean, isAnnotOrderAction?: boolean): void { let isStampAdded: boolean = false; - for (let p: number = 0; p < this.stampPageNumber.length; p++) { - if (this.stampPageNumber[p] === pageNumber && !isNullOrUndefined(isImport)){ - isStampAdded = true; - break; + if (!isImport) { + for (let p: number = 0; p < this.stampPageNumber.length; p++) { + if (this.stampPageNumber[p] === pageNumber) { + isStampAdded = true; + break; + } } } if (isImport) { @@ -170,6 +172,9 @@ export class StampAnnotation { let pageDiv: HTMLElement = document.getElementById(this.pdfViewer.element.id + '_pageDiv_' + pageIndex); // eslint-disable-next-line annotation.AnnotationSettings = annotation.AnnotationSettings ? annotation.AnnotationSettings : this.pdfViewer.annotationModule.updateSettings(this.pdfViewer.stampSettings); + if (annotation.IsLocked) { + annotation.AnnotationSettings.isLock = annotation.IsLocked; + } // eslint-disable-next-line let isImageStamp : boolean = this.stampImageData(annotation); if (stampName && annotation['IconName'] && annotation['IconName'] !== 'Draft' && !isImageStamp && (isNullOrUndefined(annotation.template) || annotation.template === "")) { @@ -1184,6 +1189,9 @@ export class StampAnnotation { // eslint-disable-next-line let storeObject: any = window.sessionStorage.getItem(this.pdfViewerBase.documentId + '_annotations_stamp'); let index: number = 0; + if (this.pdfViewerBase.isStorageExceed) { + storeObject = this.pdfViewerBase.annotationStorage[this.pdfViewerBase.documentId + '_annotations_stamp']; + } if (!storeObject) { this.pdfViewer.annotationModule.storeAnnotationCollections(annotation, pageNumber); let shapeAnnotation: IPageAnnotations = { pageIndex: pageNumber, annotations: [] }; diff --git a/controls/pdfviewer/src/pdfviewer/annotation/sticky-notes-annotation.ts b/controls/pdfviewer/src/pdfviewer/annotation/sticky-notes-annotation.ts index acb8eeda3b..667e4817ef 100644 --- a/controls/pdfviewer/src/pdfviewer/annotation/sticky-notes-annotation.ts +++ b/controls/pdfviewer/src/pdfviewer/annotation/sticky-notes-annotation.ts @@ -914,10 +914,11 @@ export class StickyNotesAnnotation { // eslint-disable-next-line max-len const commentTextBox: HTMLElement = createElement('div', { id: this.pdfViewer.element.id + '_commenttextbox_'+ pageIndex + '_' + this.commentsCount, className: 'e-pv-comment-textbox', attrs: { 'role': 'textbox', 'aria-label': "comment textbox" } }); // eslint-disable-next-line + let enableAutoComplete: any = this.pdfViewer.enableAutoComplete ? 'on' : 'off'; let editObj: any = new InPlaceEditor({ mode: 'Inline', type: 'Text', - model: { placeholder: this.pdfViewer.localeObj.getConstant('Add a comment') + '..' }, + model: { placeholder: this.pdfViewer.localeObj.getConstant('Add a comment') + '..' ,htmlAttributes: { autocomplete: enableAutoComplete}}, emptyText: '', editableOn: 'EditIconClick', saveButton: { @@ -984,6 +985,13 @@ export class StickyNotesAnnotation { this.createCommentDiv(this.commentsContainer); } } + //Task Id: 874405. If a comment is added programmatically, create a reply div container. + if (data.Note !== ' ' && data.Note !== '' && data.Note !== null) { + this.createCommentDiv(this.commentsContainer); + } + if (data.AnnotType === "Text Box" && data.Text !== ' ' && data.Text !== '' && data.Text !== null) { + this.createCommentDiv(this.commentsContainer); + } } this.isNewcommentAdded = true; commentDiv.addEventListener('click', this.commentsDivClickEvent.bind(this)); @@ -1074,12 +1082,13 @@ export class StickyNotesAnnotation { titleContainer = args.valueEle.parentElement.parentElement.previousSibling.childNodes[1]; } // eslint-disable-next-line + let enableAutoComplete: any = this.pdfViewer.enableAutoComplete ? 'on' : 'off'; let commentObj: any = new InPlaceEditor({ mode: 'Inline', type: 'Text', value: '', editableOn: 'Click', - model: { placeholder: this.pdfViewer.localeObj.getConstant('Add a reply') + '..' }, + model: { placeholder: this.pdfViewer.localeObj.getConstant('Add a reply') + '..' ,htmlAttributes: { autocomplete: enableAutoComplete}}, emptyText: this.pdfViewer.localeObj.getConstant('Add a reply'), saveButton: { content: this.pdfViewer.localeObj.getConstant('Post'), @@ -1175,12 +1184,13 @@ export class StickyNotesAnnotation { replyCommentDiv.style.borderColor = 'black'; replyCommentDiv.style.zIndex = 1002; // eslint-disable-next-line + let enableAutoComplete: any = this.pdfViewer.enableAutoComplete ? 'on' : 'off'; let saveObj: any = new InPlaceEditor({ mode: 'Inline', type: 'Text', emptyText: '', editableOn: 'EditIconClick', - model: { placeholder: this.pdfViewer.localeObj.getConstant('Add a reply') + '..' }, + model: { placeholder: this.pdfViewer.localeObj.getConstant('Add a reply') + '..' ,htmlAttributes: { autocomplete: enableAutoComplete}}, value: commentValue, saveButton: { content: this.pdfViewer.localeObj.getConstant('Post'), @@ -1246,12 +1256,13 @@ export class StickyNotesAnnotation { replyDiv.addEventListener('click', this.commentDivOnSelect.bind(this)); replyTextBox.addEventListener('dblclick', this.openEditorElement.bind(this)); // eslint-disable-next-line + let enableAutoComplete: any = this.pdfViewer.enableAutoComplete ? 'on' : 'off'; let saveObj: any = new InPlaceEditor({ mode: 'Inline', type: 'Text', emptyText: '', editableOn: 'EditIconClick', - model: { placeholder: this.pdfViewer.localeObj.getConstant('Add a reply') + '..' }, + model: { placeholder: this.pdfViewer.localeObj.getConstant('Add a reply') + '..' ,htmlAttributes: { autocomplete: enableAutoComplete}}, value: '', saveButton: { content: this.pdfViewer.localeObj.getConstant('Post'), @@ -1402,10 +1413,11 @@ export class StickyNotesAnnotation { // eslint-disable-next-line max-len const commentTextBox: HTMLElement = createElement('div', { id: this.pdfViewer.element.id + '_commenttextbox_' + pageIndex + '_' + this.commentsCount, className: 'e-pv-comment-textbox', attrs: { 'role': 'textbox', 'aria-label': "comment textbox" } }); // eslint-disable-next-line + let enableAutoComplete: any = this.pdfViewer.enableAutoComplete ? 'on' : 'off'; let editObj: any = new InPlaceEditor({ mode: 'Inline', type: 'Text', - model: { placeholder: this.pdfViewer.localeObj.getConstant('Add a comment') + '..' }, + model: { placeholder: this.pdfViewer.localeObj.getConstant('Add a comment') + '..' ,htmlAttributes: { autocomplete: enableAutoComplete}}, emptyText: '', editableOn: 'EditIconClick', saveButton: { @@ -1858,7 +1870,7 @@ export class StickyNotesAnnotation { if (isCommentLocked) { event.currentTarget.nextSibling.ej2_instances[0].enableEditMode = false; } else if (event.currentTarget && event.target) { - let isLocked : boolean = this.checkAnnotationSettings(event.target.id); + let isLocked : boolean = this.checkAnnotationSettings(event.currentTarget.id); if (!isLocked) { event.currentTarget.nextSibling.ej2_instances[0].enableEditMode = true; } @@ -1885,7 +1897,7 @@ export class StickyNotesAnnotation { for (let i: number = 0; i < annotCollection.length; i++) { // eslint-disable-next-line max-len annotCollection[i].annotationSettings = !isNullOrUndefined(annotCollection[i].annotationSettings) ? annotCollection[i].annotationSettings : {}; - const note: string = annotCollection[i].note ? annotCollection[i].note : annotCollection[i].notes; + const note: string = !isNullOrUndefined(annotCollection[i].note) ? annotCollection[i].note : annotCollection[i].notes; if (annotCollection[i].annotationSettings.isLock && (commentEvent.textContent === note || annotCollection[i].dynamicText === commentEvent.textContent)) { return true; } @@ -1912,7 +1924,7 @@ export class StickyNotesAnnotation { if (isCommentLocked) { event.currentTarget.ej2_instances[0].enableEditMode = false; } else if (event.currentTarget && event.target) { - let isLocked : boolean = this.checkAnnotationSettings(event.target.id); + let isLocked : boolean = this.checkAnnotationSettings(event.currentTarget.id); if (!isLocked) { if (!isNullOrUndefined(this.pdfViewer.selectedItems) && this.pdfViewer.selectedItems.annotations[0] && this.pdfViewer.selectedItems.annotations[0].isReadonly) { event.currentTarget.ej2_instances[0].enableEditMode = false; @@ -2040,7 +2052,7 @@ export class StickyNotesAnnotation { event.currentTarget.childNodes[1].ej2_instances[0].enableEditMode = false; } } else if (event.currentTarget && event.target) { - const isLocked : boolean = this.checkAnnotationSettings(event.target.id); + const isLocked : boolean = this.checkAnnotationSettings(event.currentTarget.id); if (!isLocked) { if (event.currentTarget.childElementCount === 2) { event.currentTarget.lastChild.ej2_instances[0].enableEditMode = true; @@ -2170,7 +2182,7 @@ export class StickyNotesAnnotation { // eslint-disable-next-line private commentsAnnotationSelect(event: any): void { const element: HTMLElement = event.currentTarget; - let isLocked: boolean = this.checkAnnotationSettings(event.target.id); + let isLocked: boolean = this.checkAnnotationSettings(element.id); // When the isLock is set to true, it comes and checks whether the allowedInteractions is select and set the isLock to false, In that case if enters the condition and makes the comment panel to editable mode. So, have removed the condition in openEditorElement, commentsDivClickEvent, openTextEditor,commentAnnotationSelect methods. (Task id: 835410) if (!isLocked) { if (element.classList.contains('e-pv-comments-border')) { @@ -2314,25 +2326,28 @@ export class StickyNotesAnnotation { } } - private checkAnnotationSettings(id: string): boolean { + private checkAnnotationSettings(annotId: any): boolean { // eslint-disable-next-line let annotationCollection: any = this.pdfViewer.annotationCollection; - let parentDivId : string = this.pdfViewer.element.id; if (annotationCollection) { - for (let i: number = 0; i < annotationCollection.length; i++) { - if(id.includes(parentDivId+"_commenttextbox") || id.includes(parentDivId+"_commentTitle") || id.includes(parentDivId+"_commentdiv")) { - if (annotationCollection[i].annotationSettings && annotationCollection[i].annotationSettings.isLock) { - return true; - } else { - return false; - } + let annot: any = annotationCollection.find((annotation: { annotationId: any; }) => annotation.annotationId === annotId); + if (annot && annot.annotationSettings && annot.annotationSettings.isLock) { + if (!annot.isCommentLock && annot.comments.length === 0 && (isNullOrUndefined(annot.note) || annot.note === '') && annot.shapeAnnotationType !== "FreeText") + return true; + else if ((!isNullOrUndefined(annot.comments) && annot.comments.length > 0 && annot.comments[0].isLock) || annot.isCommentLock) { + return true; + } + else { + return false; } + } else { + return false; } - return false; } else { return false; } } + private updateCommentsContainerWidth(): void { const accordionContainer: HTMLElement = document.getElementById(this.pdfViewer.element.id + '_accordionContentContainer'); const commentsContentContainer: HTMLElement = document.getElementById(this.pdfViewer.element.id + '_commentscontentcontainer'); @@ -3048,6 +3063,7 @@ export class StickyNotesAnnotation { if (poppedItem) { this.createCommentsContainer(poppedItem, pageNumber); this.updateUndoRedoCollections(poppedItem, pageIndex, type); + this.pdfViewer.annotationModule.storeAnnotationCollections(poppedItem, pageNumber-1); } } diff --git a/controls/pdfviewer/src/pdfviewer/annotation/text-markup-annotation.ts b/controls/pdfviewer/src/pdfviewer/annotation/text-markup-annotation.ts index e377a0069d..e2038e1c4e 100644 --- a/controls/pdfviewer/src/pdfviewer/annotation/text-markup-annotation.ts +++ b/controls/pdfviewer/src/pdfviewer/annotation/text-markup-annotation.ts @@ -589,6 +589,9 @@ export class TextMarkupAnnotation { annotation.allowedInteractions = annotation.AllowedInteractions ? annotation.AllowedInteractions : this.pdfViewer.annotationModule.updateAnnotationAllowedInteractions(annotation); // eslint-disable-next-line max-len annotation.AnnotationSettings = annotation.AnnotationSettings ? annotation.AnnotationSettings : this.pdfViewer.annotationModule.updateAnnotationSettings(annotation); + if (annotation.IsLocked) { + annotation.AnnotationSettings.isLock = annotation.IsLocked; + } // eslint-disable-next-line max-len annotationObject = { textMarkupAnnotationType: annotation.TextMarkupAnnotationType, color: annotation.Color, allowedInteractions: annotation.allowedInteractions, opacity: annotation.Opacity, bounds: annotation.Bounds, author: annotation.Author, subject: annotation.Subject, modifiedDate: annotation.ModifiedDate, note: annotation.Note, rect: annotation.Rect, @@ -1943,10 +1946,10 @@ export class TextMarkupAnnotation { // eslint-disable-next-line private getAnnotationBounds(bounds: any, pageIndex: number): any { - let left: number = bounds.left ? bounds.left : bounds.Left; - let top: number = bounds.top ? bounds.top : bounds.Top; - const height: number = bounds.height ? bounds.height : bounds.Height; - const width: number = bounds.width ? bounds.width : bounds.Width; + let left: number = !isNullOrUndefined(bounds.left) ? bounds.left : bounds.Left; + let top: number = !isNullOrUndefined(bounds.top) ? bounds.top : bounds.Top; + const height: number = !isNullOrUndefined(bounds.height) ? bounds.height : bounds.Height; + const width: number = !isNullOrUndefined(bounds.width) ? bounds.width : bounds.Width; const pageDetails: ISize = this.pdfViewerBase.pageSize[pageIndex]; left = left ? left : bounds.x; top = top ? top : bounds.y; diff --git a/controls/pdfviewer/src/pdfviewer/base/pdfviewer-base.ts b/controls/pdfviewer/src/pdfviewer/base/pdfviewer-base.ts index b5637e701e..a22655bbb7 100644 --- a/controls/pdfviewer/src/pdfviewer/base/pdfviewer-base.ts +++ b/controls/pdfviewer/src/pdfviewer/base/pdfviewer-base.ts @@ -2828,7 +2828,7 @@ export class PdfViewerBase { private wireEvents(): void { this.isDeviceiOS = ((['iPad Simulator', 'iPhone Simulator', 'iPod Simulator', 'iPad', 'iPhone', 'iPod'] as any).includes(navigator.platform) || (navigator.userAgent.includes("Mac") && "ontouchend" in document)); this.isMacSafari = navigator.userAgent.indexOf("Safari") > -1 && navigator.userAgent.indexOf("Chrome") === -1 && !this.isDeviceiOS; - this.isWebkitMobile = /Chrome/.test(navigator.userAgent) || /Google Inc/.test(navigator.vendor) || (navigator.userAgent.indexOf('Safari') !== -1); + this.isWebkitMobile = /Chrome/.test(navigator.userAgent) || /Google Inc/.test(navigator.vendor) || (navigator.userAgent.indexOf('Safari') !== -1) || (navigator.userAgent.indexOf('WebKit') !== -1); this.viewerContainer.addEventListener('scroll', this.viewerContainerOnScroll, true); if (Browser.isDevice && !this.pdfViewer.enableDesktopMode) { this.viewerContainer.addEventListener('touchmove', this.viewerContainerOnScroll, true); @@ -5218,7 +5218,7 @@ export class PdfViewerBase { topValue = this.pageGap; } // eslint-disable-next-line max-len - const size: ISize = { width: parseFloat(pageSize[0]), height: parseFloat(pageSize[1]), top: topValue, rotation: !isNullOrUndefined(pageValues.pageRotation) && pageValues.pageRotation.length > 0 ? pageValues.pageRotation[parseInt(i.toString(), 10)] : 0 }; + const size: ISize = { width: parseFloat(pageSize[0]), height: parseFloat(pageSize[1]), top: topValue, rotation: !isNullOrUndefined(pageValues.pageRotation) && ((!isNullOrUndefined(pageValues.pageRotation.length) && pageValues.pageRotation.length > 0) || (!isNullOrUndefined(Object.keys(pageValues.pageRotation).length) && Object.keys(pageValues.pageRotation).length > 0)) ? pageValues.pageRotation[parseInt(i.toString(), 10)] : 0 }; this.pageSize.push(size); } else { if (pageValues.pageSizes[i - 1] !== null && i !== 0) { @@ -5229,7 +5229,7 @@ export class PdfViewerBase { topValue = this.pageGap; } // eslint-disable-next-line max-len - const size: ISize = { width: (pageValues.pageSizes[parseInt(i.toString(), 10)].width ? pageValues.pageSizes[parseInt(i.toString(), 10)].width : pageValues.pageSizes[parseInt(i.toString(), 10)].Width), height: (pageValues.pageSizes[parseInt(i.toString(), 10)].height ? pageValues.pageSizes[parseInt(i.toString(), 10)].height : pageValues.pageSizes[parseInt(i.toString(), 10)].Height), top: topValue, rotation: !isNullOrUndefined(pageValues.pageRotation) && pageValues.pageRotation.length > 0 ? pageValues.pageRotation[parseInt(i.toString(), 10)] : 0 }; + const size: ISize = { width: (pageValues.pageSizes[parseInt(i.toString(), 10)].width ? pageValues.pageSizes[parseInt(i.toString(), 10)].width : pageValues.pageSizes[parseInt(i.toString(), 10)].Width), height: (pageValues.pageSizes[parseInt(i.toString(), 10)].height ? pageValues.pageSizes[parseInt(i.toString(), 10)].height : pageValues.pageSizes[parseInt(i.toString(), 10)].Height), top: topValue, rotation: !isNullOrUndefined(pageValues.pageRotation) && ((!isNullOrUndefined(pageValues.pageRotation.length) && pageValues.pageRotation.length > 0) || (!isNullOrUndefined(Object.keys(pageValues.pageRotation).length) && Object.keys(pageValues.pageRotation).length > 0)) ? pageValues.pageRotation[parseInt(i.toString(), 10)] : 0 }; this.pageSize.push(size); } if (this.pageSize[parseInt(i.toString(), 10)].height > this.pageSize[parseInt(i.toString(), 10)].width) { @@ -5419,7 +5419,7 @@ export class PdfViewerBase { topValue = proxy.pageGap + parseFloat(previousPageHeight) + topValue; } // eslint-disable-next-line max-len - const size: ISize = { width: parseFloat(pageSize[0]), height: parseFloat(pageSize[1]), top: topValue, rotation: !isNullOrUndefined(pageValues.pageRotation) && pageValues.pageRotation.length > 0 ? pageValues.pageRotation[parseInt(i.toString(), 10)] : 0 }; + const size: ISize = { width: parseFloat(pageSize[0]), height: parseFloat(pageSize[1]), top: topValue, rotation: !isNullOrUndefined(pageValues.pageRotation) && ((!isNullOrUndefined(pageValues.pageRotation.length) && pageValues.pageRotation.length > 0) || (!isNullOrUndefined(Object.keys(pageValues.pageRotation).length) && Object.keys(pageValues.pageRotation).length > 0)) ? pageValues.pageRotation[parseInt(i.toString(), 10)] : 0 }; proxy.pageSize.push(size); } else { if (proxy.pageSize[i - 1] !== null && i !== 0) { @@ -5427,7 +5427,7 @@ export class PdfViewerBase { topValue = proxy.pageGap + parseFloat(previousPageHeight) + topValue; } // eslint-disable-next-line max-len - const size: ISize = { width: (parseFloat(pageValues.pageSizes[parseInt(i.toString(), 10)].width) ? parseFloat(pageValues.pageSizes[parseInt(i.toString(), 10)].width) : parseFloat(pageValues.pageSizes[parseInt(i.toString(), 10)].Width)), height: (parseFloat(pageValues.pageSizes[parseInt(i.toString(), 10)].height) ? parseFloat(pageValues.pageSizes[parseInt(i.toString(), 10)].height) : parseFloat(pageValues.pageSizes[parseInt(i.toString(), 10)].Height)), top: topValue, rotation: pageValues.pageRotation[parseInt(i.toString(), 10)] }; + const size: ISize = { width: (parseFloat(pageValues.pageSizes[parseInt(i.toString(), 10)].width) ? parseFloat(pageValues.pageSizes[parseInt(i.toString(), 10)].width) : parseFloat(pageValues.pageSizes[parseInt(i.toString(), 10)].Width)), height: (parseFloat(pageValues.pageSizes[parseInt(i.toString(), 10)].height) ? parseFloat(pageValues.pageSizes[parseInt(i.toString(), 10)].height) : parseFloat(pageValues.pageSizes[parseInt(i.toString(), 10)].Height)), top: topValue, rotation: !isNullOrUndefined(pageValues.pageRotation) && ((!isNullOrUndefined(pageValues.pageRotation.length) && pageValues.pageRotation.length > 0) || (!isNullOrUndefined(Object.keys(pageValues.pageRotation).length) && Object.keys(pageValues.pageRotation).length > 0)) ? pageValues.pageRotation[parseInt(i.toString(), 10)] : 0 }; proxy.pageSize.push(size); } } @@ -7750,6 +7750,11 @@ export class PdfViewerBase { case 'LoadedStamp': proxy.pdfViewer.pdfRendererModule.renderer.initialPagesRendered(event.data); break; + case 'textExtracted': + if (event.data.message === 'textExtracted') { + proxy.pdfViewer.pdfRendererModule.textExtractionOnmessage(event); + } + break; } } } @@ -10851,6 +10856,16 @@ export class PdfViewerBase { return importAnnotations; } + // eslint-disable-next-line + private setAnnotationSettings(annotation: any): void { + if(!isNullOrUndefined(annotation)) { + annotation.AnnotationSettings = annotation.AnnotationSettings ? annotation.AnnotationSettings : this.pdfViewer.annotationModule.updateAnnotationSettings(annotation); + if (annotation.IsLocked) { + annotation.AnnotationSettings.isLock = annotation.IsLocked; + } + } + } + // eslint-disable-next-line private drawPageAnnotations(annotation: any, pageIndex: number, isNewlyAdded?: boolean): void { @@ -10894,6 +10909,9 @@ export class PdfViewerBase { annotationData = this.checkAnnotationCollections(annotObject, annotationData, pageIndex); } } + if(annotationData) { + this.setAnnotationSettings(annotationData[0]); + } annotation.textMarkupAnnotation = this.checkAnnotationCommentsCollections(annotation.textMarkupAnnotation, pageIndex); this.pdfViewer.annotationModule.renderAnnotations(pageIndex, null, null, annotationData, null, true); break; @@ -10907,6 +10925,9 @@ export class PdfViewerBase { annotObject = JSON.parse(storeObject); annotationData = this.checkAnnotationCollections(annotObject, annotationData, pageIndex); } + if(annotationData) { + this.setAnnotationSettings(annotationData[0]); + } annotation.shapeAnnotation = this.checkAnnotationCommentsCollections(annotation.shapeAnnotation, pageIndex); this.pdfViewer.annotationModule.renderAnnotations(pageIndex, annotationData, null, null, null, true); break; @@ -10920,6 +10941,9 @@ export class PdfViewerBase { annotObject = JSON.parse(storeObject); annotationData = this.checkAnnotationCollections(annotObject, annotationData, pageIndex); } + if(annotationData) { + this.setAnnotationSettings(annotationData[0]); + } annotation.measureShapeAnnotation = this.checkAnnotationCommentsCollections(annotation.measureShapeAnnotation, pageIndex); this.pdfViewer.annotationModule.renderAnnotations(pageIndex, null, annotationData, null, null, true); break; @@ -10932,6 +10956,9 @@ export class PdfViewerBase { annotObject = JSON.parse(storeObject); annotationData = this.checkAnnotationCollections(annotObject, annotationData, pageIndex); } + if(annotationData) { + this.setAnnotationSettings(annotationData[0]); + } annotation.stampAnnotations = this.checkAnnotationCommentsCollections(annotation.stampAnnotations, pageIndex); this.pdfViewer.annotationModule.stampAnnotationModule.renderStampAnnotations(annotationData, pageIndex, null, true); break; @@ -10945,6 +10972,9 @@ export class PdfViewerBase { annotObject = JSON.parse(storeObject); annotationData = this.checkAnnotationCollections(annotObject, annotationData, pageIndex); } + if(annotationData) { + this.setAnnotationSettings(annotationData[0]); + } annotation.freeTextAnnotation = this.checkAnnotationCommentsCollections(annotation.freeTextAnnotation, pageIndex); this.pdfViewer.annotationModule.freeTextAnnotationModule.renderFreeTextAnnotations(annotationData, pageIndex, true); break; @@ -10957,12 +10987,18 @@ export class PdfViewerBase { annotObject = JSON.parse(storeObject); annotationData = this.checkAnnotationCollections(annotObject, annotationData, pageIndex); } + if(annotationData) { + this.setAnnotationSettings(annotationData[0]); + } annotation.stickyNotesAnnotation = this.checkAnnotationCommentsCollections(annotation.stickyNotesAnnotation, pageIndex); this.pdfViewer.annotationModule.stickyNotesAnnotationModule.renderStickyNotesAnnotations(annotationData, pageIndex); break; case 'signature': storeObject = window.sessionStorage.getItem(this.documentId + '_annotations_sign'); annotObject = JSON.parse(storeObject); + if(annotationData) { + this.setAnnotationSettings(annotationData[0]); + } if (annotObject) { annotation.signatureAnnotation = this.checkSignatureCollections(annotObject, annotationData, pageIndex); } @@ -10978,6 +11014,9 @@ export class PdfViewerBase { annotObject = JSON.parse(storeObject); annotationData = this.checkAnnotationCollections(annotObject, annotationData, pageIndex); } + if(annotationData) { + this.setAnnotationSettings(annotationData[0]); + } annotation.signatureInkAnnotation = this.checkAnnotationCommentsCollections(annotation.signatureInkAnnotation, pageIndex); this.pdfViewer.annotationModule.inkAnnotationModule.renderExistingInkSignature(annotationData, pageIndex, true); break; @@ -11599,6 +11638,7 @@ export class PdfViewerBase { public deleteAnnotations(): void { if (this.pdfViewer.annotationModule) { + this.updateAnnotationsUndoRedo(); this.pdfViewer.annotations = []; this.pdfViewer.zIndexTable = []; this.pdfViewer.annotationCollection = []; @@ -11608,14 +11648,6 @@ export class PdfViewerBase { this.annotationComments = annotationCollection; this.documentAnnotationCollections = annotationCollection; this.annotationRenderredList = []; - window.sessionStorage.removeItem(this.documentId + '_annotations_shape'); - window.sessionStorage.removeItem(this.documentId + '_annotations_shape_measure'); - window.sessionStorage.removeItem(this.documentId + '_annotations_stamp'); - window.sessionStorage.removeItem(this.documentId + '_annotations_sticky'); - window.sessionStorage.removeItem(this.documentId + '_annotations_textMarkup'); - window.sessionStorage.removeItem(this.documentId + '_annotations_freetext'); - window.sessionStorage.removeItem(this.documentId + '_annotations_sign'); - window.sessionStorage.removeItem(this.documentId + '_annotations_ink'); for (let i: number = 0; i < this.pageCount; i++) { this.pdfViewer.annotationModule.renderAnnotations(i, null, null, null); this.pdfViewer.renderDrawing(undefined, i); @@ -11650,6 +11682,44 @@ export class PdfViewerBase { } } + // eslint-disable-next-line + private updateAnnotationsUndoRedo(): void { + for (let j: number = 0; j < this.pdfViewer.annotationCollection.length; j++) { + let currentAnnotation: any = null; + let proxy: any = this; + if (proxy.pdfViewer.annotationCollection[j].shapeAnnotationType === "textMarkup") { + currentAnnotation = proxy.pdfViewer.annotationCollection[parseInt(j.toString(), 10)]; + let pageAnnotations: any = proxy.pdfViewer.annotation.textMarkupAnnotationModule.getAnnotations(currentAnnotation.pageNumber, null); + if (pageAnnotations) { + for (let i: number = 0; i < pageAnnotations.length; i++) { + if (currentAnnotation.annotationId === pageAnnotations[parseInt(i.toString(), 10)].annotName) { + let deletedAnnotation: any = pageAnnotations.splice(parseInt(i.toString(), 10), 1)[0]; + proxy.pdfViewer.annotation.addAction(currentAnnotation.pageNumber, parseInt(i.toString(), 10), deletedAnnotation, 'Text Markup Deleted', null); + proxy.pdfViewer.annotation.stickyNotesAnnotationModule.findPosition(deletedAnnotation, 'textMarkup'); + let removeDiv: any = document.getElementById(deletedAnnotation.annotName); + if (removeDiv) { + if (removeDiv.parentElement.childElementCount === 1) { + proxy.pdfViewer.annotationModule.stickyNotesAnnotationModule.updateAccordionContainer(removeDiv); + } + else { + removeDiv.remove(); + } + } + } + } + } + proxy.pdfViewer.annotation.textMarkupAnnotationModule.manageAnnotations(pageAnnotations, currentAnnotation.pageNumber); + } + else { + currentAnnotation = proxy.pdfViewer.annotations.filter(function (s: { annotName: any; }) { return s.annotName === proxy.pdfViewer.annotationCollection[parseInt(j.toString(), 10)].annotationId; })[0]; + let undoElement: any = proxy.pdfViewer.annotation.modifyInCollections(currentAnnotation, 'delete'); + proxy.pdfViewer.annotation.undoCommentsElement.push(undoElement); + proxy.pdfViewer.annotation.addAction(currentAnnotation.pageIndex, null, currentAnnotation, 'Delete', '', undoElement, currentAnnotation); + proxy.pdfViewer.annotation.textMarkupAnnotationModule.manageAnnotations(currentAnnotation, currentAnnotation.pageNumber); + } + } + } + /** * @param pageNumber * @param isObject diff --git a/controls/pdfviewer/src/pdfviewer/base/signature.ts b/controls/pdfviewer/src/pdfviewer/base/signature.ts index b5b046e20c..34e56b4332 100644 --- a/controls/pdfviewer/src/pdfviewer/base/signature.ts +++ b/controls/pdfviewer/src/pdfviewer/base/signature.ts @@ -1608,6 +1608,7 @@ export class Signature { }; private renderSignatureText(): void { let maximumWidth: number = 750; + let enableButtons: boolean; // eslint-disable-next-line let fontDiv: any = document.getElementById(this.pdfViewer.element.id + '_font_appearance'); // eslint-disable-next-line @@ -1628,8 +1629,14 @@ export class Signature { let clickSign: any = document.getElementById('_font_signature' + i + ''); clickSign.addEventListener('click', this.typeSignatureclick.bind(this)); } - this.enableCreateButton(false); - this.enableClearbutton(false); + if (textBox.value.trim() === "") { + enableButtons = true; + } + else { + enableButtons = false; + } + this.enableCreateButton(enableButtons); + this.enableClearbutton(enableButtons); if (this.pdfViewer.element.offsetWidth < maximumWidth) this.updateCanvasSize(); this.drawSignOnTabSwitch(); @@ -1637,21 +1644,23 @@ export class Signature { private typeSignatureclick(): void { const eventTarget: HTMLElement = event.target as HTMLElement; // eslint-disable-next-line - let createButton: any = document.getElementsByClassName('e-pv-createbtn')[0]; - createButton.disabled = false; - for (let i: number = 0; i < 4; i++) { - // eslint-disable-next-line - let fontElement: any = document.getElementById('_font_signature' + i + ''); - if (fontElement) { - fontElement.style.borderColor = ''; + if (eventTarget.textContent.trim() !== "") { + let createButton: any = document.getElementsByClassName('e-pv-createbtn')[0]; + createButton.disabled = false; + for (let i: number = 0; i < 4; i++) { + // eslint-disable-next-line + let fontElement: any = document.getElementById('_font_signature' + i + ''); + if (fontElement) { + fontElement.style.borderColor = ''; + } + } + eventTarget.style.borderColor = 'red'; + this.outputString = eventTarget.textContent; + try { + this.fontName = JSON.parse(eventTarget.style.fontFamily); + } catch (e) { + this.fontName = eventTarget.style.fontFamily; } - } - eventTarget.style.borderColor = 'red'; - this.outputString = eventTarget.textContent; - try { - this.fontName = JSON.parse(eventTarget.style.fontFamily); - } catch (e) { - this.fontName = eventTarget.style.fontFamily; } } /** diff --git a/controls/pdfviewer/src/pdfviewer/drawing/drawing.ts b/controls/pdfviewer/src/pdfviewer/drawing/drawing.ts index a428f907f1..fad74d8250 100644 --- a/controls/pdfviewer/src/pdfviewer/drawing/drawing.ts +++ b/controls/pdfviewer/src/pdfviewer/drawing/drawing.ts @@ -843,7 +843,7 @@ export class Drawing { options: (obj as any).options, isChecked: (obj as any).isChecked, isSelected: (obj as any).isSelected }; this.pdfViewer.fireFormFieldRemoveEvent('formFieldRemove', field, obj.pageIndex); - this.pdfViewer.formDesignerModule.removeFieldsFromAnnotationCollections(obj.id); + this.pdfViewer.formDesignerModule.removeFieldsFromAnnotationCollections(obj.id, field.name); } } this.pdfViewer.enableServerDataBinding(allowServerDataBind, true); diff --git a/controls/pdfviewer/src/pdfviewer/form-designer/form-designer.ts b/controls/pdfviewer/src/pdfviewer/form-designer/form-designer.ts index 67c1433c85..affe5fb945 100644 --- a/controls/pdfviewer/src/pdfviewer/form-designer/form-designer.ts +++ b/controls/pdfviewer/src/pdfviewer/form-designer/form-designer.ts @@ -3410,9 +3410,11 @@ export class FormDesigner { /** * @private */ - public removeFieldsFromAnnotationCollections(annotationId: string): any { + public removeFieldsFromAnnotationCollections(annotationId: string, fieldName: string): any { var data = this.pdfViewerBase.getItemFromSessionStorage('_formDesigner'); var formFieldsData = JSON.parse(data); + let sessiondata : string = this.pdfViewerBase.getItemFromSessionStorage('_formfields'); + let sessionformFields: any = JSON.parse(sessiondata); for (let i: number = 0; i < formFieldsData.length; i++) { if (formFieldsData[i].Key.split("_")[0] === annotationId) { formFieldsData.splice(i, 1); @@ -3420,6 +3422,15 @@ export class FormDesigner { break; } } + if (!isNullOrUndefined(sessionformFields)) { + for (let i: number = 0; i < sessionformFields.length; i++) { + if (sessionformFields[i].FieldName === fieldName) { + sessionformFields.splice(parseInt(i.toString(), 10), 1); + sessionStorage.setItem(this.pdfViewerBase.documentId + '_formfields', JSON.stringify(sessionformFields)); + break; + } + } + } this.pdfViewerBase.setItemInSessionStorage(this.pdfViewerBase.formFieldCollection, '_formDesigner'); let storeObject: string = window.sessionStorage.getItem(this.pdfViewerBase.documentId + '_annotations_shape'); if (storeObject) { diff --git a/controls/pdfviewer/src/pdfviewer/pdf-base/annotation-renderer.ts b/controls/pdfviewer/src/pdfviewer/pdf-base/annotation-renderer.ts index 39cb811410..1902e2788b 100644 --- a/controls/pdfviewer/src/pdfviewer/pdf-base/annotation-renderer.ts +++ b/controls/pdfviewer/src/pdfviewer/pdf-base/annotation-renderer.ts @@ -1075,7 +1075,7 @@ export class AnnotationRenderer { let appearance: PdfTemplate = rubberStampAnnotation.appearance.normal; const dictionary: _PdfDictionary = new _PdfDictionary(page._crossReference); const state: PdfGraphicsState = graphics.save(); - let template: PdfTemplate = new PdfTemplate(); + let template: PdfTemplate = new PdfTemplate(stampAnnotation.template, dictionary._crossReference); template._isExported = true; template._appearance = stampAnnotation.template; template._crossReference = dictionary._crossReference; diff --git a/controls/pdfviewer/src/pdfviewer/pdf-base/pdf-renderer.ts b/controls/pdfviewer/src/pdfviewer/pdf-base/pdf-renderer.ts index b830bf3f51..33c81a9f10 100644 --- a/controls/pdfviewer/src/pdfviewer/pdf-base/pdf-renderer.ts +++ b/controls/pdfviewer/src/pdfviewer/pdf-base/pdf-renderer.ts @@ -174,7 +174,7 @@ export class PdfRenderer { this.m_formFields.showDigitalSignatureAppearance = jsonObject["showDigitalSignatureAppearance"]; } } - if (!isNullOrUndefined(this.m_formFields) && this.pageCount <= 100) { + if (!isNullOrUndefined(this.m_formFields) && this.pageSizes && Object.keys(this.pageSizes).length <=100) { this.m_formFields.GetFormFields(); pdfRenderedFormFields = this.m_formFields.PdfRenderedFormFields; } @@ -1139,42 +1139,9 @@ export class PdfRenderer { // eslint-disable-next-line max-len private textExtraction(pageIndex: number, isLayout: boolean, isRenderText?: boolean): Promise { - let extractedText: string = ''; - let textDataCollection: TextData[] = []; - let proxy: PdfRenderer = this; return new Promise((resolve: Function, reject: Function) => { if (!isNullOrUndefined(this.pdfViewerBase.pdfViewerRunner)) { this.pdfViewerBase.pdfViewerRunner.postMessage({ pageIndex: pageIndex, message: 'extractText', zoomFactor: this.pdfViewer.magnificationModule.zoomFactor, isTextNeed: true }); - this.pdfViewerBase.pdfViewerRunner.onmessage = function (event: any) { - if (event.data.message === 'textExtracted') { - const characterDetails: any = event.data.characterBounds; - for (let i: number = 0; i < characterDetails.length; i++) { - if (!isLayout && (characterDetails[parseInt(i.toString(), 10)].Text as string).indexOf('\r') !== -1) { - extractedText += ''; - } - else { - extractedText += characterDetails[parseInt(i.toString(), 10)].Text; - } - const cropBox: number[] = proxy.loadedDocument.getPage(pageIndex).cropBox; - // eslint-disable-next-line max-len - const bound: AnnotBounds = new AnnotBounds(proxy.convertPixelToPoint(characterDetails[parseInt(i.toString(), 10)].X), proxy.convertPixelToPoint(characterDetails[parseInt(i.toString(), 10)].Y + cropBox[1]), proxy.convertPixelToPoint(characterDetails[parseInt(i.toString(), 10)].Width), proxy.convertPixelToPoint(characterDetails[parseInt(i.toString(), 10)].Height)); - textDataCollection.push(new TextData(characterDetails[parseInt(i.toString(), 10)].Text, bound)); - } - let result: any = {}; - if (isRenderText) { - result.extractedTextDetails = { textDataCollection: textDataCollection, extractedText: extractedText }; - result.textBounds = event.data.textBounds; - result.textContent = event.data.textContent; - result.rotation = event.data.rotation; - result.pageText = event.data.pageText; - result.characterBounds = event.data.characterBounds; - } - else{ - result = { textDataCollection: textDataCollection, extractedText: extractedText }; - } - resolve(result); - } - }; } else { resolve(null); @@ -1182,6 +1149,44 @@ export class PdfRenderer { }); } + /** + * @private + */ + public textExtractionOnmessage(event: any) { + let extractedText: string = ''; + let textDataCollection: TextData[] = []; + return new Promise((resolve: Function, reject: Function) => { + if (event.data.message === 'textExtracted') { + const characterDetails: any = event.data.characterBounds; + for (let i: number = 0; i < characterDetails.length; i++) { + if (!event.data.isLayout && (characterDetails[parseInt(i.toString(), 10)].Text as string).indexOf('\r') !== -1) { + extractedText += ''; + } + else { + extractedText += characterDetails[parseInt(i.toString(), 10)].Text; + } + const cropBox: number[] = this.loadedDocument.getPage(event.data.pageIndex).cropBox; + // eslint-disable-next-line max-len + const bound: AnnotBounds = new AnnotBounds(this.convertPixelToPoint(characterDetails[parseInt(i.toString(), 10)].X), this.convertPixelToPoint(characterDetails[parseInt(i.toString(), 10)].Y + cropBox[1]), this.convertPixelToPoint(characterDetails[parseInt(i.toString(), 10)].Width), this.convertPixelToPoint(characterDetails[parseInt(i.toString(), 10)].Height)); + textDataCollection.push(new TextData(characterDetails[parseInt(i.toString(), 10)].Text, bound)); + } + let result: any = {}; + if (event.data.isRenderText) { + result.extractedTextDetails = { textDataCollection: textDataCollection, extractedText: extractedText }; + result.textBounds = event.data.textBounds; + result.textContent = event.data.textContent; + result.rotation = event.data.rotation; + result.pageText = event.data.pageText; + result.characterBounds = event.data.characterBounds; + } + else{ + result = { textDataCollection: textDataCollection, extractedText: extractedText }; + } + resolve(result); + } + }); + } + public extractTextWithPageSize(pageIndex: number): Promise<{[key: number]: PageTextData}>{ return new Promise((resolve: Function, reject: Function) => { resolve(this.extractTextDetailsWithPageSize(pageIndex)); diff --git a/controls/pdfviewer/src/pdfviewer/pdfium/pdfium-runner.ts b/controls/pdfviewer/src/pdfviewer/pdfium/pdfium-runner.ts index c9302fe896..4e2b6ebdc6 100644 --- a/controls/pdfviewer/src/pdfviewer/pdfium/pdfium-runner.ts +++ b/controls/pdfviewer/src/pdfviewer/pdfium/pdfium-runner.ts @@ -197,6 +197,8 @@ export function PdfiumRunner(): void { let ImageData: any = event.data; let data: object = firstPage.render(null, ImageData.zoomFactor, ImageData.isTextNeed, null, null, ImageData.textDetailsId); (data as any).message = 'textExtracted'; + (data as any).isLayout = event.data.isLayout; + (data as any).isRenderText = event.data.isRenderText; ctx.postMessage(data); } else if (event.data.message === 'renderThumbnail') { diff --git a/controls/pdfviewer/src/pdfviewer/pdfviewer.ts b/controls/pdfviewer/src/pdfviewer/pdfviewer.ts index 4c2527daa5..5d194a0eec 100644 --- a/controls/pdfviewer/src/pdfviewer/pdfviewer.ts +++ b/controls/pdfviewer/src/pdfviewer/pdfviewer.ts @@ -7858,10 +7858,12 @@ export class PdfViewer extends Component implements INotifyProperty case 'toolbarSettings': if (!Browser.isDevice || this.enableDesktopMode) { this.toolbar.applyToolbarSettings(); - if (!isNullOrUndefined(this.toolbar.annotationToolbarModule) && !isNullOrUndefined(this.toolbar.formDesignerToolbarModule)) { - this.toolbar.annotationToolbarModule.applyAnnotationToolbarSettings(); - this.toolbar.formDesignerToolbarModule.applyFormDesignerToolbarSettings(); - } + if (!isNullOrUndefined(this.toolbar.annotationToolbarModule)) { + this.toolbar.annotationToolbarModule.applyAnnotationToolbarSettings(); + } + if (!isNullOrUndefined(this.toolbar.formDesignerToolbarModule)) { + this.toolbar.formDesignerToolbarModule.applyFormDesignerToolbarSettings(); + } } else { this.toolbar.applyToolbarSettingsForMobile(); diff --git a/controls/pdfviewer/src/pdfviewer/text-selection/text-selection.ts b/controls/pdfviewer/src/pdfviewer/text-selection/text-selection.ts index 180d2d0fce..3c2d9f38d2 100644 --- a/controls/pdfviewer/src/pdfviewer/text-selection/text-selection.ts +++ b/controls/pdfviewer/src/pdfviewer/text-selection/text-selection.ts @@ -1918,10 +1918,10 @@ export class TextSelection { // eslint-disable-next-line max-len const topPositionValue: string = topClientValue + pageTopValue * this.pdfViewerBase.getZoomFactor() + (dropElementRect.height / 2) * this.pdfViewerBase.getZoomFactor() + 'px'; this.dropDivElementLeft.style.top = topPositionValue; - this.dropDivElementLeft.style.left = rangePosition.left - (viewerLeftPosition + (dropElementRect.width)) + 'px'; + this.dropDivElementLeft.style.left = rangePosition.left - (viewerLeftPosition + (dropElementRect.width)) + this.pdfViewerBase.viewerContainer.scrollLeft + 'px'; this.dropDivElementRight.style.top = topPositionValue; // eslint-disable-next-line max-len - this.dropDivElementRight.style.left = rangePosition.left + rangePosition.width - viewerLeftPosition + 'px'; + this.dropDivElementRight.style.left = rangePosition.left + rangePosition.width - viewerLeftPosition + this.pdfViewerBase.viewerContainer.scrollLeft + 'px'; const currentPageLeft: number = this.pdfViewerBase.getElement('_pageDiv_' + (this.pdfViewerBase.currentPageNumber - 1)).getBoundingClientRect().left; const currentRangeLeft: number = rangePosition.left - currentPageLeft; // eslint-disable-next-line max-len @@ -2032,12 +2032,14 @@ export class TextSelection { this.fireTextSelectEnd(); let top: any = event.changedTouches[0].clientY + event.currentTarget.clientHeight; var spanBounds = this.getSpanBounds(); - if ((spanBounds.bottom + this.contextMenuHeight + this.pdfViewerBase.toolbarHeight) > window.innerHeight) { - top = spanBounds.top - (this.contextMenuHeight + this.pdfViewerBase.toolbarHeight); - } else { - top = spanBounds.bottom + this.pdfViewerBase.toolbarHeight - topMargin; + if(spanBounds) { + if ((spanBounds.bottom + this.contextMenuHeight + this.pdfViewerBase.toolbarHeight) > window.innerHeight) { + top = spanBounds.top - (this.contextMenuHeight + this.pdfViewerBase.toolbarHeight); + } else { + top = spanBounds.bottom + this.pdfViewerBase.toolbarHeight - topMargin; + } + this.pdfViewerBase.contextMenuModule.open(top, (spanBounds.right - spanBounds.left) / 2, this.pdfViewerBase.viewerContainer); } - this.pdfViewerBase.contextMenuModule.open(top, (spanBounds.right - spanBounds.left) / 2, this.pdfViewerBase.viewerContainer); } } @@ -2098,7 +2100,7 @@ export class TextSelection { this.dropDivElementLeft.style.top = pageTopValue * this.pdfViewerBase.getZoomFactor() + topClientValue + 'px'; this.topStoreLeft = { pageTop: pageTopValue, topClientValue: this.getMagnifiedValue(topClientValue), pageNumber: this.pdfViewerBase.currentPageNumber - 1, left: this.getMagnifiedValue(currentRangeLeft), isHeightNeeded: false }; // eslint-disable-next-line max-len - this.dropDivElementLeft.style.left = xTouch - this.pdfViewerBase.viewerContainer.getBoundingClientRect().left - (elementClientRect.width / 2) + 'px'; + this.dropDivElementLeft.style.left = xTouch - this.pdfViewerBase.viewerContainer.getBoundingClientRect().left - (elementClientRect.width / 2) + this.pdfViewerBase.viewerContainer.scrollLeft + 'px'; this.previousScrollDifference = currentDifference; } } @@ -2144,7 +2146,7 @@ export class TextSelection { // eslint-disable-next-line max-len this.topStoreRight = { pageTop: pageTopValue, topClientValue: this.getMagnifiedValue(topClientValue), pageNumber: this.pdfViewerBase.currentPageNumber - 1, left: this.getMagnifiedValue(currentRangeLeft), isHeightNeeded: false }; // eslint-disable-next-line max-len - this.dropDivElementRight.style.left = touchX - this.pdfViewerBase.viewerContainer.getBoundingClientRect().left - (elementClientRect.width / 2) + 'px'; + this.dropDivElementRight.style.left = touchX - this.pdfViewerBase.viewerContainer.getBoundingClientRect().left - (elementClientRect.width / 2) + this.pdfViewerBase.viewerContainer.scrollLeft + 'px'; this.previousScrollDifference = currentDifference; } } diff --git a/controls/pdfviewer/src/pdfviewer/toolbar/annotation-toolbar.ts b/controls/pdfviewer/src/pdfviewer/toolbar/annotation-toolbar.ts index ceeb99587b..585c946e80 100644 --- a/controls/pdfviewer/src/pdfviewer/toolbar/annotation-toolbar.ts +++ b/controls/pdfviewer/src/pdfviewer/toolbar/annotation-toolbar.ts @@ -394,22 +394,26 @@ export class AnnotationToolbar { if (!isNullOrUndefined(this.pdfViewer.annotationModule.textMarkupAnnotationModule) && !this.pdfViewer.annotationModule.textMarkupAnnotationModule.currentTextMarkupAnnotation) { id = this.pdfViewer.element.id + '_underlineIcon'; } - else if (this.pdfViewer.selectedItems.annotations[0].shapeAnnotationType === 'FreeText') { - id = this.pdfViewer.element.id + '_annotation_freeTextEdit'; - // eslint-disable-next-line max-len - } else if (this.pdfViewer.selectedItems.annotations[0].shapeAnnotationType === 'Stamp' || this.pdfViewer.selectedItems.annotations[0].shapeAnnotationType === 'StickyNotes' || this.pdfViewer.selectedItems.annotations[0].shapeAnnotationType === 'Image') { - id = this.pdfViewer.element.id + '_annotation_stamp'; - // eslint-disable-next-line max-len - } else if (this.pdfViewer.selectedItems.annotations[0].shapeAnnotationType === 'HandWrittenSignature' || this.pdfViewer.selectedItems.annotations[0].shapeAnnotationType === 'SignatureText') { - id = this.pdfViewer.element.id + '_annotation_handwrittenSign'; - } else if(this.pdfViewer.selectedItems.annotations[0].shapeAnnotationType === 'SignatureImage'){ - id = this.pdfViewer.element.id + '_annotation_handwrittenImage'; - }else if (this.pdfViewer.selectedItems.annotations[0].shapeAnnotationType === 'Ink' || this.pdfViewer.selectedItems.annotations[0].shapeAnnotationType === 'Path') { - id = this.pdfViewer.element.id + '_annotation_inkIcon'; - } else if (shapeType === 'Highlight' || shapeType === 'Underline' || shapeType === 'Strikethrough') { - id = this.pdfViewer.element.id + '_highlightIcon'; - } else { - id = this.pdfViewer.element.id + '_annotation_shapesIcon'; + else { + if (this.pdfViewer.selectedItems.annotations[0]) { + if (this.pdfViewer.selectedItems.annotations[0].shapeAnnotationType === 'FreeText') { + id = this.pdfViewer.element.id + '_annotation_freeTextEdit'; + // eslint-disable-next-line max-len + } else if (this.pdfViewer.selectedItems.annotations[0].shapeAnnotationType === 'Stamp' || this.pdfViewer.selectedItems.annotations[0].shapeAnnotationType === 'StickyNotes' || this.pdfViewer.selectedItems.annotations[0].shapeAnnotationType === 'Image') { + id = this.pdfViewer.element.id + '_annotation_stamp'; + // eslint-disable-next-line max-len + } else if (this.pdfViewer.selectedItems.annotations[0].shapeAnnotationType === 'HandWrittenSignature' || this.pdfViewer.selectedItems.annotations[0].shapeAnnotationType === 'SignatureText') { + id = this.pdfViewer.element.id + '_annotation_handwrittenSign'; + } else if (this.pdfViewer.selectedItems.annotations[0].shapeAnnotationType === 'SignatureImage') { + id = this.pdfViewer.element.id + '_annotation_handwrittenImage'; + } else if (this.pdfViewer.selectedItems.annotations[0].shapeAnnotationType === 'Ink' || this.pdfViewer.selectedItems.annotations[0].shapeAnnotationType === 'Path') { + id = this.pdfViewer.element.id + '_annotation_inkIcon'; + } else if (shapeType === 'Highlight' || shapeType === 'Underline' || shapeType === 'Strikethrough') { + id = this.pdfViewer.element.id + '_highlightIcon'; + } else { + id = this.pdfViewer.element.id + '_annotation_shapesIcon'; + } + } } this.pdfViewer.toolbarModule.annotationToolbarModule.mobileColorpicker(id); } diff --git a/controls/pivotview/CHANGELOG.md b/controls/pivotview/CHANGELOG.md index dfe0fc1d62..d475d99354 100644 --- a/controls/pivotview/CHANGELOG.md +++ b/controls/pivotview/CHANGELOG.md @@ -2,6 +2,15 @@ ## [Unreleased] +## 25.1.37 (2024-03-26) + +### PivotTable + +#### Bug fixes + +- `#I562279`,`#I565475`,`#I566747` - The pivot table will now be properly displayed after engine export. +- `#I566095` - The filter text will now be properly displayed in the OLAP Pivot Table's filter field button. + ## 25.1.35 (2024-03-15) ### PivotTable diff --git a/controls/pivotview/package.json b/controls/pivotview/package.json index 6fb95035e5..d425460625 100644 --- a/controls/pivotview/package.json +++ b/controls/pivotview/package.json @@ -1,6 +1,6 @@ { "name": "@syncfusion/ej2-pivotview", - "version": "24.1.43", + "version": "25.1.35", "description": "The pivot grid, or pivot table, is used to visualize large sets of relational data in a cross-tabular format, similar to an Excel pivot table.", "author": "Syncfusion Inc.", "license": "SEE LICENSE IN license", diff --git a/controls/pivotview/spec/field-list/defer-update.spec.ts b/controls/pivotview/spec/field-list/defer-update.spec.ts index a9b12a6ba0..15cb82c611 100644 --- a/controls/pivotview/spec/field-list/defer-update.spec.ts +++ b/controls/pivotview/spec/field-list/defer-update.spec.ts @@ -32,6 +32,16 @@ describe('Pivot Field List Rendering - Defer Update', () => { } }); describe('Check pivot button drag and drop Actions', () => { + let down: MouseEvent = new MouseEvent('mousedown', { + 'view': window, + 'bubbles': true, + 'cancelable': true, + }); + let up: MouseEvent = new MouseEvent('mouseup', { + 'view': window, + 'bubbles': true, + 'cancelable': true, + }); let fieldListObj: PivotFieldList; let pivotGridObj: PivotView; let pivotCommon: PivotCommon; @@ -176,8 +186,9 @@ describe('Pivot Field List Rendering - Defer Update', () => { let treeObj: TreeView = fieldListObj.treeViewModule.fieldTable; let checkEle: Element[] = >treeObj.element.querySelectorAll('.e-checkbox-wrapper'); expect(checkEle.length).toBeGreaterThan(0); - util.checkTreeNode(treeObj, closest(checkEle[0], 'li')); - expect(pivotGridObj.element.querySelectorAll('.e-pivot-button').length).toBe(7); + closest(checkEle[0], 'li').dispatchEvent(down); + closest(checkEle[0], 'li').dispatchEvent(up); + expect(pivotGridObj.element.querySelectorAll('.e-pivot-button').length).toBe(6); }); it('Cancel button', (done: Function) => { document.getElementById('PivotFieldList_DeferUpdateButton2').click(); @@ -191,15 +202,16 @@ describe('Pivot Field List Rendering - Defer Update', () => { let treeObj: TreeView = fieldListObj.treeViewModule.fieldTable; let checkEle: Element[] = >treeObj.element.querySelectorAll('.e-checkbox-wrapper'); expect(checkEle.length).toBeGreaterThan(0); - util.checkTreeNode(treeObj, closest(checkEle[0], 'li')); - expect(pivotGridObj.element.querySelectorAll('.e-pivot-button').length).toBe(7); + closest(checkEle[0], 'li').dispatchEvent(down); + closest(checkEle[0], 'li').dispatchEvent(up); + expect(pivotGridObj.element.querySelectorAll('.e-pivot-button').length).toBe(6); done(); }); }); it('Apply button', (done: Function) => { setTimeout(() => { document.getElementById('PivotFieldList_DeferUpdateButton1').click(); - expect(pivotGridObj.element.querySelectorAll('.e-pivot-button').length).toBe(7); + expect(pivotGridObj.element.querySelectorAll('.e-pivot-button').length).toBe(6); done(); }); }); @@ -208,7 +220,8 @@ describe('Pivot Field List Rendering - Defer Update', () => { let treeObj: TreeView = fieldListObj.treeViewModule.fieldTable; let checkEle: Element[] = >treeObj.element.querySelectorAll('.e-checkbox-wrapper'); expect(checkEle.length).toBeGreaterThan(0); - util.checkTreeNode(treeObj, closest(checkEle[0], 'li')); + closest(checkEle[0], 'li').dispatchEvent(down); + closest(checkEle[0], 'li').dispatchEvent(up); expect(pivotGridObj.element.querySelectorAll('.e-pivot-button').length).toBe(7); done(); }); @@ -216,7 +229,7 @@ describe('Pivot Field List Rendering - Defer Update', () => { it('Cancel button', (done: Function) => { document.getElementById('PivotFieldList_DeferUpdateButton2').click(); setTimeout(() => { - expect(pivotGridObj.element.querySelectorAll('.e-pivot-button').length).toBe(7); + expect(pivotGridObj.element.querySelectorAll('.e-pivot-button').length).toBe(6); done(); }); }); @@ -224,7 +237,8 @@ describe('Pivot Field List Rendering - Defer Update', () => { let treeObj: TreeView = fieldListObj.treeViewModule.fieldTable; let checkEle: Element[] = >treeObj.element.querySelectorAll('.e-checkbox-wrapper'); expect(checkEle.length).toBeGreaterThan(0); - util.checkTreeNode(treeObj, closest(checkEle[0], 'li')); + closest(checkEle[0], 'li').dispatchEvent(down); + closest(checkEle[0], 'li').dispatchEvent(up); setTimeout(() => { expect(pivotGridObj.element.querySelectorAll('.e-pivot-button').length).toBe(7); done(); @@ -271,7 +285,8 @@ describe('Pivot Field List Rendering - Defer Update', () => { let checkEle: Element[] = >memberTreeObj.element.querySelectorAll('.e-checkbox-wrapper'); expect(checkEle.length).toBeGreaterThan(0); expect(allNode.classList.contains('e-small')).toBe(false); - util.checkTreeNode(treeObj, closest(allNode, 'li')); + closest(allNode, 'li').dispatchEvent(down); + closest(allNode, 'li').dispatchEvent(up); let checkedEle: Element[] = >memberTreeObj.element.querySelectorAll('.e-check'); expect(checkEle.length).toEqual(2); (fieldListObj.pivotCommon.filterDialog.dialogPopUp.element.querySelector('.e-ok-btn') as HTMLElement).click(); @@ -309,7 +324,8 @@ describe('Pivot Field List Rendering - Defer Update', () => { let checkEle: Element[] = >memberTreeObj.element.querySelectorAll('.e-checkbox-wrapper'); expect(checkEle.length).toBeGreaterThan(0); expect(allNode.classList.contains('e-small')).toBe(false); - util.checkTreeNode(treeObj, closest(allNode, 'li')); + closest(allNode, 'li').dispatchEvent(down); + closest(allNode, 'li').dispatchEvent(up); let checkedEle: Element[] = >memberTreeObj.element.querySelectorAll('.e-check'); expect(checkEle.length).toEqual(2); expect(fieldListObj.pivotCommon.filterDialog.dialogPopUp.element.querySelector('.e-ok-btn').getAttribute('disabled')).toBe(null); diff --git a/controls/pivotview/spec/field-list/pivotfieldlist.spec.ts b/controls/pivotview/spec/field-list/pivotfieldlist.spec.ts index 08bab8a86b..5916f7d48c 100644 --- a/controls/pivotview/spec/field-list/pivotfieldlist.spec.ts +++ b/controls/pivotview/spec/field-list/pivotfieldlist.spec.ts @@ -222,7 +222,6 @@ describe('PivotFieldList spec', () => { describe('Pivot Field List Rendering', () => { describe('Field List with Tree Node Action', () => { let fieldListObj: PivotFieldList; - let pivotCommon: PivotCommon; let elem: HTMLElement = createElement('div', { id: 'PivotFieldList', styles: 'height:400px;width:60%' }); let down: MouseEvent = new MouseEvent('mousedown', { 'view': window, @@ -286,10 +285,7 @@ describe('PivotFieldList spec', () => { } }); fieldListObj.appendTo('#PivotFieldList'); - pivotCommon = fieldListObj.pivotCommon; }); - - let persistdata: string; it('check field list tree view', () => { expect(!isNullOrUndefined(fieldListObj.element.querySelector('.e-pivotfieldlist-container'))); expect(fieldListObj.treeViewModule.fieldTable.element.classList.contains('e-field-list')); @@ -355,6 +351,37 @@ describe('PivotFieldList spec', () => { closest(checkEle[0], 'li').dispatchEvent(down); closest(checkEle[0], 'li').dispatchEvent(up); expect(checkEle.length).toBe(2); + (fieldListObj.element.getElementsByClassName('e-member-editor-dialog')[0].getElementsByClassName('e-ok-btn')[0] as HTMLElement).click(); + done(); + }, 2000); + }); + it('Node selecting with space key - check', (done: Function) => { + let keyboardEventArgs: any = { + preventDefault: (): void => {}, + action: null + }; + let fileds: HTMLElement[] = [].slice.call(fieldListObj.element.querySelectorAll('.e-list-item')); + (fieldListObj.treeViewModule.fieldTable as any).focusIn(); + keyboardEventArgs.action = 'space'; + (fieldListObj.treeViewModule.fieldTable as any).keyActionHandler(keyboardEventArgs); + setTimeout(() => { + expect(fileds[0].getAttribute('aria-checked')).toBe('true'); + expect(fileds[0].querySelectorAll('.e-check').length).toBe(1); + done(); + }); + }); + it('Node selecting with space key - uncheck', (done: Function) => { + let keyboardEventArgs: any = { + preventDefault: (): void => {}, + action: null + }; + let fileds: HTMLElement[] = [].slice.call(fieldListObj.element.querySelectorAll('.e-list-item')); + (fieldListObj.treeViewModule.fieldTable as any).focusIn(); + keyboardEventArgs.action = 'space'; + (fieldListObj.treeViewModule.fieldTable as any).keyActionHandler(keyboardEventArgs); + setTimeout(() => { + expect(fileds[0].getAttribute('aria-checked')).toBe('false'); + expect(fileds[0].querySelectorAll('.e-check').length).toBe(0); done(); }); }); diff --git a/controls/pivotview/src/base/engine.ts b/controls/pivotview/src/base/engine.ts index c861c74474..c30a0b3750 100644 --- a/controls/pivotview/src/base/engine.ts +++ b/controls/pivotview/src/base/engine.ts @@ -170,8 +170,8 @@ export class PivotEngine { * @returns {void} * @hidden */ - public clearProperties(): void { - if (!this.isPagingOrVirtualizationEnabled) { + public clearProperties(isExport?: boolean): void { + if (!this.isPagingOrVirtualizationEnabled && !isExport) { this.columnKeys = {}; this.headerCollection = { rowHeaders: [], columnHeaders: [], rowHeadersCount: 0, columnHeadersCount: 0 }; } @@ -2108,7 +2108,7 @@ export class PivotEngine { this.generateGridData(dataSource); this.isEditing = false; } - public generateGridData(dataSource: IDataOptions, requireDatasourceUpdate: boolean = false, headerCollection?: HeaderCollection): void { + public generateGridData(dataSource: IDataOptions, requireDatasourceUpdate: boolean = false, isExport?: boolean, headerCollection?: HeaderCollection): void { this.updateDataSourceSettings(dataSource, requireDatasourceUpdate); const columns: IFieldOptions[] = dataSource.columns ? dataSource.columns : []; const data: IDataSet[] = this.data as IDataSet[]; @@ -2251,7 +2251,7 @@ export class PivotEngine { this.isEngineUpdated = true; this.isEmptyDataAvail(this.rMembers, this.cMembers); // console.log(st1 - st2); - this.clearProperties(); + this.clearProperties(isExport); } private updateHeaders(rowFlag?: boolean, columnFlag?: boolean): void { /* removing the row grant-total members */ diff --git a/controls/pivotview/src/base/olap/engine.ts b/controls/pivotview/src/base/olap/engine.ts index 2988ae7ae5..81447bd288 100644 --- a/controls/pivotview/src/base/olap/engine.ts +++ b/controls/pivotview/src/base/olap/engine.ts @@ -3491,11 +3491,16 @@ export class OlapEngine { const currentItems: string[] = []; for (const selectedElement of filter.items) { // currentItems.push(selectedElement.replace(/\&/g, '&')); + let filterCaption: string; + if (!isMembersAvail && filter.items.length === 1) { + this.getMembers(this.dataSourceSettings, filter.name, undefined, undefined, undefined, filter.items[0]); + filterCaption = this.fieldList[filter.name].actualFilter[0]; + } currentItems.push(selectedElement); if (isMembersAvail) { this.fieldList[filter.name].filter.push(members[selectedElement as string].caption); } else { - this.fieldList[filter.name].filter.push(selectedElement); + this.fieldList[filter.name].filter.push(filterCaption ? filterCaption : selectedElement); } } this.filterMembers[filter.name] = currentItems; @@ -3684,16 +3689,19 @@ export class OlapEngine { return filterQuery; } public getMembers(dataSourceSettings: IDataOptions, fieldName: string, isAllFilterData?: boolean, - filterParentQuery?: string, loadLevelMember?: boolean): void { + filterParentQuery?: string, loadLevelMember?: boolean, filterItemName?: string): void { // dimProp = "dimension properties CHILDREN_CARDINALITY, MEMBER_TYPE"; const dimProp: string = 'DIMENSION PROPERTIES PARENT_UNIQUE_NAME, HIERARCHY_UNIQUE_NAME, CHILDREN_CARDINALITY, MEMBER_TYPE, MEMBER_VALUE'; let mdxQuery: string; const hasAllMember: boolean = this.fieldList[fieldName as string].hasAllMember; const hierarchy: string = (hasAllMember ? fieldName : fieldName + '.LEVELS(0)').replace(/\&/g, '&').replace(/\>/g, '>').replace(/\ 0 && this.fieldList[customArgs.fieldName] && fields[fields.length - 1].getElementsByTagName('Caption') + && fields[fields.length - 1].getElementsByTagName('Caption')[0]) { + this.fieldList[customArgs.fieldName].actualFilter[0] = fields[fields.length - 1].getElementsByTagName('Caption')[0].textContent; + } + } public getChildMembers(dataSourceSettings: IDataOptions, memberUQName: string, fieldName: string): void { // dimProp = "dimension properties CHILDREN_CARDINALITY, MEMBER_TYPE"; const dimProp: string = 'DIMENSION PROPERTIES PARENT_UNIQUE_NAME, HIERARCHY_UNIQUE_NAME, CHILDREN_CARDINALITY, MEMBER_TYPE, MEMBER_VALUE'; diff --git a/controls/pivotview/src/common/base/css-constant.ts b/controls/pivotview/src/common/base/css-constant.ts index 6bd3be2676..8b51087065 100644 --- a/controls/pivotview/src/common/base/css-constant.ts +++ b/controls/pivotview/src/common/base/css-constant.ts @@ -832,3 +832,7 @@ export const FREEZED_CELL: string = 'e-leftfreeze'; export const PIVOT_CONTENT_LOADER: string = 'e-pivot-content-loader'; /** @hidden */ export const PIVOT_HIDE_LOADER: string = 'e-hide-loader'; +/** @hidden */ +export const COLLAPSIBLE: string = 'e-icon-collapsible'; +/** @hidden */ +export const EXPANDABLE: string = 'e-icon-expandable'; \ No newline at end of file diff --git a/controls/pivotview/src/pivotfieldlist/renderer/tree-renderer.ts b/controls/pivotview/src/pivotfieldlist/renderer/tree-renderer.ts index e0cfbb70e1..3212fa3910 100644 --- a/controls/pivotview/src/pivotfieldlist/renderer/tree-renderer.ts +++ b/controls/pivotview/src/pivotfieldlist/renderer/tree-renderer.ts @@ -5,8 +5,8 @@ import * as cls from '../../common/base/css-constant'; import * as events from '../../common/base/constant'; import { IAction, FieldDropEventArgs, FieldRemoveEventArgs, FieldDragStartEventArgs } from '../../common/base/interface'; import { - TreeView, NodeCheckEventArgs, DragAndDropEventArgs, DrawNodeEventArgs, - NodeExpandEventArgs, NodeSelectEventArgs + TreeView, NodeClickEventArgs, DragAndDropEventArgs, DrawNodeEventArgs, + NodeExpandEventArgs, NodeSelectEventArgs, NodeCheckEventArgs } from '@syncfusion/ej2-navigations'; import { IFieldOptions, IField, IDataOptions, FieldItemInfo } from '../../base/engine'; import { Dialog } from '@syncfusion/ej2-popups'; @@ -35,6 +35,7 @@ export class TreeViewRenderer implements IAction { private nonSearchList: HTMLElement[]; private isSearching: boolean = false; private parentIDs: string[] = []; + private isSpaceKey: boolean = true; /** Constructor for render module * @@ -143,6 +144,7 @@ export class TreeViewRenderer implements IAction { private renderTreeView(): void { this.fieldTable = new TreeView({ fields: { dataSource: this.getTreeData(), id: 'id', text: 'caption', isChecked: 'isSelected', parentID: 'pid', iconCss: 'spriteCssClass' }, + nodeChecked: this.nodeChecked.bind(this), nodeClicked: this.nodeStateChange.bind(this), keyPress: this.nodeStateChange.bind(this), cssClass: cls.FIELD_LIST_TREE_CLASS + (this.parent.cssClass ? (' ' + this.parent.cssClass) : ''), @@ -384,6 +386,7 @@ export class TreeViewRenderer implements IAction { locale: this.parent.locale, enableHtmlSanitizer: this.parent.enableHtmlSanitizer, cssClass: this.parent.cssClass, + nodeChecked: this.nodeChecked.bind(this), nodeClicked: this.addNode.bind(this), keyPress: this.addNode.bind(this), drawNode: this.updateTreeNode.bind(this), @@ -574,31 +577,57 @@ export class TreeViewRenderer implements IAction { } return buttonElement; } - private nodeStateChange(args: NodeCheckEventArgs): void { - const id: string = args.node.getAttribute('data-uid'); + private nodeChecked(args: NodeCheckEventArgs): void { + if (this.isSpaceKey) { + const node: HTMLElement = closest(args.node, '.' + cls.TEXT_CONTENT_CLASS) as HTMLElement; + if (!isNullOrUndefined(node)) { + const li: HTMLElement = closest(node, 'li') as HTMLElement; + const id: string = li.getAttribute('data-uid'); + if (this.parent.isAdaptive) { + this.addNode(undefined, id, args.action === 'check', node); + } else { + this.nodeStateChange(undefined, id, args.action === 'check', node); + } + } + } + this.isSpaceKey = false; + } + private nodeStateChange(args: NodeClickEventArgs, id: string, isChecked: boolean, node: HTMLElement): void { + node = isNullOrUndefined(node) ? args.node : node; + id = isNullOrUndefined(id) ? node.getAttribute('data-uid') : id; if (this.parent.pivotCommon.filterDialog.dialogPopUp) { this.parent.pivotCommon.filterDialog.dialogPopUp.close(); } const list: { [key: string]: Object } = this.parent.pivotFieldList; - const selectedNode: { [key: string]: Object } = list[id as string] as { [key: string]: Object }; - const fieldInfo: FieldItemInfo = PivotUtil.getFieldInfo(id, this.parent); - const control: PivotView | PivotFieldList = this.parent.isPopupView ? this.parent.pivotGridModule : this.parent; - const parentNode: Element = args.node.closest('.' + cls.FIELD_TREE_PARENT); - let isChecked: boolean = false; /* eslint-disable @typescript-eslint/no-explicit-any */ - const getNodeDetails: any = this.fieldTable.getNode(args.node); - if ((args as any).event && (args as any).event.target && - !(args as any).event.target.classList.contains(cls.CHECK_BOX_FRAME_CLASS)) { - /* eslint-enable @typescript-eslint/no-explicit-any */ - if (getNodeDetails.isChecked === 'true') { - this.fieldTable.uncheckAll([args.node]); - isChecked = false; + const selectedNode: { [key: string]: Object; } = list[id as string] as { [key: string]: Object }; + if (!isNullOrUndefined(args)) { + /* eslint-disable @typescript-eslint/no-explicit-any */ + this.isSpaceKey = (args.event as any).action && (args.event as any).action === 'space'; + if (isNullOrUndefined(selectedNode) || node.classList.contains(cls.ICON_DISABLE) || (args.event.target && + ((args.event.target as HTMLElement).classList.contains(cls.COLLAPSIBLE) || + (args.event.target as HTMLElement).classList.contains(cls.EXPANDABLE))) || + ((args.event as any).action && (args.event as any).action !== 'enter')) { + return; + } + isChecked = false; + const getNodeDetails: any = this.fieldTable.getNode(node); + if ((args as any).event && (args as any).event.target && + !(args as any).event.target.classList.contains(cls.CHECK_BOX_FRAME_CLASS)) { + /* eslint-enable @typescript-eslint/no-explicit-any */ + if (getNodeDetails.isChecked === 'true') { + this.fieldTable.uncheckAll([node]); + isChecked = false; + } else { + this.fieldTable.checkAll([node]); + isChecked = true; + } } else { - this.fieldTable.checkAll([args.node]); - isChecked = true; + isChecked = getNodeDetails.isChecked === 'true'; } - } else { - isChecked = getNodeDetails.isChecked === 'true'; } + const control: PivotView | PivotFieldList = this.parent.isPopupView ? this.parent.pivotGridModule : this.parent; + const fieldInfo: FieldItemInfo = PivotUtil.getFieldInfo(id, this.parent); + const parentNode: Element = node.closest('.' + cls.FIELD_TREE_PARENT); if (isChecked) { const eventdrop: FieldDropEventArgs = { fieldName: id, dropField: fieldInfo.fieldItem, @@ -610,11 +639,11 @@ export class TreeViewRenderer implements IAction { }; control.trigger(events.fieldDrop, eventdrop, (observedArgs: FieldDropEventArgs) => { if (!observedArgs.cancel) { - addClass([args.node.querySelector('.' + cls.LIST_TEXT_CLASS)], cls.LIST_SELECT_CLASS); + addClass([node.querySelector('.' + cls.LIST_TEXT_CLASS)], cls.LIST_SELECT_CLASS); if (parentNode) { addClass([parentNode.querySelector('.' + cls.LIST_TEXT_CLASS)], cls.LIST_SELECT_CLASS); } - this.updateSelectedNodes(args.node, 'check'); + this.updateSelectedNodes(node, 'check'); const addNode: IFieldOptions = this.parent.pivotCommon.dataSourceUpdate.getNewField(id, fieldInfo.fieldItem); this.updateReportSettings(addNode, observedArgs); this.updateNodeStateChange(id, selectedNode, isChecked); @@ -630,11 +659,11 @@ export class TreeViewRenderer implements IAction { }; control.trigger(events.fieldRemove, removeFieldArgs, (observedArgs: FieldRemoveEventArgs) => { if (!observedArgs.cancel) { - removeClass([args.node.querySelector('.' + cls.LIST_TEXT_CLASS)], cls.LIST_SELECT_CLASS); + removeClass([node.querySelector('.' + cls.LIST_TEXT_CLASS)], cls.LIST_SELECT_CLASS); if (parentNode && isNullOrUndefined(parentNode.querySelector('.' + cls.FIELD_TREE_CHILD + ' .' + cls.NODE_CHECK_CLASS))) { removeClass([parentNode.querySelector('.' + cls.LIST_TEXT_CLASS)], cls.LIST_SELECT_CLASS); } - this.updateSelectedNodes(args.node, 'uncheck'); + this.updateSelectedNodes(node, 'uncheck'); this.parent.pivotCommon.dataSourceUpdate.removeFieldFromReport(id); if (this.parent.dataType === 'pivot' && this.parent.showValuesButton && this.parent.dataSourceSettings.values.length > 1 && fieldInfo && fieldInfo.position < this.parent.dataSourceSettings.valueIndex && @@ -774,27 +803,38 @@ export class TreeViewRenderer implements IAction { } } - private addNode(args: NodeCheckEventArgs): void { - const id: string = args.node.getAttribute('data-uid'); + private addNode(args: NodeClickEventArgs, id: string, isChecked: boolean, node: HTMLElement): void { + node = isNullOrUndefined(node) ? args.node : node; + id = isNullOrUndefined(id) ? node.getAttribute('data-uid') : id; const list: { [key: string]: Object } = this.parent.pivotFieldList; - const selectedNode: { [key: string]: Object } = list[id as string] as { [key: string]: Object }; - const fieldInfo: FieldItemInfo = PivotUtil.getFieldInfo(selectedNode.id.toString(), this.parent); - const control: PivotView | PivotFieldList = this.parent.isPopupView ? this.parent.pivotGridModule : this.parent; - let isChecked: boolean = false; /* eslint-disable @typescript-eslint/no-explicit-any */ - const getNodeDetails: any = this.fieldTable.getNode(args.node); - if ((args as any).event && (args as any).event.target && - !(args as any).event.target.classList.contains(cls.CHECK_BOX_FRAME_CLASS)) { - /* eslint-enable @typescript-eslint/no-explicit-any */ - if (getNodeDetails.isChecked === 'true') { - this.fieldTable.uncheckAll([args.node]); - isChecked = false; + const selectedNode: { [key: string]: Object; } = list[id as string] as { [key: string]: Object }; + if (!isNullOrUndefined(args)) { + /* eslint-disable @typescript-eslint/no-explicit-any */ + this.isSpaceKey = (args.event as any).key && (args.event as any).key === ' '; + if (isNullOrUndefined(selectedNode) || args.node.classList.contains(cls.ICON_DISABLE) || (args.event.target && + ((args.event.target as HTMLElement).classList.contains(cls.COLLAPSIBLE) || + (args.event.target as HTMLElement).classList.contains(cls.EXPANDABLE))) || + ((args.event as any).key && (args.event as any).key !== 'Enter')) { + return; + } + isChecked = false; + const getNodeDetails: any = this.fieldTable.getNode(args.node); + if ((args as any).event && (args as any).event.target && + !(args as any).event.target.classList.contains(cls.CHECK_BOX_FRAME_CLASS)) { + /* eslint-enable @typescript-eslint/no-explicit-any */ + if (getNodeDetails.isChecked === 'true') { + this.fieldTable.uncheckAll([args.node]); + isChecked = false; + } else { + this.fieldTable.checkAll([args.node]); + isChecked = true; + } } else { - this.fieldTable.checkAll([args.node]); - isChecked = true; + isChecked = getNodeDetails.isChecked === 'true'; } - } else { - isChecked = getNodeDetails.isChecked === 'true'; } + const fieldInfo: FieldItemInfo = PivotUtil.getFieldInfo(selectedNode.id.toString(), this.parent); + const control: PivotView | PivotFieldList = this.parent.isPopupView ? this.parent.pivotGridModule : this.parent; if (isChecked) { const axis: string[] = ['filters', 'columns', 'rows', 'values']; const eventdrop: FieldDropEventArgs = { diff --git a/controls/pivotview/src/pivotview/actions/excel-export.ts b/controls/pivotview/src/pivotview/actions/excel-export.ts index aa504a26a9..4367eeb32d 100644 --- a/controls/pivotview/src/pivotview/actions/excel-export.ts +++ b/controls/pivotview/src/pivotview/actions/excel-export.ts @@ -104,7 +104,7 @@ export class ExcelExport { } else { this.engine.pageSettings = null; } - (this.engine as PivotEngine).generateGridData(this.parent.dataSourceSettings, true); + (this.engine as PivotEngine).generateGridData(this.parent.dataSourceSettings, true, true); this.parent.applyFormatting(this.engine.pivotValues); clonedValues = PivotExportUtil.getClonedPivotValues(this.engine.pivotValues) as IAxisSet[][]; this.engine.pivotValues = currentPivotValues; diff --git a/controls/pivotview/src/pivotview/actions/pdf-export.ts b/controls/pivotview/src/pivotview/actions/pdf-export.ts index 4e812d1f9f..b05d2f24fb 100644 --- a/controls/pivotview/src/pivotview/actions/pdf-export.ts +++ b/controls/pivotview/src/pivotview/actions/pdf-export.ts @@ -530,7 +530,7 @@ export class PDFExport { } else { this.engine.pageSettings = null; } - (this.engine as PivotEngine).generateGridData(this.parent.dataSourceSettings as IDataOptions, true); + (this.engine as PivotEngine).generateGridData(this.parent.dataSourceSettings as IDataOptions, true, true); this.parent.applyFormatting(this.engine.pivotValues); clonedValues = PivotExportUtil.getClonedPivotValues(this.engine.pivotValues) as IAxisSet[][]; this.engine.pivotValues = currentPivotValues; diff --git a/controls/pivotview/src/pivotview/actions/virtualscroll.ts b/controls/pivotview/src/pivotview/actions/virtualscroll.ts index b88f93d0bf..a6bd8a13c1 100644 --- a/controls/pivotview/src/pivotview/actions/virtualscroll.ts +++ b/controls/pivotview/src/pivotview/actions/virtualscroll.ts @@ -206,7 +206,7 @@ export class VirtualScroll { this.parent.getEngine('onScroll', null, null, null, null, null, null); } else { this.parent.engineModule.generateGridData( - this.parent.dataSourceSettings, true, this.parent.engineModule.headerCollection); + this.parent.dataSourceSettings, true, false, this.parent.engineModule.headerCollection); rowStartPos = this.parent.engineModule.rowStartPos; } } else { @@ -251,7 +251,7 @@ export class VirtualScroll { if (this.parent.dataSourceSettings.mode === 'Server') { this.parent.getEngine('onScroll', null, null, null, null, null, null); } else { - pivot.engineModule.generateGridData(pivot.dataSourceSettings, true, pivot.engineModule.headerCollection); + pivot.engineModule.generateGridData(pivot.dataSourceSettings, true, false, pivot.engineModule.headerCollection); colStartPos = pivot.engineModule.colStartPos; } } else { diff --git a/controls/pivotview/src/pivotview/base/pivotview.ts b/controls/pivotview/src/pivotview/base/pivotview.ts index 7682dfb43a..566677f941 100644 --- a/controls/pivotview/src/pivotview/base/pivotview.ts +++ b/controls/pivotview/src/pivotview/base/pivotview.ts @@ -4123,7 +4123,7 @@ export class PivotView extends Component implements INotifyProperty this.getEngine('onPageChange', null, null, null, null, null, null); } else { this.engineModule.generateGridData( - this.dataSourceSettings, true, this.engineModule.headerCollection); + this.dataSourceSettings, true, false, this.engineModule.headerCollection); } this.setProperties({ pivotValues: this.engineModule.pivotValues }, true); this.enginePopulatedEventMethod('updateDataSource'); @@ -6061,7 +6061,7 @@ export class PivotView extends Component implements INotifyProperty } } else if ((this.dataSourceSettings.url !== '' && this.dataType === 'olap') || (pivot.dataSourceSettings.dataSource && (pivot.dataSourceSettings.dataSource as IDataSet[]).length > 0 - || this.engineModule.data.length > 0)) { + || (this.engineModule.data && this.engineModule.data.length > 0))) { if (pivot.dataType === 'pivot') { this.hideWaitingPopup(); pivot.engineModule.data = pivot.dataSourceSettings.dataSource; diff --git a/controls/pivotview/src/pivotview/renderer/render.ts b/controls/pivotview/src/pivotview/renderer/render.ts index b04c328357..eecd9d6386 100644 --- a/controls/pivotview/src/pivotview/renderer/render.ts +++ b/controls/pivotview/src/pivotview/renderer/render.ts @@ -1675,8 +1675,9 @@ export class Render { this.parent.resizedValue = (this.parent.showGroupingBar && this.parent.resizedValue < 250) ? 250 : this.parent.resizedValue; } this.resColWidth = !isNullOrUndefined(this.parent.resizedValue) ? this.parent.resizedValue : this.resColWidth; - const offsetWidth: number = this.parent.element.offsetWidth ? this.parent.element.offsetWidth : - this.parent.element.getBoundingClientRect().width; + const offsetWidth: number = this.parent.grid ? this.parent.grid.element.offsetWidth ? this.parent.grid.element.offsetWidth : + this.parent.grid.element.getBoundingClientRect().width : this.parent.element.offsetWidth ? + this.parent.element.offsetWidth : this.parent.element.getBoundingClientRect().width; let parWidth: number = isNaN(this.parent.width as number) ? (this.parent.width.toString().indexOf('%') > -1 ? ((parseFloat(this.parent.width.toString()) / 100) * offsetWidth) : offsetWidth) : Number(this.parent.width); diff --git a/controls/pivotview/styles/pivotfieldlist/_theme.scss b/controls/pivotview/styles/pivotfieldlist/_theme.scss index f6ebbedbc3..269f4059fb 100644 --- a/controls/pivotview/styles/pivotfieldlist/_theme.scss +++ b/controls/pivotview/styles/pivotfieldlist/_theme.scss @@ -828,6 +828,9 @@ .e-field-list-container { height: 369px; + @if ($skin-name == 'FluentUI') { + height: 394px; + } /* stylelint-disable */ .e-field-table { diff --git a/controls/popups/CHANGELOG.md b/controls/popups/CHANGELOG.md index b3c0374487..d9854d2c25 100644 --- a/controls/popups/CHANGELOG.md +++ b/controls/popups/CHANGELOG.md @@ -2,7 +2,7 @@ ## [Unreleased] -## 25.1.35 (2024-03-15) +## 25.1.37 (2024-03-26) ### Dialog diff --git a/controls/querybuilder/CHANGELOG.md b/controls/querybuilder/CHANGELOG.md index 87ddcfe9f6..22f88e4870 100644 --- a/controls/querybuilder/CHANGELOG.md +++ b/controls/querybuilder/CHANGELOG.md @@ -2,6 +2,14 @@ ## [Unreleased] +## 25.1.37 (2024-03-26) + +### QueryBuilder + +#### Bug Fixes + +- `#I568017` - Issue with QueryBuilder 'In' or 'Not in' Operator results in value field as empty list when using fieldMode as default mode has been fixed. + ## 25.1.35 (2024-03-15) ### QueryBuilder diff --git a/controls/querybuilder/package.json b/controls/querybuilder/package.json index 7abb065075..97f60bc1f2 100644 --- a/controls/querybuilder/package.json +++ b/controls/querybuilder/package.json @@ -1,6 +1,6 @@ { "name": "@syncfusion/ej2-querybuilder", - "version": "18.24.1", + "version": "25.1.35", "description": "Essential JS 2 QueryBuilder", "author": "Syncfusion Inc.", "license": "SEE LICENSE IN license", diff --git a/controls/querybuilder/src/query-builder/query-builder.ts b/controls/querybuilder/src/query-builder/query-builder.ts index 79718860eb..48ea5feed5 100644 --- a/controls/querybuilder/src/query-builder/query-builder.ts +++ b/controls/querybuilder/src/query-builder/query-builder.ts @@ -2449,17 +2449,11 @@ export class QueryBuilder extends Component implements INotifyPr if (!this.dataColl.length && values.length) { isValues = true; } - let fieldValue: string = this.selectedRule.field; - const isNested: number = this.selectedRule.field.indexOf(this.separator); - if (isNested !== 0 && this.fieldMode !== 'DropdownTree') { - const nest: string[] = this.selectedRule.field.split(this.separator); - fieldValue = nest[nest.length - 1]; - } let multiSelectValue: MultiSelectModel; multiSelectValue = { dataSource: isValues ? values : (isFetched ? ds as { [key: string]: object }[] : this.dataManager), query: new Query([rule.field]), - fields: { text: fieldValue, value: fieldValue }, + fields: { text: this.selectedRule.field, value: this.selectedRule.field }, placeholder: this.l10n.getConstant('SelectValue'), value: selectedValue, mode: 'CheckBox', @@ -5139,6 +5133,7 @@ export class QueryBuilder extends Component implements INotifyPr */ public cloneRule(ruleID: string, groupID: string, index: number): void { const getRule: RuleModel = this.getRule(ruleID.replace(this.element.id + '_', '')); + let isCloneRule: boolean = this.showButtons.cloneRule; groupID = groupID.replace(this.element.id + '_', ''); this.ruleIndex = index; this.cloneRuleBtnClick = true; @@ -5149,7 +5144,8 @@ export class QueryBuilder extends Component implements INotifyPr }], groupID); this.ruleIndex = -1; this.cloneRuleBtnClick = false; - this.showButtons.cloneRule = false; + this.showButtons.cloneRule = isCloneRule; + isCloneRule = false; } /** @@ -5163,6 +5159,7 @@ export class QueryBuilder extends Component implements INotifyPr public cloneGroup(groupID: string, parentGroupID: string, index: number): void { parentGroupID = parentGroupID.replace(this.element.id + '_', ''); const group: RuleModel = this.getGroup(parentGroupID); + let isCloneGroup: boolean = this.showButtons.cloneGroup; groupID = groupID.replace(this.element.id + '_', ''); this.groupIndex = index; this.cloneGrpBtnClick = true; @@ -5170,7 +5167,8 @@ export class QueryBuilder extends Component implements INotifyPr this.addGroups([{ 'condition': group.condition, 'not': group.not, 'rules': group.rules }], groupID); this.groupIndex = -1; this.cloneGrpBtnClick = false; - this.showButtons.cloneGroup = false; + this.showButtons.cloneGroup = isCloneGroup; + isCloneGroup = false; } /** @@ -5180,6 +5178,9 @@ export class QueryBuilder extends Component implements INotifyPr * @returns {void} */ public lockRule(ruleID: string): void { + if (ruleID.indexOf(this.element.id) < 0) { + ruleID = this.element.id + '_' + ruleID; + } const target: Element = document.getElementById(ruleID).querySelectorAll('.e-lock-rule-btn')[0]; this.ruleLock(target); } @@ -5191,6 +5192,9 @@ export class QueryBuilder extends Component implements INotifyPr * @returns {void} */ public lockGroup(groupID: string): void { + if (groupID.indexOf(this.element.id) < 0) { + groupID = this.element.id + '_' + groupID; + } const target: Element = document.getElementById(groupID).querySelectorAll('.e-lock-grp-btn')[0]; this.groupLock(target); } diff --git a/controls/querybuilder/styles/query-builder/_theme.scss b/controls/querybuilder/styles/query-builder/_theme.scss index 3e5e8c9e4f..7b165568ed 100644 --- a/controls/querybuilder/styles/query-builder/_theme.scss +++ b/controls/querybuilder/styles/query-builder/_theme.scss @@ -8,7 +8,9 @@ } & .e-group-header .e-dropdown-btn.e-add-btn, - .e-deletegroup { + .e-deletegroup, + .e-clone-grp-btn, + .e-lock-grp-btn { @if $skin-name == 'bootstrap5' { background: $qrybldr-btngroup-bgcolor; box-shadow: none; diff --git a/controls/ribbon/package.json b/controls/ribbon/package.json index d2100dcde2..f8bd5e2137 100644 --- a/controls/ribbon/package.json +++ b/controls/ribbon/package.json @@ -1,6 +1,6 @@ { "name": "@syncfusion/ej2-ribbon", - "version": "21.29.0", + "version": "25.1.35", "description": "Essential JS 2 Component", "author": "Syncfusion Inc.", "license": "SEE LICENSE IN license", diff --git a/controls/ribbon/styles/ribbon/_fabric-dark-definition.scss b/controls/ribbon/styles/ribbon/_fabric-dark-definition.scss index 7c25a1e65e..28953272a8 100644 --- a/controls/ribbon/styles/ribbon/_fabric-dark-definition.scss +++ b/controls/ribbon/styles/ribbon/_fabric-dark-definition.scss @@ -255,7 +255,7 @@ $ribbon-bigger-rtl-button-icon-padding: 11px 16px 11px 12px !default; $ribbon-bigger-caret-icon-padding: 0 12px !default; $ribbon-bigger-rtl-caret-icon-padding: 0 12px !default; -$ribbon-header-bg-color: $neutral-light-font !default; +$ribbon-header-bg-color: $neutral-lighter !default; $ribbon-border-color: unset !default; $ribbon-group-seperator-color: $neutral-quintenary !default; $ribbon-group-border-color: $neutral-quintenary !default; diff --git a/controls/ribbon/styles/ribbon/_layout.scss b/controls/ribbon/styles/ribbon/_layout.scss index 5f858f7673..c2f3d75be5 100644 --- a/controls/ribbon/styles/ribbon/_layout.scss +++ b/controls/ribbon/styles/ribbon/_layout.scss @@ -890,7 +890,7 @@ } .e-ribbon-group-overflow-ddb { - &.e-dropdown-popup { + &.e-dropdown-popup:has(.e-ribbon-overflow-target) { min-width: 190px; } .e-ribbon-of-tab:not(.e-ribbon-active) { diff --git a/controls/ribbon/styles/ribbon/_material3-definition.scss b/controls/ribbon/styles/ribbon/_material3-definition.scss index 188c7effaf..17e6faffe4 100644 --- a/controls/ribbon/styles/ribbon/_material3-definition.scss +++ b/controls/ribbon/styles/ribbon/_material3-definition.scss @@ -339,8 +339,8 @@ $ribbon-popup-common-box-shadow: $shadow-md !default; $ribbon-filemenu-popup-border-radius: 4px !default; $ribbon-contextual-tab-color: $ribbon-active-tab-indicator-color !default; $ribbon-contextual-tab-background: rgba($content-bg-color-selected) !default; -$ribbon-keytip-background-color: rgba($black) !default; -$ribbon-keytip-color: rgba($content-text-color-inverse) !default; +$ribbon-keytip-background-color: rgba($tooltip-bg-color) !default; +$ribbon-keytip-color: rgba($tooltip-text-color) !default; $ribbon-gallery-popup-background-color: rgba($content-bg-color) !default; $ribbon-gallery-item-background-color: $transparent !default; $ribbon-gallery-item-selected-color: rgba($content-bg-color-selected) !default; diff --git a/controls/richtexteditor/package.json b/controls/richtexteditor/package.json index 18ef68f2fb..8d418bc950 100644 --- a/controls/richtexteditor/package.json +++ b/controls/richtexteditor/package.json @@ -1,6 +1,6 @@ { "name": "@syncfusion/ej2-richtexteditor", - "version": "23.1.54", + "version": "25.1.35", "description": "Essential JS 2 RichTextEditor component", "author": "Syncfusion Inc.", "license": "SEE LICENSE IN license", diff --git a/controls/richtexteditor/spec/cr-issues/rich-text-editor.spec.ts b/controls/richtexteditor/spec/cr-issues/rich-text-editor.spec.ts index a9f4f7a8ec..72658b5186 100644 --- a/controls/richtexteditor/spec/cr-issues/rich-text-editor.spec.ts +++ b/controls/richtexteditor/spec/cr-issues/rich-text-editor.spec.ts @@ -1712,9 +1712,7 @@ describe('RTE CR issues', () => { const tileItems: NodeList = ( row[0] as HTMLElement ).querySelectorAll('.e-tile'); ( tileItems[9] as HTMLElement ).click(); // Background color - (document.querySelectorAll('.e-control.e-colorpicker')[1] as any).ej2_instances[0].inline = true; - (document.querySelectorAll('.e-control.e-colorpicker')[1] as any).ej2_instances[0].dataBind(); - ( document.body.querySelector('.e-apply') as HTMLElement).click(); + (rteObject.element.querySelector('.e-background-color') as HTMLElement).click(); ( dropButton[1] as HTMLElement ).click(); // Font Size const fontDropItems : NodeList= document.body.querySelectorAll('.e-item'); ( fontDropItems[6] as HTMLElement ).click(); // Apply Font size diff --git a/controls/richtexteditor/spec/editor-manager/plugin/msword-cleanup.spec.ts b/controls/richtexteditor/spec/editor-manager/plugin/msword-cleanup.spec.ts index d50aa6f8fc..55e14a2a02 100644 --- a/controls/richtexteditor/spec/editor-manager/plugin/msword-cleanup.spec.ts +++ b/controls/richtexteditor/spec/editor-manager/plugin/msword-cleanup.spec.ts @@ -1370,7 +1370,7 @@ ul Filterauswahl 5 42 - 0 + 0 `; rteObj.value = '

15

'; diff --git a/controls/richtexteditor/spec/editor-manager/plugin/selection-commands.spec.ts b/controls/richtexteditor/spec/editor-manager/plugin/selection-commands.spec.ts index 93baa00433..ee9e889d0f 100644 --- a/controls/richtexteditor/spec/editor-manager/plugin/selection-commands.spec.ts +++ b/controls/richtexteditor/spec/editor-manager/plugin/selection-commands.spec.ts @@ -743,7 +743,7 @@ describe('EJ2-58803 - Styles format not maintain properly when applied different SelectionCommands.applyFormat(document, 'underline', rteObj.inputElement, 'P'); SelectionCommands.applyFormat(document, 'italic', rteObj.inputElement, 'P'); SelectionCommands.applyFormat(document, 'bold', rteObj.inputElement, 'P'); - expect((rteObj as any).inputElement.innerHTML).toBe(`

RTE Content

`); + expect((rteObj as any).inputElement.innerHTML).toBe(`

RTE Content

`); done(); }); }); @@ -772,7 +772,7 @@ describe('EJ2-58803 - Styles format not maintain properly when applied different SelectionCommands.applyFormat(document, 'italic', rteObj.inputElement, 'P'); SelectionCommands.applyFormat(document, 'bold', rteObj.inputElement, 'P'); SelectionCommands.applyFormat(document, 'backgroundcolor', rteObj.inputElement, 'P', 'rgb(246, 198, 206)'); - expect((rteObj as any).inputElement.innerHTML).toBe(`

RTE Content

`); + expect((rteObj as any).inputElement.innerHTML).toBe(`

RTE Content

`); done(); }); }); @@ -802,7 +802,7 @@ describe('EJ2-58803 - Styles format not maintain properly when applied different SelectionCommands.applyFormat(document, 'bold', rteObj.inputElement, 'P'); SelectionCommands.applyFormat(document, 'fontcolor', rteObj.inputElement, 'P', 'rgb(83, 129, 53)'); SelectionCommands.applyFormat(document, 'backgroundcolor', rteObj.inputElement, 'P', 'rgb(246, 198, 206)'); - expect((rteObj as any).inputElement.innerHTML).toBe(`

RTE Content

`); + expect((rteObj as any).inputElement.innerHTML).toBe(`

RTE Content

`); done(); }); }); @@ -829,7 +829,7 @@ describe('EJ2-58803 - Styles format not maintain properly when applied different rteObj.formatter.editorManager.nodeSelection.setSelectionText(document, focusNode.childNodes[0], focusNode.childNodes[0], 11, 11); SelectionCommands.applyFormat(document, 'backgroundcolor', rteObj.inputElement, 'P', 'rgb(246, 198, 206)'); SelectionCommands.applyFormat(document, 'fontcolor', rteObj.inputElement, 'P', 'rgb(83, 129, 53)'); - expect((rteObj as any).inputElement.innerHTML).toBe(`

RTE Content

`); + expect((rteObj as any).inputElement.innerHTML).toBe(`

RTE Content

`); done(); }); }); diff --git a/controls/richtexteditor/spec/rich-text-editor/api/basic-properties/enableRtl.spec.ts b/controls/richtexteditor/spec/rich-text-editor/api/basic-properties/enableRtl.spec.ts index 5f0c3a683b..0359f76803 100644 --- a/controls/richtexteditor/spec/rich-text-editor/api/basic-properties/enableRtl.spec.ts +++ b/controls/richtexteditor/spec/rich-text-editor/api/basic-properties/enableRtl.spec.ts @@ -94,7 +94,7 @@ describe('RTE BASIC PROPERTIES - enableRtl - ', () => { expect(item.classList.contains('e-rtl')).toBe(true); item.click(); let dropDown: HTMLElement = document.getElementById(controlId + "_quick_FontColor-popup"); - expect(dropDown.querySelector('.e-rte-fontcolor-colorpicker').classList.contains('e-rtl')).toBe(false); + expect(dropDown.querySelector('.e-rte-fontcolor-colorpicker').classList.contains('e-rtl')).toBe(true); item.click(); done(); }); @@ -104,7 +104,7 @@ describe('RTE BASIC PROPERTIES - enableRtl - ', () => { expect(item.classList.contains('e-rtl')).toBe(true); item.click(); let dropDown: HTMLElement = document.getElementById(controlId + "_quick_BackgroundColor-popup"); - expect(dropDown.querySelector('.e-rte-backgroundcolor-colorpicker').classList.contains('e-rtl')).toBe(false); + expect(dropDown.querySelector('.e-rte-backgroundcolor-colorpicker').classList.contains('e-rtl')).toBe(true); item.click(); done(); }); @@ -322,7 +322,7 @@ describe('RTE BASIC PROPERTIES - enableRtl - ', () => { expect(item.classList.contains('e-rtl')).toBe(true); item.click(); let dropDown: HTMLElement = document.getElementById(controlId + "_quick_FontColor-popup"); - expect(dropDown.querySelector('.e-rte-fontcolor-colorpicker').classList.contains('e-rtl')).toBe(false); + expect(dropDown.querySelector('.e-rte-fontcolor-colorpicker').classList.contains('e-rtl')).toBe(true); item.click(); done(); }, 500); @@ -338,7 +338,7 @@ describe('RTE BASIC PROPERTIES - enableRtl - ', () => { expect(item.classList.contains('e-rtl')).toBe(true); item.click(); let dropDown: HTMLElement = document.getElementById(controlId + "_quick_BackgroundColor-popup"); - expect(dropDown.querySelector('.e-rte-backgroundcolor-colorpicker').classList.contains('e-rtl')).toBe(false); + expect(dropDown.querySelector('.e-rte-backgroundcolor-colorpicker').classList.contains('e-rtl')).toBe(true); done(); }, 500); }); diff --git a/controls/richtexteditor/spec/rich-text-editor/renderer/audio-module.spec.ts b/controls/richtexteditor/spec/rich-text-editor/renderer/audio-module.spec.ts index 60d9a694c5..9064c93706 100644 --- a/controls/richtexteditor/spec/rich-text-editor/renderer/audio-module.spec.ts +++ b/controls/richtexteditor/spec/rich-text-editor/renderer/audio-module.spec.ts @@ -825,43 +825,43 @@ describe('insert Audio', () => { }, 1000); }); }); - describe('Disable the insert audio dialog button when the audio is uploading', () => { - let rteObj: RichTextEditor; - beforeEach((done: Function) => { - rteObj = renderRTE({ - toolbarSettings: { - items: ['Audio'] - }, - insertAudioSettings: { - saveUrl: "https://ej2.syncfusion.com/services/api/uploadbox/Save", - path: "../Audios/" - } - }); - done(); - }) - afterEach((done: Function) => { - destroy(rteObj); - done(); - }) - it(' Button enabled with audio upload Success', (done) => { - let rteEle: HTMLElement = rteObj.element; - (rteObj.contentModule.getEditPanel() as HTMLElement).focus(); - let args = { preventDefault: function () { } }; - let range = new NodeSelection().getRange(document); - let save = new NodeSelection().save(range, document); - let evnArg = { args: MouseEvent, self: (rteObj).audioModule, selection: save, selectNode: new Array(), }; - (rteEle.querySelectorAll(".e-toolbar-item button")[0] as HTMLElement).click(); - let dialogEle: Element = rteObj.element.querySelector('.e-dialog'); - (dialogEle.querySelector('.e-audio-url') as HTMLInputElement).value = 'https://www.w3schools.com/html/horse.mp3'; - let fileObj: File = new File(["Horse"], "horse.mp3", { lastModified: 0, type: "overide/mimetype" }); - let eventArgs = { type: 'click', target: { files: [fileObj] }, preventDefault: (): void => { } }; - (rteObj).audioModule.uploadObj.onSelectFiles(eventArgs); - setTimeout(() => { - expect((dialogEle.querySelector('.e-insertAudio') as HTMLButtonElement).hasAttribute('disabled')).toBe(false); - done(); - }, 4900); - }); - }); + // describe('Disable the insert audio dialog button when the audio is uploading', () => { + // let rteObj: RichTextEditor; + // beforeEach((done: Function) => { + // rteObj = renderRTE({ + // toolbarSettings: { + // items: ['Audio'] + // }, + // insertAudioSettings: { + // saveUrl: "https://ej2.syncfusion.com/services/api/uploadbox/Save", + // path: "../Audios/" + // } + // }); + // done(); + // }) + // afterEach((done: Function) => { + // destroy(rteObj); + // done(); + // }) + // it(' Button enabled with audio upload Success', (done) => { + // let rteEle: HTMLElement = rteObj.element; + // (rteObj.contentModule.getEditPanel() as HTMLElement).focus(); + // let args = { preventDefault: function () { } }; + // let range = new NodeSelection().getRange(document); + // let save = new NodeSelection().save(range, document); + // let evnArg = { args: MouseEvent, self: (rteObj).audioModule, selection: save, selectNode: new Array(), }; + // (rteEle.querySelectorAll(".e-toolbar-item button")[0] as HTMLElement).click(); + // let dialogEle: Element = rteObj.element.querySelector('.e-dialog'); + // (dialogEle.querySelector('.e-audio-url') as HTMLInputElement).value = 'https://www.w3schools.com/html/horse.mp3'; + // let fileObj: File = new File(["Horse"], "horse.mp3", { lastModified: 0, type: "overide/mimetype" }); + // let eventArgs = { type: 'click', target: { files: [fileObj] }, preventDefault: (): void => { } }; + // (rteObj).audioModule.uploadObj.onSelectFiles(eventArgs); + // setTimeout(() => { + // expect((dialogEle.querySelector('.e-insertAudio') as HTMLButtonElement).hasAttribute('disabled')).toBe(false); + // done(); + // }, 4900); + // }); + // }); describe('Getting error while insert the audio after applied the lower case or upper case commands in Html Editor - ', () => { let rteObj: RichTextEditor; let controlId: string; @@ -1039,50 +1039,50 @@ describe('insert Audio', () => { }); }); - describe('Rename audios in success event- ', () => { - let rteObj: RichTextEditor; - beforeEach((done: Function) => { - rteObj = renderRTE({ - fileUploadSuccess: function (args : any) { - args.file.name = 'rte_audio'; - var filename : any = document.querySelectorAll(".e-file-name")[0]; - filename.innerHTML = args.file.name.replace(document.querySelectorAll(".e-file-type")[0].innerHTML, ''); - filename.title = args.file.name; - }, - insertAudioSettings: { - saveUrl:"https://aspnetmvc.syncfusion.com/services/api/uploadbox/Save", - path: "../Audios/" - }, - toolbarSettings: { - items: ['Audio'] - }, - }); - done(); - }) - afterEach((done: Function) => { - destroy(rteObj); - done(); - }) - it('Check name after renamed', (done) => { - let rteEle: HTMLElement = rteObj.element; - (rteObj.contentModule.getEditPanel() as HTMLElement).focus(); - let args = { preventDefault: function () { } }; - let range = new NodeSelection().getRange(document); - let save = new NodeSelection().save(range, document); - let evnArg = { args: MouseEvent, self: (rteObj).audioModule, selection: save, selectNode: new Array(), }; - (rteEle.querySelectorAll(".e-toolbar-item button")[0] as HTMLElement).click(); - let dialogEle: Element = rteObj.element.querySelector('.e-dialog'); - (dialogEle.querySelector('.e-audio-url') as HTMLInputElement).value = 'https://www.w3schools.com/html/horse.mp3'; - (dialogEle.querySelector('.e-audio-url') as HTMLInputElement).dispatchEvent(new Event("input")); - let fileObj: File = new File(["Horse"], "horse.mp3", { lastModified: 0, type: "overide/mimetype" }); - let eventArgs = { type: 'click', target: { files: [fileObj] }, preventDefault: (): void => { } }; - (rteObj).audioModule.uploadObj.onSelectFiles(eventArgs); - setTimeout(() => { - expect(document.querySelectorAll(".e-file-name")[0].innerHTML).toBe('rte_audio'); - done(); - }, 4900); - }); - }); + // describe('Rename audios in success event- ', () => { + // let rteObj: RichTextEditor; + // beforeEach((done: Function) => { + // rteObj = renderRTE({ + // fileUploadSuccess: function (args : any) { + // args.file.name = 'rte_audio'; + // var filename : any = document.querySelectorAll(".e-file-name")[0]; + // filename.innerHTML = args.file.name.replace(document.querySelectorAll(".e-file-type")[0].innerHTML, ''); + // filename.title = args.file.name; + // }, + // insertAudioSettings: { + // saveUrl:"https://aspnetmvc.syncfusion.com/services/api/uploadbox/Save", + // path: "../Audios/" + // }, + // toolbarSettings: { + // items: ['Audio'] + // }, + // }); + // done(); + // }) + // afterEach((done: Function) => { + // destroy(rteObj); + // done(); + // }) + // it('Check name after renamed', (done) => { + // let rteEle: HTMLElement = rteObj.element; + // (rteObj.contentModule.getEditPanel() as HTMLElement).focus(); + // let args = { preventDefault: function () { } }; + // let range = new NodeSelection().getRange(document); + // let save = new NodeSelection().save(range, document); + // let evnArg = { args: MouseEvent, self: (rteObj).audioModule, selection: save, selectNode: new Array(), }; + // (rteEle.querySelectorAll(".e-toolbar-item button")[0] as HTMLElement).click(); + // let dialogEle: Element = rteObj.element.querySelector('.e-dialog'); + // (dialogEle.querySelector('.e-audio-url') as HTMLInputElement).value = 'https://www.w3schools.com/html/horse.mp3'; + // (dialogEle.querySelector('.e-audio-url') as HTMLInputElement).dispatchEvent(new Event("input")); + // let fileObj: File = new File(["Horse"], "horse.mp3", { lastModified: 0, type: "overide/mimetype" }); + // let eventArgs = { type: 'click', target: { files: [fileObj] }, preventDefault: (): void => { } }; + // (rteObj).audioModule.uploadObj.onSelectFiles(eventArgs); + // setTimeout(() => { + // expect(document.querySelectorAll(".e-file-name")[0].innerHTML).toBe('rte_audio'); + // done(); + // }, 4900); + // }); + // }); describe('Inserting Audio as Base64 - ', () => { let rteObj: RichTextEditor; @@ -1168,58 +1168,58 @@ describe('insert Audio', () => { }); }); - describe('Insert Audio mediaSelected, mediaUploading and mediaUploadSuccess event - ', () => { - let rteObj: RichTextEditor; - let mediaSelectedSpy: jasmine.Spy = jasmine.createSpy('onFileSelected'); - let mediaUploadingSpy: boolean = false; - let mediaUploadSuccessSpy: jasmine.Spy = jasmine.createSpy('onFileUploadSuccess'); - beforeEach((done: Function) => { - rteObj = renderRTE({ - fileSelected: mediaSelectedSpy, - fileUploading: mediaUploading, - fileUploadSuccess: mediaUploadSuccessSpy, - insertAudioSettings: { - saveUrl:"https://aspnetmvc.syncfusion.com/services/api/uploadbox/Save", - path: "../Audios/" - }, - toolbarSettings: { - items: ['Audio'] - } - }); - function mediaUploading() { - mediaUploadingSpy = true; - } - done(); - }) - afterEach((done: Function) => { - destroy(rteObj); - done(); - }) - it(' Test the component insert audio events - case 1 ', (done) => { - let rteEle: HTMLElement = rteObj.element; - (rteObj.contentModule.getEditPanel() as HTMLElement).focus(); - let args = { preventDefault: function () { } }; - let range = new NodeSelection().getRange(document); - let save = new NodeSelection().save(range, document); - let evnArg = { args: MouseEvent, self: (rteObj).audioModule, selection: save, selectNode: new Array(), }; - (rteEle.querySelectorAll(".e-toolbar-item button")[0] as HTMLElement).click(); - let dialogEle: Element = rteObj.element.querySelector('.e-dialog'); - (dialogEle.querySelector('.e-audio-url') as HTMLInputElement).value = 'https://www.w3schools.com/html/horse.mp3'; - (dialogEle.querySelector('.e-audio-url') as HTMLInputElement).dispatchEvent(new Event("input")); - let fileObj: File = new File(["Horse"], "horse.mp3", { lastModified: 0, type: "overide/mimetype" }); - let eventArgs = { type: 'click', target: { files: [fileObj] }, preventDefault: (): void => { } }; - (rteObj).audioModule.uploadObj.onSelectFiles(eventArgs); - expect(mediaSelectedSpy).toHaveBeenCalled(); - expect(mediaUploadingSpy).toBe(true); - setTimeout(() => { - expect(mediaUploadSuccessSpy).toHaveBeenCalled(); - evnArg.selectNode = [rteObj.element]; - (rteObj).audioModule.deleteAudio(evnArg); - (rteObj).audioModule.uploadObj.upload((rteObj).audioModule.uploadObj.filesData[0]); - done(); - }, 4000); - }); - }); + // describe('Insert Audio mediaSelected, mediaUploading and mediaUploadSuccess event - ', () => { + // let rteObj: RichTextEditor; + // let mediaSelectedSpy: jasmine.Spy = jasmine.createSpy('onFileSelected'); + // let mediaUploadingSpy: boolean = false; + // let mediaUploadSuccessSpy: jasmine.Spy = jasmine.createSpy('onFileUploadSuccess'); + // beforeEach((done: Function) => { + // rteObj = renderRTE({ + // fileSelected: mediaSelectedSpy, + // fileUploading: mediaUploading, + // fileUploadSuccess: mediaUploadSuccessSpy, + // insertAudioSettings: { + // saveUrl:"https://aspnetmvc.syncfusion.com/services/api/uploadbox/Save", + // path: "../Audios/" + // }, + // toolbarSettings: { + // items: ['Audio'] + // } + // }); + // function mediaUploading() { + // mediaUploadingSpy = true; + // } + // done(); + // }) + // afterEach((done: Function) => { + // destroy(rteObj); + // done(); + // }) + // it(' Test the component insert audio events - case 1 ', (done) => { + // let rteEle: HTMLElement = rteObj.element; + // (rteObj.contentModule.getEditPanel() as HTMLElement).focus(); + // let args = { preventDefault: function () { } }; + // let range = new NodeSelection().getRange(document); + // let save = new NodeSelection().save(range, document); + // let evnArg = { args: MouseEvent, self: (rteObj).audioModule, selection: save, selectNode: new Array(), }; + // (rteEle.querySelectorAll(".e-toolbar-item button")[0] as HTMLElement).click(); + // let dialogEle: Element = rteObj.element.querySelector('.e-dialog'); + // (dialogEle.querySelector('.e-audio-url') as HTMLInputElement).value = 'https://www.w3schools.com/html/horse.mp3'; + // (dialogEle.querySelector('.e-audio-url') as HTMLInputElement).dispatchEvent(new Event("input")); + // let fileObj: File = new File(["Horse"], "horse.mp3", { lastModified: 0, type: "overide/mimetype" }); + // let eventArgs = { type: 'click', target: { files: [fileObj] }, preventDefault: (): void => { } }; + // (rteObj).audioModule.uploadObj.onSelectFiles(eventArgs); + // expect(mediaSelectedSpy).toHaveBeenCalled(); + // expect(mediaUploadingSpy).toBe(true); + // setTimeout(() => { + // expect(mediaUploadSuccessSpy).toHaveBeenCalled(); + // evnArg.selectNode = [rteObj.element]; + // (rteObj).audioModule.deleteAudio(evnArg); + // (rteObj).audioModule.uploadObj.upload((rteObj).audioModule.uploadObj.filesData[0]); + // done(); + // }, 4000); + // }); + // }); describe('Insert audio mediaSelected event args cancel true - ', () => { let rteObj: RichTextEditor; diff --git a/controls/richtexteditor/spec/rich-text-editor/renderer/image-module.spec.ts b/controls/richtexteditor/spec/rich-text-editor/renderer/image-module.spec.ts index 9edb72cd37..0e8d5ef006 100644 --- a/controls/richtexteditor/spec/rich-text-editor/renderer/image-module.spec.ts +++ b/controls/richtexteditor/spec/rich-text-editor/renderer/image-module.spec.ts @@ -2471,43 +2471,43 @@ client side. Customer easy to edit the contents and get the HTML content for }, 4000); }); }); - describe('EJ2-37798 - Disable the insert image dialog button when the image is uploading', () => { - let rteObj: RichTextEditor; - beforeEach((done: Function) => { - rteObj = renderRTE({ - toolbarSettings: { - items: ['Image'] - }, - insertImageSettings: { - saveUrl: "https://ej2.syncfusion.com/services/api/uploadbox/Save", - path: "../Images/" - } - }); - done(); - }) - afterEach((done: Function) => { - destroy(rteObj); - done(); - }) - it(' Button enabled with image upload Success', (done) => { - let rteEle: HTMLElement = rteObj.element; - (rteObj.contentModule.getEditPanel() as HTMLElement).focus(); - let args = { preventDefault: function () { } }; - let range = new NodeSelection().getRange(document); - let save = new NodeSelection().save(range, document); - let evnArg = { args: MouseEvent, self: (rteObj).imageModule, selection: save, selectNode: new Array(), }; - (rteEle.querySelectorAll(".e-toolbar-item button")[0] as HTMLElement).click(); - let dialogEle: Element = rteObj.element.querySelector('.e-dialog'); - (dialogEle.querySelector('.e-img-url') as HTMLInputElement).value = 'https://js.syncfusion.com/demos/web/content/images/accordion/baked-chicken-and-cheese.png'; - let fileObj: File = new File(["Nice One"], "sample.jpg", { lastModified: 0, type: "overide/mimetype" }); - let eventArgs = { type: 'click', target: { files: [fileObj] }, preventDefault: (): void => { } }; - (rteObj).imageModule.uploadObj.onSelectFiles(eventArgs); - setTimeout(() => { - expect((dialogEle.querySelector('.e-insertImage') as HTMLButtonElement).hasAttribute('disabled')).toBe(false); - done(); - }, 4500); - }); - }); + // describe('EJ2-37798 - Disable the insert image dialog button when the image is uploading', () => { + // let rteObj: RichTextEditor; + // beforeEach((done: Function) => { + // rteObj = renderRTE({ + // toolbarSettings: { + // items: ['Image'] + // }, + // insertImageSettings: { + // saveUrl: "https://ej2.syncfusion.com/services/api/uploadbox/Save", + // path: "../Images/" + // } + // }); + // done(); + // }) + // afterEach((done: Function) => { + // destroy(rteObj); + // done(); + // }) + // it(' Button enabled with image upload Success', (done) => { + // let rteEle: HTMLElement = rteObj.element; + // (rteObj.contentModule.getEditPanel() as HTMLElement).focus(); + // let args = { preventDefault: function () { } }; + // let range = new NodeSelection().getRange(document); + // let save = new NodeSelection().save(range, document); + // let evnArg = { args: MouseEvent, self: (rteObj).imageModule, selection: save, selectNode: new Array(), }; + // (rteEle.querySelectorAll(".e-toolbar-item button")[0] as HTMLElement).click(); + // let dialogEle: Element = rteObj.element.querySelector('.e-dialog'); + // (dialogEle.querySelector('.e-img-url') as HTMLInputElement).value = 'https://js.syncfusion.com/demos/web/content/images/accordion/baked-chicken-and-cheese.png'; + // let fileObj: File = new File(["Nice One"], "sample.jpg", { lastModified: 0, type: "overide/mimetype" }); + // let eventArgs = { type: 'click', target: { files: [fileObj] }, preventDefault: (): void => { } }; + // (rteObj).imageModule.uploadObj.onSelectFiles(eventArgs); + // setTimeout(() => { + // expect((dialogEle.querySelector('.e-insertImage') as HTMLButtonElement).hasAttribute('disabled')).toBe(false); + // done(); + // }, 4500); + // }); + // }); describe(' EJ2-20297: RTE Image insert link - ', () => { let rteObj: RichTextEditor; let controlId: string; @@ -3028,47 +3028,47 @@ client side. Customer easy to edit the contents and get the HTML content for }); }); - describe('Rename images in success event- ', () => { - let rteObj: RichTextEditor; - beforeEach((done: Function) => { - rteObj = renderRTE({ - imageUploadSuccess: function (args : any) { - args.file.name = 'rte_image'; - var filename : any = document.querySelectorAll(".e-file-name")[0]; - filename.innerHTML = args.file.name.replace(document.querySelectorAll(".e-file-type")[0].innerHTML, ''); - filename.title = args.file.name; - }, - insertImageSettings: { - saveUrl:"https://aspnetmvc.syncfusion.com/services/api/uploadbox/Save", - path: "../Images/" - } - }); - done(); - }) - afterEach((done: Function) => { - destroy(rteObj); - done(); - }) - it('Check name after renamed', (done) => { - let rteEle: HTMLElement = rteObj.element; - (rteObj.contentModule.getEditPanel() as HTMLElement).focus(); - let args = { preventDefault: function () { } }; - let range = new NodeSelection().getRange(document); - let save = new NodeSelection().save(range, document); - let evnArg = { args: MouseEvent, self: (rteObj).imageModule, selection: save, selectNode: new Array(), }; - (rteEle.querySelectorAll(".e-toolbar-item button")[8] as HTMLElement).click(); - let dialogEle: Element = rteObj.element.querySelector('.e-dialog'); - (dialogEle.querySelector('.e-img-url') as HTMLInputElement).value = 'https://js.syncfusion.com/demos/web/content/images/accordion/baked-chicken-and-cheese.png'; - (dialogEle.querySelector('.e-img-url') as HTMLInputElement).dispatchEvent(new Event("input")); - let fileObj: File = new File(["Nice One"], "sample.png", { lastModified: 0, type: "overide/mimetype" }); - let eventArgs = { type: 'click', target: { files: [fileObj] }, preventDefault: (): void => { } }; - (rteObj).imageModule.uploadObj.onSelectFiles(eventArgs); - setTimeout(() => { - expect(document.querySelectorAll(".e-file-name")[0].innerHTML).toBe('rte_image'); - done(); - }, 4500); - }); - }); + // describe('Rename images in success event- ', () => { + // let rteObj: RichTextEditor; + // beforeEach((done: Function) => { + // rteObj = renderRTE({ + // imageUploadSuccess: function (args : any) { + // args.file.name = 'rte_image'; + // var filename : any = document.querySelectorAll(".e-file-name")[0]; + // filename.innerHTML = args.file.name.replace(document.querySelectorAll(".e-file-type")[0].innerHTML, ''); + // filename.title = args.file.name; + // }, + // insertImageSettings: { + // saveUrl:"https://aspnetmvc.syncfusion.com/services/api/uploadbox/Save", + // path: "../Images/" + // } + // }); + // done(); + // }) + // afterEach((done: Function) => { + // destroy(rteObj); + // done(); + // }) + // it('Check name after renamed', (done) => { + // let rteEle: HTMLElement = rteObj.element; + // (rteObj.contentModule.getEditPanel() as HTMLElement).focus(); + // let args = { preventDefault: function () { } }; + // let range = new NodeSelection().getRange(document); + // let save = new NodeSelection().save(range, document); + // let evnArg = { args: MouseEvent, self: (rteObj).imageModule, selection: save, selectNode: new Array(), }; + // (rteEle.querySelectorAll(".e-toolbar-item button")[8] as HTMLElement).click(); + // let dialogEle: Element = rteObj.element.querySelector('.e-dialog'); + // (dialogEle.querySelector('.e-img-url') as HTMLInputElement).value = 'https://js.syncfusion.com/demos/web/content/images/accordion/baked-chicken-and-cheese.png'; + // (dialogEle.querySelector('.e-img-url') as HTMLInputElement).dispatchEvent(new Event("input")); + // let fileObj: File = new File(["Nice One"], "sample.png", { lastModified: 0, type: "overide/mimetype" }); + // let eventArgs = { type: 'click', target: { files: [fileObj] }, preventDefault: (): void => { } }; + // (rteObj).imageModule.uploadObj.onSelectFiles(eventArgs); + // setTimeout(() => { + // expect(document.querySelectorAll(".e-file-name")[0].innerHTML).toBe('rte_image'); + // done(); + // }, 4500); + // }); + // }); describe('Inserting Image as Base64 - ', () => { let rteObj: RichTextEditor; @@ -3148,52 +3148,52 @@ client side. Customer easy to edit the contents and get the HTML content for }); }); - describe('Insert image imageSelected, imageUploading and imageUploadSuccess event - ', () => { - let rteObj: RichTextEditor; - let imageSelectedSpy: jasmine.Spy = jasmine.createSpy('onImageSelected'); - let imageUploadingSpy: jasmine.Spy = jasmine.createSpy('onImageUploading'); - let imageUploadSuccessSpy: jasmine.Spy = jasmine.createSpy('onImageUploadSuccess'); - beforeEach((done: Function) => { - rteObj = renderRTE({ - imageSelected: imageSelectedSpy, - imageUploading: imageUploadingSpy, - imageUploadSuccess: imageUploadSuccessSpy, - insertImageSettings: { - saveUrl:"https://aspnetmvc.syncfusion.com/services/api/uploadbox/Save", - path: "../Images/" - } - }); - done(); - }) - afterEach((done: Function) => { - destroy(rteObj); - done(); - }) - it(' Test the component insert image events - case 1 ', (done) => { - let rteEle: HTMLElement = rteObj.element; - (rteObj.contentModule.getEditPanel() as HTMLElement).focus(); - let args = { preventDefault: function () { } }; - let range = new NodeSelection().getRange(document); - let save = new NodeSelection().save(range, document); - let evnArg = { args: MouseEvent, self: (rteObj).imageModule, selection: save, selectNode: new Array(), }; - (rteEle.querySelectorAll(".e-toolbar-item button")[8] as HTMLElement).click(); - let dialogEle: Element = rteObj.element.querySelector('.e-dialog'); - (dialogEle.querySelector('.e-img-url') as HTMLInputElement).value = 'https://js.syncfusion.com/demos/web/content/images/accordion/baked-chicken-and-cheese.png'; - (dialogEle.querySelector('.e-img-url') as HTMLInputElement).dispatchEvent(new Event("input")); - let fileObj: File = new File(["Nice One"], "sample.png", { lastModified: 0, type: "overide/mimetype" }); - let eventArgs = { type: 'click', target: { files: [fileObj] }, preventDefault: (): void => { } }; - (rteObj).imageModule.uploadObj.onSelectFiles(eventArgs); - expect(imageSelectedSpy).toHaveBeenCalled(); - expect(imageUploadingSpy).toHaveBeenCalled(); - setTimeout(() => { - expect(imageUploadSuccessSpy).toHaveBeenCalled(); - evnArg.selectNode = [rteObj.element]; - (rteObj).imageModule.deleteImg(evnArg); - (rteObj).imageModule.uploadObj.upload((rteObj).imageModule.uploadObj.filesData[0]); - done(); - }, 4000); - }); - }); + // describe('Insert image imageSelected, imageUploading and imageUploadSuccess event - ', () => { + // let rteObj: RichTextEditor; + // let imageSelectedSpy: jasmine.Spy = jasmine.createSpy('onImageSelected'); + // let imageUploadingSpy: jasmine.Spy = jasmine.createSpy('onImageUploading'); + // let imageUploadSuccessSpy: jasmine.Spy = jasmine.createSpy('onImageUploadSuccess'); + // beforeEach((done: Function) => { + // rteObj = renderRTE({ + // imageSelected: imageSelectedSpy, + // imageUploading: imageUploadingSpy, + // imageUploadSuccess: imageUploadSuccessSpy, + // insertImageSettings: { + // saveUrl:"https://aspnetmvc.syncfusion.com/services/api/uploadbox/Save", + // path: "../Images/" + // } + // }); + // done(); + // }) + // afterEach((done: Function) => { + // destroy(rteObj); + // done(); + // }) + // it(' Test the component insert image events - case 1 ', (done) => { + // let rteEle: HTMLElement = rteObj.element; + // (rteObj.contentModule.getEditPanel() as HTMLElement).focus(); + // let args = { preventDefault: function () { } }; + // let range = new NodeSelection().getRange(document); + // let save = new NodeSelection().save(range, document); + // let evnArg = { args: MouseEvent, self: (rteObj).imageModule, selection: save, selectNode: new Array(), }; + // (rteEle.querySelectorAll(".e-toolbar-item button")[8] as HTMLElement).click(); + // let dialogEle: Element = rteObj.element.querySelector('.e-dialog'); + // (dialogEle.querySelector('.e-img-url') as HTMLInputElement).value = 'https://js.syncfusion.com/demos/web/content/images/accordion/baked-chicken-and-cheese.png'; + // (dialogEle.querySelector('.e-img-url') as HTMLInputElement).dispatchEvent(new Event("input")); + // let fileObj: File = new File(["Nice One"], "sample.png", { lastModified: 0, type: "overide/mimetype" }); + // let eventArgs = { type: 'click', target: { files: [fileObj] }, preventDefault: (): void => { } }; + // (rteObj).imageModule.uploadObj.onSelectFiles(eventArgs); + // expect(imageSelectedSpy).toHaveBeenCalled(); + // expect(imageUploadingSpy).toHaveBeenCalled(); + // setTimeout(() => { + // expect(imageUploadSuccessSpy).toHaveBeenCalled(); + // evnArg.selectNode = [rteObj.element]; + // (rteObj).imageModule.deleteImg(evnArg); + // (rteObj).imageModule.uploadObj.upload((rteObj).imageModule.uploadObj.filesData[0]); + // done(); + // }, 4000); + // }); + // }); describe('EJ2CORE-479 - Insert image imageSelected event args cancel true - ', () => { let rteObj: RichTextEditor; diff --git a/controls/richtexteditor/spec/rich-text-editor/renderer/video-module.spec.ts b/controls/richtexteditor/spec/rich-text-editor/renderer/video-module.spec.ts index a318a1bfc7..ffd964ae70 100644 --- a/controls/richtexteditor/spec/rich-text-editor/renderer/video-module.spec.ts +++ b/controls/richtexteditor/spec/rich-text-editor/renderer/video-module.spec.ts @@ -2170,44 +2170,44 @@ client side. Customer easy to edit the contents and get the HTML content for }, 4000); }); }); - describe('Disable the insert video dialog button when the video is uploading', () => { - let rteObj: RichTextEditor; - beforeEach((done: Function) => { - rteObj = renderRTE({ - toolbarSettings: { - items: ['Video'] - }, - insertVideoSettings: { - saveUrl: "https://ej2.syncfusion.com/services/api/uploadbox/Save", - path: "../Videos/" - } - }); - done(); - }) - afterEach((done: Function) => { - destroy(rteObj); - done(); - }) - it(' Button enabled with video upload Success', (done) => { - let rteEle: HTMLElement = rteObj.element; - (rteObj.contentModule.getEditPanel() as HTMLElement).focus(); - let args = { preventDefault: function () { } }; - let range = new NodeSelection().getRange(document); - let save = new NodeSelection().save(range, document); - let evnArg = { args: MouseEvent, self: (rteObj).videoModule, selection: save, selectNode: new Array(), }; - (rteEle.querySelectorAll(".e-toolbar-item button")[0] as HTMLElement).click(); - let dialogEle: Element = rteObj.element.querySelector('.e-dialog'); - (dialogEle.querySelector('.e-video-url-wrap input#webURL') as HTMLElement).click(); - (dialogEle.querySelector('.e-video-url') as HTMLInputElement).value = 'https://www.w3schools.com/html/mov_bbb.mp4'; - let fileObj: File = new File(["mov_bob"], "horse.mp4", { lastModified: 0, type: "overide/mimetype" }); - let eventArgs = { type: 'click', target: { files: [fileObj] }, preventDefault: (): void => { } }; - (rteObj).videoModule.uploadObj.onSelectFiles(eventArgs); - setTimeout(() => { - expect((dialogEle.querySelector('.e-insertVideo') as HTMLButtonElement).hasAttribute('disabled')).toBe(false); - done(); - }, 4000); - }); - }); + // describe('Disable the insert video dialog button when the video is uploading', () => { + // let rteObj: RichTextEditor; + // beforeEach((done: Function) => { + // rteObj = renderRTE({ + // toolbarSettings: { + // items: ['Video'] + // }, + // insertVideoSettings: { + // saveUrl: "https://ej2.syncfusion.com/services/api/uploadbox/Save", + // path: "../Videos/" + // } + // }); + // done(); + // }) + // afterEach((done: Function) => { + // destroy(rteObj); + // done(); + // }) + // it(' Button enabled with video upload Success', (done) => { + // let rteEle: HTMLElement = rteObj.element; + // (rteObj.contentModule.getEditPanel() as HTMLElement).focus(); + // let args = { preventDefault: function () { } }; + // let range = new NodeSelection().getRange(document); + // let save = new NodeSelection().save(range, document); + // let evnArg = { args: MouseEvent, self: (rteObj).videoModule, selection: save, selectNode: new Array(), }; + // (rteEle.querySelectorAll(".e-toolbar-item button")[0] as HTMLElement).click(); + // let dialogEle: Element = rteObj.element.querySelector('.e-dialog'); + // (dialogEle.querySelector('.e-video-url-wrap input#webURL') as HTMLElement).click(); + // (dialogEle.querySelector('.e-video-url') as HTMLInputElement).value = 'https://www.w3schools.com/html/mov_bbb.mp4'; + // let fileObj: File = new File(["mov_bob"], "horse.mp4", { lastModified: 0, type: "overide/mimetype" }); + // let eventArgs = { type: 'click', target: { files: [fileObj] }, preventDefault: (): void => { } }; + // (rteObj).videoModule.uploadObj.onSelectFiles(eventArgs); + // setTimeout(() => { + // expect((dialogEle.querySelector('.e-insertVideo') as HTMLButtonElement).hasAttribute('disabled')).toBe(false); + // done(); + // }, 4000); + // }); + // }); describe('Getting error while insert the video after applied the lower case or upper case commands in Html Editor - ', () => { let rteObj: RichTextEditor; let controlId: string; @@ -2386,51 +2386,51 @@ client side. Customer easy to edit the contents and get the HTML content for }); }); - describe('Rename videos in success event- ', () => { - let rteObj: RichTextEditor; - beforeEach((done: Function) => { - rteObj = renderRTE({ - fileUploadSuccess: function (args : any) { - args.file.name = 'rte_video'; - var filename : any = document.querySelectorAll(".e-file-name")[0]; - filename.innerHTML = args.file.name.replace(document.querySelectorAll(".e-file-type")[0].innerHTML, ''); - filename.title = args.file.name; - }, - insertVideoSettings: { - saveUrl:"https://aspnetmvc.syncfusion.com/services/api/uploadbox/Save", - path: "../Videos/" - }, - toolbarSettings: { - items: ['Video'] - }, - }); - done(); - }) - afterEach((done: Function) => { - destroy(rteObj); - done(); - }) - it('Check name after renamed', (done) => { - let rteEle: HTMLElement = rteObj.element; - (rteObj.contentModule.getEditPanel() as HTMLElement).focus(); - let args = { preventDefault: function () { } }; - let range = new NodeSelection().getRange(document); - let save = new NodeSelection().save(range, document); - let evnArg = { args: MouseEvent, self: (rteObj).videoModule, selection: save, selectNode: new Array(), }; - (rteEle.querySelectorAll(".e-toolbar-item button")[0] as HTMLElement).click(); - let dialogEle: Element = rteObj.element.querySelector('.e-dialog'); - (dialogEle.querySelector('.e-video-url-wrap input#webURL') as HTMLElement).click(); - (dialogEle.querySelector('.e-video-url') as HTMLInputElement).value = 'https://www.w3schools.com/html/mov_bbb.mp4'; - (dialogEle.querySelector('.e-video-url') as HTMLInputElement).dispatchEvent(new Event("input")); - let fileObj: File = new File(["mov_bob"], "mov_bob.mp4", { lastModified: 0, type: "overide/mimetype" }); - let eventArgs = { type: 'click', target: { files: [fileObj] }, preventDefault: (): void => { } }; - (rteObj).videoModule.uploadObj.onSelectFiles(eventArgs); - setTimeout(() => { - expect(document.querySelectorAll(".e-file-name")[0].innerHTML).toBe('rte_video'); - done(); - }, 4500); - }); - }); + // describe('Rename videos in success event- ', () => { + // let rteObj: RichTextEditor; + // beforeEach((done: Function) => { + // rteObj = renderRTE({ + // fileUploadSuccess: function (args : any) { + // args.file.name = 'rte_video'; + // var filename : any = document.querySelectorAll(".e-file-name")[0]; + // filename.innerHTML = args.file.name.replace(document.querySelectorAll(".e-file-type")[0].innerHTML, ''); + // filename.title = args.file.name; + // }, + // insertVideoSettings: { + // saveUrl:"https://aspnetmvc.syncfusion.com/services/api/uploadbox/Save", + // path: "../Videos/" + // }, + // toolbarSettings: { + // items: ['Video'] + // }, + // }); + // done(); + // }) + // afterEach((done: Function) => { + // destroy(rteObj); + // done(); + // }) + // it('Check name after renamed', (done) => { + // let rteEle: HTMLElement = rteObj.element; + // (rteObj.contentModule.getEditPanel() as HTMLElement).focus(); + // let args = { preventDefault: function () { } }; + // let range = new NodeSelection().getRange(document); + // let save = new NodeSelection().save(range, document); + // let evnArg = { args: MouseEvent, self: (rteObj).videoModule, selection: save, selectNode: new Array(), }; + // (rteEle.querySelectorAll(".e-toolbar-item button")[0] as HTMLElement).click(); + // let dialogEle: Element = rteObj.element.querySelector('.e-dialog'); + // (dialogEle.querySelector('.e-video-url-wrap input#webURL') as HTMLElement).click(); + // (dialogEle.querySelector('.e-video-url') as HTMLInputElement).value = 'https://www.w3schools.com/html/mov_bbb.mp4'; + // (dialogEle.querySelector('.e-video-url') as HTMLInputElement).dispatchEvent(new Event("input")); + // let fileObj: File = new File(["mov_bob"], "mov_bob.mp4", { lastModified: 0, type: "overide/mimetype" }); + // let eventArgs = { type: 'click', target: { files: [fileObj] }, preventDefault: (): void => { } }; + // (rteObj).videoModule.uploadObj.onSelectFiles(eventArgs); + // setTimeout(() => { + // expect(document.querySelectorAll(".e-file-name")[0].innerHTML).toBe('rte_video'); + // done(); + // }, 4500); + // }); + // }); describe('Inserting Video as Base64 - ', () => { let rteObj: RichTextEditor; @@ -2518,56 +2518,56 @@ client side. Customer easy to edit the contents and get the HTML content for }); }); - describe('Insert Video mediaSelected, mediaUploading and mediaUploadSuccess event - ', () => { - let rteObj: RichTextEditor; - let mediaSelectedSpy: jasmine.Spy = jasmine.createSpy('onFileSelected'); - let mediaUploadingSpy: jasmine.Spy = jasmine.createSpy('onFileUploading'); - let mediaUploadSuccessSpy: jasmine.Spy = jasmine.createSpy('onFileUploadSuccess'); - beforeEach((done: Function) => { - rteObj = renderRTE({ - fileSelected: mediaSelectedSpy, - fileUploading: mediaUploadingSpy, - fileUploadSuccess: mediaUploadSuccessSpy, - insertVideoSettings: { - saveUrl:"https://aspnetmvc.syncfusion.com/services/api/uploadbox/Save", - path: "../Videos/" - }, - toolbarSettings: { - items: ['Video'] - }, - }); - done(); - }) - afterEach((done: Function) => { - destroy(rteObj); - done(); - }) - it(' Test the component insert video events - case 1 ', (done) => { - let rteEle: HTMLElement = rteObj.element; - (rteObj.contentModule.getEditPanel() as HTMLElement).focus(); - let args = { preventDefault: function () { } }; - let range = new NodeSelection().getRange(document); - let save = new NodeSelection().save(range, document); - let evnArg = { args: MouseEvent, self: (rteObj).videoModule, selection: save, selectNode: new Array(), }; - (rteEle.querySelectorAll(".e-toolbar-item button")[0] as HTMLElement).click(); - let dialogEle: Element = rteObj.element.querySelector('.e-dialog'); - (dialogEle.querySelector('.e-video-url-wrap input#webURL') as HTMLElement).click(); - (dialogEle.querySelector('.e-video-url') as HTMLInputElement).value = 'https://www.w3schools.com/html/mov_bbb.mp4'; - (dialogEle.querySelector('.e-video-url') as HTMLInputElement).dispatchEvent(new Event("input")); - let fileObj: File = new File(["mov_bob"], "mov_bob.mp4", { lastModified: 0, type: "overide/mimetype" }); - let eventArgs = { type: 'click', target: { files: [fileObj] }, preventDefault: (): void => { } }; - (rteObj).videoModule.uploadObj.onSelectFiles(eventArgs); - expect(mediaSelectedSpy).toHaveBeenCalled(); - expect(mediaUploadingSpy).toHaveBeenCalled(); - setTimeout(() => { - expect(mediaUploadSuccessSpy).toHaveBeenCalled(); - evnArg.selectNode = [rteObj.element]; - (rteObj).videoModule.deleteVideo(evnArg); - (rteObj).videoModule.uploadObj.upload((rteObj).videoModule.uploadObj.filesData[0]); - done(); - }, 4000); - }); - }); + // describe('Insert Video mediaSelected, mediaUploading and mediaUploadSuccess event - ', () => { + // let rteObj: RichTextEditor; + // let mediaSelectedSpy: jasmine.Spy = jasmine.createSpy('onFileSelected'); + // let mediaUploadingSpy: jasmine.Spy = jasmine.createSpy('onFileUploading'); + // let mediaUploadSuccessSpy: jasmine.Spy = jasmine.createSpy('onFileUploadSuccess'); + // beforeEach((done: Function) => { + // rteObj = renderRTE({ + // fileSelected: mediaSelectedSpy, + // fileUploading: mediaUploadingSpy, + // fileUploadSuccess: mediaUploadSuccessSpy, + // insertVideoSettings: { + // saveUrl:"https://aspnetmvc.syncfusion.com/services/api/uploadbox/Save", + // path: "../Videos/" + // }, + // toolbarSettings: { + // items: ['Video'] + // }, + // }); + // done(); + // }) + // afterEach((done: Function) => { + // destroy(rteObj); + // done(); + // }) + // it(' Test the component insert video events - case 1 ', (done) => { + // let rteEle: HTMLElement = rteObj.element; + // (rteObj.contentModule.getEditPanel() as HTMLElement).focus(); + // let args = { preventDefault: function () { } }; + // let range = new NodeSelection().getRange(document); + // let save = new NodeSelection().save(range, document); + // let evnArg = { args: MouseEvent, self: (rteObj).videoModule, selection: save, selectNode: new Array(), }; + // (rteEle.querySelectorAll(".e-toolbar-item button")[0] as HTMLElement).click(); + // let dialogEle: Element = rteObj.element.querySelector('.e-dialog'); + // (dialogEle.querySelector('.e-video-url-wrap input#webURL') as HTMLElement).click(); + // (dialogEle.querySelector('.e-video-url') as HTMLInputElement).value = 'https://www.w3schools.com/html/mov_bbb.mp4'; + // (dialogEle.querySelector('.e-video-url') as HTMLInputElement).dispatchEvent(new Event("input")); + // let fileObj: File = new File(["mov_bob"], "mov_bob.mp4", { lastModified: 0, type: "overide/mimetype" }); + // let eventArgs = { type: 'click', target: { files: [fileObj] }, preventDefault: (): void => { } }; + // (rteObj).videoModule.uploadObj.onSelectFiles(eventArgs); + // expect(mediaSelectedSpy).toHaveBeenCalled(); + // expect(mediaUploadingSpy).toHaveBeenCalled(); + // setTimeout(() => { + // expect(mediaUploadSuccessSpy).toHaveBeenCalled(); + // evnArg.selectNode = [rteObj.element]; + // (rteObj).videoModule.deleteVideo(evnArg); + // (rteObj).videoModule.uploadObj.upload((rteObj).videoModule.uploadObj.filesData[0]); + // done(); + // }, 4000); + // }); + // }); describe('Insert video mediaSelected event args cancel true - ', () => { let rteObj: RichTextEditor; diff --git a/controls/richtexteditor/src/editor-manager/plugin/lists.ts b/controls/richtexteditor/src/editor-manager/plugin/lists.ts index ee9256fbf4..044801f0aa 100644 --- a/controls/richtexteditor/src/editor-manager/plugin/lists.ts +++ b/controls/richtexteditor/src/editor-manager/plugin/lists.ts @@ -745,21 +745,33 @@ export class Lists { private cleanNode(): void { const liParents: Element[] = >this.parent.editableElement.querySelectorAll('ol + ol, ul + ul'); + let listStyleType: string; + let firstNodeOL: Element; for (let c: number = 0; c < liParents.length; c++) { const node: Element = liParents[c as number]; - if (this.domNode.isList(node.previousElementSibling as Element) && - this.domNode.openTagString(node) === this.domNode.openTagString(node.previousElementSibling as Element)) { + let toFindtopOlUl: boolean = true; + if (toFindtopOlUl && (liParents[c as number].parentElement.parentElement.nodeName === 'OL' || liParents[c as number].parentElement.parentElement.nodeName === 'UL')) { + toFindtopOlUl = false; + const preElement: HTMLElement = liParents[c as number].parentElement.parentElement; + listStyleType = preElement.style.listStyleType; + firstNodeOL = node.previousElementSibling; + } + if (this.domNode.isList(node.previousElementSibling as Element) && + this.domNode.openTagString(node) === this.domNode.openTagString(node.previousElementSibling as Element)) { const contentNodes: Node[] = this.domNode.contents(node); for (let f: number = 0; f < contentNodes.length; f++) { - node.previousElementSibling .appendChild(contentNodes[f as number]); + node.previousElementSibling.appendChild(contentNodes[f as number]); } node.parentNode.removeChild(node); - } else if (!isNOU(node.getAttribute('level'))){ - if (node.tagName === node.previousElementSibling.tagName){ + } else if (!isNOU(node.getAttribute('level'))) { + if (node.tagName === node.previousElementSibling.tagName) { (node.previousElementSibling.lastChild as HTMLElement).append(node); } } } + if (firstNodeOL) { + (firstNodeOL as HTMLElement).style.listStyleType = listStyleType; + } } private findUnSelected(temp: HTMLElement[], elements: HTMLElement[]): void { temp = temp.slice().reverse(); diff --git a/controls/richtexteditor/src/editor-manager/plugin/ms-word-clean-up.ts b/controls/richtexteditor/src/editor-manager/plugin/ms-word-clean-up.ts index 1d5b6b7c76..24a31c40c0 100644 --- a/controls/richtexteditor/src/editor-manager/plugin/ms-word-clean-up.ts +++ b/controls/richtexteditor/src/editor-manager/plugin/ms-word-clean-up.ts @@ -51,7 +51,7 @@ export class MsWordPaste { 'x', 'xi', 'xii', 'xiii', 'xiv', 'xv', 'xvi', 'xvii', 'xviii', 'xix', 'xx']; private lowerGreekNumber: string[] = ['α', 'β', 'γ', 'δ', 'ε', 'ζ', 'η', 'θ', 'ι', 'κ', 'λ', 'μ', 'ν', 'ξ', 'ο', 'π', 'ρ', 'σ', 'τ', 'υ', 'φ', 'χ', 'ψ', 'ω']; - private removableElements: string[] = ['o:p', 'style']; + private removableElements: string[] = ['o:p', 'style', 'w:sdt']; private listContents: string[] = []; private addEventListener(): void { this.parent.observer.on(EVENTS.MS_WORD_CLEANUP_PLUGIN, this.wordCleanup, this); @@ -422,7 +422,7 @@ export class MsWordPaste { let innerElement: string = elm.innerHTML; for (let i: number = 0; i < this.removableElements.length; i++) { // eslint-disable-next-line security/detect-non-literal-regexp - const regExpStartElem: RegExp = new RegExp('<' + this.removableElements[i as number] + '>', 'g'); + const regExpStartElem: RegExp = new RegExp('<' + this.removableElements[i as number] + '\\s*[^>]*>', 'g'); // eslint-disable-next-line security/detect-non-literal-regexp const regExpEndElem: RegExp = new RegExp('', 'g'); innerElement = innerElement.replace(regExpStartElem, ''); diff --git a/controls/richtexteditor/src/editor-manager/plugin/selection-commands.ts b/controls/richtexteditor/src/editor-manager/plugin/selection-commands.ts index b0c016cf0b..3bb8ed13d8 100644 --- a/controls/richtexteditor/src/editor-manager/plugin/selection-commands.ts +++ b/controls/richtexteditor/src/editor-manager/plugin/selection-commands.ts @@ -288,11 +288,15 @@ export class SelectionCommands { && range.endOffset === (range.startContainer as Text).length)) { const nodeIndex: number[] = []; let cloneNode: Node = nodes[index as number]; + const clonedElement:Node = cloneNode; do { nodeIndex.push(domSelection.getIndex(cloneNode)); cloneNode = cloneNode.parentNode; } while (cloneNode && (cloneNode !== formatNode)); if (nodes[index as number].nodeName !== 'BR') { + if(clonedElement.nodeName === '#text' && clonedElement.textContent.includes('\u200B')) { + (clonedElement as Element).remove(); + } cloneNode = splitNode = (isCursor && (formatNode.textContent.length - 1) === range.startOffset) ? nodeCutter.SplitNode(range, formatNode as HTMLElement, true) as HTMLElement : nodeCutter.GetSpliceNode(range, formatNode as HTMLElement) as HTMLElement; diff --git a/controls/richtexteditor/src/rich-text-editor/base/rich-text-editor.ts b/controls/richtexteditor/src/rich-text-editor/base/rich-text-editor.ts index 7cc6ac7c18..f77cc30c32 100644 --- a/controls/richtexteditor/src/rich-text-editor/base/rich-text-editor.ts +++ b/controls/richtexteditor/src/rich-text-editor/base/rich-text-editor.ts @@ -3565,9 +3565,7 @@ export class RichTextEditor extends Component implements INotifyPro }, 0); } else if (this.iframeSettings.enable) { const iframeElement: HTMLIFrameElement = this.element.querySelector('#' + this.getID() + '_rte-view'); - setTimeout(() => { - this.setAutoHeight(iframeElement); - }, 100); + this.setAutoHeight(iframeElement); this.inputElement.style.overflow = 'hidden'; } } else { diff --git a/controls/richtexteditor/src/rich-text-editor/renderer/toolbar-renderer.ts b/controls/richtexteditor/src/rich-text-editor/renderer/toolbar-renderer.ts index 3a6ab6140b..54b07e9151 100644 --- a/controls/richtexteditor/src/rich-text-editor/renderer/toolbar-renderer.ts +++ b/controls/richtexteditor/src/rich-text-editor/renderer/toolbar-renderer.ts @@ -479,8 +479,6 @@ export class ToolbarRenderer implements IRenderer { target: colorPicker.element.parentElement, cssClass: css, enablePersistence: this.parent.enablePersistence, enableRtl: this.parent.enableRtl, beforeOpen: (dropDownArgs: BeforeOpenCloseMenuEventArgs): void => { - colorPicker.inline = true; - colorPicker.dataBind(); if (proxy.parent.readonly || !proxy.parent.enabled) { dropDownArgs.cancel = true; return; } @@ -607,14 +605,18 @@ export class ToolbarRenderer implements IRenderer { const colorPicker: ColorPicker = new ColorPicker({ enablePersistence: this.parent.enablePersistence, enableRtl: this.parent.enableRtl, - inline: false, - value: '#fff', + inline: true, + value: null, + cssClass : ((item === 'backgroundcolor') ? CLS_BACKGROUND_COLOR_PICKER : CLS_FONT_COLOR_PICKER) + ' ' + args.cssClass + ' ' + 'e-rte-picker-init', created: () => { const value: string = (item === 'backgroundcolor') ? proxy.parent.backgroundColor.default : proxy.parent.fontColor.default; - colorPicker.setProperties({ value: value }); + colorPicker.cssClass = ((item === 'backgroundcolor') ? CLS_BACKGROUND_COLOR_PICKER : CLS_FONT_COLOR_PICKER) + ' ' + args.cssClass; + colorPicker.value = value; }, mode: ((item === 'backgroundcolor') ? proxy.parent.backgroundColor.mode : proxy.parent.fontColor.mode), modeSwitcher: ((item === 'backgroundcolor') ? proxy.parent.backgroundColor.modeSwitcher : proxy.parent.fontColor.modeSwitcher), + presetColors: (item === 'backgroundcolor') ? this.parent.backgroundColor.colorCode : this.parent.fontColor.colorCode, + columns: (item === 'backgroundcolor') ? this.parent.backgroundColor.columns : this.parent.fontColor.columns, beforeTileRender: (args: PaletteTileEventArgs) => { args.element.classList.add(CLS_COLOR_PALETTE); args.element.classList.add(CLS_CUSTOM_TILE); @@ -658,10 +660,6 @@ export class ToolbarRenderer implements IRenderer { } }); colorPicker.isStringTemplate = true; - colorPicker.columns = (item === 'backgroundcolor') ? this.parent.backgroundColor.columns : this.parent.fontColor.columns; - colorPicker.presetColors = (item === 'backgroundcolor') ? this.parent.backgroundColor.colorCode : - this.parent.fontColor.colorCode; - colorPicker.cssClass = ((item === 'backgroundcolor') ? CLS_BACKGROUND_COLOR_PICKER : CLS_FONT_COLOR_PICKER) + ' ' + args.cssClass; colorPicker.createElement = this.parent.createElement; colorPicker.appendTo(document.getElementById(args.target) as HTMLElement); return colorPicker; diff --git a/controls/richtexteditor/styles/rich-text-editor/_layout.scss b/controls/richtexteditor/styles/rich-text-editor/_layout.scss index 46e0516aff..e27e5e354f 100644 --- a/controls/richtexteditor/styles/rich-text-editor/_layout.scss +++ b/controls/richtexteditor/styles/rich-text-editor/_layout.scss @@ -2130,10 +2130,6 @@ box-shadow: $rte-table-popup-box; } - .e-rte-elements.e-rte-quick-popup .e-toolbar .e-toolbar-items.e-toolbar-multirow:not(.e-tbar-pos) .e-toolbar-item:first-child { - margin-left: 6px; - } - .e-rte-elements.e-rte-quick-popup .e-toolbar .e-toolbar-items .e-toolbar-item .e-tbar-btn.e-btn .e-icons.e-btn-icon { min-width: 24px; } @@ -2255,4 +2251,8 @@ display: block; } } + // Used to hide the Color picker during the rendering at the initial stage. Removed the class in the created event. + .e-rte-picker-init { + display: none !important; /* stylelint-disable-line declaration-no-important */ + } } diff --git a/controls/schedule/CHANGELOG.md b/controls/schedule/CHANGELOG.md index 5cfa68676c..0f8d33df7d 100644 --- a/controls/schedule/CHANGELOG.md +++ b/controls/schedule/CHANGELOG.md @@ -2,6 +2,15 @@ ## [Unreleased] +## 25.1.37 (2024-03-26) + +### Schedule + +#### Bug fixes + +- `#I566544` - An issue with `aria-labelledby` accessibility issue in the recurrence editor has been fixed. +- `#I549259` - An issue where the weekend dates are rendered in the Agenda view after setting up the `showWeekend` property to `false` has been fixed. + ## 25.1.35 (2024-03-15) ### Schedule diff --git a/controls/schedule/package.json b/controls/schedule/package.json index 0f0f380df0..ff56fd71e3 100644 --- a/controls/schedule/package.json +++ b/controls/schedule/package.json @@ -1,6 +1,6 @@ { "name": "@syncfusion/ej2-schedule", - "version": "22.9.0", + "version": "25.1.35", "author": "Syncfusion Inc.", "license": "SEE LICENSE IN license", "main": "./dist/ej2-schedule.umd.min.js", diff --git a/controls/schedule/spec/schedule/renderer/agenda.spec.ts b/controls/schedule/spec/schedule/renderer/agenda.spec.ts index 0d2ec6efe2..112d52c35a 100644 --- a/controls/schedule/spec/schedule/renderer/agenda.spec.ts +++ b/controls/schedule/spec/schedule/renderer/agenda.spec.ts @@ -1242,6 +1242,312 @@ describe('Agenda View', () => { }); }); + describe('ES-870019 - Hide/Show weekend and non working days based on showWeekend', () => { + let schObj: Schedule; + beforeAll((done: DoneFn) => { + const schOptions: ScheduleModel = { + height: '500px', selectedDate: new Date(2017, 10, 6), + views: [{ option: 'Agenda' }], showWeekend: false + }; + schObj = createSchedule(schOptions, defaultData, done); + }); + afterAll(() => { + destroy(schObj); + }); + it('Checking initial rendering', () => { + expect(schObj.element.querySelector('.e-date-range').firstElementChild.textContent).toEqual('November 06 - 12, 2017'); + expect(schObj.element.querySelector('.e-agenda-cells[data-date="1510358400000"]')).toBeFalsy(); + expect(schObj.element.querySelector('.e-agenda-cells[data-date="1510444800000"]')).toBeFalsy(); + }); + + it('Checking previous navigation rendering', (done: DoneFn) => { + schObj.dataBound = () => { + expect(schObj.element.querySelector('.e-date-range').firstElementChild.textContent).toEqual('November 05 - 11, 2017'); + expect(schObj.element.querySelector('.e-agenda-cells[data-date="1509840000000"]')).toBeFalsy(); + expect(schObj.element.querySelector('.e-agenda-cells[data-date="1510358400000"]')).toBeFalsy(); + done(); + }; + const prevEle: HTMLElement = schObj.element.querySelector('.e-prev') as HTMLElement; + prevEle.click(); + }); + + it('Checking next navigation rendering', (done: DoneFn) => { + schObj.dataBound = () => { + expect(schObj.element.querySelector('.e-date-range').firstElementChild.textContent).toEqual('November 06 - 12, 2017'); + expect(schObj.element.querySelector('.e-agenda-cells[data-date="1510358400000"]')).toBeFalsy(); + expect(schObj.element.querySelector('.e-agenda-cells[data-date="1510444800000"]')).toBeFalsy(); + done(); + }; + const nextEle: HTMLElement = schObj.element.querySelector('.e-next') as HTMLElement; + nextEle.click(); + }); + + it('Checking showWeekend true rendering', (done: DoneFn) => { + schObj.dataBound = () => { + expect(schObj.element.querySelector('.e-date-range').firstElementChild.textContent).toEqual('November 06 - 12, 2017'); + expect(schObj.element.querySelector('.e-agenda-cells[data-date="1510358400000"]')).toBeTruthy(); + expect(schObj.element.querySelector('.e-agenda-cells[data-date="1510444800000"]')).toBeTruthy(); + done(); + }; + schObj.showWeekend = true; + schObj.dataBind(); + }); + + it('Checking previous navigation rendering showWeekend true', (done: DoneFn) => { + schObj.dataBound = () => { + expect(schObj.element.querySelector('.e-date-range').firstElementChild.textContent).toEqual('November 05 - 11, 2017'); + expect(schObj.element.querySelector('.e-agenda-cells[data-date="1509840000000"]')).toBeTruthy(); + expect(schObj.element.querySelector('.e-agenda-cells[data-date="1510358400000"]')).toBeTruthy(); + done(); + }; + const prevEle: HTMLElement = schObj.element.querySelector('.e-prev') as HTMLElement; + prevEle.click(); + }); + + it('Checking showWeekend with next navigation rendering showWeekend true', (done: DoneFn) => { + schObj.dataBound = () => { + expect(schObj.element.querySelector('.e-date-range').firstElementChild.textContent).toEqual('November 06 - 12, 2017'); + expect(schObj.element.querySelector('.e-agenda-cells[data-date="1510358400000"]')).toBeTruthy(); + expect(schObj.element.querySelector('.e-agenda-cells[data-date="1510444800000"]')).toBeTruthy(); + done(); + }; + const nextEle: HTMLElement = schObj.element.querySelector('.e-next') as HTMLElement; + nextEle.click(); + }); + + it('Checking rendering with virtual scrolling and showWeekend true', (done: DoneFn) => { + schObj.dataBound = () => { + expect(schObj.element.querySelector('.e-date-range').firstElementChild.textContent).toEqual('November 2017'); + expect(schObj.element.querySelector('.e-agenda-cells[data-date="1510358400000"]')).toBeTruthy(); + expect(schObj.element.querySelector('.e-agenda-cells[data-date="1510444800000"]')).toBeTruthy(); + done(); + }; + schObj.views = [{ option: 'Agenda', allowVirtualScrolling: true }]; + schObj.dataBind(); + }); + + it('Checking previous navigation with virtual scrolling and showWeekend true', () => { + const scrollUp: HTMLElement = schObj.element.querySelector('.e-content-wrap') as HTMLElement; + triggerScrollEvent(scrollUp, 0); + expect(schObj.element.querySelector('.e-date-range').firstElementChild.textContent).toEqual('November 2017'); + expect(schObj.element.querySelector('.e-agenda-cells[data-date="1509753600000"]')).toBeTruthy(); + expect(schObj.element.querySelector('.e-agenda-cells[data-date="1509840000000"]')).toBeTruthy(); + expect(schObj.element.querySelector('.e-agenda-cells[data-date="1510358400000"]')).toBeTruthy(); + expect(schObj.element.querySelector('.e-agenda-cells[data-date="1510444800000"]')).toBeTruthy(); + }); + + it('Checking next navigation with virtual scrolling and showWeekend true', () => { + const scrollUp: HTMLElement = schObj.element.querySelector('.e-content-wrap') as HTMLElement; + triggerScrollEvent(scrollUp, scrollUp.scrollHeight); + expect(schObj.element.querySelector('.e-date-range').firstElementChild.textContent).toEqual('November 2017'); + expect(schObj.element.querySelector('.e-agenda-cells[data-date="1509753600000"]')).toBeTruthy(); + expect(schObj.element.querySelector('.e-agenda-cells[data-date="1509840000000"]')).toBeTruthy(); + expect(schObj.element.querySelector('.e-agenda-cells[data-date="1510358400000"]')).toBeTruthy(); + expect(schObj.element.querySelector('.e-agenda-cells[data-date="1510444800000"]')).toBeTruthy(); + expect(schObj.element.querySelector('.e-agenda-cells[data-date="1510963200000"]')).toBeTruthy(); + }); + + it('Checking rendering with virtual scrolling and showWeekend false', (done: DoneFn) => { + schObj.dataBound = () => { + expect(schObj.element.querySelector('.e-date-range').firstElementChild.textContent).toEqual('November 2017'); + expect(schObj.element.querySelector('.e-agenda-cells[data-date="1510358400000"]')).toBeFalsy(); + expect(schObj.element.querySelector('.e-agenda-cells[data-date="1510444800000"]')).toBeFalsy(); + done(); + }; + schObj.showWeekend = false; + schObj.dataBind(); + }); + + it('Checking previous navigation with virtual scrolling and showWeekend false', () => { + const scrollUp: HTMLElement = schObj.element.querySelector('.e-content-wrap') as HTMLElement; + triggerScrollEvent(scrollUp, 0); + expect(schObj.element.querySelector('.e-date-range').firstElementChild.textContent).toEqual('November 2017'); + expect(schObj.element.querySelector('.e-agenda-cells[data-date="1509753600000"]')).toBeFalsy(); + expect(schObj.element.querySelector('.e-agenda-cells[data-date="1509840000000"]')).toBeFalsy(); + expect(schObj.element.querySelector('.e-agenda-cells[data-date="1510358400000"]')).toBeFalsy(); + expect(schObj.element.querySelector('.e-agenda-cells[data-date="1510444800000"]')).toBeFalsy(); + }); + + it('Checking next navigation with virtual scrolling and showWeekend false', () => { + const scrollUp: HTMLElement = schObj.element.querySelector('.e-content-wrap') as HTMLElement; + triggerScrollEvent(scrollUp, scrollUp.scrollHeight); + expect(schObj.element.querySelector('.e-date-range').firstElementChild.textContent).toEqual('November 2017'); + expect(schObj.element.querySelector('.e-agenda-cells[data-date="1509753600000"]')).toBeFalsy(); + expect(schObj.element.querySelector('.e-agenda-cells[data-date="1509840000000"]')).toBeFalsy(); + expect(schObj.element.querySelector('.e-agenda-cells[data-date="1510358400000"]')).toBeFalsy(); + expect(schObj.element.querySelector('.e-agenda-cells[data-date="1510444800000"]')).toBeFalsy(); + expect(schObj.element.querySelector('.e-agenda-cells[data-date="1510963200000"]')).toBeFalsy(); + }); + }); + + describe('ES-870019 - Hide/Show weekend and non working days based on showWeekend for resources', () => { + let schObj: Schedule; + const eventData: Record[] = [{ + Id: 1, + Subject: 'Work week appointment', + StartTime: new Date(2017, 0, 1, 10), + EndTime: new Date(2017, 0, 1, 12), + RecurrenceRule: 'FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR;INTERVAL=1;UNTIL=20181231T033000Z;', + OwnerId: [1, 2, 3] + }, { + Id: 2, + Subject: 'Week end appointment', + StartTime: new Date(2017, 0, 1, 10), + EndTime: new Date(2017, 0, 1, 12), + RecurrenceRule: 'FREQ=WEEKLY;BYDAY=SU,SA;INTERVAL=1;UNTIL=20181231T033000Z;', + OwnerId: [1, 2, 3] + }]; + beforeAll((done: DoneFn) => { + const schOptions: ScheduleModel = { + width: '100%', height: '500px', currentView: 'Agenda', + views: [{ option: 'Agenda' }], + selectedDate: new Date(2017, 10, 6), + showWeekend: false, + group: { + resources: ['Owners'] + }, + resources: [{ + field: 'OwnerId', title: 'Owner', name: 'Owners', allowMultiple: true, + dataSource: [ + { OwnerText: 'Nancy', Id: 1, OwnerGroupId: 1, OwnerColor: '#ffaa00' }, + { OwnerText: 'Steven', Id: 2, OwnerGroupId: 2, OwnerColor: '#f8a398' }, + { OwnerText: 'Michael', Id: 3, OwnerGroupId: 1, OwnerColor: '#7499e1' } + ], + textField: 'OwnerText', idField: 'Id', groupIDField: 'OwnerGroupId', colorField: 'OwnerColor' + }], + }; + schObj = createSchedule(schOptions, eventData, done); + }); + afterAll(() => { + destroy(schObj); + }); + + it('Checking initial rendering', () => { + expect(schObj.element.querySelector('.e-date-range').firstElementChild.textContent).toEqual('November 06 - 12, 2017'); + expect(schObj.element.querySelector('.e-agenda-cells[data-date="1510358400000"]')).toBeFalsy(); + expect(schObj.element.querySelector('.e-agenda-cells[data-date="1510444800000"]')).toBeFalsy(); + }); + + it('Checking previous navigation rendering', (done: DoneFn) => { + schObj.dataBound = () => { + expect(schObj.element.querySelector('.e-date-range').firstElementChild.textContent).toEqual('November 05 - 11, 2017'); + expect(schObj.element.querySelector('.e-agenda-cells[data-date="1509840000000"]')).toBeFalsy(); + expect(schObj.element.querySelector('.e-agenda-cells[data-date="1510358400000"]')).toBeFalsy(); + done(); + }; + const prevEle: HTMLElement = schObj.element.querySelector('.e-prev') as HTMLElement; + prevEle.click(); + }); + + it('Checking next navigation rendering', (done: DoneFn) => { + schObj.dataBound = () => { + expect(schObj.element.querySelector('.e-date-range').firstElementChild.textContent).toEqual('November 06 - 12, 2017'); + expect(schObj.element.querySelector('.e-agenda-cells[data-date="1510358400000"]')).toBeFalsy(); + expect(schObj.element.querySelector('.e-agenda-cells[data-date="1510444800000"]')).toBeFalsy(); + done(); + }; + const nextEle: HTMLElement = schObj.element.querySelector('.e-next') as HTMLElement; + nextEle.click(); + }); + + it('Checking showWeekend true rendering', (done: DoneFn) => { + schObj.dataBound = () => { + expect(schObj.element.querySelector('.e-date-range').firstElementChild.textContent).toEqual('November 06 - 12, 2017'); + expect(schObj.element.querySelector('.e-agenda-cells[data-date="1510358400000"]')).toBeTruthy(); + expect(schObj.element.querySelector('.e-agenda-cells[data-date="1510444800000"]')).toBeTruthy(); + done(); + }; + schObj.showWeekend = true; + schObj.dataBind(); + }); + + it('Checking previous navigation rendering showWeekend true', (done: DoneFn) => { + schObj.dataBound = () => { + expect(schObj.element.querySelector('.e-date-range').firstElementChild.textContent).toEqual('November 05 - 11, 2017'); + expect(schObj.element.querySelector('.e-agenda-cells[data-date="1509840000000"]')).toBeTruthy(); + expect(schObj.element.querySelector('.e-agenda-cells[data-date="1510358400000"]')).toBeTruthy(); + done(); + }; + const prevEle: HTMLElement = schObj.element.querySelector('.e-prev') as HTMLElement; + prevEle.click(); + }); + + it('Checking showWeekend with next navigation rendering showWeekend true', (done: DoneFn) => { + schObj.dataBound = () => { + expect(schObj.element.querySelector('.e-date-range').firstElementChild.textContent).toEqual('November 06 - 12, 2017'); + expect(schObj.element.querySelector('.e-agenda-cells[data-date="1510358400000"]')).toBeTruthy(); + expect(schObj.element.querySelector('.e-agenda-cells[data-date="1510444800000"]')).toBeTruthy(); + done(); + }; + const nextEle: HTMLElement = schObj.element.querySelector('.e-next') as HTMLElement; + nextEle.click(); + }); + + it('Checking rendering with virtual scrolling, showWeekend and group byDate true', (done: DoneFn) => { + schObj.dataBound = () => { + expect(schObj.element.querySelector('.e-date-range').firstElementChild.textContent).toEqual('November 2017'); + expect(schObj.element.querySelector('.e-agenda-cells[data-date="1510358400000"]')).toBeTruthy(); + expect(schObj.element.querySelector('.e-agenda-cells[data-date="1510444800000"]')).toBeTruthy(); + done(); + }; + schObj.views = [{ option: 'Agenda', allowVirtualScrolling: true }]; + schObj.group.byDate = true; + schObj.dataBind(); + }); + + it('Checking previous navigation with virtual scrolling, showWeekend and group byDate true', () => { + const scrollUp: HTMLElement = schObj.element.querySelector('.e-content-wrap') as HTMLElement; + triggerScrollEvent(scrollUp, 0); + expect(schObj.element.querySelector('.e-date-range').firstElementChild.textContent).toEqual('November 2017'); + // expect(schObj.element.querySelector('.e-agenda-cells[data-date="1509753600000"]')).toBeTruthy(); + expect(schObj.element.querySelector('.e-agenda-cells[data-date="1509840000000"]')).toBeTruthy(); + expect(schObj.element.querySelector('.e-agenda-cells[data-date="1510358400000"]')).toBeTruthy(); + expect(schObj.element.querySelector('.e-agenda-cells[data-date="1510444800000"]')).toBeTruthy(); + }); + + it('Checking next navigation with virtual scrolling and showWeekend true', () => { + const scrollUp: HTMLElement = schObj.element.querySelector('.e-content-wrap') as HTMLElement; + triggerScrollEvent(scrollUp, scrollUp.scrollHeight); + expect(schObj.element.querySelector('.e-date-range').firstElementChild.textContent).toEqual('November 2017'); + // expect(schObj.element.querySelector('.e-agenda-cells[data-date="1509753600000"]')).toBeTruthy(); + expect(schObj.element.querySelector('.e-agenda-cells[data-date="1509840000000"]')).toBeTruthy(); + expect(schObj.element.querySelector('.e-agenda-cells[data-date="1510358400000"]')).toBeTruthy(); + expect(schObj.element.querySelector('.e-agenda-cells[data-date="1510444800000"]')).toBeTruthy(); + expect(schObj.element.querySelector('.e-agenda-cells[data-date="1510963200000"]')).toBeTruthy(); + }); + + it('Checking rendering with virtual scrolling and showWeekend false', (done: DoneFn) => { + schObj.dataBound = () => { + expect(schObj.element.querySelector('.e-date-range').firstElementChild.textContent).toEqual('November 2017'); + expect(schObj.element.querySelector('.e-agenda-cells[data-date="1510358400000"]')).toBeFalsy(); + expect(schObj.element.querySelector('.e-agenda-cells[data-date="1510444800000"]')).toBeFalsy(); + done(); + }; + schObj.showWeekend = false; + schObj.dataBind(); + }); + + it('Checking previous navigation with virtual scrolling and showWeekend false', () => { + const scrollUp: HTMLElement = schObj.element.querySelector('.e-content-wrap') as HTMLElement; + triggerScrollEvent(scrollUp, 0); + expect(schObj.element.querySelector('.e-date-range').firstElementChild.textContent).toEqual('November 2017'); + expect(schObj.element.querySelector('.e-agenda-cells[data-date="1509753600000"]')).toBeFalsy(); + expect(schObj.element.querySelector('.e-agenda-cells[data-date="1509840000000"]')).toBeFalsy(); + expect(schObj.element.querySelector('.e-agenda-cells[data-date="1510358400000"]')).toBeFalsy(); + expect(schObj.element.querySelector('.e-agenda-cells[data-date="1510444800000"]')).toBeFalsy(); + }); + + it('Checking next navigation with virtual scrolling and showWeekend false', () => { + const scrollUp: HTMLElement = schObj.element.querySelector('.e-content-wrap') as HTMLElement; + triggerScrollEvent(scrollUp, scrollUp.scrollHeight); + expect(schObj.element.querySelector('.e-date-range').firstElementChild.textContent).toEqual('November 2017'); + expect(schObj.element.querySelector('.e-agenda-cells[data-date="1509753600000"]')).toBeFalsy(); + expect(schObj.element.querySelector('.e-agenda-cells[data-date="1509840000000"]')).toBeFalsy(); + expect(schObj.element.querySelector('.e-agenda-cells[data-date="1510358400000"]')).toBeFalsy(); + expect(schObj.element.querySelector('.e-agenda-cells[data-date="1510444800000"]')).toBeFalsy(); + expect(schObj.element.querySelector('.e-agenda-cells[data-date="1510963200000"]')).toBeFalsy(); + }); + }); it('memory leak', () => { profile.sample(); diff --git a/controls/schedule/src/recurrence-editor/recurrence-editor.ts b/controls/schedule/src/recurrence-editor/recurrence-editor.ts index bd0df5831d..6c282c1ba0 100644 --- a/controls/schedule/src/recurrence-editor/recurrence-editor.ts +++ b/controls/schedule/src/recurrence-editor/recurrence-editor.ts @@ -804,7 +804,6 @@ export class RecurrenceEditor extends Component implements INotifyP private setTemplate(): void { const dayData: { [key: string]: string }[] = this.getDayData('narrow'); const fullDay: { [key: string]: string }[] = this.getDayData('wide'); - const labelId: string = this.element.id + '_' + 'end_label'; this.element.innerHTML = '
' + '
' + '' + '
' + - '' + + '' + '
' + '
' + '' + diff --git a/controls/schedule/src/schedule/actions/crud.ts b/controls/schedule/src/schedule/actions/crud.ts index 84e34bc565..569337eff3 100644 --- a/controls/schedule/src/schedule/actions/crud.ts +++ b/controls/schedule/src/schedule/actions/crud.ts @@ -56,6 +56,7 @@ export class Crud { private dataManagerSuccess(e: ReturnType): void { if (!this.parent || this.parent && this.parent.isDestroyed) { return; } this.parent.trigger(events.dataBinding, e, (args: ReturnType) => { + if (args.cancel) { return; } const resultData: Record[] = extend([], args.result, null, true) as Record[]; this.parent.eventsData = resultData.filter((data: Record) => !data[this.parent.eventFields.isBlock]); this.parent.blockData = resultData.filter((data: Record) => data[this.parent.eventFields.isBlock]); diff --git a/controls/schedule/src/schedule/base/css-constant.ts b/controls/schedule/src/schedule/base/css-constant.ts index 61d1e5ee16..bdb4f02d9b 100644 --- a/controls/schedule/src/schedule/base/css-constant.ts +++ b/controls/schedule/src/schedule/base/css-constant.ts @@ -122,6 +122,8 @@ export const TIMELINE_WRAPPER_CLASS: string = 'e-timeline-wrapper'; /** @private */ export const APPOINTMENT_WRAPPER_CLASS: string = 'e-appointment-wrapper'; /** @private */ +export const APPOINTMENT_WRAPPER_HIDDEN_CLASS: string = 'e-appointment-wrapper-hidden'; +/** @private */ export const DAY_WRAPPER_CLASS: string = 'e-day-wrapper'; /** @private */ export const TOOLBAR_CONTAINER: string = 'e-schedule-toolbar-container'; diff --git a/controls/schedule/src/schedule/base/type.ts b/controls/schedule/src/schedule/base/type.ts index fc228b319a..53aed1220e 100644 --- a/controls/schedule/src/schedule/base/type.ts +++ b/controls/schedule/src/schedule/base/type.ts @@ -41,7 +41,7 @@ export type CurrentAction = 'Add' | 'Save' | 'Delete' | 'DeleteOccurrence' | 'De /** * An enum that holds the options for success result. */ -export type ReturnType = { result: Record[], count: number, aggregates?: Record }; +export type ReturnType = { result: Record[], count: number, aggregates?: Record, cancel: boolean }; /** * An enum that holds the available popup types in the scheduler. They are diff --git a/controls/schedule/src/schedule/event-renderer/agenda-base.ts b/controls/schedule/src/schedule/event-renderer/agenda-base.ts index 67352de24d..a983d148c9 100644 --- a/controls/schedule/src/schedule/event-renderer/agenda-base.ts +++ b/controls/schedule/src/schedule/event-renderer/agenda-base.ts @@ -161,20 +161,33 @@ export class AgendaBase extends ViewBase { } } - public calculateResourceTableElement(tBody: Element, noOfDays: number, agendaDate: Date): void { + public calculateResourceTableElement(tBody: Element, noOfDays: number, agendaDate: Date, agendaEnd: Date = null): void { if (isNullOrUndefined(this.parent.resourceBase.lastResourceLevel)) { const level: TdData[] = this.getDateSlots(this.renderDates, this.parent.activeViewOptions.workDays); this.parent.resourceBase.generateResourceLevels(level); } - const agendaLastDate: Date = util.addDays(new Date(agendaDate.getTime()), noOfDays); + let agendaLastDate: Date = util.addDays(new Date(agendaDate.getTime()), noOfDays); const days: number = (this.parent.activeViewOptions.group.byDate || this.parent.currentView === 'MonthAgenda') ? noOfDays : 1; const resColl: ResourcesModel[] = this.parent.resourceBase.resourceCollection; const resData: TdData[] = this.parent.resourceBase.lastResourceLevel; - const initialDate: Date = agendaDate; + const agendaStart: Date = agendaDate; + let initialDate: Date = agendaDate; + const showWeekend: boolean = this.parent.activeViewOptions.showWeekend; for (let i: number = 0; i < days; i++) { const lastLevelInfo: TdData[][] = []; const tempLastLevelInfo: TdData[] = []; let tempIndex: number = 0; let eventObj: AgendaSlotData; let dateObj: TdData; - const firstDate: Date = util.addDays(initialDate, i); + let firstDate: Date = util.addDays(initialDate, i); + if (this.parent.currentView === 'Agenda' && this.parent.activeViewOptions.group.byDate && + this.parent.activeViewOptions.allowVirtualScrolling && !showWeekend && !this.isWorkDay(firstDate)) { + do { + firstDate = util.addDays(firstDate, 1); + if (firstDate >= agendaEnd) { break; } + } while (!this.isWorkDay(firstDate) || + this.parent.eventBase.filterEvents(firstDate, util.addDays(firstDate, 1)).length < 1); + if (firstDate >= agendaEnd) { break; } + initialDate = util.addDays(firstDate, -i); + agendaLastDate = util.addDays(firstDate, 1); + } const finalDate: Date = (this.parent.activeViewOptions.group.byDate || this.parent.currentView === 'MonthAgenda') ? util.addDays(firstDate, 1) : agendaLastDate; const agendaCollection: Record[] = this.parent.eventBase.filterEvents(firstDate, finalDate); @@ -189,7 +202,9 @@ export class AgendaBase extends ViewBase { for (let r: number = 0; r < noOfDays; r++) { // eslint-disable-next-line max-len const resDayCollection: Record[] = this.parent.eventBase.filterEvents(agendaDate, util.addDays(agendaDate, 1), resDataCollection, undefined); - if (resDayCollection.length > 0 || !this.parent.hideEmptyAgendaDays || + if (((showWeekend || !showWeekend && (this.parent.group.byDate ? this.isWorkDay(agendaDate) : + this.isWorkDay(agendaDate, resData[parseInt(res.toString(), 10)].workDays))) + && (resDayCollection.length > 0 || !this.parent.hideEmptyAgendaDays)) || this.parent.currentView === 'MonthAgenda') { data.push(resDayCollection[0]); eventObj = { @@ -215,16 +230,18 @@ export class AgendaBase extends ViewBase { agendaDate = util.addDays(agendaDate, 1); if (agendaDate.getTime() >= agendaLastDate.getTime() || this.parent.activeViewOptions.group.byDate || this.parent.currentView === 'MonthAgenda') { - lastLevelInfo[lastLevelInfo.length - 1][1].cssClass = cls.AGENDA_DAY_BORDER_CLASS; - const tempObj: TdData = { - rowSpan: data.length, type: 'resourceColumn', resource: resColl[parseInt((resColl.length - 1).toString(), 10)], - groupOrder: resData[parseInt(res.toString(), 10)].groupOrder.slice(0, -1), - resourceData: resData[parseInt(res.toString(), 10)].resourceData, - groupIndex: (lastLevelInfo.length - data.length), className: [cls.RESOURCE_NAME], - date: agendaDate - }; - lastLevelInfo[parseInt((lastLevelInfo.length - data.length).toString(), 10)].push(tempObj); - tempLastLevelInfo.push(extend({}, tempObj, null, true)); + if (data.length > 0) { + lastLevelInfo[lastLevelInfo.length - 1][1].cssClass = cls.AGENDA_DAY_BORDER_CLASS; + const tempObj: TdData = { + rowSpan: data.length, type: 'resourceColumn', resource: resColl[parseInt((resColl.length - 1).toString(), 10)], + groupOrder: resData[parseInt(res.toString(), 10)].groupOrder.slice(0, -1), + resourceData: resData[parseInt(res.toString(), 10)].resourceData, + groupIndex: (lastLevelInfo.length - data.length), className: [cls.RESOURCE_NAME], + date: agendaDate + }; + lastLevelInfo[parseInt((lastLevelInfo.length - data.length).toString(), 10)].push(tempObj); + tempLastLevelInfo.push(extend({}, tempObj, null, true)); + } break; } } @@ -258,9 +275,9 @@ export class AgendaBase extends ViewBase { this.createResourceTableRow(lastLevelInfo, tBody); } } - const totalCollection: Record[] = this.parent.eventBase.filterEvents(initialDate, agendaLastDate); + const totalCollection: Record[] = this.parent.eventBase.filterEvents(agendaStart, agendaLastDate); if (totalCollection.length === 0 && !this.parent.activeViewOptions.allowVirtualScrolling && this.parent.hideEmptyAgendaDays) { - this.renderEmptyContent(tBody, initialDate); + this.renderEmptyContent(tBody, agendaStart); } } diff --git a/controls/schedule/src/schedule/event-renderer/event-base.ts b/controls/schedule/src/schedule/event-renderer/event-base.ts index bb7386b434..aec491c5ab 100644 --- a/controls/schedule/src/schedule/event-renderer/event-base.ts +++ b/controls/schedule/src/schedule/event-renderer/event-base.ts @@ -1252,11 +1252,13 @@ export class EventBase { public createEventWrapper(type: string = '', index: number = 0): HTMLElement { const tr: HTMLElement = createElement('tr'); const levels: TdData[] = this.parent.activeView.colLevels.slice(-1)[0]; + const className: string = (this.parent as any).isReact && this.parent.activeViewOptions.eventTemplate ? + ' ' + cls.APPOINTMENT_WRAPPER_HIDDEN_CLASS : ''; for (let i: number = 0, len: number = levels.length; i < len; i++) { const col: TdData = levels[parseInt(i.toString(), 10)]; const appointmentWrap: HTMLElement = createElement('td', { - className: (type === 'allDay') ? cls.ALLDAY_APPOINTMENT_WRAPPER_CLASS : (type === 'timeIndicator') ? - cls.TIMELINE_WRAPPER_CLASS : cls.DAY_WRAPPER_CLASS, attrs: { 'data-date': col.date.getTime().toString() } + className: (type === 'allDay') ? cls.ALLDAY_APPOINTMENT_WRAPPER_CLASS + className : (type === 'timeIndicator') ? + cls.TIMELINE_WRAPPER_CLASS : cls.DAY_WRAPPER_CLASS + className, attrs: { 'data-date': col.date.getTime().toString() } }); if (!isNullOrUndefined(col.groupIndex)) { appointmentWrap.setAttribute('data-group-index', col.groupIndex.toString()); diff --git a/controls/schedule/src/schedule/event-renderer/vertical-view.ts b/controls/schedule/src/schedule/event-renderer/vertical-view.ts index 3e83087a64..187989e5cb 100644 --- a/controls/schedule/src/schedule/event-renderer/vertical-view.ts +++ b/controls/schedule/src/schedule/event-renderer/vertical-view.ts @@ -97,7 +97,12 @@ export class VerticalEvent extends EventBase { if (isDragging) { this.parent.crudModule.crudObj.isCrudAction = false; } - this.parent.renderTemplates(); + this.parent.renderTemplates(() => { + if ((this.parent as any).isReact && this.parent.activeViewOptions.eventTemplate) { + const wraps: Element[] = [].slice.call(this.parent.element.querySelectorAll('.' + cls.APPOINTMENT_WRAPPER_HIDDEN_CLASS)); + removeClass(wraps, cls.APPOINTMENT_WRAPPER_HIDDEN_CLASS); + } + }); } public initializeValues(): void { diff --git a/controls/schedule/src/schedule/renderer/agenda.ts b/controls/schedule/src/schedule/renderer/agenda.ts index 48ce06e3ca..5732808890 100644 --- a/controls/schedule/src/schedule/renderer/agenda.ts +++ b/controls/schedule/src/schedule/renderer/agenda.ts @@ -142,9 +142,18 @@ export class Agenda extends AgendaBase implements IRenderer { const firstDate: Date = new Date(agendaDate.getTime()); const isObject: Record[] = this.appointmentFiltering(firstDate, lastDate); if (isObject.length > 0 && this.parent.activeViewOptions.allowVirtualScrolling && this.parent.hideEmptyAgendaDays) { - agendaDate = isObject[0][fieldMapping.startTime] as Date; - agendaDate = new Date(new Date(agendaDate.getTime()).setHours(0, 0, 0, 0)); - this.updateHeaderText(isObject[0][fieldMapping.startTime] as Date); + if (!this.parent.activeViewOptions.showWeekend && !this.isAgendaWorkDay(isObject[0][fieldMapping.startTime])) { + for (const event of isObject) { + if (this.isAgendaWorkDay(event[fieldMapping.startTime])) { + agendaDate = new Date(new Date(event[fieldMapping.startTime].getTime()).setHours(0, 0, 0, 0)); + this.updateHeaderText(event[fieldMapping.startTime]); + break; + } + } + } else { + agendaDate = new Date(new Date(isObject[0][fieldMapping.startTime].getTime()).setHours(0, 0, 0, 0)); + this.updateHeaderText(isObject[0][fieldMapping.startTime]); + } } let endDate: Date; if (!this.parent.hideEmptyAgendaDays || (this.parent.agendaDaysCount > 0 && isObject.length > 0)) { @@ -158,17 +167,22 @@ export class Agenda extends AgendaBase implements IRenderer { this.parent.headerModule.updateHeaderItems('remove'); } } - this.calculateResourceTableElement(tBody, this.parent.agendaDaysCount, date); + this.calculateResourceTableElement(tBody, this.parent.agendaDaysCount, date, lastDate); } else { for (let day: number = 0; day < this.parent.agendaDaysCount; day++) { - const filterData: Record[] = this.appointmentFiltering(agendaDate); - const nTr: Element = this.createTableRowElement(agendaDate, 'data'); - if (this.element.querySelector('tr[data-row-index="' + parseInt(nTr.getAttribute('data-row-index'), 10) + '"]')) { + const nTr: HTMLElement = this.createTableRowElement(agendaDate, 'data') as HTMLElement; + const virtualContent: HTMLElement = this.element.querySelector('tr[data-row-index="' + (+(nTr.dataset.rowIndex)) + '"]'); + if (virtualContent || !this.parent.activeViewOptions.showWeekend && !this.isAgendaWorkDay(agendaDate)) { agendaDate = util.addDays(agendaDate, 1); + if (!virtualContent && this.parent.activeViewOptions.allowVirtualScrolling) { + day--; + } + if (agendaDate.getTime() > lastDate.getTime()) { break; } continue; } const dTd: Element = nTr.children[0]; const aTd: Element = nTr.children[1]; + const filterData: Record[] = this.appointmentFiltering(agendaDate); if (filterData.length > 0 || (!this.parent.hideEmptyAgendaDays && filterData.length === 0)) { const elementType: string = (!this.parent.hideEmptyAgendaDays && filterData.length === 0) ? 'noEvents' : 'data'; dTd.appendChild(this.createDateHeaderElement(agendaDate)); @@ -191,6 +205,14 @@ export class Agenda extends AgendaBase implements IRenderer { this.agendaDates = { start: firstDate, end: endDate }; } + private isAgendaWorkDay(date: Date): boolean { + if (this.parent.uiStateValues.isGroupAdaptive && !this.parent.group.byDate) { + return this.isWorkDay(date, this.parent.resourceBase.lastResourceLevel[this.parent.uiStateValues.groupIndex].workDays); + } else { + return this.isWorkDay(date); + } + } + private agendaScrolling(event: Event): void { if (this.parent.quickPopup) { this.parent.quickPopup.quickPopupHide(); @@ -295,8 +317,11 @@ export class Agenda extends AgendaBase implements IRenderer { const lastDate: Date = this.getEndDateFromStartDate(date); let daysCount: number = 0; do { - const filterData: Record[] = this.appointmentFiltering(currentDate); - if (filterData.length > 0 || !this.parent.hideEmptyAgendaDays) { daysCount++; } + if (this.parent.activeViewOptions.showWeekend || !this.parent.activeViewOptions.showWeekend && + this.isAgendaWorkDay(currentDate)) { + const filterData: Record[] = this.appointmentFiltering(currentDate); + if (filterData.length > 0 || !this.parent.hideEmptyAgendaDays) { daysCount++; } + } currentDate = util.addDays(currentDate, (type === 'next') ? 1 : -1); if (currentDate < firstDate || currentDate > lastDate) { break; } } while (daysCount !== this.parent.agendaDaysCount); diff --git a/controls/schedule/styles/schedule/_layout.scss b/controls/schedule/styles/schedule/_layout.scss index 74cead2b4e..e1b8ae3782 100644 --- a/controls/schedule/styles/schedule/_layout.scss +++ b/controls/schedule/styles/schedule/_layout.scss @@ -976,6 +976,10 @@ position: relative; } + .e-appointment-wrapper-hidden { + visibility: hidden; + } + .e-all-day-appointment-wrapper .e-appointment:not(.e-schedule-event-clone) { cursor: default; } diff --git a/controls/splitbuttons/CHANGELOG.md b/controls/splitbuttons/CHANGELOG.md index c5cf187f67..7474103514 100644 --- a/controls/splitbuttons/CHANGELOG.md +++ b/controls/splitbuttons/CHANGELOG.md @@ -2,7 +2,27 @@ ## [Unreleased] -## 25.1.35 (2024-03-15) +## 25.1.37 (2024-03-26) + +### DropDownButton + +#### Breaking Changes + +- Now, the default value of [`enableHtmlSanitizer`](https://helpej2.syncfusion.com/documentation/api/drop-down-button/#enablehtmlsanitizer) property is true to prevent the XSS attacks in the DropDownButton component. + +### ProgressButton + +#### Breaking Changes + +- Now, the default value of [`enableHtmlSanitizer`](https://helpej2.syncfusion.com/documentation/api/progress-button/#enablehtmlsanitizer) property is true to prevent the XSS attacks in the ProgressButton component. + +### SplitButton + +#### Breaking Changes + +- Now, the default value of [`enableHtmlSanitizer`](https://helpej2.syncfusion.com/documentation/api/split-button/#enablehtmlsanitizer) property is true to prevent the XSS attacks in the SplitButton component. + +## 24.2.7 (2024-02-20) ### ProgressButton @@ -336,4 +356,4 @@ SplitButton component has primary and secondary button. Primary button is used t - **Separator** - Supports Popup items grouping by using the Separator. -- **Accessibility** - Provided with built-in accessibility support that helps to access all the SplitButton component features through the keyboard, screen readers, or other assistive technology devices. +- **Accessibility** - Provided with built-in accessibility support that helps to access all the SplitButton component features through the keyboard, screen readers, or other assistive technology devices. \ No newline at end of file diff --git a/controls/splitbuttons/package.json b/controls/splitbuttons/package.json index dfd52d6a5f..8cc9ecde6b 100644 --- a/controls/splitbuttons/package.json +++ b/controls/splitbuttons/package.json @@ -1,6 +1,6 @@ { "name": "@syncfusion/ej2-splitbuttons", - "version": "24.2.5", + "version": "19.16.4", "description": "A package of feature-rich Essential JS 2 components such as DropDownButton, SplitButton, ProgressButton and ButtonGroup.", "author": "Syncfusion Inc.", "license": "SEE LICENSE IN license", @@ -12,6 +12,7 @@ "@syncfusion/ej2-popups": "*" }, "devDependencies": { + "@syncfusion/ej2-staging": "^1.0.1", "@types/chai": "^3.4.28", "@types/jasmine": "2.8.9", "@types/jasmine-ajax": "^3.1.27", diff --git a/controls/splitbuttons/spec/drop-down-button.spec.ts b/controls/splitbuttons/spec/drop-down-button.spec.ts index 85824176ea..b6567b89c3 100644 --- a/controls/splitbuttons/spec/drop-down-button.spec.ts +++ b/controls/splitbuttons/spec/drop-down-button.spec.ts @@ -112,7 +112,7 @@ describe('DropDownButton', () => { }); it('Enable Html Sanitizer', () => { - drpButton = new DropDownButton({ items: [{ text: 'cut' }, { text: 'copy' }, { text: 'paste' }], enableHtmlSanitizer: true }); + drpButton = new DropDownButton({ items: [{ text: 'cut' }, { text: 'copy' }, { text: 'paste' }] }); drpButton.appendTo('#drp-button'); drpButton.toggle(); const htmlele: Element = document.body; @@ -121,7 +121,7 @@ describe('DropDownButton', () => { }); it('Enable Html Sanitizer disabled', () => { - drpButton = new DropDownButton({ items: [{ text: 'cut' }, { text: 'copy' }, { text: 'paste' }] }); + drpButton = new DropDownButton({ items: [{ text: 'cut' }, { text: 'copy' }, { text: 'paste' }], enableHtmlSanitizer: false }); drpButton.appendTo('#drp-button'); drpButton.toggle(); const htmlele: Element = document.body; diff --git a/controls/splitbuttons/spec/progress-button.spec.ts b/controls/splitbuttons/spec/progress-button.spec.ts index 2bc74d9485..3f5a73c169 100644 --- a/controls/splitbuttons/spec/progress-button.spec.ts +++ b/controls/splitbuttons/spec/progress-button.spec.ts @@ -282,7 +282,7 @@ describe('Progress Button', () => { }); it('Enable Html Sanitizer', () => { - new ProgressButton({ content: 'Progress', enableHtmlSanitizer: true }, '#progressbtn17'); + new ProgressButton({ content: 'Progress' }, '#progressbtn17'); const htmlele: Element = document.body; expect(window.getComputedStyle(htmlele).backgroundColor).not.toBe('rgb(0, 0, 255)'); }); @@ -290,7 +290,7 @@ describe('Progress Button', () => { it('Enable Html Sanitizer disabled', () => { const ele: any = createElement('button', { id: 'progressbtn17' }); document.body.appendChild(ele); - const button: ProgressButton = new ProgressButton({ content: 'Progress' }, '#progressbtn17'); + const button: ProgressButton = new ProgressButton({ content: 'Progress', enableHtmlSanitizer: false }, '#progressbtn17'); const htmlele: Element = document.body; expect(window.getComputedStyle(htmlele).backgroundColor).toBe('rgb(0, 0, 255)'); button.destroy(); diff --git a/controls/splitbuttons/src/drop-down-button/drop-down-button-model.d.ts b/controls/splitbuttons/src/drop-down-button/drop-down-button-model.d.ts index cab80cd776..edb684ef91 100644 --- a/controls/splitbuttons/src/drop-down-button/drop-down-button-model.d.ts +++ b/controls/splitbuttons/src/drop-down-button/drop-down-button-model.d.ts @@ -46,9 +46,10 @@ export interface DropDownButtonModel extends ComponentModel{ iconPosition?: SplitButtonIconPosition; /** - * Defines whether to allow the cross-scripting site or not. + * Specifies whether to enable the rendering of untrusted HTML values in the DropDownButton component. + * If 'enableHtmlSanitizer' set to true, the component will sanitize any suspected untrusted strings and scripts before rendering them. * - * @default false + * @default true */ enableHtmlSanitizer?: boolean; diff --git a/controls/splitbuttons/src/drop-down-button/drop-down-button.ts b/controls/splitbuttons/src/drop-down-button/drop-down-button.ts index 5072e29456..6abe7ece92 100644 --- a/controls/splitbuttons/src/drop-down-button/drop-down-button.ts +++ b/controls/splitbuttons/src/drop-down-button/drop-down-button.ts @@ -91,11 +91,12 @@ export class DropDownButton extends Component implements INot public iconPosition: SplitButtonIconPosition; /** - * Defines whether to allow the cross-scripting site or not. + * Specifies whether to enable the rendering of untrusted HTML values in the DropDownButton component. + * If 'enableHtmlSanitizer' set to true, the component will sanitize any suspected untrusted strings and scripts before rendering them. * - * @default false + * @default true */ - @Property(false) + @Property(true) public enableHtmlSanitizer: boolean; /** diff --git a/controls/splitbuttons/src/progress-button/progress-button-model.d.ts b/controls/splitbuttons/src/progress-button/progress-button-model.d.ts index 374c70894f..1861d77eb7 100644 --- a/controls/splitbuttons/src/progress-button/progress-button-model.d.ts +++ b/controls/splitbuttons/src/progress-button/progress-button-model.d.ts @@ -146,9 +146,10 @@ export interface ProgressButtonModel { isToggle?: boolean; /** - * Defines whether to allow the cross-scripting site or not. + * Specifies whether to enable the rendering of untrusted HTML values in the Progress button component. + * If 'enableHtmlSanitizer' set to true, the component will sanitize any suspected untrusted strings and scripts before rendering them. * - * @default false + * @default true */ enableHtmlSanitizer?: boolean; diff --git a/controls/splitbuttons/src/progress-button/progress-button.ts b/controls/splitbuttons/src/progress-button/progress-button.ts index 7c48507a42..223cde1d23 100644 --- a/controls/splitbuttons/src/progress-button/progress-button.ts +++ b/controls/splitbuttons/src/progress-button/progress-button.ts @@ -178,11 +178,12 @@ export class ProgressButton extends Button implements INotifyPropertyChanged { public isToggle: boolean; /** - * Defines whether to allow the cross-scripting site or not. + * Specifies whether to enable the rendering of untrusted HTML values in the Progress button component. + * If 'enableHtmlSanitizer' set to true, the component will sanitize any suspected untrusted strings and scripts before rendering them. * - * @default false + * @default true */ - @Property(false) + @Property(true) public enableHtmlSanitizer: boolean; /** diff --git a/controls/splitbuttons/styles/button-group/_bds-definition.scss b/controls/splitbuttons/styles/button-group/_bds-definition.scss new file mode 100644 index 0000000000..3e758ebf0f --- /dev/null +++ b/controls/splitbuttons/styles/button-group/_bds-definition.scss @@ -0,0 +1,31 @@ +//layout variables +$btn-grp-margin: -1px !default; +$btn-grp-margin-left: -1px !default; +$btn-grp-wrapper-border: 4px !default; +$btn-grp-round-radius: 20px !default; +$btn-grp-icon-font-size: $text-lg !default; +$btn-grp-icon-font-size-bigger: 22px !default; + +//Normal Outline GroupButton +$btn-grp-outline-focus-border-color: $secondary-border-color !default; +$btn-grp-outline-focus-color: inherit !default; + +//Outline Primary +$btn-grp-outline-primary-focus-border-color: $primary !default; +$btn-grp-outline-focus-primary-color: inherit !default; + +//Outline Success +$btn-grp-outline-success-focus-border-color: $btn-success-focus-border-color !default; +$btn-grp-outline-focus-success-color: inherit !default; + +//Outline Info +$btn-grp-outline-info-focus-border-color: $btn-info-focus-border-color !default; +$btn-grp-outline-focus-info-color: inherit !default; + +//Outline warning +$btn-grp-outline-warning-focus-border-color: $btn-warning-focus-border-color !default; +$btn-grp-outline-focus-warning-color: inherit !default; + +//Outline danger +$btn-grp-outline-danger-focus-border-color: $btn-danger-focus-border-color !default; +$btn-grp-outline-focus-danger-color: inherit !default; diff --git a/controls/splitbuttons/styles/drop-down-button/_bds-definition.scss b/controls/splitbuttons/styles/drop-down-button/_bds-definition.scss new file mode 100644 index 0000000000..8937040358 --- /dev/null +++ b/controls/splitbuttons/styles/drop-down-button/_bds-definition.scss @@ -0,0 +1,89 @@ +//layout variables +$drop-down-btn-arrow-content: '\e706' !default; +$drop-down-btn-vertical-btn-padding: 6px 12px !default; +$drop-down-btn-vertical-bigger-padding: 8px 16px !default; +$drop-down-btn-bigger-font-size: $text-base !default; +$drop-down-btn-bigger-li-height: 40px !default; +$drop-down-btn-bigger-max-width: 240px !default; +$drop-down-btn-bigger-min-width: 112px !default; +$drop-down-btn-icon-font-size: $text-sm !default; +$drop-down-btn-icon-margin-right: 12px !default; +$drop-down-btn-bigger-icon-margin-right: 12px !default; +$drop-down-btn-li-border-width: 1px !default; +$drop-down-btn-li-height: 32px !default; +$drop-down-btn-li-padding: 0 12px !default; +$drop-down-btn-bigger-li-padding: 0 16px !default; +$drop-down-btn-small-li-padding: 0 8px !default; +$drop-down-btn-max-width: 178px !default; +$drop-down-btn-menu-icon-bigger-font-size: 22px !default; +$drop-down-btn-menu-icon-font-size: $text-lg !default; +$drop-down-btn-min-width: 120px !default; +$drop-down-btn-seperator-padding: 3px 0 !default; +$drop-down-btn-sub-ul-box-shadow: none !default; +$drop-down-btn-ul-border-radius: 8px !default; +$drop-down-btn-ul-padding: 4px 0 !default; +$drop-down-btn-ul-bigger-padding: 4px 0 !default; +$drop-down-btn-ul-small-padding: 4px 0 !default; +$drop-down-btn-caret-icon-font-size: $text-lg !default; +$drop-down-btn-icon-font-size-bigger: $text-base !default; +$drop-down-btn-caret-icon-font-size-bigger: 22px !default; +$drop-down-btn-box-shadow: $shadow-focus-ring2 !default; +$drop-down-btn-popup-margin-top: 4px !default; +$drop-down-btn-bigger-popup-margin-top: 4px !default; +$drop-down-btn-ul-border: 1px solid $border-light !default; +$drop-down-btn-li-box-shadow: none !default; +$drop-down-btn-li-border-style: solid !default; +$drop-down-btn-color: $secondary-text-color !default; +$drop-down-btn-disable-text: $secondary-text-color-disabled !default; +$drop-down-btn-font-size: $text-sm !default; +$drop-down-btn-font-weight: $font-weight-medium !default; +$drop-down-btn-li-bgcolor: $content-bg-color-hover !default; +$drop-down-btn-ul-bgcolor: $flyout-bg-color !default; +$drop-down-btn-li-border-color: $border-light !default; +$drop-down-btn-selected-color: $content-text-color-selected !default; +$drop-down-btn-parent-ul-box-shadow: $shadow-lg !default; +$drop-down-btn-li-focus-bgcolor: $content-bg-color-hover !default; +$drop-down-btn-li-selection-bgcolor: $content-bg-color-selected !default; +$drop-down-btn-li-selection-font-color: $content-text-color-selected !default; +$drop-down-btn-menu-icon-color: $secondary-text-color !default; +$drop-down-btn-menu-icon-disabled-color: $secondary-text-color-disabled !default; + +.e-dropdown-popup { + position: absolute; + @if $skin-name == 'tailwind' or $skin-name == 'bootstrap5' or $theme-name == 'tailwind-dark' or $theme-name == 'bootstrap5-dark' { + border-radius: $drop-down-btn-ul-border-radius; + margin-top: $drop-down-btn-popup-margin-top; + } + + & ul { + border: $drop-down-btn-ul-border; + border-radius: $drop-down-btn-ul-border-radius; + box-shadow: $drop-down-btn-parent-ul-box-shadow; + box-sizing: border-box; + font-size: $drop-down-btn-font-size; + font-weight: $drop-down-btn-font-weight; + list-style-image: none; + list-style-position: outside; + list-style-type: none; + margin: 0; + min-width: $drop-down-btn-min-width; + overflow: hidden; + padding: $drop-down-btn-ul-padding; + user-select: none; + white-space: nowrap; + + & .e-item { + cursor: pointer; + display: flex; + height: $drop-down-btn-li-height; + line-height: $drop-down-btn-li-height; + padding: $drop-down-btn-li-padding; + margin: 0 6px; + border-radius: 6px; + + &.e-url { + padding: 0; + } + } + } +} diff --git a/controls/splitbuttons/styles/drop-down-button/icons/_bds.scss b/controls/splitbuttons/styles/drop-down-button/icons/_bds.scss new file mode 100644 index 0000000000..c91b9fcac0 --- /dev/null +++ b/controls/splitbuttons/styles/drop-down-button/icons/_bds.scss @@ -0,0 +1,10 @@ +@include export-module('drop-down-button-bds-icons') { + .e-dropdown-btn, + .e-dropdown-btn.e-btn { + .e-caret { + &::before { + content: '\e729'; + } + } + } +} diff --git a/controls/splitbuttons/styles/progress-button/_bds-definition.scss b/controls/splitbuttons/styles/progress-button/_bds-definition.scss new file mode 100644 index 0000000000..5aba6afc70 --- /dev/null +++ b/controls/splitbuttons/styles/progress-button/_bds-definition.scss @@ -0,0 +1,28 @@ +//layout variables +$skin-name :'Material3'; +$progress-btn-circle-opacity: .8 !default; +$progress-btn-transition: all .3s linear !default; +$progress-btn-spinner-padding: 12px !default; +$progress-btn-spinner-padding-small: 10px !default; +$progress-btn-spinner-padding-bigger: 14px !default; +$progress-btn-spinner-padding-bigger-small: 12px !default; +$progress-btn-spin-btn-padding: 16px !default; +$progress-btn-small-spin-btn-padding: 14px !default; +$progress-btn-bigger-spin-btn-padding: 20px !default; +$progress-btn-bigger-small-spin-btn-padding: 16px !default; +$progress-btn-color: $secondary-text-color !default; +$progress-btn-bgcolor: rgba($icon-color, .2) !default; +$progress-btn-bgcolor-normal: rgba($icon-color, .2) !default; +$progress-btn-warning-progress-color: $progress-btn-bgcolor !default; +$progress-btn-flat-primary-progress-color: rgba($primary-text-color, 1) !default; +$progress-btn-flat-success-progress-color: rgba($success-text, 1) !default; +$progress-btn-flat-info-progress-color: rgba($info-text, 1) !default; +$progress-btn-flat-warning-progress-color: rgba($warning-text, 1) !default; +$progress-btn-flat-danger-progress-color: rgba($danger-text, 1) !default; +$btn-flat-primary-path-arc: $white; + +$fade-out: none !default; + +.e-path-circle { + stroke: $fade-out; +} diff --git a/controls/splitbuttons/styles/split-button/_bds-definition.scss b/controls/splitbuttons/styles/split-button/_bds-definition.scss new file mode 100644 index 0000000000..dfb16eafda --- /dev/null +++ b/controls/splitbuttons/styles/split-button/_bds-definition.scss @@ -0,0 +1,24 @@ +//layout variables +$split-btn-zindex: 2 !default; +$split-btn-border-radius: 8px !default; +$split-btn-focus-outline-offset: 0 !default; +$split-btn-vertical-secondary-icon-line-height: .334em !default; +$split-btn-seperator-border: 1px solid !default; +$split-btn-sec-btn-margin-left: -1px !default; +$split-btn-icon-btn-padding: 10px !default; +$split-btn-icon-btn-padding-bigger: 12px !default; +$split-btn-focus-border-color: $secondary-border-color !default; +$split-btn-focus-vertical-border-color: $secondary-border-color !default; +$split-btn-seperator-border-color: $secondary-border-color !default; +$split-btn-seperator-default-border-color: $secondary-border-color !default; +$split-btn-seperator-vertical-border-color: $secondary-border-color !default; +$split-btn-hover-border-color: $secondary-border-color !default; +$split-btn-hover-vertical-border-color: $secondary-border-color !default; +$split-btn-active-border-color: $secondary-bg-color-pressed !default; +$split-btn-active-vertical-border-color: $secondary-bg-color-pressed !default; +$split-btn-disabled-vertical-border-color: $secondary-border-color !default; +$split-btn-active-box-shadow: $secondary-shadow-focus !default; +$split-btn-disabled-left-border-color: $secondary-border-color !default; +$split-btn-hover-left-border-color: $secondary-border-color !default; +$split-btn-focus-left-border-color: $secondary-border-color !default; +$split-btn-active-left-border-color: $secondary-border-color !default; diff --git a/controls/spreadsheet/package.json b/controls/spreadsheet/package.json index cd97b0ffbd..d9dca058cf 100644 --- a/controls/spreadsheet/package.json +++ b/controls/spreadsheet/package.json @@ -1,6 +1,6 @@ { "name": "@syncfusion/ej2-spreadsheet", - "version": "24.7.0", + "version": "25.1.35", "description": "Feature-rich JavaScript Spreadsheet (Excel) control with built-in support for selection, editing, formatting, importing and exporting to Excel", "author": "Syncfusion Inc.", "license": "SEE LICENSE IN license", diff --git a/controls/spreadsheet/src/spreadsheet/integrations/ribbon.ts b/controls/spreadsheet/src/spreadsheet/integrations/ribbon.ts index 0c519f3211..d1fb63cf19 100644 --- a/controls/spreadsheet/src/spreadsheet/integrations/ribbon.ts +++ b/controls/spreadsheet/src/spreadsheet/integrations/ribbon.ts @@ -140,8 +140,8 @@ export class Ribbon { { template: document.getElementById(`${id}_fill_color_picker`), tooltipText: l10n.getConstant('FillColor'), id: id + '_fill_color_picker' }, { template: this.getBordersDBB(id), tooltipText: l10n.getConstant('Borders'), id: id + '_borders' }, - { template: this.getMergeSplitBtn(id), tooltipText: l10n.getConstant('MergeCells'), - htmlAttributes: { 'aria-label': l10n.getConstant('MergeCells') }, id: id + '_merge_cells', disabled: true }, + { template: this.getMergeSplitBtn(id), tooltipText: l10n.getConstant('MergeCells'), id: id + '_merge_cells', + disabled: true }, { type: 'Separator', id: id + '_separator_7' }, { template: this.getTextAlignDDB(id), tooltipText: l10n.getConstant('HorizontalAlignment'), id: id + '_text_align' }, { template: this.getVerticalAlignDDB(id), tooltipText: l10n.getConstant('VerticalAlignment'), id: id + '_vertical_align' }, @@ -1576,7 +1576,9 @@ export class Ribbon { } }, created: (): void => { - this.mergeSplitBtn.element.title = l10n.getConstant('MergeCells'); + const mergeCellTitle: string = l10n.getConstant('MergeCells'); + this.mergeSplitBtn.element.title = mergeCellTitle; + this.mergeSplitBtn.element.setAttribute('aria-label', mergeCellTitle); (this.mergeSplitBtn.element.nextElementSibling as HTMLButtonElement).title = l10n.getConstant('SelectMergeType'); }, beforeOpen: (args: MenuEventArgs) => args.element.setAttribute('aria-label', l10n.getConstant('MergeCells')) diff --git a/controls/spreadsheet/src/spreadsheet/renderer/cell.ts b/controls/spreadsheet/src/spreadsheet/renderer/cell.ts index baa73669bc..e4130de5a2 100644 --- a/controls/spreadsheet/src/spreadsheet/renderer/cell.ts +++ b/controls/spreadsheet/src/spreadsheet/renderer/cell.ts @@ -538,7 +538,7 @@ export class CellRenderer implements ICellRenderer { } else { compiledStr = compile(template); const compiledTemplate: Element | Element[] = compiledStr(cell, this.parent, 'ranges', ''); - return compiledTemplate[0] ? compiledTemplate : [compiledTemplate]; + return (compiledTemplate)[0] ? compiledTemplate : [compiledTemplate]; } } diff --git a/controls/spreadsheet/src/workbook/integrations/open.ts b/controls/spreadsheet/src/workbook/integrations/open.ts index 96939d1d0d..4169d3ce42 100644 --- a/controls/spreadsheet/src/workbook/integrations/open.ts +++ b/controls/spreadsheet/src/workbook/integrations/open.ts @@ -127,7 +127,7 @@ export class WorkbookOpen { this.parent.sheetNameCount = 1; this.parent.sheets = []; this.parent.notify(sheetsDestroyed, {}); - workbookModel.activeSheetIndex = workbookModel.activeSheetIndex || 0; + workbookModel.activeSheetIndex = workbookModel.activeSheetIndex || workbookModel.sheets.findIndex(sheet => sheet.state !== 'Hidden'); this.parent.setProperties( { 'isProtected': workbookModel.isProtected || false, diff --git a/controls/svgbase/CHANGELOG.md b/controls/svgbase/CHANGELOG.md index c26860c1bb..f874b8ad0b 100644 --- a/controls/svgbase/CHANGELOG.md +++ b/controls/svgbase/CHANGELOG.md @@ -2,6 +2,12 @@ ## [Unreleased] +## 25.1.37 (2024-03-26) + +### Svg Base + +- `#I528508` - The tooltip template div is now added based on the series count, and it renders properly. + ## 24.1.47 (2024-01-23) ### Svg Base diff --git a/controls/svgbase/package.json b/controls/svgbase/package.json index fbabe5cea0..3eaac9075b 100644 --- a/controls/svgbase/package.json +++ b/controls/svgbase/package.json @@ -1,6 +1,6 @@ { "name": "@syncfusion/ej2-svg-base", - "version": "24.1.47", + "version": "25.1.35", "description": "Essential JS 2 SVG Base Components", "author": "Syncfusion Inc.", "license": "SEE LICENSE IN license", @@ -8,6 +8,7 @@ "@syncfusion/ej2-base": "*" }, "devDependencies": { + "@syncfusion/ej2-staging": "^1.0.1", "@types/chai": "^3.4.28", "@types/es6-promise": "0.0.28", "@types/jasmine": "2.8.9", diff --git a/controls/svgbase/src/tooltip/tooltip.ts b/controls/svgbase/src/tooltip/tooltip.ts index 26c4552c24..a8b7b1dd03 100644 --- a/controls/svgbase/src/tooltip/tooltip.ts +++ b/controls/svgbase/src/tooltip/tooltip.ts @@ -1077,8 +1077,14 @@ export class Tooltip extends Component implements INotifyPropertyCh let sharedTemplateElement: HTMLCollection = this.templateFn(this.data[i], this.controlInstance, elem.id, elem.id + '_blazorTemplate', ''); if (i === 0) { templateElement = sharedTemplateElement; - } else { - templateElement[templateElement.length - 1].outerHTML += sharedTemplateElement[0].outerHTML; + } + else { + if (sharedTemplateElement.length > 1) { + templateElement[i].outerHTML = sharedTemplateElement[i].outerHTML || sharedTemplateElement[i].textContent; + } + else { + templateElement[templateElement.length - 1].outerHTML += sharedTemplateElement[0].outerHTML; + } } } } diff --git a/controls/treegrid/package.json b/controls/treegrid/package.json index b18a4224e1..e310fcb7c5 100644 --- a/controls/treegrid/package.json +++ b/controls/treegrid/package.json @@ -1,6 +1,6 @@ { "name": "@syncfusion/ej2-treegrid", - "version": "20.12.1", + "version": "25.1.35", "description": "Essential JS 2 TreeGrid Component", "author": "Syncfusion Inc.", "license": "SEE LICENSE IN license", diff --git a/controls/treegrid/src/treegrid/actions/rowdragdrop.ts b/controls/treegrid/src/treegrid/actions/rowdragdrop.ts index f883b5ad27..1cf019c16a 100644 --- a/controls/treegrid/src/treegrid/actions/rowdragdrop.ts +++ b/controls/treegrid/src/treegrid/actions/rowdragdrop.ts @@ -1,6 +1,6 @@ import { TreeGrid } from '../base/treegrid'; import { Grid, RowDD as GridDragDrop, RowDropEventArgs, parentsUntil } from '@syncfusion/ej2-grids'; -import { EJ2Intance, RowDragEventArgs, getObject, Scroll } from '@syncfusion/ej2-grids'; +import { EJ2Intance, getObject, Scroll } from '@syncfusion/ej2-grids'; import { closest, isNullOrUndefined, classList, setValue, extend, getValue, removeClass, addClass, setStyleAttribute } from '@syncfusion/ej2-base'; import { ITreeData } from '../base'; import { DataManager } from '@syncfusion/ej2-data'; @@ -8,6 +8,7 @@ import * as events from '../base/constant'; import { editAction } from './crud-actions'; import { getParentData, findChildrenRecords, isRemoteData, isOffline, isCountRequired } from '../utils'; import { TreeActionEventArgs } from '../models'; +import { RowDragEventArgs } from '../base'; /** * TreeGrid RowDragAndDrop module diff --git a/controls/treegrid/src/treegrid/base/interface.ts b/controls/treegrid/src/treegrid/base/interface.ts index 1ad1a92b69..a1b1d0de43 100644 --- a/controls/treegrid/src/treegrid/base/interface.ts +++ b/controls/treegrid/src/treegrid/base/interface.ts @@ -1,5 +1,5 @@ import { Column } from '../models/column'; -import { SaveEventArgs, DataStateChangeEventArgs as GridDataStateChangeEventArgs, ExcelExportProperties } from '@syncfusion/ej2-grids'; +import { SaveEventArgs, DataStateChangeEventArgs as GridDataStateChangeEventArgs, ExcelExportProperties, RowDragEventArgs as GridRowDragEventArgs } from '@syncfusion/ej2-grids'; import { PdfExportProperties } from '@syncfusion/ej2-grids'; /** * Specifies FlatData interfaces. @@ -182,3 +182,6 @@ export interface DataStateChangeEventArgs extends GridDataStateChangeEventArgs { /** Defines the resolve function for the promise. */ childDataBind?: Function; } +export interface RowDragEventArgs extends GridRowDragEventArgs { + dropPosition: string; +} diff --git a/controls/treegrid/src/treegrid/base/treegrid.ts b/controls/treegrid/src/treegrid/base/treegrid.ts index eae6147584..87930a7d4f 100644 --- a/controls/treegrid/src/treegrid/base/treegrid.ts +++ b/controls/treegrid/src/treegrid/base/treegrid.ts @@ -2037,7 +2037,9 @@ export class TreeGrid extends Component implements INotifyPropertyC const gridContent: Element = this.element.getElementsByClassName('e-gridcontent')[0].childNodes[0] as HTMLElement; gridContent.setAttribute('tabindex', '0'); const contentTable: Element = this.element.getElementsByClassName('e-content')[0].querySelector('.e-table') as HTMLElement; - contentTable.setAttribute('role', 'treegrid'); + if (!isNullOrUndefined(contentTable)) { + contentTable.setAttribute('role', 'treegrid'); + } if (this.isIndentEnabled) { this.refreshToolbarItems(); } @@ -3753,6 +3755,17 @@ export class TreeGrid extends Component implements INotifyPropertyC const persist2: string = 'mergeColumns'; this.grid[`${persist2}`].apply(this, [storedColumn, columns]); } + private setFrozenCount(): void { + const persist3: string = 'setFrozenCount'; + this.grid[`${persist3}`].apply(this); + } + private splitFrozenCount(columns: Column[]): void { + const persist4: string = 'splitFrozenCount'; + this.grid[`${persist4}`].apply(this, [columns]); + } + private isFrozenGrid(): boolean { + return this.grid.isFrozenGrid(); + } private updateTreeGridModel() : void { this.setProperties({ filterSettings: getObject('properties', this.grid.filterSettings) }, true); diff --git a/controls/treegrid/src/treegrid/renderer/render.ts b/controls/treegrid/src/treegrid/renderer/render.ts index 7493032221..6e2399402a 100644 --- a/controls/treegrid/src/treegrid/renderer/render.ts +++ b/controls/treegrid/src/treegrid/renderer/render.ts @@ -244,7 +244,9 @@ export class Render { this.parent['args'] = args; const columnModel: Column[] = getValue('columnModel', this.parent); const treeColumn: Column = columnModel[this.parent.treeColumnIndex]; - if ((isNullOrUndefined(this.parent.rowTemplate) && !((<{ isReact?: boolean }>this.parent).isReact))) { + if ((isNullOrUndefined(this.parent.rowTemplate) && !((<{ isReact?: boolean }>this.parent).isReact)) + || (((<{ isReact?: boolean }>this.parent).isReact) && + !args.column['template'])) { this.parent.trigger(events.queryCellInfo, args); } else if ((((<{ isReact?: boolean }>this.parent).isReact) && treeColumn.field !== args.column.field)) {