diff --git a/CHANGELOG.md b/CHANGELOG.md index bf0c893198..b40ed0e85f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,34 @@ # Change Log +# [v3.2.0](https://github.com/framework7io/framework7/compare/v3.1.1...v3.2.0) - August 28, 2018 + * Core + * Router + * Added support for routable Panels! Thanks to @bencompton 🎉 + * Added support to navigate to route by its name using `router.navigate({ name: 'someroute' })` + * Optimized Router Component ES template parsing + * Now it caches XHR-loaded Router Components (from `componentUrl`) + * Calendar + * New `backdrop` and `closeByBackdropClick` parameters + * Smart Select + * New `cssClass` parameter that will add additional class to Smart Select element + * `searchbar` parameter now can be a full object with Searchbar parameters + * New `appendSearchbarNotFound` parameter that adds additional element to Smart Select container that will be visible when there are no searchbar results + * Popup + * Fixed issue on backdrop click when multiple popups opened same time + * Device + * It now adds `device-macos` and `device-windows` html classes when relevant device is used + * Utils - 2 new methods added: + * `app.utils.uniqueNumber()` - returns unique counter number + * `app.utils.id(mask, map)` - returns randomly generated string by mask, e.g. `app.utils.id('xxxx-xxxx-xxxx-xxxx')` will return string like `d692-c811-e032-6028` + * Phenome (Vue/React) + * View component - added new `routesBeforeEnter` and `routesBeforeLeave` properties + * List component - now emits `submit` event if it is used as form + * List Item component - fixed issue with `onChange` event in React + * Actions, Popover, Sheet - added new `closeByBackdropClick` and `closeByOutsideClick` properties + * Popup - added new `closeByBackdropClick`, `backdrop`, `animate` properties + * Lots of minor fixes + # [v3.1.1](https://github.com/framework7io/framework7/compare/v3.1.0...v3.1.1) - August 3, 2018 * Core * Virtual DOM Router Components diff --git a/packages/core/components/accordion/accordion.js b/packages/core/components/accordion/accordion.js index 74dc009620..a15546effe 100644 --- a/packages/core/components/accordion/accordion.js +++ b/packages/core/components/accordion/accordion.js @@ -7,6 +7,12 @@ const Accordion = { const app = this; let $accordionItemEl = $clickedEl.closest('.accordion-item').eq(0); if (!$accordionItemEl.length) $accordionItemEl = $clickedEl.parents('li').eq(0); + + const $accordionContent = $clickedEl.parents('.accordion-item-content').eq(0); + if ($accordionContent.length) { + if ($accordionContent.parents($accordionItemEl).length) return; + } + if ($clickedEl.parents('li').length > 1 && $clickedEl.parents('li')[0] !== $accordionItemEl[0]) return; app.accordion.toggle($accordionItemEl); }, diff --git a/packages/core/components/autocomplete/autocomplete-class.js b/packages/core/components/autocomplete/autocomplete-class.js index 43b715e0ab..c387e7531d 100644 --- a/packages/core/components/autocomplete/autocomplete-class.js +++ b/packages/core/components/autocomplete/autocomplete-class.js @@ -40,7 +40,7 @@ class Autocomplete extends Framework7Class { } if (!view) view = app.views.main; - const id = Utils.now(); + const id = Utils.id(); let url = params.url; if (!url && $openerEl && $openerEl.length) { diff --git a/packages/core/components/calendar/calendar-class.js b/packages/core/components/calendar/calendar-class.js index 866e9ec84d..98a9babd8e 100644 --- a/packages/core/components/calendar/calendar-class.js +++ b/packages/core/components/calendar/calendar-class.js @@ -1355,7 +1355,8 @@ class Calendar extends Framework7Class { targetEl: $inputEl, scrollToEl: calendar.params.scrollToInput ? $inputEl : undefined, content: modalContent, - backdrop: modalType === 'popover' && app.params.popover.backdrop !== false, + backdrop: calendar.params.backdrop === true || (modalType === 'popover' && app.params.popover.backdrop !== false && calendar.params.backdrop !== false), + closeByBackdropClick: calendar.params.closeByBackdropClick, on: { open() { const modal = this; diff --git a/packages/core/components/calendar/calendar.js b/packages/core/components/calendar/calendar.js index 02be706532..e3cd006b6e 100644 --- a/packages/core/components/calendar/calendar.js +++ b/packages/core/components/calendar/calendar.js @@ -76,6 +76,8 @@ export default { routableModals: true, view: null, url: 'date/', + backdrop: null, + closeByBackdropClick: true, // Render functions renderWeekHeader: null, renderMonths: null, diff --git a/packages/core/components/dialog/dialog.js b/packages/core/components/dialog/dialog.js index 8daf91eb27..88b249b7bd 100644 --- a/packages/core/components/dialog/dialog.js +++ b/packages/core/components/dialog/dialog.js @@ -23,7 +23,9 @@ export default { }, create() { const app = this; - const defaultDialogTitle = app.params.dialog.title || app.name; + function defaultDialogTitle() { + return app.params.dialog.title || app.name; + } const destroyOnClose = app.params.dialog.destroyPredefinedDialogs; const keyboardActions = app.params.dialog.keyboardActions; app.dialog = Utils.extend( @@ -40,7 +42,7 @@ export default { [text, callbackOk, title] = args; } return new Dialog(app, { - title: typeof title === 'undefined' ? defaultDialogTitle : title, + title: typeof title === 'undefined' ? defaultDialogTitle() : title, text, buttons: [{ text: app.params.dialog.buttonOk, @@ -57,7 +59,7 @@ export default { [text, callbackOk, callbackCancel, title] = args; } return new Dialog(app, { - title: typeof title === 'undefined' ? defaultDialogTitle : title, + title: typeof title === 'undefined' ? defaultDialogTitle() : title, text, content: '
', buttons: [ @@ -85,7 +87,7 @@ export default { [text, callbackOk, callbackCancel, title] = args; } return new Dialog(app, { - title: typeof title === 'undefined' ? defaultDialogTitle : title, + title: typeof title === 'undefined' ? defaultDialogTitle() : title, text, buttons: [ { @@ -109,7 +111,7 @@ export default { [text, callbackOk, callbackCancel, title] = args; } return new Dialog(app, { - title: typeof title === 'undefined' ? defaultDialogTitle : title, + title: typeof title === 'undefined' ? defaultDialogTitle() : title, text, content: `
@@ -148,7 +150,7 @@ export default { [text, callbackOk, callbackCancel, title] = args; } return new Dialog(app, { - title: typeof title === 'undefined' ? defaultDialogTitle : title, + title: typeof title === 'undefined' ? defaultDialogTitle() : title, text, content: `
diff --git a/packages/core/components/input/input-md.less b/packages/core/components/input/input-md.less index 882f91458b..9aaa0a66e5 100644 --- a/packages/core/components/input/input-md.less +++ b/packages/core/components/input/input-md.less @@ -260,7 +260,7 @@ } .item-input-focused.color-@{colorName} .item-input-wrap:after, .input-after.color-@{colorName}:after { - background: @colorThemeValue; + background: @colorValue; } }); } diff --git a/packages/core/components/modal/modal-class.js b/packages/core/components/modal/modal-class.js index 7d2c5bccf5..6547dc07dd 100644 --- a/packages/core/components/modal/modal-class.js +++ b/packages/core/components/modal/modal-class.js @@ -173,8 +173,24 @@ class Modal extends Framework7Class { // backdrop if ($backdropEl) { - $backdropEl[animate ? 'removeClass' : 'addClass']('not-animated'); - $backdropEl.removeClass('backdrop-in'); + let needToHideBackdrop = true; + if (modal.type === 'popup') { + modal.$el.prevAll('.popup.modal-in').each((index, popupEl) => { + const popupInstance = popupEl.f7Modal; + if (!popupInstance) return; + if ( + popupInstance.params.closeByBackdropClick + && popupInstance.params.backdrop + && popupInstance.backdropEl === modal.backdropEl + ) { + needToHideBackdrop = false; + } + }); + } + if (needToHideBackdrop) { + $backdropEl[animate ? 'removeClass' : 'addClass']('not-animated'); + $backdropEl.removeClass('backdrop-in'); + } } // Modal diff --git a/packages/core/components/page/page-md.less b/packages/core/components/page/page-md.less index 8e1afad64d..6a5eb13266 100644 --- a/packages/core/components/page/page-md.less +++ b/packages/core/components/page/page-md.less @@ -43,7 +43,12 @@ opacity: 0; pointer-events: none; &.page-next-on-right { - transform: translate3d(100%, 0, 0); + .ltr({ + transform: translate3d(100%, 0, 0); + }); + .rtl({ + transform: translate3d(-100%, 0, 0); + }); } } diff --git a/packages/core/components/panel/panel-class.js b/packages/core/components/panel/panel-class.js index bd39021640..e8de662b2f 100644 --- a/packages/core/components/panel/panel-class.js +++ b/packages/core/components/panel/panel-class.js @@ -8,7 +8,12 @@ class Panel extends Framework7Class { super(params, [app]); const panel = this; - const el = params.el; + let el = params.el; + + if (!el && params.content) { + el = params.content; + } + const $el = $(el); if ($el.length === 0) return panel; if ($el[0].f7Panel) return $el[0].f7Panel; @@ -24,6 +29,8 @@ class Panel extends Framework7Class { Utils.extend(app.panel, { [side]: panel, }); + } else { + throw new Error(`Framework7: Can't create panel; app already has a ${side} panel!`); } let $backdropEl = $('.panel-backdrop'); @@ -137,6 +144,11 @@ class Panel extends Framework7Class { let panel = this; const app = panel.app; + if (!panel.$el) { + // Panel already destroyed + return; + } + panel.emit('local::beforeDestroy panelBeforeDestroy', panel); panel.$el.trigger('panel:beforedestroy', panel); @@ -161,6 +173,30 @@ class Panel extends Framework7Class { const { side, effect, $el, $backdropEl, opened } = panel; + const $panelParentEl = $el.parent(); + const wasInDom = $el.parents(document).length > 0; + + if (!$panelParentEl.is(app.root)) { + const $insertBeforeEl = app.root.children('.panel, .views, .view').eq(0); + const $insertAfterEl = app.root.children('.statusbar').eq(0); + + if ($insertBeforeEl.length) { + $el.insertBefore($insertBeforeEl); + } else if ($insertAfterEl.length) { + $el.insertAfter($insertBeforeEl); + } else { + app.root.prepend($el); + } + + panel.once('panelClosed', () => { + if (wasInDom) { + $panelParentEl.append($el); + } else { + $el.remove(); + } + }); + } + // Ignore if opened if (opened || $el.hasClass('panel-visible-by-breakpoint') || $el.hasClass('panel-active')) return false; diff --git a/packages/core/components/popup/popup-class.js b/packages/core/components/popup/popup-class.js index c4226fcce6..fad3d0b7b1 100644 --- a/packages/core/components/popup/popup-class.js +++ b/packages/core/components/popup/popup-class.js @@ -62,7 +62,21 @@ class Popup extends Modal { && popup.backdropEl && popup.backdropEl === target ) { - popup.close(); + let needToClose = true; + popup.$el.nextAll('.popup.modal-in').each((index, popupEl) => { + const popupInstance = popupEl.f7Modal; + if (!popupInstance) return; + if ( + popupInstance.params.closeByBackdropClick + && popupInstance.params.backdrop + && popupInstance.backdropEl === popup.backdropEl + ) { + needToClose = false; + } + }); + if (needToClose) { + popup.close(); + } } } } diff --git a/packages/core/components/smart-select/smart-select-class.js b/packages/core/components/smart-select/smart-select-class.js index 942ccd9b3e..972d85e6bc 100644 --- a/packages/core/components/smart-select/smart-select-class.js +++ b/packages/core/components/smart-select/smart-select-class.js @@ -6,12 +6,19 @@ class SmartSelect extends Framework7Class { constructor(app, params = {}) { super(params, [app]); const ss = this; - ss.app = app; + const defaults = Utils.extend({ on: {}, }, app.params.smartSelect); - const $el = $(params.el).eq(0); + // Extend defaults with modules params + ss.useModulesParams(defaults); + + ss.params = Utils.extend({}, defaults, params); + + ss.app = app; + + const $el = $(ss.params.el).eq(0); if ($el.length === 0) return ss; if ($el[0].f7SmartSelect) return $el[0].f7SmartSelect; @@ -19,7 +26,7 @@ class SmartSelect extends Framework7Class { const $selectEl = $el.find('select').eq(0); if ($selectEl.length === 0) return ss; - let $valueEl = $(params.valueEl); + let $valueEl = $(ss.params.valueEl); if ($valueEl.length === 0) { $valueEl = $el.find('.item-after'); } @@ -28,20 +35,11 @@ class SmartSelect extends Framework7Class { $valueEl.insertAfter($el.find('.item-title')); } - // Extend defaults with modules params - ss.useModulesParams(defaults); - // View - let view = params.view; - if (!view) { - view = $el.parents('.view').length && $el.parents('.view')[0].f7View; - } - if (!view && (params.openIn === 'page' || (params.openIn !== 'page' && params.routableModals === true))) { - throw Error('Smart Select requires initialized View'); - } + let view; // Url - let url = params.url; + let url = ss.params.url; if (!url) { if ($el.attr('href') && $el.attr('href') !== '#') url = $el.attr('href'); else url = `${$selectEl.attr('name').toLowerCase()}-select/`; @@ -50,10 +48,9 @@ class SmartSelect extends Framework7Class { const multiple = $selectEl[0].multiple; const inputType = multiple ? 'checkbox' : 'radio'; - const id = Utils.now(); + const id = Utils.id(); Utils.extend(ss, { - params: Utils.extend(defaults, params), $el, el: $el[0], $selectEl, @@ -144,6 +141,19 @@ class SmartSelect extends Framework7Class { return ss; } + getView() { + const ss = this; + let view = ss.view || ss.params.view; + if (!view) { + view = ss.$el.parents('.view').length && ss.$el.parents('.view')[0].f7View; + } + if (!view) { + throw Error('Smart Select requires initialized View'); + } + ss.view = view; + return view; + } + checkMaxLength() { const ss = this; const $containerEl = ss.$containerEl; @@ -297,8 +307,9 @@ class SmartSelect extends Framework7Class { if (typeof pageTitle === 'undefined') { pageTitle = ss.$el.find('.item-title').text().trim(); } + const cssClass = ss.params.cssClass; const pageHtml = ` -
+